@runtypelabs/persona 4.22.0 → 4.23.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.
Files changed (95) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-B_JdVK9o.d.cts → types-9Vxm-PPz.d.cts} +136 -6
  4. package/dist/animations/{types-B_JdVK9o.d.ts → types-9Vxm-PPz.d.ts} +136 -6
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/animations-extra.d.cts +136 -6
  8. package/dist/animations-extra.d.ts +136 -6
  9. package/dist/approval-ui.d.cts +148 -14
  10. package/dist/approval-ui.d.ts +148 -14
  11. package/dist/artifacts-ui.d.cts +148 -14
  12. package/dist/artifacts-ui.d.ts +148 -14
  13. package/dist/codegen.cjs +1 -1
  14. package/dist/codegen.js +1 -1
  15. package/dist/context-mentions.d.cts +148 -14
  16. package/dist/context-mentions.d.ts +148 -14
  17. package/dist/event-stream-view-GL3ADRY2.js +1 -0
  18. package/dist/event-stream-view.cjs +1 -1
  19. package/dist/event-stream-view.d.cts +148 -14
  20. package/dist/event-stream-view.d.ts +148 -14
  21. package/dist/event-stream-view.js +1 -1
  22. package/dist/forms-ui.d.cts +161 -23
  23. package/dist/forms-ui.d.ts +161 -23
  24. package/dist/history-view.d.cts +136 -6
  25. package/dist/history-view.d.ts +136 -6
  26. package/dist/index.cjs +67 -67
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +172 -27
  29. package/dist/index.d.ts +172 -27
  30. package/dist/index.global.js +51 -51
  31. package/dist/index.global.js.map +1 -1
  32. package/dist/index.js +67 -67
  33. package/dist/index.js.map +1 -1
  34. package/dist/launcher.global.js.map +1 -1
  35. package/dist/session-reconnect.d.cts +148 -14
  36. package/dist/session-reconnect.d.ts +148 -14
  37. package/dist/smart-dom-reader.d.cts +148 -14
  38. package/dist/smart-dom-reader.d.ts +148 -14
  39. package/dist/testing.cjs +3 -3
  40. package/dist/testing.d.cts +3 -4
  41. package/dist/testing.d.ts +3 -4
  42. package/dist/testing.js +3 -3
  43. package/dist/theme-editor-preview.cjs +71 -71
  44. package/dist/theme-editor-preview.d.cts +154 -14
  45. package/dist/theme-editor-preview.d.ts +154 -14
  46. package/dist/theme-editor-preview.js +48 -48
  47. package/dist/theme-editor.d.cts +148 -14
  48. package/dist/theme-editor.d.ts +148 -14
  49. package/dist/voice-runtime.d.cts +6 -2
  50. package/dist/voice-runtime.d.ts +6 -2
  51. package/package.json +1 -1
  52. package/src/ask-user-question-tool.ts +1 -1
  53. package/src/client-history-rest.test.ts +2 -1
  54. package/src/client-visitor-history.test.ts +106 -4
  55. package/src/client.composer-options.test.ts +1 -1
  56. package/src/client.interrupt.test.ts +2 -2
  57. package/src/client.test.ts +477 -915
  58. package/src/client.ts +134 -494
  59. package/src/client.unified-tools.test.ts +76 -0
  60. package/src/components/event-stream-badge-theming.test.ts +3 -0
  61. package/src/components/event-stream-view.test.ts +76 -76
  62. package/src/components/event-stream-view.ts +5 -0
  63. package/src/components/tool-bubble.test.ts +12 -0
  64. package/src/components/tool-bubble.ts +13 -2
  65. package/src/event-stream-view-bundle.test.ts +2 -2
  66. package/src/generated/runtype-openapi-contract.ts +129 -1
  67. package/src/internal/runtype-history-provider.test.ts +2 -2
  68. package/src/launcher-global.ts +1 -1
  69. package/src/session-history.test.ts +1 -1
  70. package/src/session.quote.test.ts +1 -1
  71. package/src/session.resubmit-from.test.ts +1 -1
  72. package/src/session.test.ts +5 -5
  73. package/src/session.ts +95 -36
  74. package/src/suggest-replies-tool.ts +1 -1
  75. package/src/testing/mock-stream.test.ts +37 -16
  76. package/src/testing/mock-stream.ts +14 -9
  77. package/src/types.ts +19 -14
  78. package/src/ui.ask-user-question-plugin.test.ts +7 -7
  79. package/src/ui.composer-defer-one.test.ts +6 -34
  80. package/src/ui.composer-draft-persistence.test.ts +1 -1
  81. package/src/ui.composer-quote.test.ts +1 -1
  82. package/src/ui.message-history-actions.test.ts +1 -1
  83. package/src/ui.streaming-coalescing.test.ts +10 -13
  84. package/src/ui.ts +80 -32
  85. package/src/ui.typing-indicator.test.ts +216 -0
  86. package/src/ui.voice-custom-provider.test.ts +211 -0
  87. package/src/utils/event-stream-buffer.test.ts +13 -13
  88. package/src/utils/event-stream-capture.test.ts +20 -20
  89. package/src/utils/throughput-tracker.test.ts +100 -142
  90. package/src/utils/throughput-tracker.ts +18 -132
  91. package/src/utils/voice-support.ts +18 -1
  92. package/src/voice/voice-turn-tracker.ts +18 -0
  93. package/src/webmcp-bridge.ts +1 -1
  94. package/dist/event-stream-view-FKWDY3UB.js +0 -1
  95. package/src/utils/__fixtures__/unified-translator.oracle.ts +0 -717
@@ -3,7 +3,6 @@ import { AgentWidgetClient, preferFinalStructuredContent } from './client';
3
3
  import { AgentWidgetEvent, AgentWidgetMessage, AgentWidgetArtifactsFeature } from './types';
4
4
  import { createJsonStreamParser } from './utils/formatting';
5
5
  import { VERSION } from './version';
6
- import { createUnifiedEventWrite } from './utils/__fixtures__/unified-translator.oracle';
7
6
 
8
7
  describe('AgentWidgetClient - dispatch abort signals', () => {
9
8
  const message = {
@@ -106,7 +105,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
106
105
  const encoder = new TextEncoder();
107
106
  const stream = new ReadableStream({
108
107
  start(controller) {
109
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
108
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
110
109
  controller.close();
111
110
  }
112
111
  });
@@ -159,7 +158,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
159
158
  const encoder = new TextEncoder();
160
159
  const stream = new ReadableStream({
161
160
  start(controller) {
162
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
161
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
163
162
  controller.close();
164
163
  }
165
164
  });
@@ -209,7 +208,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
209
208
  const encoder = new TextEncoder();
210
209
  const stream = new ReadableStream({
211
210
  start(controller) {
212
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
211
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
213
212
  controller.close();
214
213
  }
215
214
  });
@@ -248,7 +247,7 @@ describe('AgentWidgetClient - Empty Message Filtering', () => {
248
247
  const encoder = new TextEncoder();
249
248
  const stream = new ReadableStream({
250
249
  start(controller) {
251
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
250
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
252
251
  controller.close();
253
252
  }
254
253
  });
@@ -292,7 +291,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
292
291
  const encoder = new TextEncoder();
293
292
  const stream = new ReadableStream({
294
293
  start(controller) {
295
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
294
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
296
295
  controller.close();
297
296
  }
298
297
  });
@@ -323,7 +322,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
323
322
  const encoder = new TextEncoder();
324
323
  const stream = new ReadableStream({
325
324
  start(controller) {
326
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
325
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
327
326
  controller.close();
328
327
  }
329
328
  });
@@ -352,7 +351,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
352
351
  const encoder = new TextEncoder();
353
352
  const stream = new ReadableStream({
354
353
  start(controller) {
355
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
354
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
356
355
  controller.close();
357
356
  }
358
357
  });
@@ -384,7 +383,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
384
383
  const encoder = new TextEncoder();
385
384
  const stream = new ReadableStream({
386
385
  start(controller) {
387
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
386
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
388
387
  controller.close();
389
388
  }
390
389
  });
@@ -413,7 +412,7 @@ describe('AgentWidgetClient - llmContent Priority', () => {
413
412
  const encoder = new TextEncoder();
414
413
  const stream = new ReadableStream({
415
414
  start(controller) {
416
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
415
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
417
416
  controller.close();
418
417
  }
419
418
  });
@@ -474,91 +473,48 @@ describe('AgentWidgetClient - JSON Streaming', () => {
474
473
  });
475
474
 
476
475
  it('should stream text incrementally and not show raw JSON at the end', async () => {
477
- // Simulate the SSE stream from the user's example
478
- const sseEvents = [
479
- 'data: {"type":"flow_start","flowId":"flow_01k9pfnztzfag9tfz4t65c9c5q","flowName":"Shopping Assistant","totalSteps":1,"startedAt":"2025-11-12T23:47:39.565Z","executionId":"exec_standalone_1762991259266_7wz736k7n","executionContext":{"source":"standalone","record":{"id":"-1","name":"Streaming Chat Widget","created":false},"flow":{"id":"flow_01k9pfnztzfag9tfz4t65c9c5q","name":"Shopping Assistant","created":false}}}',
480
- '',
481
- 'data: {"type":"step_start","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","stepType":"prompt","index":1,"totalSteps":1,"startedAt":"2025-11-12T23:47:39.565Z"}',
482
- '',
483
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"{\\n"}',
484
- '',
485
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" "}',
486
- '',
487
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
488
- '',
489
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"action"}',
490
- '',
491
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\":"}',
492
- '',
493
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
494
- '',
495
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"message"}',
496
- '',
497
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\",\\n"}',
498
- '',
499
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" "}',
500
- '',
501
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
502
- '',
503
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"text"}',
504
- '',
505
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"\\":"}',
506
- '',
507
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" \\""}',
508
- '',
509
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"Great"}',
510
- '',
511
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"!"}',
512
- '',
513
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" If"}',
514
- '',
515
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" you"}',
516
- '',
517
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" have"}',
518
- '',
519
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" any"}',
520
- '',
521
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" questions"}',
522
- '',
523
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" or"}',
524
- '',
525
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" need"}',
526
- '',
527
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" help"}',
528
- '',
529
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" finding"}',
530
- '',
531
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" something"}',
532
- '',
533
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":","}',
534
- '',
535
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" just"}',
536
- '',
537
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" let"}',
538
- '',
539
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" me"}',
540
- '',
541
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":" know"}',
542
- '',
543
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"!\\"\\n"}',
544
- '',
545
- 'data: {"type":"step_chunk","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":2,"text":"}"}',
546
- '',
547
- 'data: {"type":"step_complete","id":"step_01k9x5db72fzwvmdenryn0qm48","name":"Prompt 1","executionType":"prompt","index":1,"success":true,"result":{"promptId":"step_01k9x5db72fzwvmdenryn0qm48","promptName":"Prompt 1","processedPrompt":"ok","response":"{\\"\\n \\"action\\": \\"message\\",\\n \\"text\\": \\"Great! If you have any questions or need help finding something, just let me know!\\"\\n}","tokens":{"input":1833,"output":34,"total":1867},"cost":0.000700125,"executionTime":2222,"order":2},"executionTime":2222}',
548
- '',
549
- 'data: {"type":"flow_complete","flowId":"flow_01k9pfnztzfag9tfz4t65c9c5q","success":true,"duration":2968,"completedAt":"2025-11-12T23:47:42.234Z","totalTokensUsed":0}'
550
- ];
551
-
552
- // Route the legacy step_chunk fixtures through the oracle as the 4.0 wire
553
- // (step_chunk → step_delta → text_delta), exercising the structured
554
- // JSON parser on the wire flow path: incremental text extraction, never
555
- // showing raw JSON, with the assembled response reconciled at step_complete.
476
+ // Unified text blocks exercise incremental structured JSON parsing.
556
477
  global.fetch = createRawStreamFetch(
557
- legacyToWireFrames(
558
- sseEvents
559
- .filter((f) => f.startsWith('data:'))
560
- .map((f) => f.replace('"type":"step_chunk"', '"type":"step_delta"') + '\n\n')
561
- )
478
+ [
479
+ sseEvent("execution_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 0, kind: "flow", startedAt: "2025-11-12T23:47:39.565Z", flowId: "flow_01k9pfnztzfag9tfz4t65c9c5q", flowName: "Shopping Assistant", totalSteps: 1 }),
480
+ sseEvent("step_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 1, id: "step_01k9x5db72fzwvmdenryn0qm48", name: "Prompt 1", stepType: "prompt", index: 1, totalSteps: 1, startedAt: "2025-11-12T23:47:39.565Z" }),
481
+ sseEvent("text_start", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 2, id: "text_1" }),
482
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 3, id: "text_1", delta: "{\n" }),
483
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 4, id: "text_1", delta: " " }),
484
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 5, id: "text_1", delta: " \"" }),
485
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 6, id: "text_1", delta: "action" }),
486
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 7, id: "text_1", delta: "\":" }),
487
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 8, id: "text_1", delta: " \"" }),
488
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 9, id: "text_1", delta: "message" }),
489
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 10, id: "text_1", delta: "\",\n" }),
490
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 11, id: "text_1", delta: " " }),
491
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 12, id: "text_1", delta: " \"" }),
492
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 13, id: "text_1", delta: "text" }),
493
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 14, id: "text_1", delta: "\":" }),
494
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 15, id: "text_1", delta: " \"" }),
495
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 16, id: "text_1", delta: "Great" }),
496
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 17, id: "text_1", delta: "!" }),
497
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 18, id: "text_1", delta: " If" }),
498
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 19, id: "text_1", delta: " you" }),
499
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 20, id: "text_1", delta: " have" }),
500
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 21, id: "text_1", delta: " any" }),
501
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 22, id: "text_1", delta: " questions" }),
502
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 23, id: "text_1", delta: " or" }),
503
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 24, id: "text_1", delta: " need" }),
504
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 25, id: "text_1", delta: " help" }),
505
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 26, id: "text_1", delta: " finding" }),
506
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 27, id: "text_1", delta: " something" }),
507
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 28, id: "text_1", delta: "," }),
508
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 29, id: "text_1", delta: " just" }),
509
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 30, id: "text_1", delta: " let" }),
510
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 31, id: "text_1", delta: " me" }),
511
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 32, id: "text_1", delta: " know" }),
512
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 33, id: "text_1", delta: "!\"\n" }),
513
+ sseEvent("text_delta", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 34, id: "text_1", delta: "}" }),
514
+ sseEvent("text_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 35, id: "text_1", text: "{\n \"action\": \"message\",\n \"text\": \"Great! If you have any questions or need help finding something, just let me know!\"\n}" }),
515
+ sseEvent("step_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 36, id: "step_01k9x5db72fzwvmdenryn0qm48", name: "Prompt 1", success: true, durationMs: 2222, result: { promptId: "step_01k9x5db72fzwvmdenryn0qm48", promptName: "Prompt 1", processedPrompt: "ok", response: "{\"\n \"action\": \"message\",\n \"text\": \"Great! If you have any questions or need help finding something, just let me know!\"\n}", tokens: { input: 1833, output: 34, total: 1867 }, cost: 0.000700125, executionTime: 2222, order: 2 } }),
516
+ sseEvent("execution_complete", { executionId: "exec_standalone_1762991259266_7wz736k7n", seq: 37, kind: "flow", success: true, completedAt: "2025-11-12T23:47:42.234Z", durationMs: 2968 })
517
+ ]
562
518
  );
