@steerable/agent-ui 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  export { useChatStream } from './useChatStream.js';
2
- export type { UseChatStreamOptions, UseChatStreamReturn, ChatStreamTransport, ChatStreamSendInput, } from './useChatStream.js';
2
+ export type { UseChatStreamOptions, UseChatStreamReturn, ChatStreamTransport, ChatStreamSendInput, SteerOutcome, } from './useChatStream.js';
3
3
  export { useToolCallStatus } from './useToolCallStatus.js';
4
4
  export type { ToolCallStatus, UseToolCallStatusOptions, UseToolCallStatusReturn, } from './useToolCallStatus.js';
5
5
  export { useAgentSession } from './useAgentSession.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,cAAc,EACd,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,cAAc,EACd,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAQnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAO3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AASnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAO3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -35,6 +35,17 @@ export interface ChatStreamTransport {
35
35
  */
36
36
  steer?: (content: string) => Promise<boolean>;
37
37
  }
38
+ /**
39
+ * Where `steerOrFollowUpUserMessage` actually delivered the message:
40
+ * - `'steered'` — the running turn accepted the injection.
41
+ * - `'queued'` — the turn is still running but not steerable; the message
42
+ * sits in the W6-2 follow-up queue and auto-sends at turn end.
43
+ * - `'sent'` — no turn was running by the time the steer attempt settled;
44
+ * the message went out immediately as a normal new turn.
45
+ * The union lets callers render their own locale-specific notice per
46
+ * outcome; the hook itself carries no user-facing copy.
47
+ */
48
+ export type SteerOutcome = 'steered' | 'queued' | 'sent';
38
49
  export interface UseChatStreamOptions {
39
50
  /** Required transport handle; see `ChatStreamTransport`. */
40
51
  transport: ChatStreamTransport;
@@ -62,6 +73,17 @@ export interface UseChatStreamReturn {
62
73
  * sent automatically as the next turn when the current one ends.
63
74
  */
64
75
  sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
76
+ /**
77
+ * W7-1: resume the interrupted turn recorded on the backend. Streams an
78
+ * assistant turn WITHOUT appending a user message — the transport receives
79
+ * `{ content: '', metadata: { ...input?.metadata, resume: true } }` and the
80
+ * backend replays the durable record's projection as the loop seed, so the
81
+ * user never retypes the interrupted request. No-op while streaming (a
82
+ * resume is not queueable as a follow-up — it is not a user message).
83
+ */
84
+ resumeTurn: (input?: {
85
+ metadata?: Record<string, unknown>;
86
+ }) => Promise<void>;
65
87
  /**
66
88
  * Steer the running turn with an extra user message. No-op resolving
67
89
  * `false` when not streaming or the transport has no `steer`. On success
@@ -69,6 +91,16 @@ export interface UseChatStreamReturn {
69
91
  * consumes it at the next round boundary).
70
92
  */
71
93
  steerUserMessage: (content: string) => Promise<boolean>;
94
+ /**
95
+ * Steer with a never-drop fallback (W6-2): tries `steerUserMessage` first,
96
+ * then degrades based on live turn state — see `SteerOutcome` for the
97
+ * three outcomes. The turn-already-ended race is resolved by re-checking
98
+ * `isStreaming` after the steer attempt settles: a finished turn's
99
+ * `finally` has already drained the queue, so queueing there would strand
100
+ * the message until some future turn ends — it is sent directly instead.
101
+ * Prefer this over `steerUserMessage` for composer Enter handlers.
102
+ */
103
+ steerOrFollowUpUserMessage: (content: string) => Promise<SteerOutcome>;
72
104
  /**
73
105
  * Explicitly queue a message as a follow-up (W6-2): sent automatically as
74
106
  * the next turn once the current turn finishes. When not streaming this is
@@ -1 +1 @@
1
- {"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,IAAI,CAAC;IACjE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,0EAA0E;IAC1E,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,uEAAuE;IACvE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAsID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+MrB"}
1
+ {"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,IAAI,CAAC;IACjE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;;;;OAQG;IACH,0BAA0B,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACvE;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,0EAA0E;IAC1E,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,uEAAuE;IACvE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAsID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+QrB"}
@@ -201,15 +201,14 @@ export function useChatStream(options) {
201
201
  options.onUnknownEvent?.(event);
202
202
  }
203
203
  }, [options]);
204
- const sendUserMessage = useCallback(async (input) => {
205
- if (isStreamingRef.current) {
206
- // W6-2: queue instead of dropping. The finishing turn's `finally`
207
- // drains the queue and starts the next turn automatically.
208
- followUpsRef.current = [...followUpsRef.current, input];
209
- bumpFollowUpVersion();
210
- return;
204
+ // Shared turn core: stream one assistant turn and drain the follow-up
205
+ // queue at the end. `echoUserMessage` is false only for W7-1 resume —
206
+ // the interrupted turn's user message is already in the transcript and
207
+ // the backend replays it from the durable record.
208
+ const startTurn = useCallback(async (input, opts) => {
209
+ if (opts.echoUserMessage) {
210
+ dispatch({ type: 'append', message: newUserMessage(input.content) });
211
211
  }
212
- dispatch({ type: 'append', message: newUserMessage(input.content) });
213
212
  dispatch({ type: 'append', message: newAssistantPlaceholder() });
214
213
  setStreaming(true);
215
214
  try {
@@ -238,6 +237,21 @@ export function useChatStream(options) {
238
237
  }
239
238
  }
240
239
  }, [handleEvent, options.transport, setStreaming]);
240
+ const sendUserMessage = useCallback(async (input) => {
241
+ if (isStreamingRef.current) {
242
+ // W6-2: queue instead of dropping. The finishing turn's `finally`
243
+ // drains the queue and starts the next turn automatically.
244
+ followUpsRef.current = [...followUpsRef.current, input];
245
+ bumpFollowUpVersion();
246
+ return;
247
+ }
248
+ await startTurn(input, { echoUserMessage: true });
249
+ }, [startTurn]);
250
+ const resumeTurn = useCallback(async (input) => {
251
+ if (isStreamingRef.current)
252
+ return;
253
+ await startTurn({ content: '', metadata: { ...input?.metadata, resume: true } }, { echoUserMessage: false });
254
+ }, [startTurn]);
241
255
  useEffect(() => {
242
256
  sendRef.current = sendUserMessage;
243
257
  }, [sendUserMessage]);
@@ -275,6 +289,38 @@ export function useChatStream(options) {
275
289
  }
276
290
  return ok;
277
291
  }, [options.transport]);
292
+ const steerOrFollowUpUserMessage = useCallback(async (content) => {
293
+ const input = { content };
294
+ // No turn running: there is nothing to inject into and nothing whose
295
+ // `finally` would drain the queue — a normal send is the only route
296
+ // that delivers the message.
297
+ if (!isStreamingRef.current) {
298
+ void sendRef.current?.(input);
299
+ return 'sent';
300
+ }
301
+ const steer = options.transport.steer;
302
+ if (steer) {
303
+ const ok = await steer(content);
304
+ if (ok) {
305
+ dispatch({ type: 'append', message: newUserMessage(content) });
306
+ return 'steered';
307
+ }
308
+ // The turn may have ended while the steer was in flight; its
309
+ // `finally` already ran the queue drain, so a queued message would
310
+ // sit unsent until an unrelated future turn ends. Send it as a fresh
311
+ // turn instead. If a queued follow-up already started the next turn,
312
+ // isStreamingRef is true again and queueing below is correct.
313
+ if (!isStreamingRef.current) {
314
+ void sendRef.current?.(input);
315
+ return 'sent';
316
+ }
317
+ }
318
+ // Turn still running but not steerable (transport lacks `steer`, or
319
+ // the loop rejected the injection): W6-2 queue, drained at turn end.
320
+ followUpsRef.current = [...followUpsRef.current, input];
321
+ bumpFollowUpVersion();
322
+ return 'queued';
323
+ }, [options.transport]);
278
324
  const setMessages = useCallback((messages) => {
279
325
  dispatch({ type: 'reset', messages });
280
326
  }, []);
@@ -293,7 +339,9 @@ export function useChatStream(options) {
293
339
  messages: state.messages,
294
340
  isStreaming: isStreamingRef.current,
295
341
  sendUserMessage,
342
+ resumeTurn,
296
343
  steerUserMessage,
344
+ steerOrFollowUpUserMessage,
297
345
  followUpUserMessage,
298
346
  // followUpVersion re-keys this memo so queue changes surface to the UI.
299
347
  pendingFollowUps: followUpsRef.current,
@@ -306,7 +354,9 @@ export function useChatStream(options) {
306
354
  [
307
355
  state.messages,
308
356
  sendUserMessage,
357
+ resumeTurn,
309
358
  steerUserMessage,
359
+ steerOrFollowUpUserMessage,
310
360
  followUpUserMessage,
311
361
  removeFollowUp,
312
362
  cancel,
@@ -1 +1 @@
1
- {"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAyG5E,SAAS,OAAO,CAAC,KAAY,EAAE,MAAc;IAC3C,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,KAAK,QAAQ;YACX,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,KAAK,sBAAsB,CAAC,CAAC,CAAC;YAC5B,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,MAAM,OAAO,GAAgB;gBAC3B,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;gBACtB,GAAG,MAAM,CAAC,KAAK;aACD,CAAC;YACjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACpB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,OAAO,GAAgB;gBAC3B,GAAG,MAAM;gBACT,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK;aAChC,CAAC;YACjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACpB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxB,MAAM,OAAO,GAAgB,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,KAAK,EAAiB,CAAC;YAC5E,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACpB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,KAAK,oBAAoB,CAAC,CAAC,CAAC;YAC1B,wEAAwE;YACxE,qEAAqE;YACrE,MAAM,GAAG,GAAG,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,GAAG,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnC,MAAM,OAAO,GAAgB;gBAC3B,GAAG,MAAM;gBACT,UAAU,EAAE,MAAM,CAAC,MAAM;aACX,CAAC;YACjB,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;YACpB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,KAAK,oBAAoB;YACvB,OAAO,KAAK,CAAC;QACf;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAAC,QAAuB;IACrD,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW;YAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,SAAS,uBAAuB;IAC9B,OAAO;QACL,EAAE,EAAE,eAAe,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE;QAClE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,OAAe;IACrC,OAAO;QACL,EAAE,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE;QAC3D,IAAI,EAAE,MAAM;QACZ,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAe;IACvC,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IACD,2EAA2E;IAC3E,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IACjE,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IAC5D,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,KAAe;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAkB,CAAC;IACzC,IACE,OAAO;QACP,OAAO,OAAO,KAAK,QAAQ;QAC3B,IAAI,IAAI,OAAO;QACf,MAAM,IAAI,OAAO;QACjB,WAAW,IAAI,OAAO,EACtB,CAAC;QACD,OAAO,OAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,KAAe;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAkB,CAAC;IACzC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;QACnE,OAAO,OAAqB,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,OAA6B;IAE7B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE;QAC5C,QAAQ,EAAE,OAAO,CAAC,eAAe,IAAI,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,EAAE,aAAa,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,2EAA2E;IAC3E,wEAAwE;IACxE,mDAAmD;IACnD,MAAM,YAAY,GAAG,MAAM,CAAwB,EAAE,CAAC,CAAC;IACvD,MAAM,CAAC,eAAe,EAAE,mBAAmB,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,2EAA2E;IAC3E,mCAAmC;IACnC,MAAM,OAAO,GAAG,MAAM,EAAiD,CAAC;IAExE,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,IAAa,EAAE,EAAE;QACjD,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;QAC9B,aAAa,EAAE,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,KAAe,EAAE,EAAE;QAClB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,KAAK;oBAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YACD,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACjC,IAAI,IAAI;oBAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;gBACrC,IAAI,CAAC,MAAM;oBAAE,OAAO;gBACpB,MAAM,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,MAAM,CAAC,CAAC;gBACrD,IAAI,MAAM,EAAE,CAAC;oBACX,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;gBAChD,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,CAAC,CAAC;gBACnD,CAAC;gBACD,OAAO;YACT,CAAC;YACD,KAAK,MAAM;gBACT,QAAQ,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACzC,OAAO;YACT,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,IAAI,cAAc,CAAC;gBAC5C,QAAQ,CAAC;oBACP,IAAI,EAAE,sBAAsB;oBAC5B,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,GAAG,EAAE,EAAE;iBAC5C,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,KAAK,kBAAkB,CAAC,CAAC,CAAC;gBACxB,QAAQ,CAAC;oBACP,IAAI,EAAE,sBAAsB;oBAC5B,KAAK,EAAE;wBACL,OAAO,EACL,KAAK,CAAC,OAAO;4BACb,kDAAkD;qBACrD;iBACF,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,KAAK,OAAO,CAAC;YACb,KAAK,eAAe,CAAC;YACrB,KAAK,aAAa,CAAC;YACnB,KAAK,WAAW,CAAC;YACjB;gBACE,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,MAAM,eAAe,GAAG,WAAW,CACjC,KAAK,EAAE,KAA0B,EAAE,EAAE;QACnC,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,kEAAkE;YAClE,2DAA2D;YAC3D,YAAY,CAAC,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACxD,mBAAmB,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrE,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,uBAAuB,EAAE,EAAE,CAAC,CAAC;QACjE,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YAClE,SAAS,CAAC,OAAO,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC;gBACP,IAAI,EAAE,sBAAsB;gBAC5B,KAAK,EAAE;oBACL,OAAO,EACL,iBAAiB;wBACjB,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;iBACrD;aACF,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,oEAAoE;YACpE,oEAAoE;YACpE,kDAAkD;YAClD,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC1C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,mBAAmB,EAAE,CAAC;gBACtB,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC,EACD,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAC/C,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,OAAO,GAAG,eAAe,CAAC;IACpC,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,KAA0B,EAAE,EAAE;QACrE,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC5B,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,YAAY,CAAC,OAAO,GAAG,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACxD,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,EAAE;QACnD,YAAY,CAAC,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;QAC1E,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,yEAAyE;QACzE,wEAAwE;QACxE,YAAY,CAAC,OAAO,GAAG,EAAE,CAAC;QAC1B,mBAAmB,EAAE,CAAC;QACtB,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,SAAS,CAAC,OAAO,EAAE,CAAC;YACpB,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,MAAM,gBAAgB,GAAG,WAAW,CAClC,KAAK,EAAE,OAAe,EAAoB,EAAE;QAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC;QACtC,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACzD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,EAAE,EAAE,CAAC;YACP,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,OAAO,CAAC,SAAS,CAAC,CACpB,CAAC;IAEF,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,QAAuB,EAAE,EAAE;QAC1D,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACxC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,OAAoB,EAAE,EAAE;QACzD,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;IACxC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;gBACtB,SAAS,CAAC,OAAO,EAAE,CAAC;gBACpB,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,cAAc,CAAC,OAAO;QACnC,eAAe;QACf,gBAAgB;QAChB,mBAAmB;QACnB,wEAAwE;QACxE,gBAAgB,EAAE,YAAY,CAAC,OAAO;QACtC,cAAc;QACd,MAAM;QACN,WAAW;QACX,aAAa;KACd,CAAC;IACF,uDAAuD;IACvD;QACE,KAAK,CAAC,QAAQ;QACd,eAAe;QACf,gBAAgB;QAChB,mBAAmB;QACnB,cAAc;QACd,MAAM;QACN,WAAW;QACX,aAAa;QACb,eAAe;KAChB,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Steerable framework Tier 4 — headless React hooks (useAgentSession, useChatStream, useToolCallStatus) and Tailwind-themed components (ChatPanel, MessageList, OrchestrationPlanCard, ToolCallRenderer, SSEStreamView) for embedding Steerable agents in any React app.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/storybook/",
@@ -57,7 +57,7 @@
57
57
  "README.md"
58
58
  ],
59
59
  "dependencies": {
60
- "@steerable/agent-protocol": "0.3.0"
60
+ "@steerable/agent-protocol": "0.4.0"
61
61
  },
62
62
  "peerDependencies": {
63
63
  "react": ">=18.0.0",
@@ -4,6 +4,7 @@ export type {
4
4
  UseChatStreamReturn,
5
5
  ChatStreamTransport,
6
6
  ChatStreamSendInput,
7
+ SteerOutcome,
7
8
  } from './useChatStream.js';
8
9
 
9
10
  export { useToolCallStatus } from './useToolCallStatus.js';
@@ -33,6 +33,12 @@ const {
33
33
  messages, // ChatMessage[]
34
34
  isStreaming, // boolean
35
35
  sendUserMessage, // ({ content }) => Promise<void>
36
+ resumeTurn, // ({ metadata? }?) => Promise<void> — W7-1 crash resume
37
+ steerUserMessage, // (content) => Promise<boolean> — raw mid-turn steer
38
+ steerOrFollowUpUserMessage, // (content) => Promise<SteerOutcome> — steer with never-drop fallback
39
+ followUpUserMessage, // ({ content }) => void — queue for after the turn (W6-2)
40
+ pendingFollowUps, // ChatStreamSendInput[] — queued, not yet sent
41
+ removeFollowUp, // (index) => void — withdraw a queued message
36
42
  cancel, // () => void
37
43
  setMessages, // (messages) => void
38
44
  appendMessage, // (message) => void
@@ -113,6 +119,19 @@ const { ... } = useChatStream({
113
119
  });
114
120
  ```
115
121
 
122
+ ## Steering and follow-ups (W6-2)
123
+
124
+ While a turn is streaming there are three ways to submit more input, with different delivery guarantees:
125
+
126
+ * `steerUserMessage(content)` — raw mid-turn steer. Resolves `true` when the running turn accepted the message (it is appended to the transcript at once); `false` when no steerable turn is active (no transport `steer`, or the turn already ended). On `false` nothing else happens — what to do with the draft is the caller's problem.
127
+ * `followUpUserMessage(input)` — queue the message; it is sent automatically as the next turn when the current turn's `finally` drains the queue. `pendingFollowUps` exposes the queue (in order, not yet sent) and `removeFollowUp(index)` withdraws one. `sendUserMessage` while streaming degrades into this same queue rather than dropping.
128
+ * `steerOrFollowUpUserMessage(content)` — the never-drop combinator, intended for composer Enter handlers. It tries the steer first and resolves a `SteerOutcome` telling the caller where the message landed:
129
+ * `'steered'` — injected into the running turn.
130
+ * `'queued'` — the turn is still running but not steerable; the message sits in the follow-up queue and auto-sends at turn end.
131
+ * `'sent'` — no turn was running by the time the steer attempt settled. This covers the race where the turn ends while `transport.steer` is in flight: that turn's `finally` already drained the queue, so queueing would strand the message until some unrelated future turn ends — it is sent immediately as a normal new turn instead.
132
+
133
+ The hook exposes only the neutral outcome; rendering any notice ("queued instead of injected", "N queued will be discarded") is the caller's job, in the caller's locale.
134
+
116
135
  ## Cancellation
117
136
 
118
137
  `transport.stream` may return a `() => void` cancel handle. The hook stores it and calls it when:
@@ -122,8 +141,11 @@ const { ... } = useChatStream({
122
141
 
123
142
  If your transport can't cancel, return `void` — the hook will simply mark `isStreaming` as `false` when the promise settles.
124
143
 
144
+ `cancel()` also drops every queued follow-up (W6-2): an explicit stop means "don't keep going", so queued messages are not auto-sent afterwards. If your UI shows `pendingFollowUps`, tell the user that stopping discards them before they confirm.
145
+
125
146
  ## Caveats
126
147
 
127
- * `sendUserMessage` is a no-op while `isStreaming` is true. Wire your composer's send button to `disabled={isStreaming}` to mirror this in the UI.
148
+ * `sendUserMessage` while `isStreaming` is true does NOT drop the message — it appends to the W6-2 follow-up queue and auto-sends as the next turn. If you'd rather block input during streaming, wire your composer's send button to `disabled={isStreaming}`.
149
+ * `resumeTurn` (W7-1) streams an assistant turn WITHOUT appending a user message — the transport receives `{ content: '', metadata: { resume: true } }` and the backend replays the durable record's projection as the loop seed. Use it for a "continue the interrupted turn" affordance after a crash/kill; it is a no-op while streaming and is never queued as a follow-up.
128
150
  * Tool results are attached to the **latest assistant message**. If you need per-call lookup, override `renderToolCall` on `ChatPanel` and pull the result from your own map.
129
151
  * Setting `toolResultToMessage` to a non-null returner switches to a "tool result lives in its own message" model — useful if you want to render tool results as standalone bubbles. Otherwise the result rides inline on the assistant turn.
@@ -14,13 +14,14 @@ import {
14
14
  useChatStream,
15
15
  type ChatStreamSendInput,
16
16
  type ChatStreamTransport,
17
+ type SteerOutcome,
17
18
  } from './useChatStream';
18
19
 
19
20
  /**
20
21
  * A transport whose streams stay open until the test explicitly finishes them,
21
22
  * so we can submit follow-ups mid-stream and watch the queue drain.
22
23
  */
23
- function makeControllableTransport() {
24
+ function makeControllableTransport(steer?: ChatStreamTransport['steer']) {
24
25
  const streams: Array<{
25
26
  input: ChatStreamSendInput;
26
27
  onEvent: (e: SSEEvent) => void;
@@ -38,10 +39,20 @@ function makeControllableTransport() {
38
39
  },
39
40
  });
40
41
  }),
42
+ ...(steer ? { steer } : {}),
41
43
  };
