Skip to content

fix(tui): undo admitted messages after interrupt - #58118

Open
YidaWeng wants to merge 7 commits into
anomalyco:devfrom
YidaWeng:undo-admitted-message
Open

YidaWeng wants to merge 7 commits into
anomalyco:devfrom
YidaWeng:undo-admitted-message

Conversation

@YidaWeng

@YidaWeng YidaWeng commented Oct 22, 2026 •

Copy link
Copy Markdown

Issue for this PR

Fixes #39736

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Undo and message-dialog Revert could call session.revert.stage with an admitted inbox ID that is not in session_message yet (common after interrupt, before delivery). The server then returns Message not found.

Undo now lives in the shared Solid data layer as data.session.undo / data.session.input.withdraw, on the per-session send chain so cancel cannot overtake the prompt POST (same approach as #54163). Withdraw probes session.message.get and treats only MessageNotFoundError as withdrawn; otherwise it interrupts, waits, and stages. TUI /undo, message Revert, queued-prompt undo, and app revert all use that path.

How did you verify your code works?

  • bun test test/session-undo.test.tsx in packages/tui — 4 passed (admission overtake, delivered race, rejected admission, queued already-delivered)
  • Earlier focused unit coverage for the MessageNotFound-only probe was replaced by these end-to-end fixtures

Screenshots / recordings

N/A — behavior/state fix, no UI chrome change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Fixes #57383

Related: #54163, #52537

Withdraw undelivered inbox prompts on undo/revert, and fall back to staged
revert when cancel races with promotion. Fixes message-dialog revert as well.

Fixes anomalyco#39736
@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Call out the compliance template so agent sessions fill the body before opening PRs.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes Revert from the message actions while a turn is running. I checked it with a simulated model in the TUI: on the base, Revert fills the prompt, shows Session is busy and keeps the turn running. With this PR it interrupts, waits, reverts and then restores the prompt. I couldn't reproduce the /undo failure from #39736 on the base: after a steer and an interrupt, /undo already reverts the steer there, because the base withdraws pending prompts itself. So for /undo the new part is only the fallback for when the prompt was delivered at the same moment as the cancel, and I couldn't trigger that case. Unit tests and the tui and app type checks pass.

The public API is unchanged; this only changes client-side undo and revert.

Concerns (inline): /undo no longer goes through mutatePending, so it loses that function's guard against firing twice and its error toast. The delivered-or-not check treats any error from message.get as "withdrawn". Also, #54163 makes the same cancel-then-message.get change for /undo in the shared data layer (data.session.undo, run in the session's send chain). It also handles a cancel that reaches the server before the prompt's own request. The two will conflict in index.tsx, so it would be good to agree with that PR on one shared helper instead of a separate TUI-only undo.ts.

Fixes #57383
Related: #54163, #52537

Before

After

Comment thread packages/tui/src/routes/session/undo.ts Resolved

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only new commit adds a section to the root AGENTS.md. The undo code is unchanged, so the two earlier points are still open: errors from message.get are treated as "withdrawn", and the pending branch of /undo skips mutatePending. The fix still behaves as I tested before: Revert during a running turn now interrupts, reverts and only then restores the prompt.

The public API is unchanged.

The new AGENTS.md section is a repository-wide rule for agents writing PR descriptions, which has nothing to do with this fix. Please drop it from this PR and propose it on its own if you want it.

Comment thread AGENTS.md Resolved
Keep /undo pending cancel on mutatePending for single-flight and toasts.
Treat only MessageNotFoundError from message.get as withdrawn.
@YidaWeng

Copy link
Copy Markdown
Author

Addressed the two inline review points in 53ebc0f:

  1. message.get failures other than MessageNotFoundError now propagate (toast), instead of being treated as withdrawn.
  2. /undo pending cancel goes through mutatePending again for single-flight + toast, then probes projected history.

On overlap with #54163: happy to converge on data.session.undo once that lands; this PR keeps a small client helper so dialog Revert and /undo share the cancel-then-probe behavior in the meantime.

@YidaWeng

Copy link
Copy Markdown
Author

Also dropped the unrelated AGENTS.md note from this PR (reverted). Review threads for the two undo concerns and that docs comment are resolved.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both earlier points are addressed. Only "message not found" from message.get now counts as withdrawn, and other errors show a toast and leave the prompt alone, in both the TUI and the app. /undo cancels through mutatePending again, so it keeps the guard against firing twice and the failure toast, and the prompt is not restored if the cancel fails. The AGENTS.md change is reverted too.

I ran the Revert-while-a-reply-streams steps again with a simulated model. The turn is interrupted, the message is reverted and the prompt comes back, while on the base nothing is reverted. Steer, interrupt, then /undo still works. The unit tests (7) and the tui and app type checks pass.

The public API is unchanged. I found nothing new.

Before

After

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.
@YidaWeng

Copy link
Copy Markdown
Author

Follow-up on the remaining soft concern from the first review (overlap with #54163):

In 8e24280, undo moved into the shared client data layer as data.session.undo / input.withdraw on the per-session send chain, so cancel cannot overtake the prompt POST. TUI /undo, message Revert, queued undo, and app revert all call that path. The TUI-only undo.ts helper is gone.

bun test test/session-undo.test.tsx — 4 passed.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving undo into the shared data layer (data.session.undo / data.session.input.withdraw on the per-session send chain) works as intended in the TUI. With a simulated model, Revert from Message Actions during a streaming turn now interrupts, reverts and restores the prompt. The new TUI tests (4) and the tui type check pass.

There is one blocker: this commit removes recall from createSessionRevert in packages/app/src/session/revert.ts, but packages/app/src/session/composer/region.tsx (line 222) still calls revert.recall(item). The app type check now fails with TS2339, and the app would crash when it tries to recall a prompt. This looks like a leftover from merging dev, which added recall in #57466; please restore it.

Public API: data.session.undo(...) and data.session.input.withdraw(...) are new methods on the data store in @opencode/client (solid/data.ts). This is backwards compatible.

This is now essentially the same approach as #54163, which is still open, so maintainers will need to pick one of the two.

Before

After

Comment thread packages/app/src/session/revert.ts Resolved
Keep recall on createSessionRevert; region.tsx still calls it when
recalling a prompt into the composer.
@YidaWeng

Copy link
Copy Markdown
Author

Restored recall on createSessionRevert (accidentally dropped when wiring data.session.undo). region.tsx can call revert.recall(item) again.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recall blocker is fixed: the helper is back in createSessionRevert, and the app type check passes again. I found nothing new in this commit.

Public API: unchanged from my last review. data.session.undo(...) and data.session.input.withdraw(...) are new, backwards-compatible methods on the data store in @opencode/client.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tui: reverting during generation restores the prompt but does not revert tui: undo fails for admitted message after interrupt

1 participant