Commit 5c75dd6
fix(app-shell): keep the approval envelope + pending-action id in the chat cache (#9450)
* fix(app-shell): keep the approval envelope + pending-action id in the chat cache (objectui#9232)
`sanitizeChatMessagesForCache` rebuilds each tool part field by field and wrote
neither the AI SDK `approval` envelope nor the ObjectStack `pendingActionId`.
`state` survived, so a cached `approval-requested` came back with the
awaiting-approval card lit and nothing behind it: `useHitlInChat` indexes on
`pendingActionId`, so Approve could only answer "No pending-action id found for
this tool call". The mirror of objectui#8442, which closed the same gap on the
way OUT of persisted server history.
The id travels asymmetrically, and the fix follows that rather than flattening
it. `approval` is a persisted PART key on the server path, so the cache writes
it as one and `partApproval` reads it straight back. `pendingActionId` is never
a part key anywhere, so it round-trips through a new
`pendingApprovalToCachedResult` inverse that re-mints the minimal
`{ status: 'pending_approval', pendingActionId }` envelope
`detectPendingApproval` re-parses — one parse for one contract, no second
reader on the cache side.
Entries written by the old shape are kept and read, not discarded: both readers
of the new keys already answer "absent" rather than throwing, a version bump
would blank the transcript and cards the old writer did keep, and old entries
self-heal on the next server-backed load.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
* fix(app-shell): carry the pending-action id on a part key, not by displacing the draft envelope
Patch round on objectui#9232. The first version put the pending-approval arm
FIRST in the `cachedOutput` chain, on the argument that the four detectors are
"disjoint by construction, so the order is unobservable". That argument was
wrong and `AiChatPage.runtimeMessageSeam.test.tsx` already pinned the
counter-example: the detectors are disjoint over one RESULT, but `draftReview`
and `pendingActionId` are independent KEYS on an invocation and a turn can
carry both. Pending-first therefore stopped the draft envelope reaching the
cache for such a turn — the same "Review N changes / Publish" loss on a
cache-fallback reload that the other three arms exist to prevent, and the same
loss this card's own stale-cache reasoning refused to accept from a key bump.
`output` holds one envelope, so the three existing arms keep it exactly as
before and the pending envelope is minted only when none of them claims it.
The id is no longer hostage to that slot: it is also written as a cache-side
part key, and `hydratedMessagesToChatMessages` consults `detectPendingApproval`
FIRST and only falls back to the key. On the server path the envelope always
answers, so that line behaves exactly as objectui#8442 left it and no second
dialect of the contract is introduced.
The two carriers are not redundant — each reaches where the other cannot.
`output` is the only one that survives API mode's SDK store, because
`aiInitialMessages` rebuilds each part from {type,toolCallId,toolName,input,
output,errorText,state} and drops every other key; the part key is the only one
left when a richer envelope has taken `output`, and local mode keeps it because
`normalizeMessages` passes `toolInvocations` through verbatim.
The falsified precedence pin is replaced by a both-at-once pin that drives the
round trip: the draft card comes back AND Approve reaches the pending action.
The over-reaching prose claim is replaced by the narrow claim that is true, as
an executable pin with lit controls: over ONE result the two envelopes are
mutually exclusive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3df7c5c commit 5c75dd6
4 files changed
Lines changed: 473 additions & 2 deletions
File tree
- .changeset
- packages/app-shell/src
- console/ai
- hooks
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
241 | 241 | | |
242 | 242 | | |
243 | 243 | | |
244 | | - | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
245 | 256 | | |
246 | 257 | | |
247 | 258 | | |
| |||
0 commit comments