563
519
 
564
520
  // Dispatch and collect events
@@ -659,21 +615,6 @@ function sseEvent(eventType: string, data: Record<string, unknown>): string {
659
615
  return `event: ${eventType}\ndata: ${JSON.stringify({ type: eventType, ...data })}\n\n`;
660
616
  }
661
617
 
662
- /**
663
- * Re-encode legacy `agent_*` / `flow_*` / `step_*` / `tool_*` SSE frames into the
664
- * Persona wire the 4.0 API now emits, using the same encoder the API uses
665
- * (the vendored `createUnifiedEventWrite` oracle). The 4.0 widget only consumes the
666
- * wire vocabulary, so these handler tests author the rendering intent in the
667
- * (more readable) legacy frames and inject exactly what the client sees off the
668
- * wire — the bridge translates it straight back before the dispatch chain renders.
669
- */
670
- function legacyToWireFrames(legacyFrames: string[]): string[] {
671
- const out: string[] = [];
672
- const write = createUnifiedEventWrite((chunk) => out.push(chunk));
673
- for (const frame of legacyFrames) write(frame);
674
- return out;
675
- }
676
-
677
618
  /** Stream pre-built SSE frames verbatim (no re-encode) — for fixtures already in
678
619
  * the wire vocabulary. */
679
620
  function createRawStreamFetch(frames: string[]) {
@@ -689,13 +630,6 @@ function createRawStreamFetch(frames: string[]) {
689
630
  });
690
631
  }
691
632
 
