fix(types,plugin-chatbot): the authoring tool-invocation state union sheds the three runtime-only approval states (objectui#10018) - #10308
Conversation
…sheds the three runtime-only approval states The residual clause of the objectui#8426 ruling (decision batch #86): `approval-requested`, `approval-responded` and `output-denied` leave the authoring `ChatToolInvocation.state` union and its Zod mirror, so a schema-authored invocation cannot claim an approval state, with or without an `approval` envelope. The authoring type also served as the base of the chat runtime's seam, so the split is derived rather than copied: `SeamToolInvocation.state` is the union of the authoring and runtime vocabularies, `useObjectChat`'s `initialMessages` is the seam's input shape (app-shell hands it runtime values), and `ChatbotSchema.onSend` hands back the authoring message widened by exactly the three states, named by a non-exported alias. A compile-time Equal pin ties that alias to `ChatbotEnhanced`'s runtime union. No export is added. The un-backed-approval branch in the parts builder is kept: a runtime producer (app-shell's `mergeToolResultsInto`) still constructs an envelope-less `approval-requested`. Its docblock now says so. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ssue-10018-authoring-state-sheds-approval
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
… promise; complete the changeset's disclosures Contract-review follow-ups for objectui#10018 (PR objectui#10308): - renderer.tsx header: the sentence after the `onSend` signature still said a host callback typed against the authoring `ChatMessage[]` type-checks. It now matches the corrected docblocks: infer the parameter from the schema slot; the authoring `ChatMessage[]` no longer fits; `ObjectChatMessage[]` fits `useObjectChat`'s own slot. Comment only. - changeset Break 1: the migration list now names every authorable state, adding `input-streaming` and the legacy `partial-call`. - changeset "Not breaking": discloses that `UseObjectChatOptions.initialMessages` now takes `SeamChatMessage`, declaring nine optional runtime-only keys the hook already forwarded. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
… makes false Prose only, frontmatter byte-identical. Both notes ship in the same release as objectui#10018, which is what moved the facts they state (the objectui#10019 precedent: the change that moves a fact corrects the pending note stating it). - 8442-chat-tool-approval-envelope: "Three of the ten declared `state` values" becomes "Three AI SDK `state` values" (the authoring union now declares seven). The closing paragraph said an invocation may still declare an approval state with no envelope and pointed at a pin saying so. Both are now false for the authoring type: the pin became a refusal pin and the narrowing shipped under objectui#10018. It now says so, and that the runtime type still admits an envelope-less approval state. - 6169-chatbot-authoring-face-type: the `onSend` bullet no longer quotes a `ChatMessage[]` signature. `messages` is the authoring message widened by the three runtime-only approval states. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…essages keys; say what it does Contract review of 3aa259d..04ed618 (objectui#10018, PR objectui#10308): the "Not breaking" paragraph claimed the hook "already forwarded these keys". It does not. Local mode's `normalizeMessages` forwards `toolInvocations` whole but copies only the base message fields, so `buildProgress`, `blueprintProgress` and `charts` are dropped. API mode's `aiInitialMessages` rebuilds SDK parts that carry none of the nine. Re-derived by printing both builders at head. The executable lines are unchanged since the base (only type annotations moved), so this is a prose fix. Frontmatter byte-identical. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #10018
Clause-②: no
This executes the residual clause of the objectui#8426 ruling (director seat, decision batch #86, maintainer 「继续决策」), verbatim: 「the authoring
stateunion sheds the three runtime-only approval states, so a schema-authored invocation cannot claimapproval-requestedwithout an envelope」. The shape follows the seat's round-2 answers on the card (comment5817891136): the fence against objectui#10273 is cleared for the chat-invocation hunks, the file surface is widened,onSenduses design B, and the envelope's enforcement is left to its own card.What changed
@object-ui/types.ChatToolInvocation.stateand its Zod mirrorChatToolInvocationSchemadropapproval-requested,approval-respondedandoutput-denied. An authored claim of one is refused, with or without anapprovalenvelope. The mirror refuses it as oneinvalid_valueatstate.onSendslot (design B, zero new exports).ChatbotSchema.onSendhands a host the thread the runtime holds, and in API mode that thread carries the three states. Itsmessagesis therefore typed as the authoring message widened by exactly those states. Two NON-exported aliases incomplex.tsdo this:ChatToolRuntimeOnlyStateandChatMessageHandedBack. The builtcomplex.d.tsemits both withoutexport, and no barrel changes.chatMessageAdapter.ts).SeamToolInvocation.stateis the union of the authoring state andChatbotEnhanced's runtime state.toRuntimeToolStatetakes that union. The accepted set of values is unchanged, so existing callers still compile.useObjectChat.ts.initialMessagesis now the seam's input shape, because app-shell hands it runtime values restored from server history. The parts builder's un-backed-approval branch and its tests are KEPT. A runtime producer still constructs an envelope-lessapproval-requested: app-shell'smergeToolResultsIntopromotes it from a pending-action result, andhydratedMessagesToChatMessageshands it to the builder. Its docblock now says this instead of predicting its own removal.chat-message-contract.test.tsderives the types-side alias throughChatbotSchema['onSend']and pins it EQUAL toChatbotEnhanced's runtime-only states. It also pins the set to the SDK's approval triple, pins the authoring union to hold none of them, and pinsObjectChatMessageas assignable to the slot. The old_StillAnAuthoredMessagepin asserted the subtype relation this change removes, so it is inverted to_NoLongerAnAuthoredMessage.output-availableandoutput-erroronly; the three states that require one are not authorable. The subtype promise is corrected inChatbotSchema.onSend,ObjectChatMessage,UseObjectChatOptions.onSend,packages/plugin-chatbot/src/renderer.tsx's header comment (comment only,3ce44937), the plugin-chatbot README andcontent/docs/plugins/plugin-chatbot.mdx.chat-tool-approval-envelope-8442.test.ts).invocation()now defaults tooutput-available. The id-judgment case asserts its refusal ATapproval.id: before this change it would have kept passing on the state's refusal alone, a phantom check. The old 「is OPTIONAL」 case becomes the refusal pin for the three states, envelope or not.Changeset:
minorwith a BREAKING banner (版本号策略;majoris banned)ChatbotSchema.messagesand anywhere elseChatToolInvocationis authored.onSendhandler typed against the authoringChatMessage[]stops type-checking, because the messages it receives are no longer a subtype of that shape.UseObjectChatOptions.initialMessagesnow takesSeamChatMessage, so nine optional runtime-only keys are declared on that option:buildProgress,blueprintProgress,charts;pendingActionId,draftReview,proposedPlan,proposedChanges,builderHandoff,replayOutcome. The runtime is unchanged. Local mode keeps the six tool-invocation keys (normalizeMessagesforwardstoolInvocationswhole) and drops the three message-level ones. API mode'saiInitialMessagescarries none of the nine into SDK parts.04ed6186, prose only, frontmatter byte-identical). This change makes two other cards' pending entries false in the same release, so they are corrected here (the objectui#10019 precedent). In.changeset/8442-chat-tool-approval-envelope.md, the 「ten declaredstatevalues」 sentence and the closing no-narrowing paragraph are corrected. In.changeset/6169-chatbot-authoring-face-type.md, theonSendbullet is corrected.Evidence
All readings are from the committed state. Code head:
3aa259d8, which mergesorigin/mainat8b1f0661. The later commits change no executable byte:3ce44937, comments and changeset prose): plugin-chatbot type-check exits 0.check-changeset-presence,check:changeset-claims,check:control-bytesandcheck:new-line-citationsexit 0.04ed6186, the two sibling changesets, prose only): the same gates pluscheck:pending-changeset-literalsexit 0, andcheck-changeset-overwritereads declared = declares for both.340efe60, one changeset sentence): the Not-breaking paragraph no longer claims the hook forwarded the nine keys; it states what each builder does. The same gates exit 0.The contract review of
3aa259d8..04ed6186found one false sentence, and round 5 fixes it.Pins, red first on base
5ea623ea, before any source edit:tsc -p tsconfig.test.jsonon types exits 2 with exactly 7x TS2578 (unused@ts-expect-error). They sit on the six invocation literals (each state, bare and enveloped) and the authored-message literal. No control line errs.viteston the new file readsTests 9 failed | 4 passed (13): the 9 refusals fail and the 4 controls pass.After the fix, at
3aa259d8:type-check(main + test programs, plus examples for types) exits 0 for@object-ui/types,@object-ui/plugin-chatbotand@object-ui/app-shell. The new file's membership in the types test program is shown by the red run above.vitest run packages/types/ packages/plugin-chatbot/readsTest Files 263 passed (263),Tests 5468 passed (5468).7 passed (7),72 passed (72), and lists each new case by name.Ablation 1:
approval-requestedre-added to the authoring union and its mirror.ablation-replace.mjswith anchors hit x1; blobsc7c58748toff55f890anda1f3bd36to39bdaf7d. types was rebuilt, and the dist markers read 1 (complex.d.tsunion member,zod/complex.zod.js).approval-requestedrefusal pins red, the other states' pins and every control green,_IsTheApprovalTriplered.approval-requestedliterals. vitest reads4 failed | 15 passed (19): the threeapproval-requestedrefusals plus the 8442 refusal case, with all controls passing. plugin-chatbot main exits 0, and its test program exits 2 with 1 error,_IsTheApprovalTriple.git diff HEADis empty, types was rebuilt, and the dist markers read 0.Ablation 2:
output-deniedremoved from the non-exported alias. This proves the Equal pin can fail._OneVocabulary,_IsWhatOnSendHandsBackand_HandedBackIsAuthoringPlusTriplego red, and so do renderer.tsx's threeonSend: schema.onSendforwards (main program TS2322).Gates at
3aa259d8, each exit 0:check:control-bytesandcheck:new-line-citations.node scripts/check-changeset-presence.mjsandchangeset:check(fixed group, no-major).check:changeset-claimsandcheck:pending-changeset-literals.check:doc-types,check:doc-snippets(672 of 672 blocks judged),check:doc-examples,check:doc-fences,check:doc-example-ids,check:doc-example-readers,docs:check-linksandcheck:readme-exports. The first runs of doc-snippets, doc-examples and readme-exports read PRECONDITION NOT MET (unbuilt packages); they are green after the scoped build each gate prints.check:handler-key-reads,check:component-surface-parityandcheck:test-path-roots.check-governed-queue-guard --testreads NOT GOVERNED.eslint --no-inline-config --format jsonon the 7 changed TS files: 7 files, 0 errors, 20 warnings, 0 of them on an added line. The 20 are pre-existingno-explicit-anyandreact-hookswarnings.NOT MEASURED:
pnpm lint, the fullpnpm test, and CI convergence (left to the seat per standing rules).apps/console,apps/site,examples/console-starterandexamples/schema-catalog.Overlap with objectui#10273 (per the seat's fence answer A)
This PR and objectui#10273 both edit
packages/types/src/complex.tsandzod/complex.zod.ts, and the hunks do not overlap:ChatToolInvocation, the two new non-exported aliases after it andChatbotSchema.onSendincomplex.ts;ChatToolInvocationSchemaincomplex.zod.ts. It does not editzod-mirror-parity.test.ts, because that pin's type equality moves with both sides.complex.ts;GlobalFilterSchemaincomplex.zod.ts; the parity census EXCLUSIONS.Whichever lands second merges
origin/main; the union of both is the resolution.Acceptance notes
approvalenvelope now has no authorable state whose builder arm reads it. The seat is filing its enforcement as its own card; this PR only makes its docblock true.renderer.tsx's header comment made the same subtype promise. It is retracted in3ce44937(comment only). The seat widened the claim surface to that file.warnApprovalStateWithoutEnvelopeprints ("not representable as authored") was left unchanged to avoid churning the tests that pin it. The docblock above it is what was rewritten.The seat's session is
https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC. This PR was implemented by a dispatchedos-devrun in that session (seatdomain:ui#1, claim5817363473).Generated by Claude Code