Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 8 additions & 1 deletion apps/server/src/orchestration-v2/ProviderTurnStartService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,14 @@ export const layer: Layer.Layer<
),
createdAt,
});
effectiveHandoffs = [handoff, ...effectiveHandoffs];
// Delivery ranks later handoffs as newer history. Pending handoffs from
// earlier runs predate this summary; handoffs for this run (provider-switch
// delta, merge-back) are newer and must stay last.
effectiveHandoffs = [
...effectiveHandoffs.filter((candidate) => candidate.targetRunId !== run.id),
handoff,
...effectiveHandoffs.filter((candidate) => candidate.targetRunId === run.id),
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
yield* eventSink.write({
events: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,125 @@ describe("orchestration v2 provider switching", () => {
),
);

it.live("keeps the latest turn when a resume fallback also delivers an older handoff", () =>
Effect.scoped(
Effect.gen(function* () {
const cwd = yield* checkpointWorkspace("resume-fallback-handoff-order");
const capturedTurns = yield* Ref.make<ReadonlyArray<CapturedTurn>>([]);
const injectedHistory = yield* Ref.make<ReadonlyArray<unknown>>([]);
const oldResult = `old result ${"o".repeat(2_500)}`;
const latestResult = `latest result ${"l".repeat(2_500)}`;
const layerRegistry = ProviderAdapterRegistry.layerFromAdapters([
makeTestAdapter({
instanceId: CLAUDE_MODEL_SELECTION.instanceId,
driver: CLAUDE_DRIVER,
capabilities: ClaudeProviderCapabilitiesV2,
modelSelection: CLAUDE_MODEL_SELECTION,
responseByRunOrdinal: { 1: oldResult, 3: latestResult },
capturedTurns,
injectedHistory,
failResume: true,
failedRunOrdinals: new Set([2]),
nativeThreadGeneration: yield* Ref.make(0),
}),
]);
yield* Effect.gen(function* () {
const orchestrator = yield* Orchestrator.OrchestratorV2;
const worker = yield* EffectWorker.OrchestrationEffectWorkerV2;
const runAfterDetach = Effect.fn("runAfterDetach")(function* (
ordinal: number,
text: string,
) {
const providerSessionId = (yield* orchestrator.getThreadProjection(threadId))
.providerThreads[0]?.providerSessionId;
if (providerSessionId != null) {
yield* orchestrator.dispatch({
type: "provider-session.detach",
commandId: CommandId.make(`handoff-order:detach:${ordinal}`),
threadId,
providerSessionId,
});
yield* worker.drain();
}
yield* orchestrator.dispatch({
type: "message.dispatch",
commandId: CommandId.make(`handoff-order:${ordinal}`),
threadId,
messageId: MessageId.make(`handoff-order:${ordinal}`),
createdBy: "user",
creationSource: "web",
text,
attachments: [],
modelSelection: CLAUDE_MODEL_SELECTION,
dispatchMode: { type: "start_immediately" },
});
yield* orchestrator.streamStoredEvents.pipe(
Stream.filter(
({ event }) =>
event.type === "run.updated" &&
event.payload.ordinal === ordinal &&
(event.payload.status === "completed" || event.payload.status === "failed"),
),
Stream.runHead,
);
yield* worker.drain();
});
yield* orchestrator.dispatch({
type: "thread.create",
commandId: CommandId.make("handoff-order:create"),
threadId,
projectId,
createdBy: "user",
creationSource: "web",
title: "Resume fallback handoff order",
modelSelection: CLAUDE_MODEL_SELECTION,
runtimeMode: "full-access",
interactionMode: "default",
branch: null,
worktreePath: null,
});
yield* runAfterDetach(1, `Original brief ${"b".repeat(9_000)}`);
yield* runAfterDetach(2, "Request 2");
yield* runAfterDetach(3, "Request 3");
const historyBeforeRun4 = (yield* Ref.get(injectedHistory)).length;
yield* runAfterDetach(4, "Request 4");

const projection = yield* orchestrator.getThreadProjection(threadId);
assert.deepEqual(
projection.runs.map((run) => run.status),
["completed", "failed", "completed", "completed"],
);
const run4NativeThreadId = (yield* Ref.get(capturedTurns)).at(-1)?.nativeThreadId;
assert.equal(
projection.contextHandoffs.find(
(handoff) => handoff.targetRunId === projection.runs[1]?.id,
)?.delivery?.nativeThreadId,
run4NativeThreadId,
);
const run4History = yield* encodeJson(
(yield* Ref.get(injectedHistory)).slice(historyBeforeRun4),
);
assert.include(run4History, "Original brief");
assert.include(run4History, latestResult);
}).pipe(
Effect.provide(
ProviderReplayHarness.layerWithRegistry(
{
name: "resume-fallback-handoff-order",
runtimePolicyOverride: {
cwd,
approvalPolicy: "never",
sandboxPolicy: { type: "readOnly" },
},
},
layerRegistry,
),
),
);
}),
),
);

it.live.each([
["background work", new ClaudeBackgroundWorkBlocksQueryReplacementError()],
["an unreadable append file", new ClaudeAppendSystemPromptFileError({ path: "missing.md" })],
Expand Down
Loading