fix(core): persist client approval decisions on buffered tool parts

TO
5h ago
+515-1

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: 0 retains 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 --noEmit passes. 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.