Repository navigation
Agent pane: a guest's message waits for the owner (Approve / Decline) - #26742
teamleaderleo wants to merge 21 commits into
Conversation
|
Bead |
|
Found 138 test failures on Blacksmith runners: Failures
...and 128 more test failures. View all on Blacksmith: run 40652525325, run 40652525208
|
05b26eb to
ba613c0
Compare
53c392b to
796ec1a
Compare
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) red: web / Web status; base red: the same failure on the base 87b569c; does not block |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
A queued prompt acpmux holds for the owner (awaitingOwner, from a link guest)
is tracked by its authoritative record. THE RULE (direct.ts Hold): it is
approvable only when the pane holds that record (a queued / queue_updated
record, or _acpmux/queue_get) for its current revision; a listing never makes
it so. Text, images, parts and revision always come from one record.
- answerHeld sends _acpmux/queue_approve {sessionId, promptId, revision} with
the revision the card drew, or _acpmux/queue_decline {sessionId, promptId}.
- A held prompt whose record the pane lacks (an attach page that starts after
it) is read with one queue_get, keyed by selection generation and prompt,
only where acpmux names queueGet; without it the prompt stays updating.
- queueApproval and queueGet come from initialize's features.
- An owner's own queued prompt still settles its bubble on its queued record.
- Needs you lists each held prompt (where queueApproval is served) with one
action, Review, which scrolls to and focuses its card; it never approves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36
cc7ccf0 to
d39e194
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36
The held message's card on the pane model (lane/mp-held-model): a guest's held prompt draws in the transcript as that person's message, its record whole (text as sent, images, documents, audio and links), with Approve (primary) and Decline (quiet) and the quiet label "Waiting for you"; Return approves when the card has focus. Approve names the revision the card drew; "Updating…" (no answers) while the pane lacks the record at its current revision; "Edited since you looked" and "Already answered" for acpmux's queue.stale and queue.not_found. Approved, it waits as a queued message; declined, one quiet line "Declined: <first line>" that opens on a click. The relay allows queue_approve (sessionId, promptId, revision; a gesture), queue_decline and queue_get. Gallery agent-pane.guest-approval with three looks (inline, the pick; bar; menu). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36
d39e194 to
f21a9cc
Compare
|
Owned by an agent session · session |
|
Orphaned: no activity for 13 h, and no lane holds a lease on it. The orphan sweep could not merge |
|
Conflict wave: not merged. This PR is stacked on the acpmux held-prompt base, now carried by #28859. #28859 is under active work by its lane: delta reviews ran at 02:56Z and 03:18Z, and it was updated at 04:54Z. Its acpmux |
|
Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over. Carried on #31144: rebuilt on #31066 (the held prompt in the pane model, which carries #26862) on current feat-cmux-next, with this commit cherry-picked and its authorship kept. The merge resolutions are listed in its body. |
… (carries #26742) (#31144) * Agent pane: a guest's held prompt in the pane's model A queued prompt acpmux holds for the owner (awaitingOwner, from a link guest) is tracked by its authoritative record. THE RULE (direct.ts Hold): it is approvable only when the pane holds that record (a queued / queue_updated record, or _acpmux/queue_get) for its current revision; a listing never makes it so. Text, images, parts and revision always come from one record. - answerHeld sends _acpmux/queue_approve {sessionId, promptId, revision} with the revision the card drew, or _acpmux/queue_decline {sessionId, promptId}. - A held prompt whose record the pane lacks (an attach page that starts after it) is read with one queue_get, keyed by selection generation and prompt, only where acpmux names queueGet; without it the prompt stays updating. - queueApproval and queueGet come from initialize's features. - An owner's own queued prompt still settles its bubble on its queued record. - Needs you lists each held prompt (where queueApproval is served) with one action, Review, which scrolls to and focuses its card; it never approves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36 (cherry picked from commit 9241815) * test: no history paging means no _acpmux/events read of queue records Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36 (cherry picked from commit ee2f0a1) * Agent pane: Approve names the held prompt's turnId; one features read; a failed held-text read is not retried every update The review at 63d8d08 found: initialize declared `features` twice (a compile error after the merge); `_acpmux/queue_approve` requires `turnId` (the queued incarnation the owner saw) and the model dropped it; a failing `queue_get` was retried on every queue-bearing update. Held entries now carry the record's or listing's turnId, Approve sends it (and sends nothing without one), and a held-text read is keyed by listed revision and kept once settled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Agent pane: a guest's message waits for the owner (Approve / Decline) The held message's card on the pane model (lane/mp-held-model): a guest's held prompt draws in the transcript as that person's message, its record whole (text as sent, images, documents, audio and links), with Approve (primary) and Decline (quiet) and the quiet label "Waiting for you"; Return approves when the card has focus. Approve names the revision the card drew; "Updating…" (no answers) while the pane lacks the record at its current revision; "Edited since you looked" and "Already answered" for acpmux's queue.stale and queue.not_found. Approved, it waits as a queued message; declined, one quiet line "Declined: <first line>" that opens on a click. The relay allows queue_approve (sessionId, promptId, revision; a gesture), queue_decline and queue_get. Gallery agent-pane.guest-approval with three looks (inline, the pick; bar; menu). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-c3300437b0af7f1bba36 (cherry picked from commit f21a9cc) * Agent pane: held card fits base's turnId Approve and line layout Base's direct.ts answerHeld names the held prompt's turnId with its revision, so the native relay's queue_approve allowlist takes turnId (relay test and the pane's allowlist test send it). The held card's Approve and Decline stay shown in the DisclosureRow action slot, with its quiet note in caption type, as the line draws on this base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Agent pane: a held guest prompt draws whole, never as a lane notice; card tests fit the card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * test: the read-only transcript draws a guest's lane-report-looking prompt whole Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Agent pane: the read-only transcript draws a person's message whole, never as a lane notice The review at 75448eb found the read-only row (guest page, read-only transcript) still read a guest's prompt as a lane notice, so one that starts like a lane report lost its lead text there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Agent pane: a person's words are never a lane notice, on any row Round 3 of the review at c36794f found two more paths that read a guest's prompt as a lane notice: a held row whose listing names no author, and a declined guest row that lane-notice batching could regroup. Instead of one check per caller, a held or declined guest prompt's message is marked `fromPerson`, and laneNotice (which every card, line and batch reads) returns nothing for it. The read-only row also draws held and declined rows whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * test: no notice entry point turns a person's prompt into a notice Sweeps every person row (held with and without an author, a guest's queued prompt, declined, and a row marked only by its queue marks) through laneNotice, noticeOf, batchLaneNotices, appendLaneNotice and the read-only transcript row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Agent pane: noticeOf never reads a person's row as a notice A row marked person, held or declined is no notice whatever its message object says, so lane-notice batching, inbox projection and every card agree with laneNotice's own fromPerson rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Held message card: no pointing hand on Approve, Decline or the declined line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 * Held message card: its reset buttons set the arrow again all: unset drops the cursor desktop.css gives clickables, so Approve, Decline and the declined line set cursor: default themselves (clickable-cursor.test.ts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-049fca947e3297e5ccd3 --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
HOLD: stacked on #26747 (acpmux held prompts,
lane/mp-holdat 208b5a7). It lands after #26747; rebase onto feat-cmux-next once that merges, then drop this line.What
A guest's message waits for the owner in the agent pane. Leo (2026-10-28), on Theo's T3 Code "Share with guest": "we want to be having, be able to do this kind of flow for CMux multiplayer ... we can work on the agent chat multiplayer." In Theo's flow "each message they send waits for you"; the owner approves each message. Approve sends it to the agent, Decline drops it.
Spec: leo-workspace
design/chat-multiplayer.md, section 2 "Held message card" and "Presence"; plan and inventory manaflow-ai/cmuxterm-hq#4766. This is PR 3 of that plan (owner UI).awaitingOwnerrenders at its place in the transcript as an incoming message from that person: their face and name chip ("Cy Park (guest)" for aguest:id), the text, and in the line's action area Approve (primary text button) and Decline (quiet), then the quiet delivery label "Waiting for you". Return approves while the card has the focus. No motion, no dot, no toast, no focus steal._acpmux/queue_approve {sessionId, promptId, revision}with the revision the queue lists. Decline sends_acpmux/queue_decline {sessionId, promptId}. Both buttons wait while one answer is in flight.queue_approved) the row is the normal queued card ("Read after this turn"). After Decline (queue_declined) it collapses to one quiet line "Declined: " that opens to the whole text on a click.queue.stale(a guest edit since the owner looked) reads quietly in the row's label as "Edited since you looked", with the new text shown and the buttons live again.queue.not_foundreads "Already answered". There is no auto-retry and no toast.initializenames thequeueApprovalfeature. An older daemon draws the guest's message with no actions.needsYou/model.ts._acpmux/queue_approveand_acpmux/queue_declineadded to the pane relay's allowlists (requests, session-scoped, known params). Approve needs a user gesture (.always) because it runs a prompt. Decline needs none. Nothing else changes in Swift.IncomingMessageLine(a newpersonprop: face in the icon column, name chip, no chat lookup), the received line's action area, andQueuedMessageCard's ⋯ menu shape. The action row is not forked.Variants (gallery
agent-pane.guest-approval)look-bar,look-bar-three).look-menu).I picked A. The spec puts the actions "in the row's action area", and chat-transcript.md says all message kinds share one action row. A keeps both answers one click away and visible without hover. It adds no height, so three held messages stack as compactly as three queued ones. B adds a row per message, which costs a lot of height in the three-held stack. C hides the decision behind a menu, which is wrong for the one thing the owner must act on. B and C ship only as gallery variants through
<html data-held-card-look>, which the app never sets.Before / after (gallery captures, fixture content, Chromium on the fast-checks runner)
Before (feat-cmux-next at 5f87f20, the same fixture): a guest's prompt is just a composer-queue row under their name, with no way to hold or answer it.
After, A (pick):
Before for the three-held state:
The header's Needs you badge counts the held messages (1, then 3).
Tests
webviews/src/agent-session/acpmux/conversation/heldMessage.test.tsxcovers these cases:staleis said in the row and the buttons come back.AcpmuxDirectClientand the mock daemon:queued {awaitingOwner, revision}and then a guest edit (queue_updatedat revision 1). Approve sends_acpmux/queue_approve {sessionId, promptId, revision: 1}, Decline sends_acpmux/queue_decline {sessionId, promptId}, and the declined row appears.Review fixes (Astra, BLOCK at 53c392b). Each fix has a regression test, and each test fails on the 53c392b sources and passes at head:
revisionon_acpmux/queue_approve, so a native Approve is no longer refused. Decline stays{sessionId, promptId}. Tests:AgentPaneQueueApprovalRelayTests.swift(shapes, scope, gesture), and a webview test that checks the requests the pane sends againstAcpmuxPaneMethods+Params.swift.queuedSendsand the composer's acceptance on itsqueuedrecord again. Only a guest's held prompt waits forqueue_approved. Test: a send to an idle chat that acpmux queues leaves no pending bubble, and the composer is released.queue.stale. Test: a card drawn at revision 3 sends 3.queueApprovalis served, and its Approve also names the item's revision. Test included.Second review (re-review at e0be7f0, BLOCK on two more):
5. A listed revision never joins text read from an older record. Before this fix, an edit past the queue list's 200-character preview left the old full text paired with the new revision. A held row now always carries the revision of the text it shows. When acpmux lists a newer revision whose full text hasn't arrived, the card reads "Updating…" and both Approve and Decline are disabled until the edit's own
queue_updatedbrings the text. Needs you offers no answers for such an item. Test: an edit beyond 200 characters, with the list arriving before the edit record.6. Rendered-revision proof: the test draws the card at revision r, sends the client a
queue_updatedto r+1 without re-rendering the card, and clicks Approve on the old card. The outgoingqueue_approvecarries r. On the 53c392b sources the same test sends r+1 (run 40661617401).Third review (at bc83751, two P2s where the card could stay stuck in "Updating…"):
7. A queue list that names a revision lower than the text the pane already holds is stale and ignored. This happens when an attach replays live records and then merges its older snapshot. The row shows "Updating…" only when acpmux lists a newer revision than its text. Test: an older snapshot merged after revision 1's record (no Updating), plus a newer listing (Updating).
8. A held prompt whose
queuedorqueue_updatedrecord is older than the opened page has only its list preview. The pane now reads that prompt's newest record with an existing call,_acpmux/events(kinds: ["queued", "queue_updated"], newest first, 200 records a page, at most 10 pages). It takes text and revision from that one record and never goes back to an older revision; a record that arrives live meanwhile wins. Test: a cold attach whose page starts after the held record. The card opens updating, reads the record, and becomes approvable. acpmux needs no change.Fourth review (at 7caa97c, two P2s in the backfill):
9. Held-record reads are now keyed by selection generation and prompt id, the set of reads in flight is cleared when the session resets, and a read for a selection the pane has left stops at its next page. Before, leaving and reopening a chat in the middle of a read (A, then B, then A) suppressed the reopened card's read and left it stuck in "Updating…". Test: the card is left mid-read and reopened; the late result from the first read is discarded, and the card becomes approvable.
10. A held row's text, images and revision always come from one record. The backfill installs the record's own images. Kept text keeps its own images when a stale or same-revision listing arrives. A live held
queuedrecord names only its own images. Test: the images change between the list and the record, and the card shows the record's images with its revision; a stale, same or newer listing never swaps the images under kept text.Fifth review (at 472a7e5): one rule replaces the preview heuristics. A held card is approvable only when the pane holds that prompt's authoritative record (a
queued/queue_updatedrecord, or a read of one) for its current revision.direct.ts(Hold),revisionis set only from a record, together with that record's text, images and parts. A listing (a 200-character preview) never sets it, whatever its preview looks like.queuedMessageRowsmarks every held row without a record revision asupdating, so the card has one place to learn whether it may offer Approve. Test: a short preview with no "…" is not approvable.recordedFiles, from UserBubble), and documents and audio use the same file chips.queuedMessageRowskeeps the held row'sinlineParts. Test: a 14-line message with a PDF, an audio note and a link._acpmux/eventsfilters only by kind and seq, and_acpmux/composerslists the 200-character preview, so a lookup needs a new_acpmux/queue_get(on acpmux: a guest's prompt waits for the owner #26747; not added here). Until it exists, the existing bounded read stays as the interim, with no new paging. A held prompt whose record is not found stays "Updating…" and cannot be approved.Receipts (cmux-ci fast checks on this PR):
heldMessage,queuedMessages,queueAuthor,promptAccepted,promptDelivery,needsYou/*,multiplayer/*,test/agent-pane-needs-you.test.tsxandtest/queued-user-footer.test.ts. All pass,vp fmt --checkis clean,vp checkreports no type errors, andvp lintfinds 0 errors (run 40678885462).test/agent-pane-queue-collapse.test.tsandtest/agent-pane-queue-row-click.test.ts: 4 fail on head and the same 4 (of 6) fail on base 458d9bf, so they are base reds and not from this PR.gallery:build, thenscripts/gallery-matrix/runner.ts, Chromium). The review fixes change no pixels, so I did not recapture.Observed / Not run
AgentPaneQueueApprovalRelayTests(macOS only; they need a fleet Mac run). There is also no live daemon end to end; that is plan PR 8.Risk
risk:normal. The change is UI plus two relay allowlist entries for owner-only acpmux methods. acpmux refuses both for any authored caller (queue.owner_only), and Approve needs a fresh user gesture in the relay. Nothing changes for chats without guests. Localization: 9 new keys in the agent pane catalog in all 21 locales (multiplayer.guestName,queue.held.waiting|approve|decline|declined|stale|gone|updating|review).Size: about 1230 changed lines, of which about 740 are tests (webview and Swift) and gallery fixtures.
Changelog
Added: in a shared chat, a guest's message waits for the owner with Approve and Decline.
Agent session: agent session
🤖 Generated with Claude Code