ts-server: Rich Interactions runtime + choices kind (AskUserQuestion) - #483
Merged
Conversation
Port the kind-agnostic Rich Interactions framework and the `choices` kind to the TypeScript server, mirroring the merged Rust reference (PR #475). Framework (interaction.ts): the InteractionKind seam, InteractionRegistry of hosted kinds, a session-keyed InteractionParkRegistry (the interaction analog of the write-confirmation registry), and the per-kind request_<kind> raise tool + generic submit_interaction fallback tool. A raise on a session that declared the kind's render capability parks the turn (the raise tool awaits inside execute), the server emits interaction_required, and a submit_interaction action validates + resumes (invalid -> retryable interaction_invalid, never terminal). A text-only channel degrades to the kind's conversational fallback directive. choices kind (choices.ts): request_choices (1-4 questions, header <=12 unique, 2-4 options, optional multiSelect), validate_choices rules mirrored one-for-one, capability choice_chips. Session `supports` is now persisted (in-memory + Postgres) and gates rich-vs-fallback per kind; the server hosts choices by default. Tests: validator/parse/kind unit tests + a WS park/resume integration test (raise -> interaction_required -> submit_interaction -> resume; invalid -> interaction_invalid -> resubmit; mismatch; no-pending; text-only fallback), all cross-checked against the shared spec/conformance choices fixtures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YbN45JeWDbcjvFqGJvmVD3
🦋 Changeset detectedLatest commit: b6ddbd2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wave 2 of the full-polyglot Rich Interactions effort: ports the kind-agnostic Rich Interactions runtime + the
choiceskind (AskUserQuestion) to the TypeScript server, mirroring the merged Rust reference (PR #475).Framework → TS mapping
smooth-operator/src/interaction.rs(InteractionKind,InteractionRegistry,InteractionRequest/Outcome/FieldError)typescript/server/src/interaction.tssmooth-operator/src/tools/interaction.rs(RequestInteractionTool,SubmitInteractionTool, park channel)interaction.ts(requestInteractionTool,submitInteractionTool,InteractionParkRegistry)smooth-operator/src/choices.rs(validate_choices,ChoicesKind,parse_questions)typescript/server/src/choices.tsinteraction_required/interaction_invalidbuildersprotocol.ts(interactionRequired,interactionInvalid)handle_submit_interaction+PendingInteractionmapframeDispatcher.ts(handleSubmitInteraction,interactionPark)session_capabilitiesfromsupportssupportspersisted on the session (in-memory + Postgres), read inhandleSendMessageThe raise tool parks the turn by awaiting a promise inside
execute(the engine already runs turns as background tasks, so the reader stays free forsubmit_interaction) — the TS-idiomatic analog of the Rust tool awaiting a tokio channel. No engine changes.The kind
request_choices— 1–4 questions (each a short ≤12-char unique header, 2–4 options, optionalmultiSelect) + areason.validateChoicesmirrorschoices.rsone-for-one: every question answered, each label offered, single-select takes exactly one pick (label XOR other), multi-select ≥1, blankotherdropped, one-pass errors. Capability idchoice_chips. Invalid submits emit a retryableinteraction_invalidand keep the turn parked. Text-only channels get the conversational fallback directive. The server hostschoicesby default.Tests
test/choices.test.ts— validator +parseQuestionscontract +ChoicesKindwiring + protocol builders, cross-checked against the sharedspec/conformance/fixtures.jsonchoicesfixtures (validate(spec, values) == payload.values).test/submit-interaction.test.ts— WS integration over the real server: raise →interaction_required→submit_interaction→ resume; invalid →interaction_invalid→ resubmit;INTERACTION_MISMATCH;NO_PENDING_INTERACTION; text-only fallback (nointeraction_required).cd typescript/server && pnpm build && pnpm test→ 357 passed (38 files). Anchor guard: no stamped trees touched (TS server is versioned directly).Changeset:
@smooai/smooth-operator-server(minor).🤖 Generated with Claude Code