Repository navigation
Conversation
/undo restored a prompt whenever session.inbox.cancel returned 204. That cancel is a no-op when the prompt was already delivered, and it could reach the server before the prompt's own POST. Undo now runs in the per-session send chain, cancels, and reads the message back. When the message exists, it interrupts, waits, and stages the revert. Fixes #54150
There was a problem hiding this comment.
The fix works. With the bot's #54150 script and the simulated model, /undo right after sending left ALPHA and its reply in the next model request and in message.list on v2. With this PR both are gone and only the replacement prompt is stored. Undo while streaming and after the reply finished still works. typecheck passes in packages/tui and packages/client, the new TUI tests pass here (they fail on the base), and the nearby client data tests pass.
The logic looks sound: on the server, delivering a prompt removes it from the inbox and creates its message in a single step, so cancel followed by message.get reliably shows whether the prompt was delivered.
Public API: unchanged. The only additions are data.session.undo and data.session.input.withdraw, internal methods in the shared Solid data layer. Server, protocol and generated clients are untouched.
One request about the test, see the inline comment.
Related: #41428
|
|
||
| type WorldMode = "overtake" | "delivered" | "rejected" | "queued" | ||
|
|
||
| function createWorld(mode: WorldMode, sessionID: string) { |
There was a problem hiding this comment.
This test is very large for what it checks: a 350-line fake server tested through the TUI, with a polling waitUntil/Bun.sleep loop and manual barriers. The behaviour that changed lives in createData (session.undo / session.input.withdraw). Could the request-order checks (cancel → message.get → withdrawn, or → interrupt/wait/revert.stage) move into a short test next to packages/client/test/solid-inbox-race.test.ts? At most one small TUI case would then cover the /undo wiring. That would be easier to follow and less timing-sensitive.
Move cancel-then-probe undo into data.session.undo / input.withdraw so cancel cannot overtake the prompt POST, matching the approach in anomalyco#54163. Wire TUI /undo, message Revert, queued undo, and app revert through it.


Why
/undoright after sending put the prompt back in the composer but left the turn in the session, so the next request still carried it. The TUI chose its branch from a pending view that lags the server. It then treated the204fromsession.inbox.cancelas proof that the prompt was withdrawn. That cancel is an idempotent no-op when the prompt was already delivered, and it could also reach the server before the prompt's own POST, which waits forswitchModel.Fixes #54150.
What changed
data.session.undoruns in the per-session send chain, after the prompt's admission settles. While the store still shows the prompt as pending, it cancels and then reads the message back. A missing message means the prompt was withdrawn. Otherwise it interrupts, waits, and stages the revert, as/undodid before.data.session.input.withdrawreturns"withdrawn"or"delivered". Queued prompt undo uses it and warns instead of restoring a prompt that was already sent./undoand queued prompt undo call these methods instead ofsession.inbox.cancel.Scope
session.inbox.cancelstays the idempotent204from the API audit. Server, protocol, and generated clients do not change.Tradeoffs
409on cancel would reverse the audit decision and would still lose the race where the cancel arrives before the prompt POST./undo. A local drop would make that instant, but it needs extra state in the data layer.Blast Radius
The change touches the TUI undo commands and adds two methods to the shared Solid data layer. Undo of an already delivered turn sends the same three requests as before. Undo of a pending prompt adds one message read after the cancel.
Verification
packages/tui/test/session-undo.test.tsxdrives the real/undoagainst a fake server. It covers the delivered race, the overtaking cancel, a rejected admission, and queued undo. All four cases fail onv2and pass here.v2, the fresh and latency runs kept the undone exchange in the next model request and inmessage.list. Here all three runs exclude it.bun typecheckinpackages/clientandpackages/tui, andbun run checkat the root.Added by the opencode bot (AI-generated).
Related: #41428