692
- /**
693
- * Mock fetch that streams the given legacy events as the 4.0 wire.
694
- */
695
- function createAgentStreamFetch(events: string[]) {
696
- return createRawStreamFetch(legacyToWireFrames(events));
697
- }
698
-
699
633
  describe('AgentWidgetClient - Agent Mode Detection', () => {
700
634
  it('should detect agent mode when agent config is provided', () => {
701
635
  const client = new AgentWidgetClient({
@@ -732,7 +666,7 @@ describe('AgentWidgetClient - target routing', () => {
732
666
  ok: true,
733
667
  body: new ReadableStream({
734
668
  start(controller) {
735
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
669
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
736
670
  executionId: 'exec_1', agentId: 'agent_123', success: true, iterations: 1,
737
671
  completedAt: new Date().toISOString(), seq: 1,
738
672
  })));
@@ -758,7 +692,7 @@ describe('AgentWidgetClient - target routing', () => {
758
692
  ok: true,
759
693
  body: new ReadableStream({
760
694
  start(controller) {
761
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
695
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
762
696
  controller.close();
763
697
  },
764
698
  }),
@@ -782,7 +716,7 @@ describe('AgentWidgetClient - target routing', () => {
782
716
  ok: true,
783
717
  body: new ReadableStream({
784
718
  start(controller) {
785
- controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
719
+ controller.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
786
720
  controller.close();
787
721
  },
788
722
  }),
@@ -816,7 +750,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
816
750
  const encoder = new TextEncoder();
817
751
  const stream = new ReadableStream({
818
752
  start(controller) {
819
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
753
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
820
754
  executionId: 'exec_1',
821
755
  agentId: 'agent_123',
822
756
  success: true,
@@ -872,7 +806,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
872
806
  ok: true,
873
807
  body: new ReadableStream({
874
808
  start(controller) {
875
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
809
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
876
810
  executionId: 'exec_1',
877
811
  agentId: 'agent_123',
878
812
  success: true,
@@ -920,7 +854,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
920
854
  ok: true,
921
855
  body: new ReadableStream({
922
856
  start(controller) {
923
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
857
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
924
858
  executionId: 'exec_1',
925
859
  agentId: 'agent_123',
926
860
  success: true,
@@ -972,7 +906,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
972
906
  const encoder = new TextEncoder();
973
907
  const stream = new ReadableStream({
974
908
  start(controller) {
975
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
909
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
976
910
  executionId: 'exec_1',
977
911
  agentId: 'virtual',
978
912
  success: true,
@@ -1034,7 +968,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
1034
968
  const encoder = new TextEncoder();
1035
969
  const stream = new ReadableStream({
1036
970
  start(controller) {
1037
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
971
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
1038
972
  executionId: 'exec_1', agentId: 'virtual', success: true,
1039
973
  iterations: 1, stopReason: 'max_iterations',
1040
974
  completedAt: new Date().toISOString(), seq: 1,
@@ -1073,40 +1007,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1073
1007
  const events: AgentWidgetEvent[] = [];
1074
1008
  const execId = 'exec_test_1';
1075
1009
 
1076
- global.fetch = createAgentStreamFetch([
1077
- sseEvent('agent_start', {
1078
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1079
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1080
- }),
1081
- sseEvent('agent_iteration_start', {
1082
- executionId: execId, iteration: 1, maxTurns: 1,
1083
- startedAt: new Date().toISOString(), seq: 2,
1084
- }),
1085
- sseEvent('agent_turn_start', {
1086
- executionId: execId, iteration: 1, turnIndex: 0,
1087
- role: 'assistant', turnId: 'turn_1', seq: 3,
1088
- }),
1089
- sseEvent('agent_turn_delta', {
1090
- executionId: execId, iteration: 1, delta: 'Hello',
1091
- contentType: 'text', turnId: 'turn_1', seq: 4,
1092
- }),
1093
- sseEvent('agent_turn_delta', {
1094
- executionId: execId, iteration: 1, delta: ' World',
1095
- contentType: 'text', turnId: 'turn_1', seq: 5,
1096
- }),
1097
- sseEvent('agent_turn_complete', {
1098
- executionId: execId, iteration: 1, role: 'assistant',
1099
- turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 6,
1100
- }),
1101
- sseEvent('agent_iteration_complete', {
1102
- executionId: execId, iteration: 1, toolCallsMade: 0,
1103
- stopConditionMet: false, completedAt: new Date().toISOString(), seq: 7,
1104
- }),
1105
- sseEvent('agent_complete', {
1106
- executionId: execId, agentId: 'virtual', success: true,
1107
- iterations: 1, stopReason: 'max_iterations',
1108
- completedAt: new Date().toISOString(), seq: 8,
1109
- }),
1010
+ global.fetch = createRawStreamFetch([
1011
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1012
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
1013
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
1014
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hello" }),
1015
+ sseEvent("text_delta", { executionId: execId, seq: 4, id: "text_1", delta: " World" }),
1016
+ sseEvent("text_complete", { executionId: execId, seq: 5, id: "text_1", text: "Hello World" }),
1017
+ sseEvent("turn_complete", { executionId: execId, seq: 6, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
1018
+ sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1110
1019
  ]);
1111
1020
 
1112
1021
  const client = new AgentWidgetClient({
@@ -1139,58 +1048,19 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1139
1048
  const events: AgentWidgetEvent[] = [];
1140
1049
  const execId = 'exec_test_2';
1141
1050
 
1142
- global.fetch = createAgentStreamFetch([
1143
- sseEvent('agent_start', {
1144
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1145
- maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
1146
- }),
1147
- // Iteration 1
1148
- sseEvent('agent_iteration_start', {
1149
- executionId: execId, iteration: 1, maxTurns: 2,
1150
- startedAt: new Date().toISOString(), seq: 2,
1151
- }),
1152
- sseEvent('agent_turn_start', {
1153
- executionId: execId, iteration: 1, turnIndex: 0,
1154
- role: 'assistant', turnId: 'turn_1', seq: 3,
1155
- }),
1156
- sseEvent('agent_turn_delta', {
1157
- executionId: execId, iteration: 1, delta: 'First iteration',
1158
- contentType: 'text', turnId: 'turn_1', seq: 4,
1159
- }),
1160
- sseEvent('agent_turn_complete', {
1161
- executionId: execId, iteration: 1, role: 'assistant',
1162
- turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5,
1163
- }),
1164
- sseEvent('agent_iteration_complete', {
1165
- executionId: execId, iteration: 1, toolCallsMade: 0,
1166
- stopConditionMet: false, completedAt: new Date().toISOString(), seq: 6,
1167
- }),
1168
- // Iteration 2
1169
- sseEvent('agent_iteration_start', {
1170
- executionId: execId, iteration: 2, maxTurns: 2,
1171
- startedAt: new Date().toISOString(), seq: 7,
1172
- }),
1173
- sseEvent('agent_turn_start', {
1174
- executionId: execId, iteration: 2, turnIndex: 0,
1175
- role: 'assistant', turnId: 'turn_2', seq: 8,
1176
- }),
1177
- sseEvent('agent_turn_delta', {
1178
- executionId: execId, iteration: 2, delta: 'Second iteration',
1179
- contentType: 'text', turnId: 'turn_2', seq: 9,
1180
- }),
1181
- sseEvent('agent_turn_complete', {
1182
- executionId: execId, iteration: 2, role: 'assistant',
1183
- turnId: 'turn_2', completedAt: new Date().toISOString(), seq: 10,
1184
- }),
1185
- sseEvent('agent_iteration_complete', {
1186
- executionId: execId, iteration: 2, toolCallsMade: 0,
1187
- stopConditionMet: false, completedAt: new Date().toISOString(), seq: 11,
1188
- }),
1189
- sseEvent('agent_complete', {
1190
- executionId: execId, agentId: 'virtual', success: true,
1191
- iterations: 2, stopReason: 'max_iterations',
1192
- completedAt: new Date().toISOString(), seq: 12,
1193
- }),
1051
+ global.fetch = createRawStreamFetch([
1052
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
1053
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
1054
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
1055
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "First iteration" }),
1056
+ sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "First iteration" }),
1057
+ sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
1058
+ sseEvent("turn_start", { executionId: execId, seq: 6, id: "turn_2", iteration: 2, turnIndex: 0, role: "assistant" }),
1059
+ sseEvent("text_start", { executionId: execId, seq: 7, id: "text_2", role: "assistant" }),
1060
+ sseEvent("text_delta", { executionId: execId, seq: 8, id: "text_2", delta: "Second iteration" }),
1061
+ sseEvent("text_complete", { executionId: execId, seq: 9, id: "text_2", text: "Second iteration" }),
1062
+ sseEvent("turn_complete", { executionId: execId, seq: 10, id: "turn_2", iteration: 2, role: "assistant", completedAt: new Date().toISOString() }),
1063
+ sseEvent("execution_complete", { executionId: execId, seq: 11, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1194
1064
  ]);
1195
1065
 
1196
1066
  const client = new AgentWidgetClient({
@@ -1229,36 +1099,13 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1229
1099
  const events: AgentWidgetEvent[] = [];
1230
1100
  const execId = 'exec_test_3';
1231
1101
 
1232
- global.fetch = createAgentStreamFetch([
1233
- sseEvent('agent_start', {
1234
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1235
- maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
1236
- }),
1237
- sseEvent('agent_iteration_start', {
1238
- executionId: execId, iteration: 1, maxTurns: 2,
1239
- startedAt: new Date().toISOString(), seq: 2,
1240
- }),
1241
- sseEvent('agent_turn_delta', {
1242
- executionId: execId, iteration: 1, delta: 'First',
1243
- contentType: 'text', turnId: 'turn_1', seq: 3,
1244
- }),
1245
- sseEvent('agent_iteration_complete', {
1246
- executionId: execId, iteration: 1, toolCallsMade: 0,
1247
- stopConditionMet: false, completedAt: new Date().toISOString(), seq: 4,
1248
- }),
1249
- sseEvent('agent_iteration_start', {
1250
- executionId: execId, iteration: 2, maxTurns: 2,
1251
- startedAt: new Date().toISOString(), seq: 5,
1252
- }),
1253
- sseEvent('agent_turn_delta', {
1254
- executionId: execId, iteration: 2, delta: ' Second',
1255
- contentType: 'text', turnId: 'turn_2', seq: 6,
1256
- }),
1257
- sseEvent('agent_complete', {
1258
- executionId: execId, agentId: 'virtual', success: true,
1259
- iterations: 2, stopReason: 'max_iterations',
1260
- completedAt: new Date().toISOString(), seq: 7,
1261
- }),
1102
+ global.fetch = createRawStreamFetch([
1103
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
1104
+ sseEvent("text_start", { executionId: execId, seq: 1, id: "text_1", role: "assistant" }),
1105
+ sseEvent("text_delta", { executionId: execId, seq: 2, id: "text_1", delta: "First" }),
1106
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: " Second" }),
1107
+ sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "First Second" }),
1108
+ sseEvent("execution_complete", { executionId: execId, seq: 5, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1262
1109
  ]);
1263
1110
 
1264
1111
  const client = new AgentWidgetClient({
@@ -1292,38 +1139,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1292
1139
  const events: AgentWidgetEvent[] = [];
1293
1140
  const execId = 'exec_test_4';
1294
1141
 
1295
- global.fetch = createAgentStreamFetch([
1296
- sseEvent('agent_start', {
1297
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1298
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1299
- }),
1300
- sseEvent('agent_iteration_start', {
1301
- executionId: execId, iteration: 1, maxTurns: 1,
1302
- startedAt: new Date().toISOString(), seq: 2,
1303
- }),
1304
- sseEvent('agent_tool_start', {
1305
- executionId: execId, iteration: 1, toolCallId: 'tc_1',
1306
- toolName: 'search', toolType: 'function',
1307
- parameters: { query: 'weather' }, seq: 3,
1308
- }),
1309
- sseEvent('agent_tool_delta', {
1310
- executionId: execId, iteration: 1, toolCallId: 'tc_1',
1311
- delta: 'Searching...', seq: 4,
1312
- }),
1313
- sseEvent('agent_tool_complete', {
1314
- executionId: execId, iteration: 1, toolCallId: 'tc_1',
1315
- toolName: 'search', success: true,
1316
- result: { temperature: 72 }, executionTime: 150, seq: 5,
1317
- }),
1318
- sseEvent('agent_turn_delta', {
1319
- executionId: execId, iteration: 1, delta: 'The weather is 72F.',
1320
- contentType: 'text', turnId: 'turn_1', seq: 6,
1321
- }),
1322
- sseEvent('agent_complete', {
1323
- executionId: execId, agentId: 'virtual', success: true,
1324
- iterations: 1, stopReason: 'max_iterations',
1325
- completedAt: new Date().toISOString(), seq: 7,
1326
- }),
1142
+ global.fetch = createRawStreamFetch([
1143
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1144
+ sseEvent("tool_start", { executionId: execId, seq: 1, toolCallId: "tc_1", toolName: "search", toolType: "function", iteration: 1, parameters: { query: "weather" } }),
1145
+ sseEvent("tool_output_delta", { executionId: execId, seq: 2, toolCallId: "tc_1", delta: "Searching..." }),
1146
+ sseEvent("tool_complete", { executionId: execId, seq: 3, toolCallId: "tc_1", toolName: "search", success: true, result: { temperature: 72 }, executionTime: 150, iteration: 1 }),
1147
+ sseEvent("text_start", { executionId: execId, seq: 4, id: "text_1", role: "assistant" }),
1148
+ sseEvent("text_delta", { executionId: execId, seq: 5, id: "text_1", delta: "The weather is 72F." }),
1149
+ sseEvent("text_complete", { executionId: execId, seq: 6, id: "text_1", text: "The weather is 72F." }),
1150
+ sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1327
1151
  ]);
1328
1152
 
1329
1153
  const client = new AgentWidgetClient({
@@ -1364,32 +1188,16 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1364
1188
  const events: AgentWidgetEvent[] = [];
1365
1189
  const execId = 'exec_test_5';
1366
1190
 
1367
- global.fetch = createAgentStreamFetch([
1368
- sseEvent('agent_start', {
1369
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1370
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1371
- }),
1372
- sseEvent('agent_iteration_start', {
1373
- executionId: execId, iteration: 1, maxTurns: 1,
1374
- startedAt: new Date().toISOString(), seq: 2,
1375
- }),
1376
- sseEvent('agent_turn_delta', {
1377
- executionId: execId, iteration: 1, delta: 'Let me think...',
1378
- contentType: 'thinking', turnId: 'think_1', seq: 3,
1379
- }),
1380
- sseEvent('agent_turn_complete', {
1381
- executionId: execId, iteration: 1, role: 'assistant',
1382
- turnId: 'think_1', completedAt: new Date().toISOString(), seq: 4,
1383
- }),
1384
- sseEvent('agent_turn_delta', {
1385
- executionId: execId, iteration: 1, delta: 'The answer is 42.',
1386
- contentType: 'text', turnId: 'turn_1', seq: 5,
1387
- }),
1388
- sseEvent('agent_complete', {
1389
- executionId: execId, agentId: 'virtual', success: true,
1390
- iterations: 1, stopReason: 'max_iterations',
1391
- completedAt: new Date().toISOString(), seq: 6,
1392
- }),
1191
+ global.fetch = createRawStreamFetch([
1192
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1193
+ sseEvent("reasoning_start", { executionId: execId, seq: 1, id: "reason_1" }),
1194
+ sseEvent("reasoning_delta", { executionId: execId, seq: 2, id: "reason_1", delta: "Let me think..." }),
1195
+ sseEvent("reasoning_complete", { executionId: execId, seq: 3, id: "reason_1", text: "Let me think..." }),
1196
+ sseEvent("turn_complete", { executionId: execId, seq: 4, id: "think_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
1197
+ sseEvent("text_start", { executionId: execId, seq: 5, id: "text_2", role: "assistant" }),
1198
+ sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_2", delta: "The answer is 42." }),
1199
+ sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_2", text: "The answer is 42." }),
1200
+ sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1393
1201
  ]);
1394
1202
 
1395
1203
  const client = new AgentWidgetClient({
@@ -1428,21 +1236,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1428
1236
  const events: AgentWidgetEvent[] = [];
1429
1237
  const execId = 'exec_test_6';
1430
1238
 
1431
- global.fetch = createAgentStreamFetch([
1432
- sseEvent('agent_start', {
1433
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1434
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1435
- }),
1436
- sseEvent('agent_error', {
1437
- executionId: execId, iteration: 1,
1438
- error: 'Rate limit hit, retrying...',
1439
- recoverable: true, seq: 2,
1440
- }),
1441
- sseEvent('agent_error', {
1442
- executionId: execId, iteration: 1,
1443
- error: 'Fatal: model unavailable',
1444
- recoverable: false, seq: 3,
1445
- }),
1239
+ global.fetch = createRawStreamFetch([
1240
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1241
+ sseEvent("error", { executionId: execId, seq: 1, error: "Rate limit hit, retrying...", recoverable: true }),
1242
+ sseEvent("execution_error", { executionId: execId, seq: 2, kind: "agent", error: "Fatal: model unavailable" })
1446
1243
  ]);
1447
1244
 
1448
1245
  const client = new AgentWidgetClient({
@@ -1463,13 +1260,15 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1463
1260
  }
1464
1261
  });
1465
1262
 
1466
- it('should emit error and finalize streaming on step_error', async () => {
1263
+ it('should emit error and finalize streaming on a failed step_complete', async () => {
1467
1264
  const events: AgentWidgetEvent[] = [];
1468
1265
 
1469
- global.fetch = createAgentStreamFetch([
1470
- 'data: {"type":"flow_start","flowId":"f1","flowName":"Test","totalSteps":1}\n\n',
1471
- 'data: {"type":"step_delta","id":"s1","name":"Prompt","executionType":"prompt","text":"partial"}\n\n',
1472
- sseEvent('step_error', { error: 'step blew up', seq: 3 }),
1266
+ global.fetch = createRawStreamFetch([
1267
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1268
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1269
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "partial" }),
1270
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "partial" }),
1271
+ sseEvent("step_complete", { executionId: "exec-test", seq: 4, success: false, error: "step blew up" })
1473
1272
  ]);
1474
1273
 
1475
1274
  const client = new AgentWidgetClient({
@@ -1498,13 +1297,18 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1498
1297
  expect(lastAssistant?.type === 'message' && lastAssistant.message.streaming).toBe(false);
1499
1298
  });
1500
1299
 
1501
- it('should emit error and finalize streaming on dispatch_error (message only)', async () => {
1300
+ it('should emit error and finalize streaming on execution_error', async () => {
1502
1301
  const events: AgentWidgetEvent[] = [];
1503
1302
 
1504
- global.fetch = createAgentStreamFetch([
1505
- 'data: {"type":"flow_start","flowId":"f1","flowName":"Test","totalSteps":1}\n\n',
1506
- 'data: {"type":"step_delta","id":"s1","name":"Prompt","executionType":"prompt","text":"x"}\n\n',
1507
- sseEvent('dispatch_error', { message: 'bad config', seq: 2 }),
1303
+ global.fetch = createRawStreamFetch([
1304
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1305
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1306
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "x" }),
1307
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "x" }),
1308
+ sseEvent("execution_error", {
1309
+ executionId: "exec-test", seq: 4,
1310
+ kind: "flow", error: { code: "error", message: "bad config" },
1311
+ })
1508
1312
  ]);
1509
1313
 
1510
1314
  const client = new AgentWidgetClient({
@@ -1531,20 +1335,11 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1531
1335
  const events: AgentWidgetEvent[] = [];
1532
1336
  const execId = 'exec_test_7';
1533
1337
 
1534
- global.fetch = createAgentStreamFetch([
1535
- sseEvent('agent_start', {
1536
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1537
- maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
1538
- }),
1539
- sseEvent('agent_reflection', {
1540
- executionId: execId, iteration: 1,
1541
- reflection: 'I should try a different approach.', seq: 2,
1542
- }),
1543
- sseEvent('agent_complete', {
1544
- executionId: execId, agentId: 'virtual', success: true,
1545
- iterations: 2, stopReason: 'max_iterations',
1546
- completedAt: new Date().toISOString(), seq: 3,
1547
- }),
1338
+ global.fetch = createRawStreamFetch([
1339
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 2 }),
1340
+ sseEvent("reasoning_start", { executionId: execId, seq: 1, id: "reason_1", scope: "loop" }),
1341
+ sseEvent("reasoning_complete", { executionId: execId, seq: 2, id: "reason_1", text: "I should try a different approach.", scope: "loop" }),
1342
+ sseEvent("execution_complete", { executionId: execId, seq: 3, kind: "agent", success: true, iterations: 2, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1548
1343
  ]);
1549
1344
 
1550
1345
  const client = new AgentWidgetClient({
@@ -1573,27 +1368,17 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1573
1368
  expect(reflectionMessages[0].reasoning?.chunks.join('')).toBe('I should try a different approach.');
1574
1369
  });
1575
1370
 
1576
- it('should handle agent_ping events gracefully', async () => {
1371
+ it('should handle ping events gracefully', async () => {
1577
1372
  const events: AgentWidgetEvent[] = [];
1578
1373
  const execId = 'exec_test_8';
1579
1374
 
1580
- global.fetch = createAgentStreamFetch([
1581
- sseEvent('agent_start', {
1582
- executionId: execId, agentId: 'virtual', agentName: 'Test',
1583
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1584
- }),
1585
- sseEvent('agent_ping', {
1586
- executionId: execId, timestamp: new Date().toISOString(), seq: 2,
1587
- }),
1588
- sseEvent('agent_turn_delta', {
1589
- executionId: execId, iteration: 1, delta: 'Hi',
1590
- contentType: 'text', turnId: 'turn_1', seq: 3,
1591
- }),
1592
- sseEvent('agent_complete', {
1593
- executionId: execId, agentId: 'virtual', success: true,
1594
- iterations: 1, stopReason: 'max_iterations',
1595
- completedAt: new Date().toISOString(), seq: 4,
1596
- }),
1375
+ global.fetch = createRawStreamFetch([
1376
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1377
+ sseEvent("ping", { executionId: execId, seq: 1, timestamp: new Date().toISOString() }),
1378
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
1379
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hi" }),
1380
+ sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "Hi" }),
1381
+ sseEvent("execution_complete", { executionId: execId, seq: 5, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
1597
1382
  ]);
1598
1383
 
1599
1384
  const client = new AgentWidgetClient({
@@ -1622,7 +1407,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1622
1407
  });
1623
1408
 
1624
1409
  // ============================================================================
1625
- // Wire event name support (chunk → delta, agent_tool_* → tool_* with agentContext)
1410
+ // Unified text and tool event handling
1626
1411
  // ============================================================================
1627
1412
 
1628
1413
  // ============================================================================
@@ -1633,20 +1418,20 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1633
1418
  it('should split flow text segments at a tool boundary', async () => {
1634
1419
  const events: AgentWidgetEvent[] = [];
1635
1420
 
1636
- global.fetch = createAgentStreamFetch([
1637
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1638
- sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
1639
- sseEvent('text_start', { messageId: 'msg_s1' }),
1640
- sseEvent('step_delta', { id: 's1', text: 'Let me search' }),
1641
- sseEvent('step_delta', { id: 's1', text: ' for that!' }),
1642
- sseEvent('text_end', { messageId: 'msg_s1' }),
1643
- sseEvent('tool_start', { toolId: 'tc_1', name: 'search', toolType: 'mcp', startedAt: new Date().toISOString() }),
1644
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'search', result: { found: true }, success: true, completedAt: new Date().toISOString(), executionTime: 200 }),
1645
- sseEvent('text_start', { messageId: 'msg_s1' }),
1646
- sseEvent('step_delta', { id: 's1', text: 'Found it! Here' }),
1647
- sseEvent('step_delta', { id: 's1', text: ' are the results.' }),
1648
- sseEvent('text_end', { messageId: 'msg_s1' }),
1649
- sseEvent('flow_complete', { success: true }),
1421
+ global.fetch = createRawStreamFetch([
1422
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1423
+ sseEvent("step_start", { executionId: "exec-test", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
1424
+ sseEvent("text_start", { executionId: "exec-test", seq: 2, id: "text_1" }),
1425
+ sseEvent("text_delta", { executionId: "exec-test", seq: 3, id: "text_1", delta: "Let me search" }),
1426
+ sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: " for that!" }),
1427
+ sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "Let me search for that!" }),
1428
+ sseEvent("tool_start", { executionId: "exec-test", seq: 6, toolCallId: "tc_1", toolName: "search", toolType: "mcp", startedAt: new Date().toISOString() }),
1429
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 7, toolCallId: "tc_1", toolName: "search", success: true, result: { found: true }, executionTime: 200 }),
1430
+ sseEvent("text_start", { executionId: "exec-test", seq: 8, id: "text_2" }),
1431
+ sseEvent("text_delta", { executionId: "exec-test", seq: 9, id: "text_2", delta: "Found it! Here" }),
1432
+ sseEvent("text_delta", { executionId: "exec-test", seq: 10, id: "text_2", delta: " are the results." }),
1433
+ sseEvent("text_complete", { executionId: "exec-test", seq: 11, id: "text_2", text: "Found it! Here are the results." }),
1434
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 12, kind: "flow", success: true })
1650
1435
  ]);
1651
1436
 
1652
1437
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1690,17 +1475,17 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1690
1475
  it('should split assistant messages using text_start/text_end lifecycle events', async () => {
1691
1476
  const events: AgentWidgetEvent[] = [];
1692
1477
 
1693
- global.fetch = createAgentStreamFetch([
1694
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1695
- sseEvent('text_start', { messageId: 'msg_s1' }),
1696
- sseEvent('step_delta', { id: 's1', text: 'Preamble text.' }),
1697
- sseEvent('text_end', { messageId: 'msg_s1' }),
1698
- sseEvent('tool_start', { toolId: 'tc_1', name: 'get_weather', toolType: 'builtin', startedAt: new Date().toISOString() }),
1699
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'get_weather', result: { temp: 72 }, success: true, completedAt: new Date().toISOString(), executionTime: 100 }),
1700
- sseEvent('text_start', { messageId: 'msg_s1' }),
1701
- sseEvent('step_delta', { id: 's1', text: 'The weather is 72F.' }),
1702
- sseEvent('text_end', { messageId: 'msg_s1' }),
1703
- sseEvent('flow_complete', { success: true }),
1478
+ global.fetch = createRawStreamFetch([
1479
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1480
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1481
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Preamble text." }),
1482
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Preamble text." }),
1483
+ sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "get_weather", toolType: "builtin", startedAt: new Date().toISOString() }),
1484
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "get_weather", success: true, result: { temp: 72 }, executionTime: 100 }),
1485
+ sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
1486
+ sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "The weather is 72F." }),
1487
+ sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "The weather is 72F." }),
1488
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
1704
1489
  ]);
1705
1490
 
1706
1491
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1741,15 +1526,15 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1741
1526
  // Tool UI is the first meaningful output. Some providers still emit
1742
1527
  // newline-only text lifecycle events around the tool boundary; those must
1743
1528
  // not become an empty assistant message bubble.
1744
- global.fetch = createAgentStreamFetch([
1745
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1746
- sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 }),
1747
- sseEvent('tool_start', { toolId: 'tc_1', name: 'add_to_cart', toolType: 'local' }),
1748
- sseEvent('text_start', { messageId: 'msg_s1' }),
1749
- sseEvent('step_delta', { id: 's1', text: '\n' }),
1750
- sseEvent('text_end', { messageId: 'msg_s1' }),
1751
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'add_to_cart', success: true, completedAt: new Date().toISOString(), executionTime: 20 }),
1752
- sseEvent('flow_complete', { success: true }),
1529
+ global.fetch = createRawStreamFetch([
1530
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1531
+ sseEvent("step_start", { executionId: "exec-test", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 1, totalSteps: 1 }),
1532
+ sseEvent("tool_start", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "add_to_cart", toolType: "local" }),
1533
+ sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
1534
+ sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "\n" }),
1535
+ sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "\n" }),
1536
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 6, toolCallId: "tc_1", toolName: "add_to_cart", success: true, executionTime: 20 }),
1537
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
1753
1538
  ]);
1754
1539
 
1755
1540
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1778,11 +1563,13 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1778
1563
  const events: AgentWidgetEvent[] = [];
1779
1564
 
1780
1565
  // No text_end / tool boundary between the two deltas → one text block → one bubble.
1781
- global.fetch = createAgentStreamFetch([
1782
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1783
- sseEvent('step_delta', { id: 's1', text: 'Hello ' }),
1784
- sseEvent('step_delta', { id: 's1', text: 'world' }),
1785
- sseEvent('flow_complete', { success: true }),
1566
+ global.fetch = createRawStreamFetch([
1567
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1568
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1569
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Hello " }),
1570
+ sseEvent("text_delta", { executionId: "exec-test", seq: 3, id: "text_1", delta: "world" }),
1571
+ sseEvent("text_complete", { executionId: "exec-test", seq: 4, id: "text_1", text: "Hello world" }),
1572
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 5, kind: "flow", success: true })
1786
1573
  ]);
1787
1574
 
1788
1575
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1809,27 +1596,22 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1809
1596
  it('should handle multiple tool calls with proper text interleaving', async () => {
1810
1597
  const events: AgentWidgetEvent[] = [];
1811
1598
 
1812
- global.fetch = createAgentStreamFetch([
1813
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1814
- // preamble segment
1815
- sseEvent('text_start', { messageId: 'msg_s1' }),
1816
- sseEvent('step_delta', { id: 's1', text: 'Searching...' }),
1817
- sseEvent('text_end', { messageId: 'msg_s1' }),
1818
- // tool 1
1819
- sseEvent('tool_start', { toolId: 'tc_1', name: 'search', toolType: 'mcp' }),
1820
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'search', result: { id: 27 }, success: true, executionTime: 100 }),
1821
- // between-tools segment
1822
- sseEvent('text_start', { messageId: 'msg_s1' }),
1823
- sseEvent('step_delta', { id: 's1', text: 'Adding to cart...' }),
1824
- sseEvent('text_end', { messageId: 'msg_s1' }),
1825
- // tool 2
1826
- sseEvent('tool_start', { toolId: 'tc_2', name: 'add_to_cart', toolType: 'mcp' }),
1827
- sseEvent('tool_complete', { toolId: 'tc_2', name: 'add_to_cart', result: { success: true }, success: true, executionTime: 50 }),
1828
- // final segment
1829
- sseEvent('text_start', { messageId: 'msg_s1' }),
1830
- sseEvent('step_delta', { id: 's1', text: 'Done! Item added.' }),
1831
- sseEvent('text_end', { messageId: 'msg_s1' }),
1832
- sseEvent('flow_complete', { success: true }),
1599
+ global.fetch = createRawStreamFetch([
1600
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1601
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1602
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Searching..." }),
1603
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Searching..." }),
1604
+ sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "search", toolType: "mcp" }),
1605
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "search", success: true, result: { id: 27 }, executionTime: 100 }),
1606
+ sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
1607
+ sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "Adding to cart..." }),
1608
+ sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "Adding to cart..." }),
1609
+ sseEvent("tool_start", { executionId: "exec-test", seq: 9, toolCallId: "tc_2", toolName: "add_to_cart", toolType: "mcp" }),
1610
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 10, toolCallId: "tc_2", toolName: "add_to_cart", success: true, result: { success: true }, executionTime: 50 }),
1611
+ sseEvent("text_start", { executionId: "exec-test", seq: 11, id: "text_3" }),
1612
+ sseEvent("text_delta", { executionId: "exec-test", seq: 12, id: "text_3", delta: "Done! Item added." }),
1613
+ sseEvent("text_complete", { executionId: "exec-test", seq: 13, id: "text_3", text: "Done! Item added." }),
1614
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 14, kind: "flow", success: true })
1833
1615
  ]);
1834
1616
 
1835
1617
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1876,17 +1658,17 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1876
1658
  it('should give split messages unique IDs even when assistantMessageId is provided', async () => {
1877
1659
  const events: AgentWidgetEvent[] = [];
1878
1660
 
1879
- global.fetch = createAgentStreamFetch([
1880
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1881
- sseEvent('text_start', { messageId: 'msg_s1' }),
1882
- sseEvent('step_delta', { id: 's1', text: 'Before tool.' }),
1883
- sseEvent('text_end', { messageId: 'msg_s1' }),
1884
- sseEvent('tool_start', { toolId: 'tc_1', name: 'lookup', toolType: 'mcp' }),
1885
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'lookup', result: {}, success: true, executionTime: 50 }),
1886
- sseEvent('text_start', { messageId: 'msg_s1' }),
1887
- sseEvent('step_delta', { id: 's1', text: 'After tool.' }),
1888
- sseEvent('text_end', { messageId: 'msg_s1' }),
1889
- sseEvent('flow_complete', { success: true }),
1661
+ global.fetch = createRawStreamFetch([
1662
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1663
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1664
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Before tool." }),
1665
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Before tool." }),
1666
+ sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tc_1", toolName: "lookup", toolType: "mcp" }),
1667
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tc_1", toolName: "lookup", success: true, result: { }, executionTime: 50 }),
1668
+ sseEvent("text_start", { executionId: "exec-test", seq: 6, id: "text_2" }),
1669
+ sseEvent("text_delta", { executionId: "exec-test", seq: 7, id: "text_2", delta: "After tool." }),
1670
+ sseEvent("text_complete", { executionId: "exec-test", seq: 8, id: "text_2", text: "After tool." }),
1671
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
1890
1672
  ]);
1891
1673
 
1892
1674
  // Use agent mode so assistantMessageId is forwarded to streamResponse
@@ -1930,19 +1712,19 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1930
1712
  // (sealed by text_end → text_complete). The step's full structured response
1931
1713
  // (`step_complete.result.response`) reconciles rawContent without clobbering
1932
1714
  // either sealed bubble's displayed content.
1933
- global.fetch = createAgentStreamFetch([
1934
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1, executionId: 'exec_f1' }),
1935
- sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
1936
- sseEvent('text_start', { messageId: 'msg_s1' }),
1937
- sseEvent('step_delta', { id: 's1', text: 'First part.' }),
1938
- sseEvent('text_end', { messageId: 'msg_s1' }),
1939
- sseEvent('tool_start', { toolId: 'tc_1', name: 'action', toolType: 'mcp', startedAt: new Date().toISOString() }),
1940
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'action', result: {}, success: true, completedAt: new Date().toISOString(), executionTime: 10 }),
1941
- sseEvent('text_start', { messageId: 'msg_s1' }),
1942
- sseEvent('step_delta', { id: 's1', text: 'Second part.' }),
1943
- sseEvent('text_end', { messageId: 'msg_s1' }),
1944
- sseEvent('step_complete', { id: 's1', name: 'Prompt', stepType: 'prompt', success: true, result: { response: 'First part.Second part.' }, executionTime: 500 }),
1945
- sseEvent('flow_complete', { success: true }),
1715
+ global.fetch = createRawStreamFetch([
1716
+ sseEvent("execution_start", { executionId: "exec_f1", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1717
+ sseEvent("step_start", { executionId: "exec_f1", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
1718
+ sseEvent("text_start", { executionId: "exec_f1", seq: 2, id: "text_1" }),
1719
+ sseEvent("text_delta", { executionId: "exec_f1", seq: 3, id: "text_1", delta: "First part." }),
1720
+ sseEvent("text_complete", { executionId: "exec_f1", seq: 4, id: "text_1", text: "First part." }),
1721
+ sseEvent("tool_start", { executionId: "exec_f1", seq: 5, toolCallId: "tc_1", toolName: "action", toolType: "mcp", startedAt: new Date().toISOString() }),
1722
+ sseEvent("tool_complete", { executionId: "exec_f1", seq: 6, toolCallId: "tc_1", toolName: "action", success: true, result: { }, executionTime: 10 }),
1723
+ sseEvent("text_start", { executionId: "exec_f1", seq: 7, id: "text_2" }),
1724
+ sseEvent("text_delta", { executionId: "exec_f1", seq: 8, id: "text_2", delta: "Second part." }),
1725
+ sseEvent("text_complete", { executionId: "exec_f1", seq: 9, id: "text_2", text: "Second part." }),
1726
+ sseEvent("step_complete", { executionId: "exec_f1", seq: 10, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "First part.Second part." } }),
1727
+ sseEvent("execution_complete", { executionId: "exec_f1", seq: 11, kind: "flow", success: true })
1946
1728
  ]);
1947
1729
 
1948
1730
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -1973,18 +1755,15 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1973
1755
  it('should not duplicate text when step_complete follows text_end', async () => {
1974
1756
  const events: AgentWidgetEvent[] = [];
1975
1757
 
1976
- global.fetch = createAgentStreamFetch([
1977
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1978
- // Tools fire first (no text before them)
1979
- sseEvent('tool_start', { toolId: 'tc_1', name: 'test_tool', toolType: 'custom', startedAt: new Date().toISOString() }),
1980
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'test_tool', success: true, completedAt: new Date().toISOString(), executionTime: 0 }),
1981
- // Then text segment
1982
- sseEvent('text_start', { messageId: 'msg_s1' }),
1983
- sseEvent('step_delta', { id: 's1', text: 'Tool returned a result.' }),
1984
- sseEvent('text_end', { messageId: 'msg_s1' }),
1985
- // step_complete with full response (should NOT create a duplicate)
1986
- sseEvent('step_complete', { id: 's1', name: 'Response', stepType: 'prompt', success: true, result: { response: 'Tool returned a result.' }, executionTime: 500 }),
1987
- sseEvent('flow_complete', { success: true }),
1758
+ global.fetch = createRawStreamFetch([
1759
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1760
+ sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tc_1", toolName: "test_tool", toolType: "custom", startedAt: new Date().toISOString() }),
1761
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "test_tool", success: true, executionTime: 0 }),
1762
+ sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
1763
+ sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "Tool returned a result." }),
1764
+ sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "Tool returned a result." }),
1765
+ sseEvent("step_complete", { executionId: "exec-test", seq: 6, id: "s1", name: "Response", stepType: "prompt", success: true, durationMs: 500, result: { response: "Tool returned a result." } }),
1766
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
1988
1767
  ]);
1989
1768
 
1990
1769
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -2041,26 +1820,19 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
2041
1820
  };
2042
1821
  };
2043
1822
 
2044
- // Wire (via the oracle): a single flow text block carrying partial
1823
+ // Unified wire: a single flow text block carrying partial
2045
1824
  // structured JSON, sealed by text_end, then the authoritative final structured
2046
1825
  // response on step_complete. Exercises the async structured-content parser +
2047
1826
  // sealed-segment reconciliation on the wire flow path.
2048
- global.fetch = createAgentStreamFetch([
2049
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
2050
- sseEvent('tool_start', { toolId: 'tc_1', name: 'test_tool', toolType: 'custom', startedAt: new Date().toISOString() }),
2051
- sseEvent('tool_complete', { toolId: 'tc_1', name: 'test_tool', success: true, completedAt: new Date().toISOString(), executionTime: 0 }),
2052
- sseEvent('text_start', { messageId: 'msg_s1' }),
2053
- sseEvent('step_delta', { id: 's1', text: '{"text":"Tool returned a re' }),
2054
- sseEvent('text_end', { messageId: 'msg_s1' }),
2055
- sseEvent('step_complete', {
2056
- id: 's1',
2057
- name: 'Response',
2058
- stepType: 'prompt',
2059
- success: true,
2060
- result: { response: opts.stepCompleteResponse },
2061
- executionTime: 500
2062
- }),
2063
- sseEvent('flow_complete', { success: true }),
1827
+ global.fetch = createRawStreamFetch([
1828
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1829
+ sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tc_1", toolName: "test_tool", toolType: "custom", startedAt: new Date().toISOString() }),
1830
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 2, toolCallId: "tc_1", toolName: "test_tool", success: true, executionTime: 0 }),
1831
+ sseEvent("text_start", { executionId: "exec-test", seq: 3, id: "text_1" }),
1832
+ sseEvent("text_delta", { executionId: "exec-test", seq: 4, id: "text_1", delta: "{\"text\":\"Tool returned a re" }),
1833
+ sseEvent("text_complete", { executionId: "exec-test", seq: 5, id: "text_1", text: "{\"text\":\"Tool returned a re" }),
1834
+ sseEvent("step_complete", { executionId: "exec-test", seq: 6, id: "s1", name: "Response", stepType: "prompt", success: true, durationMs: 500, result: { response: opts.stepCompleteResponse } }),
1835
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 7, kind: "flow", success: true })
2064
1836
  ]);
2065
1837
 
2066
1838
  const client = new AgentWidgetClient({
@@ -2112,7 +1884,7 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
2112
1884
 
2113
1885
  describe('AgentWidgetClient - flow continuation stream without execution_start', () => {
2114
1886
  // A tool-driven `/resume` continues a flow on a brand-new stream that does NOT
2115
- // re-emit `execution_start` (`flow_start`). Each stream resolves its execution
1887
+ // re-emit `execution_start`. Each stream resolves its execution
2116
1888
  // kind independently and defaults to `"agent"`, so the continuation used to
2117
1889
  // mis-route the final prompt-step finalization: the streamed text block was
2118
1890
  // sealed via the agent path (which never records it as the sealed flow bubble),
@@ -2132,16 +1904,15 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
2132
1904
  it('does not duplicate the final message when a flow resumes without execution_start', async () => {
2133
1905
  const events: AgentWidgetEvent[] = [];
2134
1906
 
2135
- // No `flow_start`/`execution_start`: this is exactly the wire a tool-driven
1907
+ // No `execution_start`: this is exactly the wire a tool-driven
2136
1908
  // `/resume` continuation delivers.
2137
- global.fetch = createAgentStreamFetch([
2138
- sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
2139
- sseEvent('text_start', { messageId: 'msg_s1' }),
2140
- sseEvent('step_delta', { id: 's1', text: 'Done! Added to your cart.' }),
2141
- sseEvent('text_end', { messageId: 'msg_s1' }),
2142
- // The authoritative final response mirrors the streamed text.
2143
- sseEvent('step_complete', { id: 's1', name: 'Prompt', stepType: 'prompt', success: true, result: { response: 'Done! Added to your cart.' }, executionTime: 500 }),
2144
- sseEvent('flow_complete', { success: true }),
1909
+ global.fetch = createRawStreamFetch([
1910
+ sseEvent("step_start", { executionId: "exec-test", seq: 0, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
1911
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1912
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Done! Added to your cart." }),
1913
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Done! Added to your cart." }),
1914
+ sseEvent("step_complete", { executionId: "exec-test", seq: 4, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "Done! Added to your cart." } }),
1915
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 5, kind: "flow", success: true })
2145
1916
  ]);
2146
1917
 
2147
1918
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -2161,15 +1932,15 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
2161
1932
  it('still reconciles in place when execution_start IS present (initial flow stream)', async () => {
2162
1933
  const events: AgentWidgetEvent[] = [];
2163
1934
 
2164
- // Same shape but WITH flow_start — the pre-existing, already-correct path.
2165
- global.fetch = createAgentStreamFetch([
2166
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1, executionId: 'exec_f1' }),
2167
- sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
2168
- sseEvent('text_start', { messageId: 'msg_s1' }),
2169
- sseEvent('step_delta', { id: 's1', text: 'Done! Added to your cart.' }),
2170
- sseEvent('text_end', { messageId: 'msg_s1' }),
2171
- sseEvent('step_complete', { id: 's1', name: 'Prompt', stepType: 'prompt', success: true, result: { response: 'Done! Added to your cart.' }, executionTime: 500 }),
2172
- sseEvent('flow_complete', { success: true }),
1935
+ // Same shape but WITH execution_start — the pre-existing, already-correct path.
1936
+ global.fetch = createRawStreamFetch([
1937
+ sseEvent("execution_start", { executionId: "exec_f1", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1938
+ sseEvent("step_start", { executionId: "exec_f1", seq: 1, id: "s1", name: "Prompt", stepType: "prompt", index: 0, totalSteps: 1 }),
1939
+ sseEvent("text_start", { executionId: "exec_f1", seq: 2, id: "text_1" }),
1940
+ sseEvent("text_delta", { executionId: "exec_f1", seq: 3, id: "text_1", delta: "Done! Added to your cart." }),
1941
+ sseEvent("text_complete", { executionId: "exec_f1", seq: 4, id: "text_1", text: "Done! Added to your cart." }),
1942
+ sseEvent("step_complete", { executionId: "exec_f1", seq: 5, id: "s1", name: "Prompt", stepType: "prompt", success: true, durationMs: 500, result: { response: "Done! Added to your cart." } }),
1943
+ sseEvent("execution_complete", { executionId: "exec_f1", seq: 6, kind: "flow", success: true })
2173
1944
  ]);
2174
1945
 
2175
1946
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -2188,39 +1959,17 @@ describe('AgentWidgetClient - flow continuation stream without execution_start',
2188
1959
  const events: AgentWidgetEvent[] = [];
2189
1960
  const execId = 'exec_a1';
2190
1961
 
2191
- // Agent loops use `agent_*`/turn_* frames and never carry a `stepType`, so the
1962
+ // Agent loops use `turn_*` frames and never carry a `stepType`, so the
2192
1963
  // flow recovery must not engage. A single streamed turn yields exactly one
2193
1964
  // bubble (regression guard so the recovery can't over-fire on agents).
2194
- global.fetch = createAgentStreamFetch([
2195
- sseEvent('agent_start', {
2196
- executionId: execId, agentId: 'virtual', agentName: 'Test',
2197
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2198
- }),
2199
- sseEvent('agent_iteration_start', {
2200
- executionId: execId, iteration: 1, maxTurns: 1,
2201
- startedAt: new Date().toISOString(), seq: 2,
2202
- }),
2203
- sseEvent('agent_turn_start', {
2204
- executionId: execId, iteration: 1, turnIndex: 0,
2205
- role: 'assistant', turnId: 'turn_1', seq: 3,
2206
- }),
2207
- sseEvent('agent_turn_delta', {
2208
- executionId: execId, iteration: 1, delta: 'Hi there!',
2209
- contentType: 'text', turnId: 'turn_1', seq: 4,
2210
- }),
2211
- sseEvent('agent_turn_complete', {
2212
- executionId: execId, iteration: 1, role: 'assistant',
2213
- turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5,
2214
- }),
2215
- sseEvent('agent_iteration_complete', {
2216
- executionId: execId, iteration: 1, toolCallsMade: 0,
2217
- stopConditionMet: false, completedAt: new Date().toISOString(), seq: 6,
2218
- }),
2219
- sseEvent('agent_complete', {
2220
- executionId: execId, agentId: 'virtual', success: true,
2221
- iterations: 1, stopReason: 'max_iterations',
2222
- completedAt: new Date().toISOString(), seq: 7,
2223
- }),
1965
+ global.fetch = createRawStreamFetch([
1966
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
1967
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
1968
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
1969
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Hi there!" }),
1970
+ sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "Hi there!" }),
1971
+ sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", completedAt: new Date().toISOString() }),
1972
+ sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
2224
1973
  ]);
2225
1974
 
2226
1975
  const client = new AgentWidgetClient({
@@ -2244,20 +1993,17 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
2244
1993
  // .parentToolCallId, and the widget routes that block into the parent tool's row.
2245
1994
  it('routes nested flow text into the parent tool row, not the top-level assistant', async () => {
2246
1995
  const events: AgentWidgetEvent[] = [];
2247
- global.fetch = createAgentStreamFetch([
2248
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
2249
- // top-level parent text
2250
- sseEvent('text_start', { messageId: 'msg_parent' }),
2251
- sseEvent('step_delta', { id: 's1', text: 'Parent says hi.' }),
2252
- sseEvent('text_end', { messageId: 'msg_parent' }),
2253
- // a nested flow runs as a tool
2254
- sseEvent('tool_start', { toolId: 'tool_nested_1', name: 'run_subflow', toolType: 'flow', startedAt: new Date().toISOString() }),
2255
- // nested flow text, enriched with toolContext.toolId
2256
- sseEvent('text_start', { messageId: 'msg_nested', toolContext: { toolId: 'tool_nested_1' } }),
2257
- sseEvent('step_delta', { id: 's2', text: 'Nested result.', toolContext: { toolId: 'tool_nested_1' } }),
2258
- sseEvent('text_end', { messageId: 'msg_nested', toolContext: { toolId: 'tool_nested_1' } }),
2259
- sseEvent('tool_complete', { toolId: 'tool_nested_1', name: 'run_subflow', success: true, executionTime: 100 }),
2260
- sseEvent('flow_complete', { success: true }),
1996
+ global.fetch = createRawStreamFetch([
1997
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
1998
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
1999
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Parent says hi." }),
2000
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Parent says hi." }),
2001
+ sseEvent("tool_start", { executionId: "exec-test", seq: 4, toolCallId: "tool_nested_1", toolName: "run_subflow", toolType: "flow", startedAt: new Date().toISOString() }),
2002
+ sseEvent("text_start", { executionId: "exec-test", seq: 5, id: "text_2", parentToolCallId: "tool_nested_1" }),
2003
+ sseEvent("text_delta", { executionId: "exec-test", seq: 6, id: "text_2", delta: "Nested result." }),
2004
+ sseEvent("text_complete", { executionId: "exec-test", seq: 7, id: "text_2", text: "Nested result." }),
2005
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 8, toolCallId: "tool_nested_1", toolName: "run_subflow", success: true, executionTime: 100 }),
2006
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 9, kind: "flow", success: true })
2261
2007
  ]);
2262
2008
 
2263
2009
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
@@ -2284,12 +2030,12 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
2284
2030
 
2285
2031
  it('does not tag top-level flow text with a parentToolId', async () => {
2286
2032
  const events: AgentWidgetEvent[] = [];
2287
- global.fetch = createAgentStreamFetch([
2288
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
2289
- sseEvent('text_start', { messageId: 'msg_1' }),
2290
- sseEvent('step_delta', { id: 's1', text: 'Just top-level.' }),
2291
- sseEvent('text_end', { messageId: 'msg_1' }),
2292
- sseEvent('flow_complete', { success: true }),
2033
+ global.fetch = createRawStreamFetch([
2034
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
2035
+ sseEvent("text_start", { executionId: "exec-test", seq: 1, id: "text_1" }),
2036
+ sseEvent("text_delta", { executionId: "exec-test", seq: 2, id: "text_1", delta: "Just top-level." }),
2037
+ sseEvent("text_complete", { executionId: "exec-test", seq: 3, id: "text_1", text: "Just top-level." }),
2038
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 4, kind: "flow", success: true })
2293
2039
  ]);
2294
2040
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
2295
2041
  await client.dispatch(
@@ -2306,14 +2052,14 @@ describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
2306
2052
 
2307
2053
  it('routes nested flow reasoning into the parent tool row', async () => {
2308
2054
  const events: AgentWidgetEvent[] = [];
2309
- global.fetch = createAgentStreamFetch([
2310
- sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
2311
- sseEvent('tool_start', { toolId: 'tool_nested_2', name: 'run_subflow', toolType: 'flow', startedAt: new Date().toISOString() }),
2312
- sseEvent('reason_start', { toolContext: { toolId: 'tool_nested_2' } }),
2313
- sseEvent('reason_delta', { reasoningText: 'thinking nested', toolContext: { toolId: 'tool_nested_2' } }),
2314
- sseEvent('reason_complete', { toolContext: { toolId: 'tool_nested_2' } }),
2315
- sseEvent('tool_complete', { toolId: 'tool_nested_2', name: 'run_subflow', success: true, executionTime: 50 }),
2316
- sseEvent('flow_complete', { success: true }),
2055
+ global.fetch = createRawStreamFetch([
2056
+ sseEvent("execution_start", { executionId: "exec-test", seq: 0, kind: "flow", startedAt: "2025-01-01T00:00:00.000Z", flowId: "f1", flowName: "Test", totalSteps: 1 }),
2057
+ sseEvent("tool_start", { executionId: "exec-test", seq: 1, toolCallId: "tool_nested_2", toolName: "run_subflow", toolType: "flow", startedAt: new Date().toISOString() }),
2058
+ sseEvent("reasoning_start", { executionId: "exec-test", seq: 2, id: "reason_1", parentToolCallId: "tool_nested_2" }),
2059
+ sseEvent("reasoning_delta", { executionId: "exec-test", seq: 3, id: "reason_1", delta: "thinking nested" }),
2060
+ sseEvent("reasoning_complete", { executionId: "exec-test", seq: 4, id: "reason_1", text: "thinking nested" }),
2061
+ sseEvent("tool_complete", { executionId: "exec-test", seq: 5, toolCallId: "tool_nested_2", toolName: "run_subflow", success: true, executionTime: 50 }),
2062
+ sseEvent("execution_complete", { executionId: "exec-test", seq: 6, kind: "flow", success: true })
2317
2063
  ]);
2318
2064
  const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
2319
2065
  await client.dispatch(
@@ -2409,7 +2155,7 @@ describe('preferFinalStructuredContent', () => {
2409
2155
  });
2410
2156
 
2411
2157
  // ============================================================================
2412
- // stopReason wiring (agent_turn_complete / step_complete)
2158
+ // stopReason wiring (turn_complete / step_complete)
2413
2159
  // ============================================================================
2414
2160
 
2415
2161
  describe('AgentWidgetClient - stopReason propagation', () => {
@@ -2423,7 +2169,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
2423
2169
  if (stopReason) data.stopReason = stopReason;
2424
2170
  return [
2425
2171
  `data: ${JSON.stringify(data)}\n\n`,
2426
- `data: ${JSON.stringify({ type: 'flow_complete', success: true })}\n\n`,
2172
+ `data: ${JSON.stringify({ type: 'execution_complete', success: true })}\n\n`,
2427
2173
  ];
2428
2174
  };
2429
2175
 
@@ -2487,7 +2233,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
2487
2233
  result: { response: '' },
2488
2234
  stopReason: 'max_tool_calls',
2489
2235
  })}\n\n`,
2490
- `data: ${JSON.stringify({ type: 'flow_complete', success: true })}\n\n`,
2236
+ `data: ${JSON.stringify({ type: 'execution_complete', success: true })}\n\n`,
2491
2237
  ]);
2492
2238
  const final = collectFinalAssistant(events);
2493
2239
  expect(final).not.toBeNull();
@@ -2495,41 +2241,18 @@ describe('AgentWidgetClient - stopReason propagation', () => {
2495
2241
  expect(final!.stopReason).toBe('max_tool_calls');
2496
2242
  });
2497
2243
 
2498
- it('agent_turn_complete.stopReason overrides any earlier step_complete value (agent-loop path)', async () => {
2499
- // Build an agent-mode stream that emits both events. agent_turn_complete
2244
+ it('turn_complete.stopReason overrides any earlier step_complete value (agent-loop path)', async () => {
2245
+ // Build an agent-mode stream that emits both events. turn_complete
2500
2246
  // arrives last; its stopReason should win.
2501
2247
  const execId = 'exec_stopreason';
2502
- global.fetch = createAgentStreamFetch([
2503
- sseEvent('agent_start', {
2504
- executionId: execId, agentId: 'virtual', agentName: 'Test',
2505
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2506
- }),
2507
- sseEvent('agent_iteration_start', {
2508
- executionId: execId, iteration: 1, maxTurns: 1,
2509
- startedAt: new Date().toISOString(), seq: 2,
2510
- }),
2511
- sseEvent('agent_turn_start', {
2512
- executionId: execId, iteration: 1, turnIndex: 0,
2513
- role: 'assistant', turnId: 'turn_1', seq: 3,
2514
- }),
2515
- sseEvent('agent_turn_delta', {
2516
- executionId: execId, iteration: 1, delta: 'partial answer',
2517
- contentType: 'text', turnId: 'turn_1', seq: 4,
2518
- }),
2519
- sseEvent('agent_turn_complete', {
2520
- executionId: execId, iteration: 1, role: 'assistant',
2521
- turnId: 'turn_1', completedAt: new Date().toISOString(),
2522
- stopReason: 'max_tool_calls', seq: 5,
2523
- }),
2524
- sseEvent('agent_iteration_complete', {
2525
- executionId: execId, iteration: 1, toolCallsMade: 0,
2526
- stopConditionMet: true, completedAt: new Date().toISOString(), seq: 6,
2527
- }),
2528
- sseEvent('agent_complete', {
2529
- executionId: execId, agentId: 'virtual', success: true,
2530
- iterations: 1, stopReason: 'max_iterations',
2531
- completedAt: new Date().toISOString(), seq: 7,
2532
- }),
2248
+ global.fetch = createRawStreamFetch([
2249
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2250
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
2251
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
2252
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "partial answer" }),
2253
+ sseEvent("text_complete", { executionId: execId, seq: 4, id: "text_1", text: "partial answer" }),
2254
+ sseEvent("turn_complete", { executionId: execId, seq: 5, id: "turn_1", iteration: 1, role: "assistant", stopReason: "max_tool_calls", completedAt: new Date().toISOString() }),
2255
+ sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
2533
2256
  ]);
2534
2257
 
2535
2258
  const client = new AgentWidgetClient({
@@ -2551,7 +2274,7 @@ describe('AgentWidgetClient - stopReason propagation', () => {
2551
2274
 
2552
2275
  // ============================================================================
2553
2276
  // Within-turn text/tool interleaving: assistant text bubbles must seal at
2554
- // each agent_tool_start so the chronological text→tool→text→tool sequence
2277
+ // each tool_start so the chronological text→tool→text→tool sequence
2555
2278
  // renders as distinct timeline entries instead of one merged bubble that
2556
2279
  // appears below all the tool cards.
2557
2280
  // ============================================================================
@@ -2568,65 +2291,22 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
2568
2291
  return order.map((id) => byId.get(id)!);
2569
2292
  };
2570
2293
 
2571
- it('seals the assistant text bubble at each agent_tool_start so subsequent text creates a new bubble', async () => {
2294
+ it('seals the assistant text bubble at each tool_start so subsequent text creates a new bubble', async () => {
2572
2295
  const execId = 'exec_interleave';
2573
- global.fetch = createAgentStreamFetch([
2574
- sseEvent('agent_start', {
2575
- executionId: execId, agentId: 'virtual', agentName: 'Test',
2576
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2577
- }),
2578
- sseEvent('agent_iteration_start', {
2579
- executionId: execId, iteration: 1, maxTurns: 1,
2580
- startedAt: new Date().toISOString(), seq: 2,
2581
- }),
2582
- sseEvent('agent_turn_start', {
2583
- executionId: execId, iteration: 1, turnIndex: 0,
2584
- role: 'assistant', turnId: 'turn_1', seq: 3,
2585
- }),
2586
- sseEvent('agent_turn_delta', {
2587
- executionId: execId, iteration: 1, delta: 'before tool 1',
2588
- contentType: 'text', turnId: 'turn_1', seq: 4,
2589
- }),
2590
- sseEvent('agent_tool_start', {
2591
- executionId: execId, iteration: 1,
2592
- toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
2593
- }),
2594
- sseEvent('agent_tool_complete', {
2595
- executionId: execId, iteration: 1,
2596
- toolCallId: 'call_1', toolName: 'search', success: true,
2597
- executionTime: 10, seq: 6,
2598
- }),
2599
- sseEvent('agent_turn_delta', {
2600
- executionId: execId, iteration: 1, delta: 'between tools',
2601
- contentType: 'text', turnId: 'turn_1', seq: 7,
2602
- }),
2603
- sseEvent('agent_tool_start', {
2604
- executionId: execId, iteration: 1,
2605
- toolCallId: 'call_2', toolName: 'fetch', toolType: 'builtin', seq: 8,
2606
- }),
2607
- sseEvent('agent_tool_complete', {
2608
- executionId: execId, iteration: 1,
2609
- toolCallId: 'call_2', toolName: 'fetch', success: true,
2610
- executionTime: 10, seq: 9,
2611
- }),
2612
- sseEvent('agent_turn_delta', {
2613
- executionId: execId, iteration: 1, delta: 'after tool 2',
2614
- contentType: 'text', turnId: 'turn_1', seq: 10,
2615
- }),
2616
- sseEvent('agent_turn_complete', {
2617
- executionId: execId, iteration: 1, role: 'assistant',
2618
- turnId: 'turn_1', completedAt: new Date().toISOString(),
2619
- stopReason: 'end_turn', seq: 11,
2620
- }),
2621
- sseEvent('agent_iteration_complete', {
2622
- executionId: execId, iteration: 1, toolCallsMade: 2,
2623
- stopConditionMet: true, completedAt: new Date().toISOString(), seq: 12,
2624
- }),
2625
- sseEvent('agent_complete', {
2626
- executionId: execId, agentId: 'virtual', success: true,
2627
- iterations: 1, stopReason: 'complete',
2628
- completedAt: new Date().toISOString(), seq: 13,
2629
- }),
2296
+ global.fetch = createRawStreamFetch([
2297
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2298
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
2299
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
2300
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "before tool 1" }),
2301
+ sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
2302
+ sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
2303
+ sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_1", delta: "between tools" }),
2304
+ sseEvent("tool_start", { executionId: execId, seq: 7, toolCallId: "call_2", toolName: "fetch", toolType: "builtin", iteration: 1 }),
2305
+ sseEvent("tool_complete", { executionId: execId, seq: 8, toolCallId: "call_2", toolName: "fetch", success: true, executionTime: 10, iteration: 1 }),
2306
+ sseEvent("text_delta", { executionId: execId, seq: 9, id: "text_1", delta: "after tool 2" }),
2307
+ sseEvent("text_complete", { executionId: execId, seq: 10, id: "text_1", text: "before tool 1between toolsafter tool 2" }),
2308
+ sseEvent("turn_complete", { executionId: execId, seq: 11, id: "turn_1", iteration: 1, role: "assistant", stopReason: "end_turn", completedAt: new Date().toISOString() }),
2309
+ sseEvent("execution_complete", { executionId: execId, seq: 12, kind: "agent", success: true, iterations: 1, stopReason: "complete", completedAt: new Date().toISOString() })
2630
2310
  ]);
2631
2311
 
2632
2312
  const client = new AgentWidgetClient({
@@ -2652,52 +2332,19 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
2652
2332
  expect(new Set(assistants.map((m) => m.id)).size).toBe(3);
2653
2333
  });
2654
2334
 
2655
- it('attaches agent_turn_complete.stopReason to the final assistant text segment when the turn ends with text', async () => {
2335
+ it('attaches turn_complete.stopReason to the final assistant text segment when the turn ends with text', async () => {
2656
2336
  const execId = 'exec_stopreason_tail_text';
2657
- global.fetch = createAgentStreamFetch([
2658
- sseEvent('agent_start', {
2659
- executionId: execId, agentId: 'virtual', agentName: 'Test',
2660
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2661
- }),
2662
- sseEvent('agent_iteration_start', {
2663
- executionId: execId, iteration: 1, maxTurns: 1,
2664
- startedAt: new Date().toISOString(), seq: 2,
2665
- }),
2666
- sseEvent('agent_turn_start', {
2667
- executionId: execId, iteration: 1, turnIndex: 0,
2668
- role: 'assistant', turnId: 'turn_1', seq: 3,
2669
- }),
2670
- sseEvent('agent_turn_delta', {
2671
- executionId: execId, iteration: 1, delta: 'first segment',
2672
- contentType: 'text', turnId: 'turn_1', seq: 4,
2673
- }),
2674
- sseEvent('agent_tool_start', {
2675
- executionId: execId, iteration: 1,
2676
- toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
2677
- }),
2678
- sseEvent('agent_tool_complete', {
2679
- executionId: execId, iteration: 1,
2680
- toolCallId: 'call_1', toolName: 'search', success: true,
2681
- executionTime: 10, seq: 6,
2682
- }),
2683
- sseEvent('agent_turn_delta', {
2684
- executionId: execId, iteration: 1, delta: 'final segment',
2685
- contentType: 'text', turnId: 'turn_1', seq: 7,
2686
- }),
2687
- sseEvent('agent_turn_complete', {
2688
- executionId: execId, iteration: 1, role: 'assistant',
2689
- turnId: 'turn_1', completedAt: new Date().toISOString(),
2690
- stopReason: 'length', seq: 8,
2691
- }),
2692
- sseEvent('agent_iteration_complete', {
2693
- executionId: execId, iteration: 1, toolCallsMade: 1,
2694
- stopConditionMet: true, completedAt: new Date().toISOString(), seq: 9,
2695
- }),
2696
- sseEvent('agent_complete', {
2697
- executionId: execId, agentId: 'virtual', success: true,
2698
- iterations: 1, stopReason: 'complete',
2699
- completedAt: new Date().toISOString(), seq: 10,
2700
- }),
2337
+ global.fetch = createRawStreamFetch([
2338
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2339
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
2340
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
2341
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "first segment" }),
2342
+ sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
2343
+ sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
2344
+ sseEvent("text_delta", { executionId: execId, seq: 6, id: "text_1", delta: "final segment" }),
2345
+ sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_1", text: "first segmentfinal segment" }),
2346
+ sseEvent("turn_complete", { executionId: execId, seq: 8, id: "turn_1", iteration: 1, role: "assistant", stopReason: "length", completedAt: new Date().toISOString() }),
2347
+ sseEvent("execution_complete", { executionId: execId, seq: 9, kind: "agent", success: true, iterations: 1, stopReason: "complete", completedAt: new Date().toISOString() })
2701
2348
  ]);
2702
2349
 
2703
2350
  const client = new AgentWidgetClient({
@@ -2720,48 +2367,18 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
2720
2367
  expect(assistants[1].stopReason).toBe('length');
2721
2368
  });
2722
2369
 
2723
- it('attaches agent_turn_complete.stopReason to the preceding text segment when the turn ends with a tool call', async () => {
2370
+ it('attaches turn_complete.stopReason to the preceding text segment when the turn ends with a tool call', async () => {
2724
2371
  const execId = 'exec_stopreason_tail_tool';
2725
- global.fetch = createAgentStreamFetch([
2726
- sseEvent('agent_start', {
2727
- executionId: execId, agentId: 'virtual', agentName: 'Test',
2728
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2729
- }),
2730
- sseEvent('agent_iteration_start', {
2731
- executionId: execId, iteration: 1, maxTurns: 1,
2732
- startedAt: new Date().toISOString(), seq: 2,
2733
- }),
2734
- sseEvent('agent_turn_start', {
2735
- executionId: execId, iteration: 1, turnIndex: 0,
2736
- role: 'assistant', turnId: 'turn_1', seq: 3,
2737
- }),
2738
- sseEvent('agent_turn_delta', {
2739
- executionId: execId, iteration: 1, delta: 'about to call tool',
2740
- contentType: 'text', turnId: 'turn_1', seq: 4,
2741
- }),
2742
- sseEvent('agent_tool_start', {
2743
- executionId: execId, iteration: 1,
2744
- toolCallId: 'call_1', toolName: 'search', toolType: 'builtin', seq: 5,
2745
- }),
2746
- sseEvent('agent_tool_complete', {
2747
- executionId: execId, iteration: 1,
2748
- toolCallId: 'call_1', toolName: 'search', success: true,
2749
- executionTime: 10, seq: 6,
2750
- }),
2751
- sseEvent('agent_turn_complete', {
2752
- executionId: execId, iteration: 1, role: 'assistant',
2753
- turnId: 'turn_1', completedAt: new Date().toISOString(),
2754
- stopReason: 'max_tool_calls', seq: 7,
2755
- }),
2756
- sseEvent('agent_iteration_complete', {
2757
- executionId: execId, iteration: 1, toolCallsMade: 1,
2758
- stopConditionMet: true, completedAt: new Date().toISOString(), seq: 8,
2759
- }),
2760
- sseEvent('agent_complete', {
2761
- executionId: execId, agentId: 'virtual', success: true,
2762
- iterations: 1, stopReason: 'max_tool_calls',
2763
- completedAt: new Date().toISOString(), seq: 9,
2764
- }),
2372
+ global.fetch = createRawStreamFetch([
2373
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2374
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
2375
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
2376
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "about to call tool" }),
2377
+ sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "call_1", toolName: "search", toolType: "builtin", iteration: 1 }),
2378
+ sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "call_1", toolName: "search", success: true, executionTime: 10, iteration: 1 }),
2379
+ sseEvent("text_complete", { executionId: execId, seq: 6, id: "text_1", text: "about to call tool" }),
2380
+ sseEvent("turn_complete", { executionId: execId, seq: 7, id: "turn_1", iteration: 1, role: "assistant", stopReason: "max_tool_calls", completedAt: new Date().toISOString() }),
2381
+ sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_tool_calls", completedAt: new Date().toISOString() })
2765
2382
  ]);
2766
2383
 
2767
2384
  const client = new AgentWidgetClient({
@@ -2787,9 +2404,8 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
2787
2404
  // step_await (LOCAL tool pause) + resumeFlow
2788
2405
  // ============================================================================
2789
2406
 
2790
- describe('AgentWidgetClient: step_await parsing', () => {
2791
- // Wire collapses the legacy flow `step_await` (a `local_tool_required` pause)
2792
- // into the neutral `await` event the native handler consumes.
2407
+ describe('AgentWidgetClient: unified local-tool await parsing', () => {
2408
+ // Both dispatch kinds use the unified await event for local tools.
2793
2409
  const buildAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2794
2410
  const encoder = new TextEncoder();
2795
2411
  const body = `event: await\ndata: ${JSON.stringify({ type: 'await', ...payload })}\n\n`;
@@ -2800,8 +2416,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2800
2416
  },
2801
2417
  });
2802
2418
  };
2803
- // Legacy raw `step_await` frame — still exercised by the approval-reason guard
2804
- // below until Phase C removes the legacy approval branch from the handler.
2419
+ // Unsupported legacy events must not create approval or local-tool bubbles.
2805
2420
  const buildStepAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2806
2421
  const encoder = new TextEncoder();
2807
2422
  const body = `event: step_await\ndata: ${JSON.stringify({ type: 'step_await', ...payload })}\n\n`;
@@ -2865,7 +2480,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2865
2480
  toolCallId: 'tc_webmcp_1',
2866
2481
  toolName: 'webmcp:get_product_by_url',
2867
2482
  executionId: 'exec_abc',
2868
- startedAt: 1234,
2483
+ awaitedAt: new Date(1234).toISOString(),
2869
2484
  parameters: { path: '/jade/' },
2870
2485
  }),
2871
2486
  });
@@ -2893,7 +2508,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2893
2508
  expect(toolMsg!.agentMetadata?.awaitingLocalTool).toBe(true);
2894
2509
  });
2895
2510
 
2896
- it('ignores step_await events whose awaitReason is not local_tool_required', async () => {
2511
+ it('ignores the obsolete step_await approval event', async () => {
2897
2512
  global.fetch = vi.fn().mockResolvedValue({
2898
2513
  ok: true,
2899
2514
  body: buildStepAwaitStream({
@@ -2917,17 +2532,18 @@ describe('AgentWidgetClient: step_await parsing', () => {
2917
2532
  .map((e) => (e as { message: AgentWidgetMessage }).message)
2918
2533
  .find((m) => m.agentMetadata?.awaitingLocalTool);
2919
2534
  expect(toolMsg).toBeUndefined();
2535
+ expect(events.some((event) => event.type === "message" && event.message.variant === "approval")).toBe(false);
2920
2536
  });
2921
2537
  });
2922
2538
 
2923
2539
  // ============================================================================
2924
- // agent_await (AGENT-dispatch LOCAL tool pause) — resolves through the same
2925
- // path as step_await; carries a bare tool name + origin instead of a webmcp:
2540
+ // Agent-dispatch LOCAL tool pauses use await, with a bare name + origin
2541
+ // instead of a webmcp:
2926
2542
  // prefix + awaitReason.
2927
2543
  // ============================================================================
2928
2544
 
2929
- describe('AgentWidgetClient: agent_await parsing', () => {
2930
- // Wire collapses the legacy `step_await`/`agent_await` pair into one `await`
2545
+ describe('AgentWidgetClient: agent await parsing', () => {
2546
+ // Agent and flow dispatch share the same `await`
2931
2547
  // event; the dispatch origin survives as the `origin` field on the payload.
2932
2548
  const buildAgentAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2933
2549
  const encoder = new TextEncoder();
@@ -2940,7 +2556,7 @@ describe('AgentWidgetClient: agent_await parsing', () => {
2940
2556
  });
2941
2557
  };
2942
2558
 
2943
- it('normalizes a WebMCP agent_await (origin "webmcp") to a running webmcp: tool message', async () => {
2559
+ it('normalizes a WebMCP await (origin "webmcp") to a running webmcp: tool message', async () => {
2944
2560
  global.fetch = vi.fn().mockResolvedValue({
2945
2561
  ok: true,
2946
2562
  body: buildAgentAwaitStream({
@@ -2979,7 +2595,7 @@ describe('AgentWidgetClient: agent_await parsing', () => {
2979
2595
  expect(toolMsg!.agentMetadata?.webMcpToolCallId).toBe('tc_webmcp_1');
2980
2596
  });
2981
2597
 
2982
- it('emits a complete tool message for a non-WebMCP agent_await (origin "sdk")', async () => {
2598
+ it('emits a complete tool message for a non-WebMCP await (origin "sdk")', async () => {
2983
2599
  global.fetch = vi.fn().mockResolvedValue({
2984
2600
  ok: true,
2985
2601
  body: buildAgentAwaitStream({
@@ -3095,7 +2711,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
3095
2711
  (client as unknown as { readVisitorToken: () => Promise<string> }).readVisitorToken =
3096
2712
  async () => 'cvt_resume';
3097
2713
 
3098
- await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } }, { after: '18' });
2714
+ await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } }, { after: '18.2' });
3099
2715
 
3100
2716
  // Session-authed sibling of /v1/client/chat: no Bearer key, sessionId in body.
3101
2717
  expect(capturedUrl).toBe('https://api.runtype.com/v1/client/resume');
@@ -3104,7 +2720,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
3104
2720
  toolOutputs: { toolu_A: { ok: true } },
3105
2721
  streamResponse: true,
3106
2722
  sessionId: 'cs_123',
3107
- after: '18',
2723
+ after: '18.2',
3108
2724
  });
3109
2725
  expect(capturedHeaders!['Authorization']).toBeUndefined();
3110
2726
  expect(capturedHeaders!['X-Visitor-Token']).toBe('cvt_resume');
@@ -3164,10 +2780,10 @@ describe('AgentWidgetClient.resumeFlow', () => {
3164
2780
  });
3165
2781
 
3166
2782
  // ============================================================================
3167
- // agent_media Event Handling
2783
+ // media Event Handling
3168
2784
  // ============================================================================
3169
2785
 
3170
- describe('AgentWidgetClient - agent_media events', () => {
2786
+ describe('AgentWidgetClient - media events', () => {
3171
2787
  const collectMediaMessages = (events: AgentWidgetEvent[]): AgentWidgetMessage[] => {
3172
2788
  const byId = new Map<string, AgentWidgetMessage>();
3173
2789
  for (const event of events) {
@@ -3180,36 +2796,14 @@ describe('AgentWidgetClient - agent_media events', () => {
3180
2796
 
3181
2797
  it('renders a base64 image (AI SDK v6 type:"media") as a synthetic message', async () => {
3182
2798
  const execId = 'exec_media_image';
3183
- global.fetch = createAgentStreamFetch([
3184
- sseEvent('agent_start', {
3185
- executionId: execId, agentId: 'virtual', agentName: 'Test',
3186
- maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
3187
- }),
3188
- sseEvent('agent_iteration_start', {
3189
- executionId: execId, iteration: 1, maxTurns: 1,
3190
- startedAt: new Date().toISOString(), seq: 2,
3191
- }),
3192
- sseEvent('agent_tool_start', {
3193
- executionId: execId, iteration: 1, toolCallId: 'tc_shot',
3194
- toolName: 'browser:screenshot', startedAt: new Date().toISOString(), seq: 3,
3195
- }),
3196
- sseEvent('agent_tool_complete', {
3197
- executionId: execId, iteration: 1, toolCallId: 'tc_shot',
3198
- toolName: 'browser:screenshot', completedAt: new Date().toISOString(), seq: 4,
3199
- }),
3200
- sseEvent('agent_media', {
3201
- executionId: execId, iteration: 1, toolCallId: 'tc_shot',
3202
- toolName: 'browser:screenshot',
3203
- media: [
3204
- { type: 'media', data: 'iVBORw==', mediaType: 'image/png' },
3205
- ],
3206
- seq: 5,
3207
- }),
3208
- sseEvent('agent_complete', {
3209
- executionId: execId, agentId: 'virtual', success: true,
3210
- iterations: 1, stopReason: 'max_iterations',
3211
- completedAt: new Date().toISOString(), seq: 6,
3212
- }),
2799
+ global.fetch = createRawStreamFetch([
2800
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2801
+ sseEvent("tool_start", { executionId: execId, seq: 1, toolCallId: "tc_shot", toolName: "browser:screenshot", toolType: "builtin", iteration: 1 }),
2802
+ sseEvent("tool_complete", { executionId: execId, seq: 2, toolCallId: "tc_shot", toolName: "browser:screenshot", success: true, iteration: 1 }),
2803
+ sseEvent("media_start", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_shot" }),
2804
+ sseEvent("media_delta", { executionId: execId, seq: 4, id: "media_1", delta: "iVBORw==" }),
2805
+ sseEvent("media_complete", { executionId: execId, seq: 5, id: "media_1", mediaType: "image/png", data: "iVBORw==", toolCallId: "tc_shot" }),
2806
+ sseEvent("execution_complete", { executionId: execId, seq: 6, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3213
2807
  ]);
3214
2808
 
3215
2809
  const client = new AgentWidgetClient({
@@ -3242,15 +2836,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3242
2836
 
3243
2837
  it('renders a hosted image (AI SDK v3/v4 type:"image-url") as a synthetic message', async () => {
3244
2838
  const execId = 'exec_media_url';
3245
- global.fetch = createAgentStreamFetch([
3246
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3247
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3248
- sseEvent('agent_media', {
3249
- executionId: execId, iteration: 1, toolCallId: 'tc_dalle', toolName: 'dalle',
3250
- media: [{ type: 'image-url', url: 'https://r2.example.com/img.png' }],
3251
- seq: 3,
3252
- }),
3253
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
2839
+ global.fetch = createRawStreamFetch([
2840
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2841
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image", role: "assistant", toolCallId: "tc_dalle" }),
2842
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://r2.example.com/img.png" }),
2843
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image", url: "https://r2.example.com/img.png", toolCallId: "tc_dalle" }),
2844
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3254
2845
  ]);
3255
2846
 
3256
2847
  const client = new AgentWidgetClient({
@@ -3275,15 +2866,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3275
2866
 
3276
2867
  it('preserves mediaType on image-url parts when provided', async () => {
3277
2868
  const execId = 'exec_media_url_typed';
3278
- global.fetch = createAgentStreamFetch([
3279
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3280
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3281
- sseEvent('agent_media', {
3282
- executionId: execId, iteration: 1, toolCallId: 'tc_dalle', toolName: 'dalle',
3283
- media: [{ type: 'image-url', url: 'https://r2.example.com/img.png', mediaType: 'image/png' }],
3284
- seq: 3,
3285
- }),
3286
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
2869
+ global.fetch = createRawStreamFetch([
2870
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2871
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_dalle" }),
2872
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://r2.example.com/img.png" }),
2873
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", url: "https://r2.example.com/img.png", toolCallId: "tc_dalle" }),
2874
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3287
2875
  ]);
3288
2876
 
3289
2877
  const client = new AgentWidgetClient({
@@ -3306,15 +2894,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3306
2894
 
3307
2895
  it('renders a base64 audio part with mediaType', async () => {
3308
2896
  const execId = 'exec_media_audio';
3309
- global.fetch = createAgentStreamFetch([
3310
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3311
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3312
- sseEvent('agent_media', {
3313
- executionId: execId, iteration: 1, toolCallId: 'tc_tts', toolName: 'elevenlabs-tts',
3314
- media: [{ type: 'media', data: 'AAAA', mediaType: 'audio/mpeg' }],
3315
- seq: 3,
3316
- }),
3317
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
2897
+ global.fetch = createRawStreamFetch([
2898
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2899
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "audio/mpeg", role: "assistant", toolCallId: "tc_tts" }),
2900
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
2901
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "audio/mpeg", data: "AAAA", toolCallId: "tc_tts" }),
2902
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3318
2903
  ]);
3319
2904
 
3320
2905
  const client = new AgentWidgetClient({
@@ -3339,19 +2924,18 @@ describe('AgentWidgetClient - agent_media events', () => {
3339
2924
 
3340
2925
  it('routes file-url parts by mediaType (audio/video/file)', async () => {
3341
2926
  const execId = 'exec_media_file_url';
3342
- global.fetch = createAgentStreamFetch([
3343
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3344
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3345
- sseEvent('agent_media', {
3346
- executionId: execId, iteration: 1, toolCallId: 'tc_files', toolName: 'multi',
3347
- media: [
3348
- { type: 'file-url', url: 'https://example.com/a.mp3', mediaType: 'audio/mpeg' },
3349
- { type: 'file-url', url: 'https://example.com/v.mp4', mediaType: 'video/mp4' },
3350
- { type: 'file-url', url: 'https://example.com/r.pdf', mediaType: 'application/pdf' },
3351
- ],
3352
- seq: 3,
3353
- }),
3354
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
2927
+ global.fetch = createRawStreamFetch([
2928
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2929
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "audio/mpeg", role: "assistant", toolCallId: "tc_files" }),
2930
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "https://example.com/a.mp3" }),
2931
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "audio/mpeg", url: "https://example.com/a.mp3", toolCallId: "tc_files" }),
2932
+ sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "video/mp4", role: "assistant", toolCallId: "tc_files" }),
2933
+ sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "https://example.com/v.mp4" }),
2934
+ sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "video/mp4", url: "https://example.com/v.mp4", toolCallId: "tc_files" }),
2935
+ sseEvent("media_start", { executionId: execId, seq: 7, id: "media_3", mediaType: "application/pdf", role: "assistant", toolCallId: "tc_files" }),
2936
+ sseEvent("media_delta", { executionId: execId, seq: 8, id: "media_3", delta: "https://example.com/r.pdf" }),
2937
+ sseEvent("media_complete", { executionId: execId, seq: 9, id: "media_3", mediaType: "application/pdf", url: "https://example.com/r.pdf", toolCallId: "tc_files" }),
2938
+ sseEvent("execution_complete", { executionId: execId, seq: 10, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3355
2939
  ]);
3356
2940
 
3357
2941
  const client = new AgentWidgetClient({
@@ -3383,19 +2967,18 @@ describe('AgentWidgetClient - agent_media events', () => {
3383
2967
 
3384
2968
  it('renders each mixed media part as its own synthetic message', async () => {
3385
2969
  const execId = 'exec_media_mixed';
3386
- global.fetch = createAgentStreamFetch([
3387
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3388
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3389
- sseEvent('agent_media', {
3390
- executionId: execId, iteration: 1, toolCallId: 'tc_mix', toolName: 'multi',
3391
- media: [
3392
- { type: 'media', data: 'IMG', mediaType: 'image/png' },
3393
- { type: 'image-url', url: 'https://example.com/dalle.png' },
3394
- { type: 'media', data: 'FILE', mediaType: 'application/pdf' },
3395
- ],
3396
- seq: 3,
3397
- }),
3398
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
2970
+ global.fetch = createRawStreamFetch([
2971
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
2972
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_mix" }),
2973
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "IMG" }),
2974
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", data: "IMG", toolCallId: "tc_mix" }),
2975
+ sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image", role: "assistant", toolCallId: "tc_mix" }),
2976
+ sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "https://example.com/dalle.png" }),
2977
+ sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image", url: "https://example.com/dalle.png", toolCallId: "tc_mix" }),
2978
+ sseEvent("media_start", { executionId: execId, seq: 7, id: "media_3", mediaType: "application/pdf", role: "assistant", toolCallId: "tc_mix" }),
2979
+ sseEvent("media_delta", { executionId: execId, seq: 8, id: "media_3", delta: "FILE" }),
2980
+ sseEvent("media_complete", { executionId: execId, seq: 9, id: "media_3", mediaType: "application/pdf", data: "FILE", toolCallId: "tc_mix" }),
2981
+ sseEvent("execution_complete", { executionId: execId, seq: 10, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3399
2982
  ]);
3400
2983
 
3401
2984
  const client = new AgentWidgetClient({
@@ -3422,21 +3005,22 @@ describe('AgentWidgetClient - agent_media events', () => {
3422
3005
 
3423
3006
  it('inserts media between tool bubble and the next text turn', async () => {
3424
3007
  const execId = 'exec_media_order';
3425
- global.fetch = createAgentStreamFetch([
3426
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3427
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3428
- sseEvent('agent_turn_start', { executionId: execId, iteration: 1, turnIndex: 0, role: 'assistant', turnId: 'turn_1', seq: 3 }),
3429
- sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: 'Calling tool...', contentType: 'text', turnId: 'turn_1', seq: 4 }),
3430
- sseEvent('agent_tool_start', { executionId: execId, iteration: 1, toolCallId: 'tc_1', toolName: 'browser:screenshot', startedAt: new Date().toISOString(), seq: 5 }),
3431
- sseEvent('agent_tool_complete', { executionId: execId, iteration: 1, toolCallId: 'tc_1', toolName: 'browser:screenshot', completedAt: new Date().toISOString(), seq: 6 }),
3432
- sseEvent('agent_media', {
3433
- executionId: execId, iteration: 1, toolCallId: 'tc_1', toolName: 'browser:screenshot',
3434
- media: [{ type: 'media', data: 'PNG', mediaType: 'image/png' }],
3435
- seq: 7,
3436
- }),
3437
- sseEvent('agent_turn_start', { executionId: execId, iteration: 1, turnIndex: 1, role: 'assistant', turnId: 'turn_2', seq: 8 }),
3438
- sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: 'Done!', contentType: 'text', turnId: 'turn_2', seq: 9 }),
3439
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 10 }),
3008
+ global.fetch = createRawStreamFetch([
3009
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3010
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
3011
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
3012
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Calling tool..." }),
3013
+ sseEvent("tool_start", { executionId: execId, seq: 4, toolCallId: "tc_1", toolName: "browser:screenshot", toolType: "builtin", iteration: 1 }),
3014
+ sseEvent("tool_complete", { executionId: execId, seq: 5, toolCallId: "tc_1", toolName: "browser:screenshot", success: true, iteration: 1 }),
3015
+ sseEvent("media_start", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_1" }),
3016
+ sseEvent("media_delta", { executionId: execId, seq: 7, id: "media_2", delta: "PNG" }),
3017
+ sseEvent("media_complete", { executionId: execId, seq: 8, id: "media_2", mediaType: "image/png", data: "PNG", toolCallId: "tc_1" }),
3018
+ sseEvent("text_complete", { executionId: execId, seq: 9, id: "text_1", text: "Calling tool..." }),
3019
+ sseEvent("turn_start", { executionId: execId, seq: 10, id: "turn_2", iteration: 1, turnIndex: 1, role: "assistant" }),
3020
+ sseEvent("text_start", { executionId: execId, seq: 11, id: "text_3", role: "assistant" }),
3021
+ sseEvent("text_delta", { executionId: execId, seq: 12, id: "text_3", delta: "Done!" }),
3022
+ sseEvent("text_complete", { executionId: execId, seq: 13, id: "text_3", text: "Done!" }),
3023
+ sseEvent("execution_complete", { executionId: execId, seq: 14, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3440
3024
  ]);
3441
3025
 
3442
3026
  const client = new AgentWidgetClient({
@@ -3474,19 +3058,15 @@ describe('AgentWidgetClient - agent_media events', () => {
3474
3058
 
3475
3059
  it('skips malformed media parts that have neither data nor url', async () => {
3476
3060
  const execId = 'exec_media_empty';
3477
- global.fetch = createAgentStreamFetch([
3478
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3479
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3480
- sseEvent('agent_media', {
3481
- executionId: execId, iteration: 1, toolCallId: 'tc_x', toolName: 'noop',
3482
- media: [
3483
- { type: 'media', mediaType: 'image/png' }, // missing data
3484
- { type: 'image-url' }, // missing url
3485
- { type: 'unknown-shape', payload: 'whatever' }, // unknown discriminator
3486
- ],
3487
- seq: 3,
3488
- }),
3489
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
3061
+ global.fetch = createRawStreamFetch([
3062
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3063
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_x" }),
3064
+ sseEvent("media_complete", { executionId: execId, seq: 2, id: "media_1", mediaType: "image/png", toolCallId: "tc_x" }),
3065
+ sseEvent("media_start", { executionId: execId, seq: 3, id: "media_2", mediaType: "image", role: "assistant", toolCallId: "tc_x" }),
3066
+ sseEvent("media_complete", { executionId: execId, seq: 4, id: "media_2", mediaType: "image", toolCallId: "tc_x" }),
3067
+ sseEvent("media_start", { executionId: execId, seq: 5, id: "media_3", mediaType: "application/octet-stream", role: "assistant", toolCallId: "tc_x" }),
3068
+ sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_3", mediaType: "application/octet-stream", toolCallId: "tc_x" }),
3069
+ sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3490
3070
  ]);
3491
3071
 
3492
3072
  const client = new AgentWidgetClient({
@@ -3502,22 +3082,17 @@ describe('AgentWidgetClient - agent_media events', () => {
3502
3082
  expect(collectMediaMessages(events)).toHaveLength(0);
3503
3083
  });
3504
3084
 
3505
- it('produces unique ids for repeated agent_media events on the same toolCallId', async () => {
3085
+ it('produces unique ids for repeated media events on the same toolCallId', async () => {
3506
3086
  const execId = 'exec_media_repeat';
3507
- global.fetch = createAgentStreamFetch([
3508
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3509
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3510
- sseEvent('agent_media', {
3511
- executionId: execId, iteration: 1, toolCallId: 'tc_repeat', toolName: 'multi',
3512
- media: [{ type: 'media', data: 'A', mediaType: 'image/png' }],
3513
- seq: 3,
3514
- }),
3515
- sseEvent('agent_media', {
3516
- executionId: execId, iteration: 1, toolCallId: 'tc_repeat', toolName: 'multi',
3517
- media: [{ type: 'media', data: 'B', mediaType: 'image/png' }],
3518
- seq: 4,
3519
- }),
3520
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 5 }),
3087
+ global.fetch = createRawStreamFetch([
3088
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3089
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "image/png", role: "assistant", toolCallId: "tc_repeat" }),
3090
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "A" }),
3091
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "image/png", data: "A", toolCallId: "tc_repeat" }),
3092
+ sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_repeat" }),
3093
+ sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "B" }),
3094
+ sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", data: "B", toolCallId: "tc_repeat" }),
3095
+ sseEvent("execution_complete", { executionId: execId, seq: 7, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3521
3096
  ]);
3522
3097
 
3523
3098
  const client = new AgentWidgetClient({
@@ -3545,20 +3120,18 @@ describe('AgentWidgetClient - agent_media events', () => {
3545
3120
  expect(first).toBeDefined();
3546
3121
  });
3547
3122
 
3548
- it('seals an in-flight assistant text bubble before splitting on agent_media', async () => {
3123
+ it('seals an in-flight assistant text bubble before splitting on media', async () => {
3549
3124
  const execId = 'exec_media_seal';
3550
- global.fetch = createAgentStreamFetch([
3551
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3552
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3553
- sseEvent('agent_turn_start', { executionId: execId, iteration: 1, turnIndex: 0, role: 'assistant', turnId: 'turn_1', seq: 3 }),
3554
- sseEvent('agent_turn_delta', { executionId: execId, iteration: 1, delta: 'Streaming...', contentType: 'text', turnId: 'turn_1', seq: 4 }),
3555
- // Media arrives mid-stream: earlier text bubble is still streaming.
3556
- sseEvent('agent_media', {
3557
- executionId: execId, iteration: 1, toolCallId: 'tc_seal', toolName: 'shot',
3558
- media: [{ type: 'media', data: 'PNG', mediaType: 'image/png' }],
3559
- seq: 5,
3560
- }),
3561
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 6 }),
3125
+ global.fetch = createRawStreamFetch([
3126
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3127
+ sseEvent("turn_start", { executionId: execId, seq: 1, id: "turn_1", iteration: 1, turnIndex: 0, role: "assistant" }),
3128
+ sseEvent("text_start", { executionId: execId, seq: 2, id: "text_1", role: "assistant" }),
3129
+ sseEvent("text_delta", { executionId: execId, seq: 3, id: "text_1", delta: "Streaming..." }),
3130
+ sseEvent("media_start", { executionId: execId, seq: 4, id: "media_2", mediaType: "image/png", role: "assistant", toolCallId: "tc_seal" }),
3131
+ sseEvent("media_delta", { executionId: execId, seq: 5, id: "media_2", delta: "PNG" }),
3132
+ sseEvent("media_complete", { executionId: execId, seq: 6, id: "media_2", mediaType: "image/png", data: "PNG", toolCallId: "tc_seal" }),
3133
+ sseEvent("text_complete", { executionId: execId, seq: 7, id: "text_1", text: "Streaming..." }),
3134
+ sseEvent("execution_complete", { executionId: execId, seq: 8, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3562
3135
  ]);
3563
3136
 
3564
3137
  const client = new AgentWidgetClient({
@@ -3590,16 +3163,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3590
3163
 
3591
3164
  it('routes audio parts case-insensitively (RFC 7231)', async () => {
3592
3165
  const execId = 'exec_media_case';
3593
- global.fetch = createAgentStreamFetch([
3594
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3595
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3596
- sseEvent('agent_media', {
3597
- executionId: execId, iteration: 1, toolCallId: 'tc_case', toolName: 'tts',
3598
- // Non-canonical casing should still land in the audio bucket, not the file bucket.
3599
- media: [{ type: 'media', data: 'AAAA', mediaType: 'Audio/MPEG' }],
3600
- seq: 3,
3601
- }),
3602
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
3166
+ global.fetch = createRawStreamFetch([
3167
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3168
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "Audio/MPEG", role: "assistant", toolCallId: "tc_case" }),
3169
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
3170
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "Audio/MPEG", data: "AAAA", toolCallId: "tc_case" }),
3171
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3603
3172
  ]);
3604
3173
 
3605
3174
  const client = new AgentWidgetClient({
@@ -3623,15 +3192,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3623
3192
 
3624
3193
  it('renders a base64 text/csv attachment as a file part (not silently dropped)', async () => {
3625
3194
  const execId = 'exec_media_csv';
3626
- global.fetch = createAgentStreamFetch([
3627
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3628
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3629
- sseEvent('agent_media', {
3630
- executionId: execId, iteration: 1, toolCallId: 'tc_csv', toolName: 'export',
3631
- media: [{ type: 'media', data: 'YSxiCjEsMg==', mediaType: 'text/csv' }],
3632
- seq: 3,
3633
- }),
3634
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
3195
+ global.fetch = createRawStreamFetch([
3196
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3197
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "text/csv", role: "assistant", toolCallId: "tc_csv" }),
3198
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "YSxiCjEsMg==" }),
3199
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "text/csv", data: "YSxiCjEsMg==", toolCallId: "tc_csv" }),
3200
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3635
3201
  ]);
3636
3202
 
3637
3203
  const client = new AgentWidgetClient({
@@ -3655,16 +3221,12 @@ describe('AgentWidgetClient - agent_media events', () => {
3655
3221
 
3656
3222
  it('defaults missing mediaType on a type:"media" part to application/octet-stream', async () => {
3657
3223
  const execId = 'exec_media_no_type';
3658
- global.fetch = createAgentStreamFetch([
3659
- sseEvent('agent_start', { executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3660
- sseEvent('agent_iteration_start', { executionId: execId, iteration: 1, maxTurns: 1, startedAt: new Date().toISOString(), seq: 2 }),
3661
- sseEvent('agent_media', {
3662
- executionId: execId, iteration: 1, toolCallId: 'tc_blob', toolName: 'opaque',
3663
- // mediaType is empty: should not produce a malformed `data:;base64,...` URI.
3664
- media: [{ type: 'media', data: 'AAAA', mediaType: '' }],
3665
- seq: 3,
3666
- }),
3667
- sseEvent('agent_complete', { executionId: execId, agentId: 'virtual', success: true, iterations: 1, stopReason: 'max_iterations', completedAt: new Date().toISOString(), seq: 4 }),
3224
+ global.fetch = createRawStreamFetch([
3225
+ sseEvent("execution_start", { executionId: execId, seq: 0, kind: "agent", startedAt: new Date().toISOString(), agentId: "virtual", agentName: "Test", maxTurns: 1 }),
3226
+ sseEvent("media_start", { executionId: execId, seq: 1, id: "media_1", mediaType: "", role: "assistant", toolCallId: "tc_blob" }),
3227
+ sseEvent("media_delta", { executionId: execId, seq: 2, id: "media_1", delta: "AAAA" }),
3228
+ sseEvent("media_complete", { executionId: execId, seq: 3, id: "media_1", mediaType: "", data: "AAAA", toolCallId: "tc_blob" }),
3229
+ sseEvent("execution_complete", { executionId: execId, seq: 4, kind: "agent", success: true, iterations: 1, stopReason: "max_iterations", completedAt: new Date().toISOString() })
3668
3230
  ]);
3669
3231
 
3670
3232
  const client = new AgentWidgetClient({
@@ -4324,7 +3886,7 @@ describe('AgentWidgetClient - version header', () => {
4324
3886
  const encoder = new TextEncoder();
4325
3887
  const stream = new ReadableStream({
4326
3888
  start(c) {
4327
- c.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
3889
+ c.enqueue(encoder.encode('data: {"type":"execution_complete","success":true}\n\n'));
4328
3890
  c.close();
4329
3891
  },
4330
3892
  });
@@ -4610,7 +4172,7 @@ describe('AgentWidgetClient - Artifact display modes', () => {
4610
4172
 
4611
4173
  describe('AgentWidgetClient - mention context', () => {
4612
4174
  const agentDone = (controller: ReadableStreamDefaultController, encoder: TextEncoder) => {
4613
- controller.enqueue(encoder.encode(sseEvent('agent_complete', {
4175
+ controller.enqueue(encoder.encode(sseEvent('execution_complete', {
4614
4176
  executionId: 'exec_1',
4615
4177
  agentId: 'agent_123',
4616
4178
  success: true,