Question card: say whether an unanswered ask was skipped or stopped, and mark answers from another device

Merged
TE
13d ago
+609-27

Leo's ask, in his words: "ask user question tool and its various states need looking at allegedly there's some sort of vibe-coded thing that agents can do to have prettier ask question situations...? idk but we could look into that/research that then support it..."

Leo-ask: ask-427cc7d8f5

What was wrong

This is part 3 of 4 of the ask, built on #24325's compact answered state (landed). Part 1 is #24348 (HTML previews), part 2 is #24378 (inline Markdown).

  • Unanswered asks all looked the same. An ask that ended without an answer always read "Not answered: ". That covered two different things: the person pressed Skip, or the turn was stopped (or the chat closed or restarted) before anyone answered.
  • Remote answers were unmarked. When the person answered from another device through the feed, the record carried answeredBy {device, feedItem}, but the card dropped it. The answer read as if it had been given here.

The fix

All of it is in the webview's question card. acpmux and Swift are unchanged.

  • record.ts cancelReason reads how the ask ended from the decision record acpmux already writes:
    • Skip sends the ask's reject option, so that option chosen reads Skipped: .
    • A bare {outcome: "cancelled"} on an ask that has a reject option can only be acpmux's own cancel (a stopped turn, close or shutdown), since Skip would have sent the option. It reads Stopped before an answer: .
    • An ask without a reject option can't tell the two apart, so it keeps Not answered: .
  • respondentOf reads answeredBy.device as a remote answer. The device is an install id, not a name, so the answered row says Answered on another device. A session answer ({session, name}) reads as before.
  • cancelledKey picks the line, and the card and Copy both use it, so they read the same.
  • Three new strings in the agent-pane catalog, in all 21 locales.

Do not touch: the compact answered layout, hover and previews (#24325), the pending card, every default. The muted line keeps #24325's look.

Follow-up, not here: a real device name ("Answered on Leo's iPhone") needs the feed bridge to send one; today it sends only an install id.

Gallery

  • agent-pane.question-card gains skipped, stopped and answered-elsewhere states.

  • agent-pane.question-card-ended shows three looks of the unanswered line, each for Skipped and Stopped:

    • line (shipped, my pick): #24325's muted line, with only the words changed. It is an ended ask, so it should stay quiet under the turn.
    • pill: in the answered row's pill.
    • rule: a hairline at its start.

    The alternatives are gallery CSS only.

Before/after (synthetic fixtures only), built at 3af6c482d8b9: https://cmux-lawrences-mac-mini.tail137216.ts.net:18796/compare/tp-askquestion-answered/?entry=question-card (tailnet). The gallery advisory run attaches the stills.

No capture: webview-only change.

Test

question/record.test.ts:

  • The reject option chosen reads as skipped, and a bare cancel on the same ask as stopped.
  • A bare cancel on an ask without a reject option keeps no reason.
  • answeredBy {device, feedItem} reads as a remote answer; {session, name} still names the session.

Observed / Not run

  • huge-blue, at a9dd1be14745 (3af6c482 only formats):
    • bun test src/agent-session/acpmux/question: 21 pass, 0 fail.
    • vp lint: 0 warnings, 0 errors. vp check --no-fmt --no-lint (tsc): no type errors. acpmux-protocol:check: current.
    • vp fmt --check: only src/pages/settings/components/HostSections.tsx, which this PR doesn't touch; it fails the same way on base, and #24440 (another lane) fixes it.
  • Not run: a live Skip, stop and remote answer through acpmux. The tests use the record shapes acpmux writes (permission_decision, hub/person.rs answered_by).

Changelog

Changed: An agent's question that ended without an answer now says whether it was skipped or the turn stopped, and an answer given on another device says so.

Agent session: cosmic-gray

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Makes the agent question card show how an unanswered ask ended and whether the answer came from another device.

An ask that ended without an answer previously always read "Not answered: ". It now distinguishes three cases: the person pressed Skip, so it says Skipped: ; the turn was stopped or the chat closed before an answer, so it says Stopped before an answer: ; or the record can't tell (the ask has no reject option, so a Skip and a stop leave the same trace), and it keeps Not answered: . The line now also reads the question with its Markdown marks stripped. An answer made from another device, which the record names by install id only, now reads Answered on another device instead of looking like a local answer.

All changes are in the webview's question card; acpmux and Swift are unchanged.

  • The card and Copy both build the line with cancelledArgs, so each strips the question's inline marks and reads the same text.
  • Added skipped, stopped, and answeredElsewhere strings in all 21 locales.
  • A new gallery entry shows the unanswered line in three looks: the shipped muted line, plus a pill and a hairline rule as CSS-only alternatives.