Repository navigation
feat(chat): continue a stopped answer, and ask a bad one again - #153
Merged
Merged
Conversation
Closes #151, the second half of #143 and the last child of #138. A stopped, truncated or failed answer is kept and explained (#142), and until now that was a dead end: the reader could see what arrived and had no way to get the rest. Two actions, two meanings: - **Continue** writes *into the same answer*. The part that arrived seeds the new turn — on both sides, Main and the renderer, from the same record — so the message grows instead of being replaced by its continuation, and the prompt carries the partial answer plus an instruction to carry on from it. - **Retry** opens a **sibling**: a new row with `attempt_of` pointing at the *first* attempt of the group, so a third attempt joins the same group rather than nesting under the second, and the attempt that failed is left alone. `attempt_of` is option (a) from the issue: one column, additive, queryable, and the same grouping that "regenerate" and multi-branch answers will need. It is deliberately not a foreign key — deleting one attempt must not take its siblings with it, and a dangling id is only ever used to group rows that live and die with the same session. Migration `0018` is a single `ALTER TABLE`. The prompt for both is **rebuilt, not remembered**: the history up to and including the question, so a retry asks the same thing in the same context instead of continuing a conversation that has moved on. That required reading retrieval out of `send-message` into one helper — the third caller is what made the extraction worth it — and the cut is made at the group's first attempt, so re-asking a retry does not include the previous attempt's answer. The buttons appear only where they can help: `canRecover` excludes `blocked`, because the provider refused and an identical request would be refused the same way. Verified: `npm test` 340 pass (four new: the manager links a sibling to the group's first attempt, a continuation extends the same message and creates no row, the renderer appends to what is on screen, and the renderer adds a sibling that points at its question), `npm run typecheck`, `npm run check:design`, `npm run build`, `npm run build:unpack` + `smoke:packaged` — which also round-trips `attempt_of` through the packaged database (24 checks).
15 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #151, the second half of #143 and the last child of epic #138.
A stopped, truncated or failed answer is kept and explained (#142). Until now that was a dead end: the reader could see what arrived and had no way to get the rest.
Two actions, two meanings
attempt_ofpoints at the first attempt of the group, so a third attempt joins the same group rather than nesting under the second, and the attempt that failed is left exactly as it was.The buttons appear only where they can help:
canRecoverexcludesblocked— the provider refused, and an identical request would be refused the same way.The record change (option (a))
One column,
chat_messages.attempt_of, migration0018— a single additiveALTER TABLE. Additive, queryable, and the same grouping that "regenerate" and multi-branch answers will need, so it is built once.It is deliberately not a foreign key: deleting one attempt must not take its siblings with it, and a dangling id is only ever used to group rows that live and die with the same session (they are all in one session, which cascades as a whole).
The prompt is rebuilt, not remembered
replayPrompttakes the history up to and including the question, so a retry asks the same thing in the same context instead of continuing a conversation that has moved on. Two details that took a moment to get right:send-messageinto one helper, because the third caller is what makes the extraction worth it. The failure semantics are unchanged: a failed search is recorded asfailedand never blocks the answer.Verification
npm testnpm run typechecknpm run check:designnpm run buildnpm run build:unpack+smoke:packagedattempt_ofround-trips through the packaged databasenpx eslint <changed files>no-explicit-anyinchatHandlers.ts; the two new handlers useunknown[])New coverage: the manager links a sibling to the group's first attempt, and a continuation extends the same message without creating a row; the renderer appends to what is on screen rather than starting a second message, and adds a sibling that points at its question while leaving the failed attempt untouched.
Where the epic stands
With this merged, every Definition-of-Done item in #138 is satisfied — statuses are persisted and rendered, the terminal outcome is set exactly once, the renderer is told after the write, a simulated provider stream is driven end to end, and the four checks pass. #143's parent issue closes with its two halves (#150 merged, this one) and #138 closes with it.