fix(core): persist client approval decisions on buffered tool parts
Summary
Fixes #1535. A restored conversation kept the approval request's { id } without the explicit client decision. After tool output arrived, AI SDK UIMessage validation rejected that persisted part on a later turn.
ConversationBuffer.applyApprovalResponses now copies only explicit boolean client decisions to buffered tool parts, marks changed messages pending, and seeds the explicit input part if limited/semantic memory omitted it. Terminal output/error/denied parts keep their stored approval evidence instead of accepting a conflicting stale decision. No approval is inferred from output existence or a tool configuration flag.
Wired after both buffer-ingestion paths; semantic input accepts UI messages and converted model-message histories.
Validation
- Approved result retains
approved: true, and a turn-3 follow-up returns the expected mock response. - Denied response retains
approved: false. contextLimit: 0retains explicit approval evidence.- Buffer tests verify pending true/false decisions, terminal evidence preservation, missing-context seeding, and no invented decision when boolean approval is absent.
- The three pending/terminal buffer regressions fail against the prior implementation and pass with the changes.
- Four related suites: 165 tests pass.
tsc --noEmitpasses. Biome has only existing agent.ts complexity warnings. - Patch changeset included.
Scope
No public API change. The separate pre-existing denied-output normalization path remains outside this fix: it stores an execution-denied model output under output-available, which may fail later validation. #1538's interruption window and replay protection are handled separately in stacked PR #1541.
Omitted-context follow-up
Hydrate complete stored approval-message owners before seeding them, and retain the whole supplied message when the buffer has no owner, rather than upserting a one-part snapshot. The regression now supplies a partial approval message under runtime memory.options.contextLimit: 0 and verifies stored text and a completed sibling tool survive. Earlier fixtures incorrectly put contextLimit in the Memory constructor, where it was ignored; those have been corrected. A legacy terminal part missing its boolean decision accepts an explicit repair without changing terminal state; existing terminal decisions remain protected. Core package typechecking excludes *.spec.ts, so a clean package typecheck alone is not proof of fixture validity.
Summary by CodeRabbit
- Bug Fixes
- Approval and rejection decisions for tool calls now persist across conversation turns, including when conversation history is restored or only part of a message is submitted. Tool results and existing approval decisions are retained, helping prevent stale or missing decisions from affecting follow-up turns.