import { assert, it } from "@effect/vitest";import { type ModelSelection, NodeId, type OrchestrationV2ProviderThread, type OrchestrationV2ThreadProjection, ProjectId, ProviderDriverKind, ProviderInstanceId, ProviderSessionId, ProviderThreadId, ProviderTurnId, RunAttemptId, RunId, ThreadId,} from "@t3tools/contracts";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import * as Stream from "effect/Stream"; import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";import { ProjectionStoreV2 } from "./ProjectionStore.ts";import type { ProviderAdapterV2SessionRuntime } from "./ProviderAdapter.ts";import { ProviderSessionManagerV2 } from "./ProviderSessionManager.ts";import { layer as providerTurnControlLayer, ProviderTurnControlServiceV2,} from "./ProviderTurnControlService.ts"; const driver = ProviderDriverKind.make("codex");const providerInstanceId = ProviderInstanceId.make("codex");const modelSelection = { instanceId: providerInstanceId, model: "gpt-5.4",} satisfies ModelSelection; function makeProjection(input: { readonly now: DateTime.Utc; readonly threadId: ThreadId; readonly providerThread: OrchestrationV2ProviderThread; readonly providerTurnId: ProviderTurnId; readonly attemptId: RunAttemptId;}): OrchestrationV2ThreadProjection { const runId = RunId.make("run:restart-session"); const nodeId = NodeId.make("node:restart-session"); return { thread: { createdBy: "user", creationSource: "web", id: input.threadId, projectId: ProjectId.make("project:restart-session"), title: "Restart session", providerInstanceId, modelSelection, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: "/workspace", activeProviderThreadId: input.providerThread.id, lineage: { parentThreadId: null, relationshipToParent: null, rootThreadId: input.threadId, }, forkedFrom: null, createdAt: input.now, updatedAt: input.now, archivedAt: null, settledOverride: null, settledAt: null, lastVisitedAt: null, deletedAt: null, }, runs: [], attempts: [ { id: input.attemptId, runId, attemptOrdinal: 1, rootNodeId: nodeId, providerInstanceId, providerThreadId: input.providerThread.id, providerTurnId: input.providerTurnId, reason: "initial", status: "superseded", startedAt: input.now, completedAt: input.now, }, ], nodes: [], subagents: [], providerSessions: [], providerThreads: [input.providerThread], providerTurns: [ { id: input.providerTurnId, providerThreadId: input.providerThread.id, nodeId, runAttemptId: input.attemptId, nativeTurnRef: { driver, nativeId: "native-turn:restart-session", strength: "strong", }, ordinal: 1, status: "running", startedAt: input.now, completedAt: null, }, ], runtimeRequests: [], messages: [], plans: [], turnItems: [], checkpointScopes: [], checkpoints: [], contextHandoffs: [], contextTransfers: [], visibleTurnItems: [], updatedAt: input.now, };} it.effect( "interrupts the historical session only for the exact committed restart replacement", () => Effect.gen(function* () { const now = yield* DateTime.now; const threadId = ThreadId.make("thread:restart-session"); const oldSessionId = ProviderSessionId.make("provider-session:restart-session:old"); const replacementSessionId = ProviderSessionId.make( "provider-session:restart-session:replacement", ); const unrelatedSessionId = ProviderSessionId.make( "provider-session:restart-session:unrelated", ); const providerThreadId = ProviderThreadId.make("provider-thread:restart-session"); const providerTurnId = ProviderTurnId.make("provider-turn:restart-session"); const attemptId = RunAttemptId.make("run-attempt:restart-session"); const providerThread: OrchestrationV2ProviderThread = { id: providerThreadId, driver, providerInstanceId, // The restart command has already projected this replacement binding // before the process-bound restart effect executes. providerSessionId: replacementSessionId, appThreadId: threadId, ownerNodeId: null, nativeThreadRef: { driver, nativeId: "native-thread:restart-session", strength: "strong", }, nativeConversationHeadRef: null, status: "not_loaded", firstRunOrdinal: 1, lastRunOrdinal: 1, handoffIds: [], forkedFrom: null, createdAt: now, updatedAt: now, }; const projection = yield* Ref.make( makeProjection({ now, threadId, providerThread, providerTurnId, attemptId }), ); const interruptedThread = yield* Ref.make<OrchestrationV2ProviderThread | null>(null); const providerSession = { id: oldSessionId, driver, providerInstanceId, status: "running" as const, cwd: "/workspace", model: modelSelection.model, capabilities: CodexProviderCapabilitiesV2, createdAt: now, updatedAt: now, lastError: null, }; const runtime: ProviderAdapterV2SessionRuntime = { instanceId: providerInstanceId, driver, providerSessionId: oldSessionId, providerSession, events: Stream.empty, ensureThread: () => Effect.die("unused ensureThread"), resumeThread: () => Effect.die("unused resumeThread"), startTurn: () => Effect.die("unused startTurn"), steerTurn: () => Effect.die("unused steerTurn"), interruptTurn: ({ providerThread: target }) => Effect.all( [ Ref.set(interruptedThread, target), Ref.update(projection, (current) => ({ ...current, providerTurns: current.providerTurns.map((turn) => turn.id === providerTurnId ? { ...turn, status: "interrupted" as const, completedAt: now } : turn, ), })), ], { discard: true }, ), respondToRuntimeRequest: () => Effect.die("unused respondToRuntimeRequest"), readThreadSnapshot: () => Effect.die("unused readThreadSnapshot"), rollbackThread: () => Effect.die("unused rollbackThread"), forkThread: () => Effect.die("unused forkThread"), }; const projectionLayer = Layer.succeed( ProjectionStoreV2, ProjectionStoreV2.of({ apply: () => Effect.void, getShellSnapshot: () => Effect.die("unused getShellSnapshot"), getThreadShell: () => Effect.die("unused getThreadShell"), getThread: () => Ref.get(projection).pipe(Effect.map((state) => state.thread)), getSettlementCandidates: () => Effect.die("unused getSettlementCandidates"), getThreadProjection: () => Effect.die("control effects must not load transcript"), getRuntimeRequest: () => Effect.die("unused getRuntimeRequest"), getPendingNativeUserInputs: () => Effect.die("unused getPendingNativeUserInputs"), getProviderControlContext: (_threadId, target) => Ref.get(projection).pipe( Effect.map((current) => ({ providerThread: current.providerThreads.find( (thread) => thread.id === target.providerThreadId, ), providerTurn: current.providerTurns.find( (turn) => turn.id === target.providerTurnId, ), attempt: current.attempts.find((attempt) => attempt.id === target.attemptId), message: undefined, run: undefined, })), ), getCheckpointContext: () => Effect.die("not used"), getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"), getUnreadableThreadIds: () => Effect.die("unused getUnreadableThreadIds"), getThreadSnapshot: () => Effect.die("unused getThreadSnapshot"), getThreadSnapshotWindow: () => Effect.die("unused getThreadSnapshotWindow"), }), ); const sessionManagerLayer = Layer.succeed( ProviderSessionManagerV2, ProviderSessionManagerV2.of({ shutdown: Effect.void, open: () => Effect.die("unused open"), get: (providerSessionId) => Effect.succeed( providerSessionId === oldSessionId ? Option.some(runtime) : Option.none(), ), close: () => Effect.void, release: () => Effect.void, detach: () => Effect.void, }), ); const controlLayer = providerTurnControlLayer.pipe( Layer.provide(Layer.merge(projectionLayer, sessionManagerLayer)), ); const [ordinaryInterrupt, unrelatedRestart] = yield* Effect.gen(function* () { const control = yield* ProviderTurnControlServiceV2; const ordinary = yield* Effect.exit( control.interrupt({ threadId, providerSessionId: oldSessionId, providerThreadId, providerTurnId, }), ); const unrelated = yield* Effect.exit( control.interruptAndAwaitTerminal({ threadId, providerSessionId: oldSessionId, replacementProviderSessionId: unrelatedSessionId, providerThreadId, providerTurnId, interruptedAttemptId: attemptId, }), ); return [ordinary, unrelated] as const; }).pipe(Effect.provide(controlLayer)); assert.isTrue(Exit.isFailure(ordinaryInterrupt)); assert.isTrue(Exit.isFailure(unrelatedRestart)); assert.isNull(yield* Ref.get(interruptedThread)); yield* Effect.gen(function* () { const control = yield* ProviderTurnControlServiceV2; yield* control.interruptAndAwaitTerminal({ threadId, providerSessionId: oldSessionId, replacementProviderSessionId: replacementSessionId, providerThreadId, providerTurnId, interruptedAttemptId: attemptId, }); }).pipe(Effect.provide(controlLayer)); const interrupted = yield* Ref.get(interruptedThread); assert.isNotNull(interrupted); assert.equal(interrupted?.providerSessionId, oldSessionId); assert.equal(interrupted?.id, providerThreadId); assert.equal(interrupted?.nativeThreadRef?.nativeId, "native-thread:restart-session"); }),);
import { assert, it } from "@effect/vitest";
import {
type ModelSelection,
NodeId,
type OrchestrationV2ProviderThread,
type OrchestrationV2ThreadProjection,
ProjectId,
ProviderDriverKind,
ProviderInstanceId,
ProviderSessionId,
ProviderThreadId,
ProviderTurnId,
RunAttemptId,
RunId,
ThreadId,
} from "@t3tools/contracts";
import * as DateTime from "effect/DateTime";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import * as Stream from "effect/Stream";
import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
import { ProjectionStoreV2 } from "./ProjectionStore.ts";
import type { ProviderAdapterV2SessionRuntime } from "./ProviderAdapter.ts";
import { ProviderSessionManagerV2 } from "./ProviderSessionManager.ts";
import {
layer as providerTurnControlLayer,
ProviderTurnControlServiceV2,
} from "./ProviderTurnControlService.ts";
const driver = ProviderDriverKind.make("codex");
const providerInstanceId = ProviderInstanceId.make("codex");
const modelSelection = {
instanceId: providerInstanceId,
model: "gpt-5.4",
} satisfies ModelSelection;
function makeProjection(input: {
readonly now: DateTime.Utc;
readonly threadId: ThreadId;
readonly providerThread: OrchestrationV2ProviderThread;
readonly providerTurnId: ProviderTurnId;
readonly attemptId: RunAttemptId;
}): OrchestrationV2ThreadProjection {
const runId = RunId.make("run:restart-session");
const nodeId = NodeId.make("node:restart-session");
return {
thread: {
createdBy: "user",
creationSource: "web",
id: input.threadId,
projectId: ProjectId.make("project:restart-session"),
title: "Restart session",
providerInstanceId,
modelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: "/workspace",
activeProviderThreadId: input.providerThread.id,
lineage: {
parentThreadId: null,
relationshipToParent: null,
rootThreadId: input.threadId,
},
forkedFrom: null,
createdAt: input.now,
updatedAt: input.now,
archivedAt: null,
settledOverride: null,
settledAt: null,
lastVisitedAt: null,
deletedAt: null,
},
runs: [],
attempts: [
{
id: input.attemptId,
runId,
attemptOrdinal: 1,
rootNodeId: nodeId,
providerInstanceId,
providerThreadId: input.providerThread.id,
providerTurnId: input.providerTurnId,
reason: "initial",
status: "superseded",
startedAt: input.now,
completedAt: input.now,
},
],
nodes: [],
subagents: [],
providerSessions: [],
providerThreads: [input.providerThread],
providerTurns: [
{
id: input.providerTurnId,
providerThreadId: input.providerThread.id,
nodeId,
runAttemptId: input.attemptId,
nativeTurnRef: {
driver,
nativeId: "native-turn:restart-session",
strength: "strong",
},
ordinal: 1,
status: "running",
startedAt: input.now,
completedAt: null,
},
],
runtimeRequests: [],
messages: [],
plans: [],
turnItems: [],
checkpointScopes: [],
checkpoints: [],
contextHandoffs: [],
contextTransfers: [],
visibleTurnItems: [],
updatedAt: input.now,
};
}
it.effect(
"interrupts the historical session only for the exact committed restart replacement",
() =>
Effect.gen(function* () {
const now = yield* DateTime.now;
const threadId = ThreadId.make("thread:restart-session");
const oldSessionId = ProviderSessionId.make("provider-session:restart-session:old");
const replacementSessionId = ProviderSessionId.make(
"provider-session:restart-session:replacement",
);
const unrelatedSessionId = ProviderSessionId.make(
"provider-session:restart-session:unrelated",
);
const providerThreadId = ProviderThreadId.make("provider-thread:restart-session");
const providerTurnId = ProviderTurnId.make("provider-turn:restart-session");
const attemptId = RunAttemptId.make("run-attempt:restart-session");
const providerThread: OrchestrationV2ProviderThread = {
id: providerThreadId,
driver,
providerInstanceId,
// The restart command has already projected this replacement binding
// before the process-bound restart effect executes.
providerSessionId: replacementSessionId,
appThreadId: threadId,
ownerNodeId: null,
nativeThreadRef: {
driver,
nativeId: "native-thread:restart-session",
strength: "strong",
},
nativeConversationHeadRef: null,
status: "not_loaded",
firstRunOrdinal: 1,
lastRunOrdinal: 1,
handoffIds: [],
forkedFrom: null,
createdAt: now,
updatedAt: now,
};
const projection = yield* Ref.make(
makeProjection({ now, threadId, providerThread, providerTurnId, attemptId }),
);
const interruptedThread = yield* Ref.make<OrchestrationV2ProviderThread | null>(null);
const providerSession = {
id: oldSessionId,
driver,
providerInstanceId,
status: "running" as const,
cwd: "/workspace",
model: modelSelection.model,
capabilities: CodexProviderCapabilitiesV2,
createdAt: now,
updatedAt: now,
lastError: null,
};
const runtime: ProviderAdapterV2SessionRuntime = {
instanceId: providerInstanceId,
driver,
providerSessionId: oldSessionId,
providerSession,
events: Stream.empty,
ensureThread: () => Effect.die("unused ensureThread"),
resumeThread: () => Effect.die("unused resumeThread"),
startTurn: () => Effect.die("unused startTurn"),
steerTurn: () => Effect.die("unused steerTurn"),
interruptTurn: ({ providerThread: target }) =>
Effect.all(
[
Ref.set(interruptedThread, target),
Ref.update(projection, (current) => ({
...current,
providerTurns: current.providerTurns.map((turn) =>
turn.id === providerTurnId
? { ...turn, status: "interrupted" as const, completedAt: now }
: turn,
),
})),
],
{ discard: true },
),
respondToRuntimeRequest: () => Effect.die("unused respondToRuntimeRequest"),
readThreadSnapshot: () => Effect.die("unused readThreadSnapshot"),
rollbackThread: () => Effect.die("unused rollbackThread"),
forkThread: () => Effect.die("unused forkThread"),
};
const projectionLayer = Layer.succeed(
ProjectionStoreV2,
ProjectionStoreV2.of({
apply: () => Effect.void,
getShellSnapshot: () => Effect.die("unused getShellSnapshot"),
getThreadShell: () => Effect.die("unused getThreadShell"),
getThread: () => Ref.get(projection).pipe(Effect.map((state) => state.thread)),
getSettlementCandidates: () => Effect.die("unused getSettlementCandidates"),
getThreadProjection: () => Effect.die("control effects must not load transcript"),
getRuntimeRequest: () => Effect.die("unused getRuntimeRequest"),
getPendingNativeUserInputs: () => Effect.die("unused getPendingNativeUserInputs"),
getProviderControlContext: (_threadId, target) =>
Ref.get(projection).pipe(
Effect.map((current) => ({
providerThread: current.providerThreads.find(
(thread) => thread.id === target.providerThreadId,
),
providerTurn: current.providerTurns.find(
(turn) => turn.id === target.providerTurnId,
),
attempt: current.attempts.find((attempt) => attempt.id === target.attemptId),
message: undefined,
run: undefined,
})),
),
getCheckpointContext: () => Effect.die("not used"),
getRecoveryThreadIds: () => Effect.die("unused getRecoveryThreadIds"),
getUnreadableThreadIds: () => Effect.die("unused getUnreadableThreadIds"),
getThreadSnapshot: () => Effect.die("unused getThreadSnapshot"),
getThreadSnapshotWindow: () => Effect.die("unused getThreadSnapshotWindow"),
}),
);
const sessionManagerLayer = Layer.succeed(
ProviderSessionManagerV2,
ProviderSessionManagerV2.of({
shutdown: Effect.void,
open: () => Effect.die("unused open"),
get: (providerSessionId) =>
Effect.succeed(
providerSessionId === oldSessionId ? Option.some(runtime) : Option.none(),
),
close: () => Effect.void,
release: () => Effect.void,
detach: () => Effect.void,
}),
);
const controlLayer = providerTurnControlLayer.pipe(
Layer.provide(Layer.merge(projectionLayer, sessionManagerLayer)),
);
const [ordinaryInterrupt, unrelatedRestart] = yield* Effect.gen(function* () {
const control = yield* ProviderTurnControlServiceV2;
const ordinary = yield* Effect.exit(
control.interrupt({
threadId,
providerSessionId: oldSessionId,
providerThreadId,
providerTurnId,
}),
);
const unrelated = yield* Effect.exit(
control.interruptAndAwaitTerminal({
threadId,
providerSessionId: oldSessionId,
replacementProviderSessionId: unrelatedSessionId,
providerThreadId,
providerTurnId,
interruptedAttemptId: attemptId,
}),
);
return [ordinary, unrelated] as const;
}).pipe(Effect.provide(controlLayer));
assert.isTrue(Exit.isFailure(ordinaryInterrupt));
assert.isTrue(Exit.isFailure(unrelatedRestart));
assert.isNull(yield* Ref.get(interruptedThread));
yield* Effect.gen(function* () {
const control = yield* ProviderTurnControlServiceV2;
yield* control.interruptAndAwaitTerminal({
threadId,
providerSessionId: oldSessionId,
replacementProviderSessionId: replacementSessionId,
providerThreadId,
providerTurnId,
interruptedAttemptId: attemptId,
});
}).pipe(Effect.provide(controlLayer));
const interrupted = yield* Ref.get(interruptedThread);
assert.isNotNull(interrupted);
assert.equal(interrupted?.providerSessionId, oldSessionId);
assert.equal(interrupted?.id, providerThreadId);
assert.equal(interrupted?.nativeThreadRef?.nativeId, "native-thread:restart-session");
}),
);