Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2543,6 +2543,7 @@ describe("orchestration v2 provider switching", () => {
Effect.gen(function* () {
const cwd = yield* checkpointWorkspace("provider-switch");
const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
const codexNativeThreadGeneration = yield* Ref.make(0);
const registryLayer = makeProviderAdapterRegistryLayer([
makeTestAdapter({
instanceId: ProviderInstanceId.make("codex"),
Expand All @@ -2555,6 +2556,7 @@ describe("orchestration v2 provider switching", () => {
},
capturedTurns,
failResume: true,
nativeThreadGeneration: codexNativeThreadGeneration,
}),
makeTestAdapter({
instanceId: ProviderInstanceId.make("claudeAgent"),
Expand Down Expand Up @@ -2629,6 +2631,20 @@ describe("orchestration v2 provider switching", () => {
CLAUDE_MODEL_SELECTION,
);
yield* waitForIdle(threadId);
// Stopping the shared Codex process drops its loaded native thread, so
// returning to Codex has to resume it (and fall back when that fails).
const codexSession = (yield* orchestrator.getThreadProjection(
threadId,
)).providerSessions.find(
(session) => session.providerInstanceId === CODEX_MODEL_SELECTION.instanceId,
)!;
yield* orchestrator.dispatch({
type: "provider-session.detach",
commandId: CommandId.make("command:provider-switch:stop-codex"),
threadId,
providerSessionId: codexSession.id,
});
yield* (yield* OrchestrationEffectWorkerV2).drain();
yield* orchestrator.dispatch(commands[3]!);
assert.deepEqual(
(yield* orchestrator.getThreadProjection(threadId)).thread.modelSelection,
Expand Down Expand Up @@ -2667,9 +2683,24 @@ describe("orchestration v2 provider switching", () => {
assert.lengthOf(projection.providerThreads, 2);
assert.equal(projection.runs[0]?.providerThreadId, projection.runs[2]?.providerThreadId);
assert.notEqual(projection.runs[0]?.providerThreadId, projection.runs[1]?.providerThreadId);
// The failed resume bound a fresh native Codex thread to the same row.
assert.equal(yield* Ref.get(codexNativeThreadGeneration), 2);
const codexThread = projection.providerThreads.find(
(providerThread) => providerThread.id === projection.runs[2]?.providerThreadId,
);
assert.equal(codexThread?.nativeThreadRef?.nativeId, `codex:${threadId}:1`);
assert.deepEqual(
projection.contextHandoffs.map((handoff) => handoff.strategy),
["full_thread_summary", "delta_since_target_last_seen"],
projection.contextHandoffs.map((handoff) => [
handoff.strategy,
handoff.coveredRunOrdinals,
handoff.delivery?.status,
handoff.delivery?.nativeThreadId,
]),
[
["full_thread_summary", { from: 1, to: 1 }, "inline", `claudeAgent:${threadId}`],
["delta_since_target_last_seen", { from: 2, to: 2 }, "inline", `codex:${threadId}:1`],
["full_thread_summary", { from: 1, to: 2 }, "inline", `codex:${threadId}:1`],
],
);
assert.deepEqual(
projection.contextTransfers.map((transfer) => [
Expand All @@ -2680,6 +2711,7 @@ describe("orchestration v2 provider switching", () => {
[
["provider_handoff", "consumed", "portable_context"],
["provider_handoff", "consumed", "delta_context"],
["provider_handoff", "resolved_portable", "portable_context"],
],
);
assert.deepEqual(
Expand All @@ -2703,10 +2735,13 @@ describe("orchestration v2 provider switching", () => {
assert.include(turns[1]?.text ?? "", "Context handoff (full_thread_summary):");
assert.include(turns[1]?.text ?? "", "codex before switch");
assert.include(turns[1]?.text ?? "", claudePrompt);
// The fresh native thread has none of the earlier Codex turn, so the
// portable fallback re-sends it alongside the Claude delta.
assert.include(turns[2]?.text ?? "", "Context handoff (full_thread_summary):");
assert.include(turns[2]?.text ?? "", "Context handoff (delta_since_target_last_seen):");
assert.include(turns[2]?.text ?? "", "codex before switch");
assert.include(turns[2]?.text ?? "", "claude switched response");
assert.include(turns[2]?.text ?? "", returnPrompt);
assert.notInclude(turns[2]?.text ?? "", "codex before switch");
assert.equal(turns[0]?.providerThreadId, turns[2]?.providerThreadId);
}),
),
Expand Down
Loading