diff --git a/webviews/src/agent-session/acpmux/needsYou/NeedsYou.gallery.ts b/webviews/src/agent-session/acpmux/needsYou/NeedsYou.gallery.ts index 1c3a356a1732..6c455230458a 100644 --- a/webviews/src/agent-session/acpmux/needsYou/NeedsYou.gallery.ts +++ b/webviews/src/agent-session/acpmux/needsYou/NeedsYou.gallery.ts @@ -98,7 +98,9 @@ const sessions = [ host: "build-box", hostKind: "cloud", indicator: "input", - preview: "Which tag should the notes start from?", + // Its ask lives on the build box: the row says the question its summary names, never this reply. + preview: "Drafted the notes from the merged pull requests.", + waitingOn: { on: "question", prompt: "Which tag should the notes start from?" }, updatedAt: ago(32), }), ]; diff --git a/webviews/src/agent-session/acpmux/needsYou/model.ts b/webviews/src/agent-session/acpmux/needsYou/model.ts index 1513af18d51a..b766472f7e60 100644 --- a/webviews/src/agent-session/acpmux/needsYou/model.ts +++ b/webviews/src/agent-session/acpmux/needsYou/model.ts @@ -105,15 +105,20 @@ export function needsYouItems(input: NeedsYouInput): NeedsYouItem[] { } } // A chat that waits on the person with no ask read here (a chat on another computer, or one - // whose ask is still being read): one item, so it is never missing from the list. + // whose ask is still being read): one item, so it is never missing from the list. Its summary + // still names what it waits on (acpmux `waitingOn`, with a question's first prompt): the row + // says that question; only a chat that names no question falls back to its last reply. for (const session of input.sessions) { if (asked.has(session.sessionId) || session.archived) continue; if (session.indicator !== "input" && !(session.pendingPermissions && session.pendingPermissions > 0)) continue; + const waiting = session.waitingOn; + const prompt = waiting?.on === "question" ? waiting.prompt : undefined; + const ask = prompt ?? session.preview ?? ""; items.push({ id: `chat:${session.sessionId}`, - kind: "input", + kind: !waiting ? "input" : waiting.on === "permission" ? "approval" : VERIFY.test(ask) ? "verify" : "decision", sessionId: session.sessionId, - ask: session.preview ?? "", + ask, who: who(session, session.sessionId, byId), ...(session.updatedAt ? { at: session.updatedAt } : {}), answers: [], diff --git a/webviews/test/agent-pane-needs-you.test.tsx b/webviews/test/agent-pane-needs-you.test.tsx index e51d41b08f51..84b1e7aba422 100644 --- a/webviews/test/agent-pane-needs-you.test.tsx +++ b/webviews/test/agent-pane-needs-you.test.tsx @@ -128,6 +128,53 @@ test("a chat waiting with no readable ask still shows, once; an archived chat ne expect(items[1]!.ask).toBe("Pick a branch"); }); +// Leo 2026-10-21: a row read "Waiting 5m" over the chat's last reply while a question waited. A chat +// whose ask the page cannot read (another computer's) still names it in its summary (acpmux +// `waitingOn`, `waitingPrompt`): the row shows that question; only a chat that names nothing falls +// back to its last reply. +test("a waiting chat's row shows the question it waits on, not its last reply", () => { + const items = needsYouItems({ + sessions: [ + session("question", { + indicator: "input", + updatedAt: 5, + preview: "I moved the retry helper into fetch.ts.", + waitingOn: { on: "question", prompt: "Which branch should the docs target?" }, + }), + session("permission", { + indicator: "input", + updatedAt: 4, + preview: "The tests pass; pushing next.", + waitingOn: { on: "permission" }, + }), + session("unnamed", { + indicator: "input", + updatedAt: 3, + preview: "Which tag should the notes start from?", + waitingOn: { on: "question" }, + }), + session("plain", { indicator: "input", updatedAt: 2, preview: "Pick a branch" }), + ], + asks: [], + }); + expect(items.map((item) => [item.id, item.kind, item.ask])).toEqual([ + ["chat:question", "decision", "Which branch should the docs target?"], + ["chat:permission", "approval", "The tests pass; pushing next."], + ["chat:unnamed", "decision", "Which tag should the notes start from?"], + ["chat:plain", "input", "Pick a branch"], + ]); + expect(items.every((item) => item.answers.length === 0)).toBe(true); + const html = renderToStaticMarkup( + undefined, goTo: () => undefined, ask: async () => "" }} + />, + ); + expect(html).toContain("Which branch should the docs target?"); + expect(html).not.toContain("I moved the retry helper"); + expect(html).toContain(translate("needsYou.kind.decision")); +}); + test("the open chat's held agent pair messages are one item with Resume; a resumed pair is not", () => { const pair = (peer: string, resumed: boolean) => ({