Skip to content

feat(desktop): regenerate the last reply by taking the turn back first - #1165

Draft
sosidudku1 wants to merge 2 commits into
rel/xp-buildfrom
fix/ato-1300-regenerate-last-reply
Draft

sosidudku1 wants to merge 2 commits into
rel/xp-buildfrom
fix/ato-1300-regenerate-last-reply

Conversation

@sosidudku1

@sosidudku1 sosidudku1 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

What

  • Bridge (ATO-1299): AgentClient.rollbackLastTurn(sessionId, expectText), an IPC handler agent:rollbackLastTurn and a preload method. A 404 with session_not_found means the chat is unknown. Any other 404 means an agent without the route (supported: false). A 409 carries the agent's refusal code.
  • Regenerate (ATO-1300): the button under the last reply of a turn without tools is now "Regenerate", with its own data-regen. It checks the same composer gates as resendUser before asking the agent, because once the turn is gone the question only lives in the window. Those gates are: open microphone, a draft in the way, a running turn. Then it takes the turn back, removes the question and the rows after it from S.log in place (the transcript window keys on that array), and sends the question through submit().
    • An agent without the route, an unsaved chat or an older question keeps the old resend.
    • Any other refusal shows why and changes nothing.
  • Try again (ATO-1301): "Try again" on a failed turn's row uses the same handler. On any refusal other than a running turn it falls back to the old resend, so a failed turn that ran a tool can still be retried.
  • Unchanged: the user bubble's own button, the retry command and the greyed hint under a reply that used tools.
  • Matching the question: the question on screen goes along as expectText and expectPriorQuestions (its position). If the agent's last question is a different one (a failed turn it never stored), or the turn was stopped, it removes nothing and the window falls back to the resend.
  • After the call: if another chat was opened meanwhile, the question is kept and put in the composer when that chat opens again. If the composer was in use, the chat is redrawn from the agent and the question waits for the box.

Why

Users hit "Try again" after provider errors, and it showed the question twice: on screen, after a reopen, and in the model's context. The context bar grew on every retry.

Needs the agent route from the main PR (#1162). Without it, the old behaviour stays.

How it was verified

  • New desktop/test/rollback-last-turn.test.mjs covers the client against a stand-in agent and regenerateReply in a vm. retry-answer and acted-retry-hint are updated for the new button.
  • Only node --check has run. Builds, tests and the app have not been run yet.

Refs ATO-1299, ATO-1300, ATO-1301

Not yet checked on the second Mac.

"Ask again" under the last reply and a failed turn's "Try again" used to
send the question once more on top of the old turn, so the transcript and
the agent's context kept both copies. The agent can now remove its last
question and answer (POST /api/sessions/{id}/rollback-last-turn), so the
new answer replaces the old one.

- agent-client: rollbackLastTurn(sessionId) returns {ok, supported, text,
  code, error}. The route's 404 (session_not_found) is an unknown chat; an
  agent without the route (404 with no code) is reported as unsupported.
  A 409 carries the agent's refusal code. IPC agent:rollbackLastTurn and
  the preload method go with it.
- renderer: the reply's button is "Regenerate" with its own data-regen.
  regenerateReply checks the same composer gates as resendUser before the
  agent is asked (once the turn is gone the question only lives in the
  window), then drops the question and the rows after it from S.log in
  place (the transcript window keys on the array) and sends the question
  through submit(). An agent without the route, an unsaved chat, a running
  turn or an older question keep the old resend. A refusal under the reply
  says why and changes nothing. If the agent took back a different
  question than the one on screen, the chat is redrawn from the agent and
  the question waits in the composer.
- Try again on a failed turn uses the same handler and falls back to the
  resend on any refusal other than a running turn (a failed turn that ran
  a tool stays retryable). The retry verb and the user bubble's button do
  not change; the greyed hint under a reply that used tools stays.

Refs ATO-1299, ATO-1300, ATO-1301
…n the chat changes

Regenerate and Try again now send the question's position (questions
before it, steers left out) with its text, so a failed "continue" that
never reached the agent cannot take back an earlier "continue" and its
real answer. A stopped turn, which the agent now refuses (turn_stopped),
falls back to the old resend like a mismatch.

After the agent took the turn back: if another chat was opened during
the call, the question is kept and put in the composer when that chat is
opened again; if the composer was in use, the chat is drawn again from
the agent (the old answer is gone there) and the question waits for the
box to be free.

Refs ATO-1299, ATO-1300, ATO-1301

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.

1 participant