Skip to content

feat(chat): surface sidecar interactions via onInteraction on the chat-stream primitive#176

Merged
drewstone merged 2 commits into
mainfrom
feat/chat-stream-oninteraction
Jul 8, 2026
Merged

feat(chat): surface sidecar interactions via onInteraction on the chat-stream primitive#176
drewstone merged 2 commits into
mainfrom
feat/chat-stream-oninteraction

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

What

Lifts the proven client-side chat-interaction handling into the web-react chat-stream primitive so any product on @tangle-network/agent-app gets live agent asks (kind question/plan/…) without hand-rolling the wire codec.

Closes the verified gap: the backend already threads backend.interactions into the stream (#166), so the sidecar interaction line IS in the stream — but the client parser (dispatchChatStreamLine / consumeChatStream / streamChatTurn) exposed callbacks for text/reasoning/tool/usage/metadata/error and no onInteraction, so it silently dropped the ask.

Refs #175.

Changes

  • src/web-react/chat-interactions.ts (new) — the framework-agnostic interaction contract, lifted verbatim in semantics from the reference consumer (gtm-agent): the ChatInteraction type, wire↔persisted codecs (interactionFromWireRequest, interactionToPersistedPart, persistedPartToInteraction, parseInteractionRequest/parseInteractionCancel), and the pure helpers (dedupeQuestionInteractionsByContent, canTransitionInteractionStatus, cancelStatusFor, composerAnswerDeliveries/composerAnswerData/fieldAcceptsFreeText, isRenderableInteractionKind, part-key helpers). No React, no server imports — schema validation via the @tangle-network/agent-interface peer. One source of truth so gtm/tax/legal/physim stop forking it.
  • ChatStreamCallbacks.onInteraction? — optional callback: (interaction: ChatInteraction) => void.
  • dispatchChatStreamLine — recognizes the {type:'interaction', data:{request}} line (and the {kind:'event'} envelope form), validates with the shared codec, and fires onInteraction with the parsed ChatInteraction; a malformed ask is logged and skipped (never half-surfaced). Counts as received turn activity (the run is blocked on the user).
  • src/web-react/index.tsx — barrel re-exports the contract next to the chat-stream primitives.
  • src/web-react/chat-stream.test.ts (new) — feeds an interaction line through dispatchChatStreamLine and asserts onInteraction fires with the right ChatInteraction; covers the {kind:'event'} envelope, the consumeChatStream drain path, a malformed-line drop, and — proving non-breaking — a consumer with no onInteraction that parses the same stream unchanged.

Non-breaking

Purely additive: the callback is optional and no existing signature changed. Existing consumers compile and behave identically (covered by a dedicated "no onInteraction" test). Consumers opt in by wiring onInteraction and importing the shared contract instead of hand-rolling it.

Verification

  • pnpm typecheck — clean.
  • pnpm test — 137 files / 2227 tests green (includes 6 new interaction tests).
  • pnpm build (tsup ESM + d.ts) — clean; onInteraction + the contract exports present in dist/web-react/index.d.ts.

Not published to npm — that's the separate gated release step.

drewstone added 2 commits July 8, 2026 13:23
Lift the framework-agnostic interaction contract (ChatInteraction type,
wire<->persisted codecs, dedupe/transition/compose-answer helpers) into
web-react so every agent-app consumer imports one source of truth instead
of hand-rolling it. Pure module (no React), re-exported from the web-react
barrel alongside the chat-stream primitives.
dispatchChatStreamLine silently dropped the sidecar 'interaction' NDJSON
line — the backend already threads it into the stream (#166), but the
client parser had no seam for it. Add an optional onInteraction callback
to ChatStreamCallbacks and a case in the switch that validates the ask
with the shared wire codec and surfaces the parsed ChatInteraction; a
malformed ask is logged and skipped. Optional callback keeps every
existing consumer unchanged. Covers dispatch, the {kind:'event'} envelope,
and consumeChatStream drain, plus a non-breaking (no callback) path.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — 5abf89de

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-08T19:25:09Z

@drewstone drewstone merged commit a49303b into main Jul 8, 2026
1 check passed
@drewstone drewstone deleted the feat/chat-stream-oninteraction branch July 8, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants