@neuronection/assistant-ui 0.50.1 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @neuronection/assistant-ui
2
2
 
3
+ ## 0.51.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`fabdc5d`](https://github.com/neuronection/assistant-ui/commit/fabdc5da389b64b96152acdd9092aa6c7455b356) Thanks [@constLiakos](https://github.com/constLiakos)! - **chat-core**: new terminal `flow_interrupted` stream event — `{ event: 'flow_interrupted'; reason?: 'user' | 'server'; partial?: boolean }` (plan 24 §2, additive). The server broadcasts it when a turn is cancelled so every consumer terminalizes — not just the one that issued the stop (a stop in one tab reaches the others). The reducer maps it to `status: 'interrupted'`, `stopped: true`, with `finishedAt` — the same terminality class as `flow_finished`/`flow_failed`, and distinct from the resumable HITL `interrupt` pause. `useChatStream` treats it as terminal (late events ignored, watchdog cleared, `reset()` re-arms).
8
+
3
9
  ## 0.50.1
4
10
 
5
11
  ### Patch Changes
@@ -8,9 +8,12 @@ interface FlowStepInfo {
8
8
  }
9
9
  /**
10
10
  * Family streaming vocabulary (guidelines/ai-features.md §5), extended with
11
- * the `tool_call` observation event. Transports map their wire format
12
- * (SSE named events, WS topic payloads) onto these shapes app-side; the
13
- * reducer tolerates unknown `event` values by ignoring them.
11
+ * the `tool_call` observation event and the terminal `flow_interrupted`
12
+ * stop. `interrupt` is the resumable HITL pause; `flow_interrupted` is the
13
+ * terminal cancel the server broadcasts so every consumer of the turn
14
+ * learns it was stopped. Transports map their wire format (SSE named
15
+ * events, WS topic payloads) onto these shapes app-side; the reducer
16
+ * tolerates unknown `event` values by ignoring them.
14
17
  */
