import { ProviderInstanceId, RunId, ThreadId, TurnItemId, type OrchestrationV2ThreadProjection,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { expect, it } from "vite-plus/test"; import * as EffectWorker from "./EffectWorker.ts";import * as EffectOutbox from "./EffectOutbox.ts";import * as EventSink from "./EventSink.ts";import * as IdAllocator from "./IdAllocator.ts";import * as ProjectionStore from "./ProjectionStore.ts";import * as ProviderRuntimeRecovery from "./ProviderRuntimeRecoveryService.ts";import * as ServerSettings from "../serverSettings.ts"; it("uses the thread provider for stale background work without provider threads", async () => { const threadId = ThreadId.make("thread_recovery_background_no_provider_threads"); const runId = RunId.make("run_recovery_background_no_provider_threads"); const itemId = TurnItemId.make("turn_item_recovery_background_no_provider_threads"); const providerInstanceId = ProviderInstanceId.make("codex"); let committedInput: Parameters<EventSink.EventSinkV2["Service"]["commitCommand"]>[0] | null = null; const projection = { thread: { id: threadId, providerInstanceId }, runtimeRequests: [], providerSessions: [], providerThreads: [], providerTurns: [], runs: [{ id: runId, status: "completed", providerInstanceId }], attempts: [], nodes: [], subagents: [], messages: [], turnItems: [ { id: itemId, runId: null, nodeId: null, providerThreadId: null, type: "command_execution", status: "running", }, ], } as unknown as OrchestrationV2ThreadProjection; const layer = ProviderRuntimeRecovery.layer.pipe( Layer.provide(ServerSettings.layerTest()), Layer.provide( Layer.mergeAll( Layer.mock(ProjectionStore.ProjectionStoreV2)({ getRecoveryThreadIds: () => Effect.succeed([threadId]), getThreadProjection: () => Effect.succeed(projection), }), Layer.mock(EventSink.EventSinkV2)({ commitCommand: (input) => { committedInput = input; return Effect.succeed({ committed: true, cancelledEffectCount: 0 } as never); }, }), IdAllocator.layer, Layer.mock(EffectWorker.OrchestrationEffectWorkerV2)({ runRecoveryOnce: Effect.succeed(false), }), Layer.mock(EffectOutbox.EffectOutboxV2)({ reconcileAfterProcessLoss: Effect.succeed({ requeued: 0, cancelled: 0 }), }), ), ), ); await Effect.gen(function* () { yield* (yield* ProviderRuntimeRecovery.ProviderRuntimeRecoveryService).reconcile("startup"); const event = committedInput?.events.find( (candidate) => candidate.type === "turn-item.updated", ); expect(event?.providerInstanceId).toBe(providerInstanceId); expect(event?.type === "turn-item.updated" ? event.payload.status : null).toBe("cancelled"); }).pipe(Effect.provide(layer), Effect.runPromise);});
import {
ProviderInstanceId,
RunId,
ThreadId,
TurnItemId,
type OrchestrationV2ThreadProjection,
} from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { expect, it } from "vite-plus/test";
import * as EffectWorker from "./EffectWorker.ts";
import * as EffectOutbox from "./EffectOutbox.ts";
import * as EventSink from "./EventSink.ts";
import * as IdAllocator from "./IdAllocator.ts";
import * as ProjectionStore from "./ProjectionStore.ts";
import * as ProviderRuntimeRecovery from "./ProviderRuntimeRecoveryService.ts";
import * as ServerSettings from "../serverSettings.ts";
it("uses the thread provider for stale background work without provider threads", async () => {
const threadId = ThreadId.make("thread_recovery_background_no_provider_threads");
const runId = RunId.make("run_recovery_background_no_provider_threads");
const itemId = TurnItemId.make("turn_item_recovery_background_no_provider_threads");
const providerInstanceId = ProviderInstanceId.make("codex");
let committedInput: Parameters<EventSink.EventSinkV2["Service"]["commitCommand"]>[0] | null =
null;
const projection = {
thread: { id: threadId, providerInstanceId },
runtimeRequests: [],
providerSessions: [],
providerThreads: [],
providerTurns: [],
runs: [{ id: runId, status: "completed", providerInstanceId }],
attempts: [],
nodes: [],
subagents: [],
messages: [],
turnItems: [
{
id: itemId,
runId: null,
nodeId: null,
providerThreadId: null,
type: "command_execution",
status: "running",
},
],
} as unknown as OrchestrationV2ThreadProjection;
const layer = ProviderRuntimeRecovery.layer.pipe(
Layer.provide(ServerSettings.layerTest()),
Layer.provide(
Layer.mergeAll(
Layer.mock(ProjectionStore.ProjectionStoreV2)({
getRecoveryThreadIds: () => Effect.succeed([threadId]),
getThreadProjection: () => Effect.succeed(projection),
}),
Layer.mock(EventSink.EventSinkV2)({
commitCommand: (input) => {
committedInput = input;
return Effect.succeed({ committed: true, cancelledEffectCount: 0 } as never);
},
}),
IdAllocator.layer,
Layer.mock(EffectWorker.OrchestrationEffectWorkerV2)({
runRecoveryOnce: Effect.succeed(false),
}),
Layer.mock(EffectOutbox.EffectOutboxV2)({
reconcileAfterProcessLoss: Effect.succeed({ requeued: 0, cancelled: 0 }),
}),
),
),
);
await Effect.gen(function* () {
yield* (yield* ProviderRuntimeRecovery.ProviderRuntimeRecoveryService).reconcile("startup");
const event = committedInput?.events.find(
(candidate) => candidate.type === "turn-item.updated",
);
expect(event?.providerInstanceId).toBe(providerInstanceId);
expect(event?.type === "turn-item.updated" ? event.payload.status : null).toBe("cancelled");
}).pipe(Effect.provide(layer), Effect.runPromise);
});