Public: anyone can read this; the crew can change it.
/talk takes a message while the agent is working: queue it next, or steer the current turn #8893
Check · Review · Land · Live done — Landed and live in production.
`/talk` now keeps the composer open while the agent works. Send queues the message for after the turn, shown dimmed as "Queued" with a Cancel. Steer stops the turn and sends the message at once. The `keep_plan` tool wasn't available to me, so there is no plan on the issue.
shipped production is serving it deploy bbd5a0b
/talk now keeps the composer open while the agent works. Send queues the message for after the turn, shown dimmed as "Queued" with a Cancel. Steer stops the turn and sends the message at once. The keep_plan tool wasn't available to me, so there is no plan on the issue.
Tests pass for the new queue file, the conversation suites and the talk page. tsc, design-lint and gate.ts are clean. I ran only the tests covering this change, not the whole suite, which the forge's check runs on push.
- Queue: while a turn runs, Send holds the message in memory. When the turn ends, the next one starts as its own turn, in order. At most 5 can wait, and a sixth is refused with 429
queue_full. Cancel puts the text back in the box. - Steer: stops the running turn, so the partial reply stays and a "Stopped." line follows it. The steering message goes ahead of anything already queued.
- Stop: now a separate button, and it also clears the queue.
- API:
POST /api/v1/conversation/messagestakes an optionalduringofqueueorsteer. Without it, callers likeforge talk, ssh and MCP behave as before and the words are handed to the running turn.DELETE /api/v1/conversation/queue/:idcancels a queued message, andGET …/turnslistsqueued. Both are in the OpenAPI contract. - Page: the server draws queued messages on reload. The plain no-script form also queues.
- Tests:
test/conversation-queue.test.tshas the two you asked for, plus Stop clearing the queue. I added DOM tests for the buttons intest/talk-page.test.tsand replaced the old "Send becomes Stop" test, since Send no longer turns into Stop.
Changed on purpose:
- Script size cap: the talk page's script cap in
test/talk-page.test.tsgoes from 11.6 KB to 13.9 KB for this feature. It is now 13.76 KB. - Design doc:
.talk-queuedis named indocs/design/areas/talk.md.
Left out:
- Restart: the queue lives in memory, so a forge restart drops waiting messages. A restart already cuts and resumes the running turn, and I didn't persist the queue on top of that.
- Attachments: they are attached when the queued message actually starts, so a picture queued behind a running turn works but isn't checked until then.
- Queued edits: a queued message can't be edited, only cancelled and retyped.
To try it, send a message, then send another while the agent answers. It shows as queued. Cancel returns it to the box, and Steer interrupts the turn.
Queue/steer logic in conversation.ts reads sound (single-threaded drain, per-user queue, cancel scoped to the caller's conversation, escaping fine). Changes needed, all in one round:
- HIGH, both doors: nothing in src/tui/ gains queue/steer/cancel, and there is no docs/features/talk.md update. What a person does on the page must work in the terminal too; add the screen path and the how-to.
- HIGH, no API tests: nothing covers
duringvalidation (invalid_during 422),queue_full429 on the 6th,steervia POST, DELETE /conversation/queue/:id (404 for another user's id, cross-origin 403, read-only token 403, unauthenticated 401), or thequeuedfield of GET turns. Add them; each must fail without the change. - HIGH, design/JS-off: the server-rendered queued item (src/web/pages/talk.ts) carries a Cancel
<button type=button>that does nothing without JavaScript, and mutations must be a button in a post form. Make Cancel a post form to a route that works with JS off (or do not draw it without script). - MEDIUM: Cancel in talk_client.ts overwrites whatever the person has since typed in the box (
box.value = ...); append or only restore when empty. Also any non-ok response (403, 500) is treated as 'already gone' and removes the item; only 404 should. - MEDIUM: queued messages keep their pending attachment ids un-claimed until they start, so the same file can be sent twice or deleted meanwhile; reserve or re-validate at queue time. Queued items also show only in the sender's tab, not other tabs/terminal viewers.
- LOW: stray formatting at conversation.ts
stoppers.set(...); const signal(two statements on one line) - split it. - LOW: the new tests poll with sleeps up to 4 s and a sh/sed stand-in; keep them quick and non-flaky. The 11.6 KB to 13.9 KB script cap raise is large; trim the client where possible.
I've pushed fixes for all seven review items to the same branch. tsc, design-lint, gate.ts and the 504 tests for talk, conversation and the API contract pass here. The forge's full check runs on the push.
- Terminal:
ssh <forge> talk /queue <words>,/steer <words>,/queuedand/unqueue <id>now exist, and they work with--attach.docs/features/talk.mdhas a new "Queue and Steer while an answer runs" section covering web, API and terminal, and the queue row in its feature table now points at #8819. - API tests: the new
test/conversation-queue-api.test.tscovers these:
- during: 422 invalid_during for an unknown value or a non-string.
- Queue limit: 429 queue_full on the sixth queued message.
- Steer: steer by POST returns interrupted.
- Cancel: DELETE of a queue id gives 401 signed out, 403 cross-origin, 403 for a read-only token, 404 for another user's id, and 200 then 404 for the owner.
- Listing: the queued field of GET …/turns.
- Files: the file-held rules.
- Terminal and stream: the ssh commands and the stream's queued frame.
- Cancel without JavaScript: Cancel is now a post form to
POST /talk/queue/:id/cancel. It checks the session and the same origin, and redirects with a flash message when JavaScript is off. The script only does it in place. The route has a test. - Cancel in the browser: it now appends to whatever is in the box instead of overwriting it. Only a 404 removes the item; a 403 or 500 keeps it and shows an error. Both have DOM tests.
- Attachments and other tabs:
- A file in a queued message is held. It can't be removed (409), and a second message that names it is refused with file_taken. A file that is gone by the time the message runs is left out.
- The stream sends a queued frame whenever the list changes, so every open tab sees the queue. The terminal sees it with talk /queued.
- The joined statement at
conversation.ts:454is split. - I trimmed the script cap raise from 13.9 KB to 13.4 KB. The script is 13.3 KB and was 11.6 KB before this feature. I didn't squeeze it further.
Not changed: the new tests still poll with short sleeps (10–20 ms steps, giving up after a couple of seconds only when something has failed). They use the sh stand-in already used in this repo's other conversation tests. The API tests start turns through say() so the 12-a-minute limit doesn't affect them.
The check failed because no commit message had a "Try:" line. I added an empty commit with one and pushed it. The code is unchanged, and I didn't re-run the gate, tsc or the tests here.
The line reads: "open /talk on the preview, send a message, and while the forge answers send another (it shows Queued with Cancel), then press Steer to stop the answer and send at once."
How to try it: on the preview of #8893, open /talk, send a message, and send another while the forge answers. The second shows as Queued with a Cancel. Press Steer to stop the answer and send the message at once.
Behaviour:
- Queue: Send queues a message behind the running turn, up to 5. Each runs as its own turn, in order.
- Steer: stops the turn, keeps what it had said and a "Stopped." line, then sends the message ahead of anything queued.
- Stop: also clears the queue.
- Restart: a forge restart drops anything waiting.
Doors:
- API:
POST /api/v1/conversation/messagestakesduringset toqueueorsteer.DELETE /api/v1/conversation/queue/:idtakes a queued message back, andGET …/turnslistsqueued. - Page: Cancel is a post form to
POST /talk/queue/:id/cancel, so it works without JavaScript. Every open tab sees the queue over the stream. - Terminal:
ssh <forge> talk /queue,/steer,/queuedand/unqueue <id>.
A file attached to a queued message is held, so it can't be removed or sent twice. The talk page's script cap is raised from 11.6 KB to 13.4 KB.
Every earlier ask is met: terminal door (/queue, /steer, /queued, /unqueue) with docs/features/talk.md (README untouched); API tests for during, queue_full, DELETE auth/origin/scope/ownership; Cancel as a post form that works without JS; Cancel appends to the box and only a 404 removes the item; file held in a queued message; split statement. Reads clean: queued text goes through html``, cancel is owner-scoped, same-origin and scope checked, queue capped at 5, Stop clears it. Non-blocking: the queue is memory-only, so a restart drops waiting messages (stated in the docs).
/talknow keeps the composer open while the agent works. Send queues the message for after the turn, shown dimmed as "Queued" with a Cancel. Steer stops the turn and sends the message at once. Thekeep_plantool wasn't available to me, so there is no plan on the issue.Tests pass for the new queue file, the conversation suites and the talk page.
tsc,design-lintandgate.tsare clean. I ran only the tests covering this change, not the whole suite, which the forge's check runs on push.queue_full. Cancel puts the text back in the box.POST /api/v1/conversation/messagestakes an optionalduringofqueueorsteer. Without it, callers likeforge talk, ssh and MCP behave as before and the words are handed to the running turn.DELETE /api/v1/conversation/queue/:idcancels a queued message, andGET …/turnslistsqueued. Both are in the OpenAPI contract.test/conversation-queue.test.tshas the two you asked for, plus Stop clearing the queue. I added DOM tests for the buttons intest/talk-page.test.tsand replaced the old "Send becomes Stop" test, since Send no longer turns into Stop.Changed on purpose:
test/talk-page.test.tsgoes from 11.6 KB to 13.9 KB for this feature. It is now 13.76 KB..talk-queuedis named indocs/design/areas/talk.md.Left out:
To try it, send a message, then send another while the agent answers. It shows as queued. Cancel returns it to the box, and Steer interrupts the turn.
Closes #8819