42
44
  return { transport, streams };
43
45
  }
44
46
 
47
+ /** Manually-resolved promise, for parking a steer attempt mid-flight. */
48
+ function makeDeferred<T>() {
49
+ let resolve!: (value: T) => void;
50
+ const promise = new Promise<T>((r) => {
51
+ resolve = r;
52
+ });
53
+ return { promise, resolve };
54
+ }
55
+
45
56
  function makeTransport(
46
57
  script: SSEEvent[][],
47
58
  ): { transport: ChatStreamTransport; cancel: ReturnType<typeof vi.fn> } {
@@ -337,6 +348,148 @@ describe('steerUserMessage', () => {
337
348
  });
338
349
  });
339
350
 
351
+ describe('steerOrFollowUpUserMessage', () => {
352
+ it("resolves 'steered' and appends the message when the turn accepts the injection", async () => {
353
+ const steer = vi.fn().mockResolvedValue(true);
354
+ const { transport, streams } = makeControllableTransport(steer);
355
+ const { result } = renderHook(() => useChatStream({ transport }));
356
+
357
+ act(() => {
358
+ void result.current.sendUserMessage({ content: 'start' });
359
+ });
360
+ await waitFor(() => expect(streams).toHaveLength(1));
361
+
362
+ let outcome: SteerOutcome | undefined;
363
+ await act(async () => {
364
+ outcome = await result.current.steerOrFollowUpUserMessage('补充一句');
365
+ });
366
+ expect(outcome).toBe('steered');
367
+ expect(
368
+ result.current.messages.some((m) => m.role === 'user' && m.content === '补充一句'),
369
+ ).toBe(true);
370
+ expect(result.current.pendingFollowUps).toEqual([]);
371
+ expect(streams).toHaveLength(1);
372
+
373
+ await act(async () => {
374
+ streams[0]!.finish();
375
+ });
376
+ });
377
+
378
+ it("resolves 'queued' when the transport has no steer, then drains at turn end", async () => {
379
+ const { transport, streams } = makeControllableTransport();
380
+ const { result } = renderHook(() => useChatStream({ transport }));
381
+
382
+ act(() => {
383
+ void result.current.sendUserMessage({ content: 'first' });
384
+ });
385
+ await waitFor(() => expect(streams).toHaveLength(1));
386
+
387
+ let outcome: SteerOutcome | undefined;
388
+ await act(async () => {
389
+ outcome = await result.current.steerOrFollowUpUserMessage('排队等下轮');
390
+ });
391
+ expect(outcome).toBe('queued');
392
+ expect(result.current.pendingFollowUps).toEqual([{ content: '排队等下轮' }]);
393
+
394
+ await act(async () => {
395
+ streams[0]!.finish();
396
+ });
397
+ await waitFor(() => expect(streams).toHaveLength(2));
398
+ expect(streams[1]!.input.content).toBe('排队等下轮');
399
+ await act(async () => {
400
+ streams[1]!.finish();
401
+ });
402
+ });
403
+
404
+ it("resolves 'queued' when the steer is rejected and the turn is still running", async () => {
405
+ const steer = vi.fn().mockResolvedValue(false);
406
+ const { transport, streams } = makeControllableTransport(steer);
407
+ const { result } = renderHook(() => useChatStream({ transport }));
408
+
409
+ act(() => {
410
+ void result.current.sendUserMessage({ content: 'first' });
411
+ });
412
+ await waitFor(() => expect(streams).toHaveLength(1));
413
+
414
+ let outcome: SteerOutcome | undefined;
415
+ await act(async () => {
416
+ outcome = await result.current.steerOrFollowUpUserMessage('拒收的转向');
417
+ });
418
+ expect(outcome).toBe('queued');
419
+ expect(steer).toHaveBeenCalledWith('拒收的转向');
420
+ expect(result.current.pendingFollowUps).toEqual([{ content: '拒收的转向' }]);
421
+
422
+ await act(async () => {
423
+ streams[0]!.finish();
424
+ });
425
+ await waitFor(() => expect(streams).toHaveLength(2));
426
+ expect(streams[1]!.input.content).toBe('拒收的转向');
427
+ await act(async () => {
428
+ streams[1]!.finish();
429
+ });
430
+ });
431
+
432
+ it("resolves 'sent' when the turn ended while the steer attempt was in flight", async () => {
433
+ const gate = makeDeferred<boolean>();
434
+ const steer = vi.fn(() => gate.promise);
435
+ const { transport, streams } = makeControllableTransport(steer);
436
+ const { result } = renderHook(() => useChatStream({ transport }));
437
+
438
+ act(() => {
439
+ void result.current.sendUserMessage({ content: 'first' });
440
+ });
441
+ await waitFor(() => expect(streams).toHaveLength(1));
442
+
443
+ let outcomePromise: Promise<SteerOutcome>;
444
+ await act(async () => {
445
+ outcomePromise = result.current.steerOrFollowUpUserMessage('来迟的补充');
446
+ });
447
+ expect(steer).toHaveBeenCalledWith('来迟的补充');
448
+
449
+ // The turn ends while the steer is still pending: its `finally` drains
450
+ // the (empty) queue and clears isStreaming before the steer settles.
451
+ await act(async () => {
452
+ streams[0]!.finish();
453
+ });
454
+ await waitFor(() => expect(result.current.isStreaming).toBe(false));
455
+
456
+ let outcome: SteerOutcome | undefined;
457
+ await act(async () => {
458
+ gate.resolve(false);
459
+ outcome = await outcomePromise;
460
+ });
461
+ // Queueing here would strand the message (the drain already ran), so it
462
+ // goes out immediately as a fresh turn instead.
463
+ expect(outcome).toBe('sent');
464
+ expect(result.current.pendingFollowUps).toEqual([]);
465
+ await waitFor(() => expect(streams).toHaveLength(2));
466
+ expect(streams[1]!.input.content).toBe('来迟的补充');
467
+ await act(async () => {
468
+ streams[1]!.finish();
469
+ });
470
+ });
471
+
472
+ it("resolves 'sent' without touching steer when no turn is streaming", async () => {
473
+ const steer = vi.fn().mockResolvedValue(true);
474
+ const { transport, streams } = makeControllableTransport(steer);
475
+ const { result } = renderHook(() => useChatStream({ transport }));
476
+
477
+ let outcome: SteerOutcome | undefined;
478
+ await act(async () => {
479
+ outcome = await result.current.steerOrFollowUpUserMessage('直接发出');
480
+ });
481
+ expect(outcome).toBe('sent');
482
+ expect(steer).not.toHaveBeenCalled();
483
+ expect(streams).toHaveLength(1);
484
+ expect(streams[0]!.input.content).toBe('直接发出');
485
+ expect(result.current.pendingFollowUps).toEqual([]);
486
+
487
+ await act(async () => {
488
+ streams[0]!.finish();
489
+ });
490
+ });
491
+ });
492
+
340
493
  describe('follow-up queue (W6-2)', () => {
341
494
  it('sendUserMessage while streaming queues instead of dropping, then auto-sends next turn', async () => {
342
495
  const { transport, streams } = makeControllableTransport();
@@ -462,3 +615,90 @@ describe('follow-up queue (W6-2)', () => {
462
615
  expect(streams).toHaveLength(1);
463
616
  });
464
617
  });
618
+
619
+ describe('resumeTurn (W7-1)', () => {
620
+ it('streams an assistant turn with resume:true and NO new user message', async () => {
621
+ const { transport, streams } = makeControllableTransport();
622
+ const { result } = renderHook(() =>
623
+ useChatStream({
624
+ transport,
625
+ initialMessages: [
626
+ {
627
+ id: 'u1',
628
+ role: 'user',
629
+ content: '被中断的请求',
630
+ createdAt: new Date().toISOString(),
631
+ },
632
+ ],
633
+ }),
634
+ );
635
+
636
+ act(() => {
637
+ void result.current.resumeTurn({ metadata: { mode: 'plan' } });
638
+ });
639
+ await waitFor(() => expect(streams).toHaveLength(1));
640
+ await act(async () => {
641
+ streams[0]!.finish();
642
+ });
643
+ await waitFor(() => expect(result.current.isStreaming).toBe(false));
644
+
645
+ // The wire input carries the resume marker and the caller's metadata,
646
+ // with empty content — the backend replays the record, so no user text
647
+ // is re-sent.
648
+ expect(streams).toHaveLength(1);
649
+ expect(streams[0]!.input).toEqual({
650
+ content: '',
651
+ metadata: { mode: 'plan', resume: true },
652
+ });
653
+ // The transcript gains ONLY the assistant placeholder — the interrupted
654
+ // turn's user message is already there (initialMessages), not duplicated.
655
+ expect(result.current.messages.map((m) => m.role)).toEqual([
656
+ 'user',
657
+ 'assistant',
658
+ ]);
659
+ expect(result.current.isStreaming).toBe(false);
660
+ });
661
+
662
+ it('is a no-op while streaming (a resume is not queueable)', async () => {
663
+ const { transport, streams } = makeControllableTransport();
664
+ const { result } = renderHook(() => useChatStream({ transport }));
665
+
666
+ act(() => {
667
+ void result.current.sendUserMessage({ content: 'first' });
668
+ });
669
+ await waitFor(() => expect(streams).toHaveLength(1));
670
+
671
+ await act(async () => {
672
+ await result.current.resumeTurn();
673
+ });
674
+ expect(streams).toHaveLength(1);
675
+ expect(result.current.pendingFollowUps).toEqual([]);
676
+
677
+ await act(async () => {
678
+ streams[0]!.finish();
679
+ });
680
+ });
681
+
682
+ it('drains the follow-up queue after the resumed turn ends', async () => {
683
+ const { transport, streams } = makeControllableTransport();
684
+ const { result } = renderHook(() => useChatStream({ transport }));
685
+
686
+ act(() => {
687
+ void result.current.resumeTurn();
688
+ });
689
+ await waitFor(() => expect(streams).toHaveLength(1));
690
+
691
+ await act(async () => {
692
+ result.current.followUpUserMessage({ content: 'next question' });
693
+ });
694
+
695
+ await act(async () => {
696
+ streams[0]!.finish();
697
+ });
698
+ await waitFor(() => expect(streams).toHaveLength(2));
699
+ expect(streams[1]!.input.content).toBe('next question');
700
+ await act(async () => {
701
+ streams[1]!.finish();
702
+ });
703
+ });
704
+ });
@@ -48,6 +48,18 @@ export interface ChatStreamTransport {
48
48
  steer?: (content: string) => Promise<boolean>;
49
49
  }
50
50
 
51
+ /**
52
+ * Where `steerOrFollowUpUserMessage` actually delivered the message:
53
+ * - `'steered'` — the running turn accepted the injection.
54
+ * - `'queued'` — the turn is still running but not steerable; the message
55
+ * sits in the W6-2 follow-up queue and auto-sends at turn end.
56
+ * - `'sent'` — no turn was running by the time the steer attempt settled;
57
+ * the message went out immediately as a normal new turn.
58
+ * The union lets callers render their own locale-specific notice per
59
+ * outcome; the hook itself carries no user-facing copy.
60
+ */
61
+ export type SteerOutcome = 'steered' | 'queued' | 'sent';
62
+
51
63
  export interface UseChatStreamOptions {
52
64
  /** Required transport handle; see `ChatStreamTransport`. */
53
65
  transport: ChatStreamTransport;
@@ -76,6 +88,15 @@ export interface UseChatStreamReturn {
76
88
  * sent automatically as the next turn when the current one ends.
77
89
  */
78
90
  sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
91
+ /**
92
+ * W7-1: resume the interrupted turn recorded on the backend. Streams an
93
+ * assistant turn WITHOUT appending a user message — the transport receives
94
+ * `{ content: '', metadata: { ...input?.metadata, resume: true } }` and the
95
+ * backend replays the durable record's projection as the loop seed, so the
96
+ * user never retypes the interrupted request. No-op while streaming (a
97
+ * resume is not queueable as a follow-up — it is not a user message).
98
+ */
99
+ resumeTurn: (input?: { metadata?: Record<string, unknown> }) => Promise<void>;
79
100
  /**
80
101
  * Steer the running turn with an extra user message. No-op resolving
81
102
  * `false` when not streaming or the transport has no `steer`. On success
@@ -83,6 +104,16 @@ export interface UseChatStreamReturn {
83
104
  * consumes it at the next round boundary).
84
105
  */
85
106
  steerUserMessage: (content: string) => Promise<boolean>;
107
+ /**
108
+ * Steer with a never-drop fallback (W6-2): tries `steerUserMessage` first,
109
+ * then degrades based on live turn state — see `SteerOutcome` for the
110
+ * three outcomes. The turn-already-ended race is resolved by re-checking
111
+ * `isStreaming` after the steer attempt settles: a finished turn's
112
+ * `finally` has already drained the queue, so queueing there would strand
113
+ * the message until some future turn ends — it is sent directly instead.
114
+ * Prefer this over `steerUserMessage` for composer Enter handlers.
115
+ */
116
+ steerOrFollowUpUserMessage: (content: string) => Promise<SteerOutcome>;
86
117
  /**
87
118
  * Explicitly queue a message as a follow-up (W6-2): sent automatically as
88
119
  * the next turn once the current turn finishes. When not streaming this is
@@ -313,16 +344,15 @@ export function useChatStream(
313
344
  [options],
314
345
  );
315
346
 
316
- const sendUserMessage = useCallback(
317
- async (input: ChatStreamSendInput) => {
318
- if (isStreamingRef.current) {
319
- // W6-2: queue instead of dropping. The finishing turn's `finally`
320
- // drains the queue and starts the next turn automatically.
321
- followUpsRef.current = [...followUpsRef.current, input];
322
- bumpFollowUpVersion();
323
- return;
347
+ // Shared turn core: stream one assistant turn and drain the follow-up
348
+ // queue at the end. `echoUserMessage` is false only for W7-1 resume —
349
+ // the interrupted turn's user message is already in the transcript and
350
+ // the backend replays it from the durable record.
351
+ const startTurn = useCallback(
352
+ async (input: ChatStreamSendInput, opts: { echoUserMessage: boolean }) => {
353
+ if (opts.echoUserMessage) {
354
+ dispatch({ type: 'append', message: newUserMessage(input.content) });
324
355
  }
325
- dispatch({ type: 'append', message: newUserMessage(input.content) });
326
356
  dispatch({ type: 'append', message: newAssistantPlaceholder() });
327
357
  setStreaming(true);
328
358
  try {
@@ -353,6 +383,31 @@ export function useChatStream(
353
383
  [handleEvent, options.transport, setStreaming],
354
384
  );
355
385
 
386
+ const sendUserMessage = useCallback(
387
+ async (input: ChatStreamSendInput) => {
388
+ if (isStreamingRef.current) {
389
+ // W6-2: queue instead of dropping. The finishing turn's `finally`
390
+ // drains the queue and starts the next turn automatically.
391
+ followUpsRef.current = [...followUpsRef.current, input];
392
+ bumpFollowUpVersion();
393
+ return;
394
+ }
395
+ await startTurn(input, { echoUserMessage: true });
396
+ },
397
+ [startTurn],
398
+ );
399
+
400
+ const resumeTurn = useCallback(
401
+ async (input?: { metadata?: Record<string, unknown> }) => {
402
+ if (isStreamingRef.current) return;
403
+ await startTurn(
404
+ { content: '', metadata: { ...input?.metadata, resume: true } },
405
+ { echoUserMessage: false },
406
+ );
407
+ },
408
+ [startTurn],
409
+ );
410
+
356
411
  useEffect(() => {
357
412
  sendRef.current = sendUserMessage;
358
413
  }, [sendUserMessage]);
@@ -398,6 +453,42 @@ export function useChatStream(
398
453
  [options.transport],
399
454
  );
400
455
 
456
+ const steerOrFollowUpUserMessage = useCallback(
457
+ async (content: string): Promise<SteerOutcome> => {
458
+ const input: ChatStreamSendInput = { content };
459
+ // No turn running: there is nothing to inject into and nothing whose
460
+ // `finally` would drain the queue — a normal send is the only route
461
+ // that delivers the message.
462
+ if (!isStreamingRef.current) {
463
+ void sendRef.current?.(input);
464
+ return 'sent';
465
+ }
466
+ const steer = options.transport.steer;
467
+ if (steer) {
468
+ const ok = await steer(content);
469
+ if (ok) {
470
+ dispatch({ type: 'append', message: newUserMessage(content) });
471
+ return 'steered';
472
+ }
473
+ // The turn may have ended while the steer was in flight; its
474
+ // `finally` already ran the queue drain, so a queued message would
475
+ // sit unsent until an unrelated future turn ends. Send it as a fresh
476
+ // turn instead. If a queued follow-up already started the next turn,
477
+ // isStreamingRef is true again and queueing below is correct.
478
+ if (!isStreamingRef.current) {
479
+ void sendRef.current?.(input);
480
+ return 'sent';
481
+ }
482
+ }
483
+ // Turn still running but not steerable (transport lacks `steer`, or
484
+ // the loop rejected the injection): W6-2 queue, drained at turn end.
485
+ followUpsRef.current = [...followUpsRef.current, input];
486
+ bumpFollowUpVersion();
487
+ return 'queued';
488
+ },
489
+ [options.transport],
490
+ );
491
+
401
492
  const setMessages = useCallback((messages: ChatMessage[]) => {
402
493
  dispatch({ type: 'reset', messages });
403
494
  }, []);
@@ -420,7 +511,9 @@ export function useChatStream(
420
511
  messages: state.messages,
421
512
  isStreaming: isStreamingRef.current,
422
513
  sendUserMessage,
514
+ resumeTurn,
423
515
  steerUserMessage,
516
+ steerOrFollowUpUserMessage,
424
517
  followUpUserMessage,
425
518
  // followUpVersion re-keys this memo so queue changes surface to the UI.
426
519
  pendingFollowUps: followUpsRef.current,
@@ -433,7 +526,9 @@ export function useChatStream(
433
526
  [
434
527
  state.messages,
435
528
  sendUserMessage,
529
+ resumeTurn,
436
530
  steerUserMessage,
531
+ steerOrFollowUpUserMessage,
437
532
  followUpUserMessage,
438
533
  removeFollowUp,
439
534
  cancel,