15
18
  type ChatStreamEvent = {
16
19
  event: 'flow_started';
@@ -58,6 +61,10 @@ type ChatStreamEvent = {
58
61
  message: string;
59
62
  retryable: boolean;
60
63
  run_id?: string;
64
+ } | {
65
+ event: 'flow_interrupted';
66
+ reason?: 'user' | 'server';
67
+ partial?: boolean;
61
68
  };
62
69
 
63
70
  type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error';
@@ -77,9 +84,14 @@ interface LiveToolCall {
77
84
  }
78
85
  /**
79
86
  * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are
80
- * `null` until the first delta (study's "not streaming yet" sentinel);
81
- * `interrupted` is a resumable pause (HITL) unless `stopped` marks a user
82
- * cancel; `stopped`/`done`/`error` are terminal for event intake.
87
+ * `null` until the first delta (study's "not streaming yet" sentinel).
88
+ * `status: 'interrupted'` has two distinct readings: the HITL `interrupt`
89
+ * event is a *resumable pause* — it carries `interruptPayload` and the
90
+ * next delta streams again — while a *stop* is terminal for event intake:
91
+ * the local `stop` action (the user cancel this consumer issued) or the
92
+ * server-broadcast `flow_interrupted` event (how a consumer that did NOT
93
+ * issue the stop learns the turn was cancelled, e.g. another tab). Both
94
+ * stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.
83
95
  */
84
96
  interface LiveTurnState {
85
97
  status: LiveTurnStatus;
package/dist/chat-core.js CHANGED
@@ -1,4 +1,4 @@
1
- export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-6GNLY2EB.js';
1
+ export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-O3OLATVE.js';
2
2
  export { activePathSet, buildBranchTree, linearTree, variantInfo, walkActivePath } from './chunk-Z4IBQ53C.js';
3
3
  //# sourceMappingURL=chat-core.js.map
4
4
  //# sourceMappingURL=chat-core.js.map
@@ -70,7 +70,8 @@ function liveTurnReducer(state, action) {
70
70
  if (state.status === "idle" || isTerminal(state)) {
71
71
  return state;
72
72
  }
73
- if (event.run_id !== void 0 && state.runId !== null && event.run_id !== state.runId) {
73
+ const eventRunId = "run_id" in event ? event.run_id : void 0;
74
+ if (eventRunId !== void 0 && state.runId !== null && eventRunId !== state.runId) {
74
75
  return state;
75
76
  }
76
77
  switch (event.event) {
@@ -104,6 +105,8 @@ function liveTurnReducer(state, action) {
104
105
  error: { code: event.code, message: event.message, retryable: event.retryable },
105
106
  finishedAt: at
106
107
  };
108
+ case "flow_interrupted":
109
+ return { ...state, status: "interrupted", stopped: true, finishedAt: at };
107
110
  default:
108
111
  return state;
109
112
  }
@@ -178,7 +181,7 @@ function useChatStream(options) {
178
181
  return;
179
182
  }
180
183
  flush();
181
- if (event.event === "flow_finished" || event.event === "flow_failed") {
184
+ if (event.event === "flow_finished" || event.event === "flow_failed" || event.event === "flow_interrupted") {
182
185
  terminalRef.current = true;
183
186
  clearTimeoutTimer();
184
187
  }
@@ -268,5 +271,5 @@ function useChatStream(options) {
268
271
  }
269
272
 
270
273
  export { initialLiveTurnState, liveTurnReducer, useChatStream };
271
- //# sourceMappingURL=chunk-6GNLY2EB.js.map
272
- //# sourceMappingURL=chunk-6GNLY2EB.js.map
274
+ //# sourceMappingURL=chunk-O3OLATVE.js.map
275
+ //# sourceMappingURL=chunk-O3OLATVE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-core/liveTurnReducer.ts","../src/components/chat-core/useChatStream.ts"],"names":[],"mappings":"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","file":"chunk-O3OLATVE.js","sourcesContent":["import type { ChatStreamEvent } from './events'\nimport type { ChatError } from './types'\n\nexport type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error'\n\nexport interface LiveNodeState {\n id: string\n label?: string\n status: 'pending' | 'running' | 'done' | 'failed' | 'interrupted'\n}\n\nexport interface LiveToolCall {\n id: string\n name: string\n title?: string\n status: 'running' | 'done' | 'failed'\n args?: string\n result?: string\n durationMs?: number\n}\n\n/**\n * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are\n * `null` until the first delta (study's \"not streaming yet\" sentinel).\n * `status: 'interrupted'` has two distinct readings: the HITL `interrupt`\n * event is a *resumable pause* — it carries `interruptPayload` and the\n * next delta streams again — while a *stop* is terminal for event intake:\n * the local `stop` action (the user cancel this consumer issued) or the\n * server-broadcast `flow_interrupted` event (how a consumer that did NOT\n * issue the stop learns the turn was cancelled, e.g. another tab). Both\n * stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.\n */\nexport interface LiveTurnState {\n status: LiveTurnStatus\n text: string | null\n reasoning: string | null\n toolCalls: LiveToolCall[]\n nodes: LiveNodeState[]\n error: ChatError | null\n interruptPayload: unknown | null\n runId: string | null\n stopped: boolean\n startedAt: number | null\n finishedAt: number | null\n}\n\nexport type LiveTurnAction =\n | { type: 'send'; at: number }\n | { type: 'event'; event: ChatStreamEvent; at?: number }\n | { type: 'stop'; at: number }\n | { type: 'reset' }\n\nexport const initialLiveTurnState: LiveTurnState = {\n status: 'idle',\n text: null,\n reasoning: null,\n toolCalls: [],\n nodes: [],\n error: null,\n interruptPayload: null,\n runId: null,\n stopped: false,\n startedAt: null,\n finishedAt: null,\n}\n\nfunction isTerminal(state: LiveTurnState): boolean {\n return state.status === 'done' || state.status === 'error' || state.stopped\n}\n\nfunction upsertNode(nodes: LiveNodeState[], id: string, label: string | undefined, status: LiveNodeState['status']): LiveNodeState[] {\n const index = nodes.findIndex((node) => node.id === id)\n if (index === -1) {\n return [...nodes, { id, label, status }]\n }\n const next = nodes.slice()\n const current = next[index]!\n next[index] = { ...current, label: label ?? current.label, status }\n return next\n}\n\nfunction upsertToolCall(state: LiveTurnState, event: Extract<ChatStreamEvent, { event: 'tool_call' }>): LiveToolCall[] {\n const index = state.toolCalls.findIndex((call) => call.id === event.id)\n const merged = {\n id: event.id,\n name: event.name,\n title: event.title,\n status: event.status,\n args: event.args,\n result: event.result,\n durationMs: event.durationMs,\n }\n if (index === -1) {\n return [...state.toolCalls, merged]\n }\n const next = state.toolCalls.slice()\n const current = next[index]!\n next[index] = {\n ...current,\n ...merged,\n title: merged.title ?? current.title,\n args: merged.args ?? current.args,\n result: merged.result ?? current.result,\n durationMs: merged.durationMs ?? current.durationMs,\n }\n return next\n}\n\n/**\n * Pure state machine for one streamed assistant turn — the fixture-testable\n * core behind `useChatStream` (same role career's `chatFlow.ts` plays for\n * `FlowStatusCard`). Unknown event names are ignored (additive contract).\n */\nexport function liveTurnReducer(state: LiveTurnState, action: LiveTurnAction): LiveTurnState {\n switch (action.type) {\n case 'send':\n return { ...initialLiveTurnState, status: 'pending', startedAt: action.at }\n case 'stop': {\n if (state.status !== 'pending' && state.status !== 'streaming') {\n return state\n }\n return { ...state, status: 'interrupted', stopped: true, finishedAt: action.at }\n }\n case 'reset':\n return initialLiveTurnState\n case 'event': {\n const { event, at = Date.now() } = action\n if (state.status === 'idle' || isTerminal(state)) {\n return state\n }\n const eventRunId = 'run_id' in event ? event.run_id : undefined\n if (eventRunId !== undefined && state.runId !== null && eventRunId !== state.runId) {\n return state\n }\n switch (event.event) {\n case 'flow_started':\n return {\n ...state,\n runId: event.run_id ?? state.runId,\n nodes:\n event.steps !== undefined && event.steps.length > 0\n ? event.steps.map((step): LiveNodeState => ({ id: step.id, label: step.label, status: 'pending' }))\n : state.nodes,\n }\n case 'node_started':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, 'running') }\n case 'node_finished':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, event.outcome) }\n case 'delta': {\n const streaming = { ...state, status: 'streaming' as const }\n if (event.kind === 'reasoning') {\n return { ...streaming, reasoning: (state.reasoning ?? '') + event.text }\n }\n return { ...streaming, text: (state.text ?? '') + event.text }\n }\n case 'tool_call':\n return { ...state, toolCalls: upsertToolCall(state, event) }\n case 'interrupt':\n return { ...state, status: 'interrupted', interruptPayload: event.payload, finishedAt: at }\n case 'flow_finished':\n return { ...state, status: 'done', finishedAt: at }\n case 'flow_failed':\n return {\n ...state,\n status: 'error',\n error: { code: event.code, message: event.message, retryable: event.retryable },\n finishedAt: at,\n }\n case 'flow_interrupted':\n return { ...state, status: 'interrupted', stopped: true, finishedAt: at }\n default:\n return state\n }\n }\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ChatStreamEvent } from './events'\nimport { initialLiveTurnState, liveTurnReducer, type LiveTurnAction, type LiveTurnState } from './liveTurnReducer'\n\nexport interface ChatStreamTransport {\n /** Enqueue/send a user turn. Resolves when accepted; errors surface as a failed turn. */\n send(input: { text: string }): Promise<void>\n /** Subscribe to the session's stream events; returns an unsubscribe function. */\n subscribe(handlers: { onEvent: (event: ChatStreamEvent) => void }): () => void\n /** Best-effort cancel of the in-flight turn (backend stop endpoint). */\n stop?(): Promise<void>\n}\n\nexport interface UseChatStreamOptions {\n transport: ChatStreamTransport\n /** Delta coalescing window. Default 33 ms (study's rAF-scale budget). */\n flushMs?: number\n /** Watchdog for a turn that never terminates. Default 90 000 ms. */\n timeoutMs?: number\n}\n\nexport interface UseChatStreamResult extends LiveTurnState {\n /** The live turn, or `null` when idle — hand to the transcript's `live` slot. */\n live: LiveTurnState | null\n /** Returns false and does nothing for blank input or while a turn is active. */\n send(text: string): Promise<boolean>\n stop(): Promise<void>\n reset(): void\n}\n\n/**\n * Transport-injected live-turn state machine for chat surfaces: send →\n * pending → streaming (coalesced text/reasoning deltas, tool calls, node\n * progress) → done / error / interrupted. Persists nothing — the app owns\n * sessions, messages and the branching endpoints; after a terminal state\n * the app refetches and calls `reset()`.\n */\nexport function useChatStream(options: UseChatStreamOptions): UseChatStreamResult {\n const { transport, flushMs = 33, timeoutMs = 90_000 } = options\n const [state, setState] = useState<LiveTurnState>(initialLiveTurnState)\n const stateRef = useRef(state)\n const dispatch = useCallback((action: LiveTurnAction) => {\n const next = liveTurnReducer(stateRef.current, action)\n stateRef.current = next\n setState(next)\n }, [])\n\n const transportRef = useRef(transport)\n transportRef.current = transport\n\n const textBufferRef = useRef('')\n const reasoningBufferRef = useRef('')\n const flushTimerRef = useRef<number | null>(null)\n const timeoutTimerRef = useRef<number | null>(null)\n const terminalRef = useRef(false)\n\n const cancelFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n window.clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n }, [])\n\n const flush = useCallback(() => {\n cancelFlush()\n const text = textBufferRef.current\n const reasoning = reasoningBufferRef.current\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n if (text !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text, kind: 'text' } })\n }\n if (reasoning !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text: reasoning, kind: 'reasoning' } })\n }\n }, [cancelFlush, dispatch])\n\n const scheduleFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n return\n }\n flushTimerRef.current = window.setTimeout(() => {\n flushTimerRef.current = null\n flush()\n }, flushMs)\n }, [flush, flushMs])\n\n const clearTimeoutTimer = useCallback(() => {\n if (timeoutTimerRef.current !== null) {\n window.clearTimeout(timeoutTimerRef.current)\n timeoutTimerRef.current = null\n }\n }, [])\n\n useEffect(() => {\n const unsubscribe = transportRef.current.subscribe({\n onEvent: (event) => {\n if (terminalRef.current) {\n return\n }\n if (event.event === 'delta') {\n if (event.kind === 'reasoning') {\n reasoningBufferRef.current += event.text\n } else {\n textBufferRef.current += event.text\n }\n scheduleFlush()\n return\n }\n flush()\n if (event.event === 'flow_finished' || event.event === 'flow_failed' || event.event === 'flow_interrupted') {\n terminalRef.current = true\n clearTimeoutTimer()\n }\n dispatch({ type: 'event', event })\n },\n })\n return unsubscribe\n }, [scheduleFlush, flush, clearTimeoutTimer, dispatch])\n\n useEffect(\n () => () => {\n cancelFlush()\n clearTimeoutTimer()\n },\n [cancelFlush, clearTimeoutTimer],\n )\n\n const send = useCallback(\n async (text: string) => {\n const trimmed = text.trim()\n const active = stateRef.current.status === 'pending' || stateRef.current.status === 'streaming'\n if (trimmed === '' || active) {\n return false\n }\n terminalRef.current = false\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'send', at: Date.now() })\n timeoutTimerRef.current = window.setTimeout(() => {\n if (terminalRef.current) {\n return\n }\n terminalRef.current = true\n flush()\n dispatch({\n type: 'event',\n event: { event: 'flow_failed', code: 'timeout', message: 'Chat turn timed out', retryable: true },\n })\n }, timeoutMs)\n try {\n await transportRef.current.send({ text: trimmed })\n } catch (sendError) {\n if (terminalRef.current) {\n return true\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({\n type: 'event',\n event: {\n event: 'flow_failed',\n code: 'send_failed',\n message: sendError instanceof Error ? sendError.message : String(sendError),\n retryable: true,\n },\n })\n }\n return true\n },\n [cancelFlush, clearTimeoutTimer, dispatch, flush, timeoutMs],\n )\n\n const stop = useCallback(async () => {\n const current = stateRef.current\n if (current.status !== 'pending' && current.status !== 'streaming') {\n return\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({ type: 'stop', at: Date.now() })\n await transportRef.current.stop?.().catch(() => undefined)\n }, [clearTimeoutTimer, dispatch, flush])\n\n const reset = useCallback(() => {\n terminalRef.current = false\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'reset' })\n }, [cancelFlush, clearTimeoutTimer, dispatch])\n\n return {\n ...state,\n live: state.status === 'idle' ? null : state,\n send,\n stop,\n reset,\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -2079,9 +2079,12 @@ interface FlowStepInfo {
2079
2079
  }
2080
2080
  /**
2081
2081
  * Family streaming vocabulary (guidelines/ai-features.md §5), extended with
2082
- * the `tool_call` observation event. Transports map their wire format
2083
- * (SSE named events, WS topic payloads) onto these shapes app-side; the
2084
- * reducer tolerates unknown `event` values by ignoring them.
2082
+ * the `tool_call` observation event and the terminal `flow_interrupted`
2083
+ * stop. `interrupt` is the resumable HITL pause; `flow_interrupted` is the
2084
+ * terminal cancel the server broadcasts so every consumer of the turn
2085
+ * learns it was stopped. Transports map their wire format (SSE named
2086
+ * events, WS topic payloads) onto these shapes app-side; the reducer
2087
+ * tolerates unknown `event` values by ignoring them.
2085
2088
  */
2086
2089
  type ChatStreamEvent = {
2087
2090
  event: 'flow_started';
@@ -2129,6 +2132,10 @@ type ChatStreamEvent = {
2129
2132
  message: string;
2130
2133
  retryable: boolean;
2131
2134
  run_id?: string;
2135
+ } | {
2136
+ event: 'flow_interrupted';
2137
+ reason?: 'user' | 'server';
2138
+ partial?: boolean;
2132
2139
  };
2133
2140
 
2134
2141
  type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error';
@@ -2148,9 +2155,14 @@ interface LiveToolCall {
2148
2155
  }
2149
2156
  /**
2150
2157
  * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are
2151
- * `null` until the first delta (study's "not streaming yet" sentinel);
2152
- * `interrupted` is a resumable pause (HITL) unless `stopped` marks a user
2153
- * cancel; `stopped`/`done`/`error` are terminal for event intake.
2158
+ * `null` until the first delta (study's "not streaming yet" sentinel).
2159
+ * `status: 'interrupted'` has two distinct readings: the HITL `interrupt`
2160
+ * event is a *resumable pause* — it carries `interruptPayload` and the
2161
+ * next delta streams again — while a *stop* is terminal for event intake:
2162
+ * the local `stop` action (the user cancel this consumer issued) or the
2163
+ * server-broadcast `flow_interrupted` event (how a consumer that did NOT
2164
+ * issue the stop learns the turn was cancelled, e.g. another tab). Both
2165
+ * stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.
2154
2166
  */
2155
2167
  interface LiveTurnState {
2156
2168
  status: LiveTurnStatus;
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ export { AiMagicFill } from './chunk-X6L5Z5LV.js';
70
70
  export { FormModal } from './chunk-QPY2Y532.js';
71
71
  export { Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, ModalTrigger, modalContentVariants } from './chunk-FVQWIDB5.js';
72
72
  export { Breadcrumbs } from './chunk-FILCIMXX.js';
73
- export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-6GNLY2EB.js';
73
+ export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-O3OLATVE.js';
74
74
  export { activePathSet, buildBranchTree, linearTree, variantInfo, walkActivePath } from './chunk-Z4IBQ53C.js';
75
75
  export { ChatLauncher } from './chunk-E3NZM4U7.js';
76
76
  export { Portal } from './chunk-XZWLTTHA.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuronection/assistant-ui",
3
- "version": "0.50.1",
3
+ "version": "0.51.0",
4
4
  "description": "Shared React component library for the Assistant family (career-, study-, health-, desktop-assistant). Family look by default, app-flavored via CSS-variable tokens.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/chat-core/liveTurnReducer.ts","../src/components/chat-core/useChatStream.ts"],"names":[],"mappings":"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","file":"chunk-6GNLY2EB.js","sourcesContent":["import type { ChatStreamEvent } from './events'\nimport type { ChatError } from './types'\n\nexport type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error'\n\nexport interface LiveNodeState {\n id: string\n label?: string\n status: 'pending' | 'running' | 'done' | 'failed' | 'interrupted'\n}\n\nexport interface LiveToolCall {\n id: string\n name: string\n title?: string\n status: 'running' | 'done' | 'failed'\n args?: string\n result?: string\n durationMs?: number\n}\n\n/**\n * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are\n * `null` until the first delta (study's \"not streaming yet\" sentinel);\n * `interrupted` is a resumable pause (HITL) unless `stopped` marks a user\n * cancel; `stopped`/`done`/`error` are terminal for event intake.\n */\nexport interface LiveTurnState {\n status: LiveTurnStatus\n text: string | null\n reasoning: string | null\n toolCalls: LiveToolCall[]\n nodes: LiveNodeState[]\n error: ChatError | null\n interruptPayload: unknown | null\n runId: string | null\n stopped: boolean\n startedAt: number | null\n finishedAt: number | null\n}\n\nexport type LiveTurnAction =\n | { type: 'send'; at: number }\n | { type: 'event'; event: ChatStreamEvent; at?: number }\n | { type: 'stop'; at: number }\n | { type: 'reset' }\n\nexport const initialLiveTurnState: LiveTurnState = {\n status: 'idle',\n text: null,\n reasoning: null,\n toolCalls: [],\n nodes: [],\n error: null,\n interruptPayload: null,\n runId: null,\n stopped: false,\n startedAt: null,\n finishedAt: null,\n}\n\nfunction isTerminal(state: LiveTurnState): boolean {\n return state.status === 'done' || state.status === 'error' || state.stopped\n}\n\nfunction upsertNode(nodes: LiveNodeState[], id: string, label: string | undefined, status: LiveNodeState['status']): LiveNodeState[] {\n const index = nodes.findIndex((node) => node.id === id)\n if (index === -1) {\n return [...nodes, { id, label, status }]\n }\n const next = nodes.slice()\n const current = next[index]!\n next[index] = { ...current, label: label ?? current.label, status }\n return next\n}\n\nfunction upsertToolCall(state: LiveTurnState, event: Extract<ChatStreamEvent, { event: 'tool_call' }>): LiveToolCall[] {\n const index = state.toolCalls.findIndex((call) => call.id === event.id)\n const merged = {\n id: event.id,\n name: event.name,\n title: event.title,\n status: event.status,\n args: event.args,\n result: event.result,\n durationMs: event.durationMs,\n }\n if (index === -1) {\n return [...state.toolCalls, merged]\n }\n const next = state.toolCalls.slice()\n const current = next[index]!\n next[index] = {\n ...current,\n ...merged,\n title: merged.title ?? current.title,\n args: merged.args ?? current.args,\n result: merged.result ?? current.result,\n durationMs: merged.durationMs ?? current.durationMs,\n }\n return next\n}\n\n/**\n * Pure state machine for one streamed assistant turn — the fixture-testable\n * core behind `useChatStream` (same role career's `chatFlow.ts` plays for\n * `FlowStatusCard`). Unknown event names are ignored (additive contract).\n */\nexport function liveTurnReducer(state: LiveTurnState, action: LiveTurnAction): LiveTurnState {\n switch (action.type) {\n case 'send':\n return { ...initialLiveTurnState, status: 'pending', startedAt: action.at }\n case 'stop': {\n if (state.status !== 'pending' && state.status !== 'streaming') {\n return state\n }\n return { ...state, status: 'interrupted', stopped: true, finishedAt: action.at }\n }\n case 'reset':\n return initialLiveTurnState\n case 'event': {\n const { event, at = Date.now() } = action\n if (state.status === 'idle' || isTerminal(state)) {\n return state\n }\n if (event.run_id !== undefined && state.runId !== null && event.run_id !== state.runId) {\n return state\n }\n switch (event.event) {\n case 'flow_started':\n return {\n ...state,\n runId: event.run_id ?? state.runId,\n nodes:\n event.steps !== undefined && event.steps.length > 0\n ? event.steps.map((step): LiveNodeState => ({ id: step.id, label: step.label, status: 'pending' }))\n : state.nodes,\n }\n case 'node_started':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, 'running') }\n case 'node_finished':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, event.outcome) }\n case 'delta': {\n const streaming = { ...state, status: 'streaming' as const }\n if (event.kind === 'reasoning') {\n return { ...streaming, reasoning: (state.reasoning ?? '') + event.text }\n }\n return { ...streaming, text: (state.text ?? '') + event.text }\n }\n case 'tool_call':\n return { ...state, toolCalls: upsertToolCall(state, event) }\n case 'interrupt':\n return { ...state, status: 'interrupted', interruptPayload: event.payload, finishedAt: at }\n case 'flow_finished':\n return { ...state, status: 'done', finishedAt: at }\n case 'flow_failed':\n return {\n ...state,\n status: 'error',\n error: { code: event.code, message: event.message, retryable: event.retryable },\n finishedAt: at,\n }\n default:\n return state\n }\n }\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ChatStreamEvent } from './events'\nimport { initialLiveTurnState, liveTurnReducer, type LiveTurnAction, type LiveTurnState } from './liveTurnReducer'\n\nexport interface ChatStreamTransport {\n /** Enqueue/send a user turn. Resolves when accepted; errors surface as a failed turn. */\n send(input: { text: string }): Promise<void>\n /** Subscribe to the session's stream events; returns an unsubscribe function. */\n subscribe(handlers: { onEvent: (event: ChatStreamEvent) => void }): () => void\n /** Best-effort cancel of the in-flight turn (backend stop endpoint). */\n stop?(): Promise<void>\n}\n\nexport interface UseChatStreamOptions {\n transport: ChatStreamTransport\n /** Delta coalescing window. Default 33 ms (study's rAF-scale budget). */\n flushMs?: number\n /** Watchdog for a turn that never terminates. Default 90 000 ms. */\n timeoutMs?: number\n}\n\nexport interface UseChatStreamResult extends LiveTurnState {\n /** The live turn, or `null` when idle — hand to the transcript's `live` slot. */\n live: LiveTurnState | null\n /** Returns false and does nothing for blank input or while a turn is active. */\n send(text: string): Promise<boolean>\n stop(): Promise<void>\n reset(): void\n}\n\n/**\n * Transport-injected live-turn state machine for chat surfaces: send →\n * pending → streaming (coalesced text/reasoning deltas, tool calls, node\n * progress) → done / error / interrupted. Persists nothing — the app owns\n * sessions, messages and the branching endpoints; after a terminal state\n * the app refetches and calls `reset()`.\n */\nexport function useChatStream(options: UseChatStreamOptions): UseChatStreamResult {\n const { transport, flushMs = 33, timeoutMs = 90_000 } = options\n const [state, setState] = useState<LiveTurnState>(initialLiveTurnState)\n const stateRef = useRef(state)\n const dispatch = useCallback((action: LiveTurnAction) => {\n const next = liveTurnReducer(stateRef.current, action)\n stateRef.current = next\n setState(next)\n }, [])\n\n const transportRef = useRef(transport)\n transportRef.current = transport\n\n const textBufferRef = useRef('')\n const reasoningBufferRef = useRef('')\n const flushTimerRef = useRef<number | null>(null)\n const timeoutTimerRef = useRef<number | null>(null)\n const terminalRef = useRef(false)\n\n const cancelFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n window.clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n }, [])\n\n const flush = useCallback(() => {\n cancelFlush()\n const text = textBufferRef.current\n const reasoning = reasoningBufferRef.current\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n if (text !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text, kind: 'text' } })\n }\n if (reasoning !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text: reasoning, kind: 'reasoning' } })\n }\n }, [cancelFlush, dispatch])\n\n const scheduleFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n return\n }\n flushTimerRef.current = window.setTimeout(() => {\n flushTimerRef.current = null\n flush()\n }, flushMs)\n }, [flush, flushMs])\n\n const clearTimeoutTimer = useCallback(() => {\n if (timeoutTimerRef.current !== null) {\n window.clearTimeout(timeoutTimerRef.current)\n timeoutTimerRef.current = null\n }\n }, [])\n\n useEffect(() => {\n const unsubscribe = transportRef.current.subscribe({\n onEvent: (event) => {\n if (terminalRef.current) {\n return\n }\n if (event.event === 'delta') {\n if (event.kind === 'reasoning') {\n reasoningBufferRef.current += event.text\n } else {\n textBufferRef.current += event.text\n }\n scheduleFlush()\n return\n }\n flush()\n if (event.event === 'flow_finished' || event.event === 'flow_failed') {\n terminalRef.current = true\n clearTimeoutTimer()\n }\n dispatch({ type: 'event', event })\n },\n })\n return unsubscribe\n }, [scheduleFlush, flush, clearTimeoutTimer, dispatch])\n\n useEffect(\n () => () => {\n cancelFlush()\n clearTimeoutTimer()\n },\n [cancelFlush, clearTimeoutTimer],\n )\n\n const send = useCallback(\n async (text: string) => {\n const trimmed = text.trim()\n const active = stateRef.current.status === 'pending' || stateRef.current.status === 'streaming'\n if (trimmed === '' || active) {\n return false\n }\n terminalRef.current = false\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'send', at: Date.now() })\n timeoutTimerRef.current = window.setTimeout(() => {\n if (terminalRef.current) {\n return\n }\n terminalRef.current = true\n flush()\n dispatch({\n type: 'event',\n event: { event: 'flow_failed', code: 'timeout', message: 'Chat turn timed out', retryable: true },\n })\n }, timeoutMs)\n try {\n await transportRef.current.send({ text: trimmed })\n } catch (sendError) {\n if (terminalRef.current) {\n return true\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({\n type: 'event',\n event: {\n event: 'flow_failed',\n code: 'send_failed',\n message: sendError instanceof Error ? sendError.message : String(sendError),\n retryable: true,\n },\n })\n }\n return true\n },\n [cancelFlush, clearTimeoutTimer, dispatch, flush, timeoutMs],\n )\n\n const stop = useCallback(async () => {\n const current = stateRef.current\n if (current.status !== 'pending' && current.status !== 'streaming') {\n return\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({ type: 'stop', at: Date.now() })\n await transportRef.current.stop?.().catch(() => undefined)\n }, [clearTimeoutTimer, dispatch, flush])\n\n const reset = useCallback(() => {\n terminalRef.current = false\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'reset' })\n }, [cancelFlush, clearTimeoutTimer, dispatch])\n\n return {\n ...state,\n live: state.status === 'idle' ? null : state,\n send,\n stop,\n reset,\n }\n}\n"]}