diff --git a/packages/client-runtime/src/handoff.test.ts b/packages/client-runtime/src/handoff.test.ts index 45718998eaf6f..5df784020540d 100644 --- a/packages/client-runtime/src/handoff.test.ts +++ b/packages/client-runtime/src/handoff.test.ts @@ -75,6 +75,7 @@ describe("handoff endpoints shared by web and mobile", () => { const projection = { runs: [{ ...run("source", 1, from, "source"), startedAt: "2026-10-11T00:00:00Z" }], + visibleTurnItems: [{ item: { type: "user_message" } }], } as unknown as OrchestrationV2ThreadProjection; describe("pending context handoff", () => { @@ -89,6 +90,18 @@ describe("pending context handoff", () => { expect(resolvePendingHandoff(projection, { instanceId: from, model: "source" }, [])).toBeNull(); expect(resolvePendingHandoff(projection, { instanceId: from, model: "other" }, [])).toBeNull(); }); + it("stays hidden while the thread's timeline is empty", () => { + const empty = { ...projection, visibleTurnItems: [] }; + expect(resolvePendingHandoff(empty, { instanceId: to, model: "first" }, [])).toBeNull(); + // Paging can leave a window of only tool rows after the prompt. + const toolRowsOnly = { + ...projection, + visibleTurnItems: [{ item: { type: "command_execution" } }], + } as unknown as OrchestrationV2ThreadProjection; + expect( + resolvePendingHandoff(toolRowsOnly, { instanceId: to, model: "first" }, [])?.to.model, + ).toBe("first"); + }); it("only previews a same-provider model change when it needs a fresh native thread", () => { const providers = [ { instanceId: from, requiresNewThreadForModelChange: true }, diff --git a/packages/client-runtime/src/handoff.ts b/packages/client-runtime/src/handoff.ts index 3dadda690dede..7c93ecbf28f6b 100644 --- a/packages/client-runtime/src/handoff.ts +++ b/packages/client-runtime/src/handoff.ts @@ -25,7 +25,10 @@ export function resolvePendingHandoff( selection: ModelSelection | null | undefined, providers: ReadonlyArray, ): HandoffEndpoints | null { - const from = projection?.runs.findLast( + // A thread with an empty timeline has nothing to hand off. Any loaded row + // counts: paging can leave a window of only tool rows. + if (!projection?.visibleTurnItems.length) return null; + const from = projection.runs.findLast( (run) => run.startedAt !== null && run.status !== "rolled_back", )?.modelSelection; if (from === undefined || selection == null) return null;