diff --git a/ui/src/components/task-chat/transcript-adapter.test.ts b/ui/src/components/task-chat/transcript-adapter.test.ts index d123b2ee2e4..8740fc1fa46 100644 --- a/ui/src/components/task-chat/transcript-adapter.test.ts +++ b/ui/src/components/task-chat/transcript-adapter.test.ts @@ -34,12 +34,25 @@ import type { TaskChatPlanDocumentItem, TaskChatTurnItem, } from "./task-chat-model"; +import { latestPendingRuntimeRequest } from "./task-chat-model"; import { providerActivityPresentation } from "./task-chat-activity-presentation"; import { nativeRunEventsToTranscript } from "../transcript/native-run-events"; const TS = "2026-07-31T12:00:00.000Z"; describe("runtime requests across steering sections", () => { + it("selects the newer request when an older pending permission crosses steering", () => { + const older: TranscriptEntry = { kind: "runtime_request", ts: TS, requestId: "older-permission", + requestKind: "permission_approval", turnId: "provider-turn", requestType: "permission", status: "pending", + prompt: "Earlier write", choices: [{ key: "decline", label: "Deny" }], fields: [] }; + const newer: TranscriptEntry = { ...older, ts: "2026-07-31T12:00:02.000Z", requestId: "newer-permission", prompt: "Later write" }; + const options = { runId: "run-steering", running: true }; + const context = runtimeRequestSegmentContext([older, newer], options); + const tail = reconcileSegmentRuntimeRequests(transcriptToTaskChatItems([newer], options), [newer], context, true); + expect(tail.map(item => item.id)).toEqual(["run-steering:runtime-request:older-permission", "run-steering:runtime-request:newer-permission"]); + expect(latestPendingRuntimeRequest(tail)?.requestId).toBe("newer-permission"); + }); + it.each(["pending", "resolved", "cancelled"] as const)("keeps one %s permission under whole-run authority", status => { const request: TranscriptEntry = { kind: "runtime_request", ts: TS, requestId: "permission-1", requestKind: "permission_approval", turnId: "provider-turn", requestType: "permission", status: "pending", diff --git a/ui/src/components/task-chat/transcript-adapter.ts b/ui/src/components/task-chat/transcript-adapter.ts index 607462f57da..b33148ed5a4 100644 --- a/ui/src/components/task-chat/transcript-adapter.ts +++ b/ui/src/components/task-chat/transcript-adapter.ts @@ -606,12 +606,15 @@ export function reconcileSegmentRuntimeRequests( retained.add(item.id); return [state.item]; }); + const carried: TaskChatItem[] = []; if (carryPending) { for (const { item } of context.values()) { - if (item.status === "pending" && !retained.has(item.id)) result.push(item); + if (item.status === "pending" && !retained.has(item.id)) carried.push(item); } } - return result; + // A carried request predates this section. Keep newer requests last so the + // composer selects the latest input instead of reviving an older one. + return [...carried, ...result]; } /**