Skip to content
Merged
Show file tree
Hide file tree
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 @@ -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),
}),
];
Expand Down
11 changes: 8 additions & 3 deletions webviews/src/agent-session/acpmux/needsYou/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down
47 changes: 47 additions & 0 deletions webviews/test/agent-pane-needs-you.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<NeedsYouPanel
items={items.slice(0, 1)}
actions={{ answer: async () => 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) =>
({
Expand Down