Skip to content

finding(app-shell): hydratedMessagesToChatMessages keeps the three SDK approval states but drops the approval envelope and pendingActionId that make them actionable #8442

Description

@os-justin

Found while measuring objectui#8426 (typing useObjectChat's aiInitialMessages builder). Not fixed there — out of that card's scope. ⛔ Not claimed.

What

packages/app-shell/src/console/ai/AiChatPage.tsx, partToolState (currently :135) and hydratedMessagesToChatMessages (currently :165), measured on origin/main @ ca3942729.

partToolState returns 'approval-requested', 'approval-responded' and 'output-denied' verbatim from the server-persisted part (:146-151). The part it reads is HydratedUIMessagePart, declared { type: string; text?: string; [key: string]: unknown } — so whatever else the server persisted on that part is present in the value and reachable.

The mapper then builds the invocation from exactly six things: toolCallId, toolName, state, result, the four detected ObjectStack envelopes, and errorText (:205-217). It never reads:

  • the AI SDK's own approval envelope on the part ({ id, approved?, reason?, isAutomatic?, signature? }), which the SDK's part union makes required for each of those three states; and
  • pendingActionId — which mapMessages.extractToolInvocations does lift on the live path (packages/plugin-chatbot/src/mapMessages.ts:687), and which useHitlInChat uses as its index key (useHitlInChat.ts:154-166: it indexes toolCallId -> pendingActionId and skips any invocation without one).

Why it matters

Two consequences, stated at the confidence each was measured at:

  1. Measured by type, at objectui#8426. These three states are not constructible as a declared AI SDK UIMessagePart without the approval envelope. Three explicit pushes, each a compiler error TS2345 against UIMessagePart in packages/plugin-chatbot:
    { type: 'tool-NAME', toolCallId, state: 'approval-requested', input }, and the same for 'approval-responded' and 'output-denied'. The seven other authored states build fine. So this drop is the direct blocker on objectui#8426's contract-first fix — the state survives the hydration hop, the data that state requires does not.

  2. Read from the source, not exercised in a browser. A rehydrated pending approval reaches useHitlInChat with state: 'approval-requested' and no pendingActionId, so it is not indexed by that hook. ChatbotEnhanced.tsx:2128 gates the awaiting-approval affordance on state and onToolApprove only, so the card still renders as awaiting. Whether the operator then gets a working Approve/Reject or a dead one was NOT verified end to end here — the reading is the missing index entry, and someone should confirm the UX before costing a fix.

The live path does not have this gap: mapMessages synthesizes state: 'approval-requested' from the result envelope and lifts pendingActionId in the same pass, so the two always arrive together there. Only the hydration path can produce one without the other.

Note on scope

The output-denied pass-through is deliberate and pinned: packages/app-shell/src/console/ai/__tests__/AiChatPage.hydration.test.ts:44 feeds a persisted state: 'output-denied' part and :49 asserts the invocation keeps that state. So the fix direction is "carry the envelope too", not "stop carrying the state".

Dedup

search_issues over this repo for the hydration mapper and the dropped approval id — 2 hits, both read: objectui#2477 (open, Console AI chat post-cleanup UX follow-ups; does not name this) and objectui#2450 (closed, second-handoff auto-send). Neither names the dropped approval envelope or pendingActionId.

Related

objectui#8426 (blocked on the ruling this feeds) · objectui#8378 · objectui#8342 · objectui#5695

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpackage: app-shellpm:queuepriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions