@runtypelabs/persona 4.22.0 → 4.22.1

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 (93) 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 +166 -27
  29. package/dist/index.d.ts +166 -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/session-reconnect.d.cts +148 -14
  35. package/dist/session-reconnect.d.ts +148 -14
  36. package/dist/smart-dom-reader.d.cts +148 -14
  37. package/dist/smart-dom-reader.d.ts +148 -14
  38. package/dist/testing.cjs +3 -3
  39. package/dist/testing.d.cts +3 -4
  40. package/dist/testing.d.ts +3 -4
  41. package/dist/testing.js +3 -3
  42. package/dist/theme-editor-preview.cjs +71 -71
  43. package/dist/theme-editor-preview.d.cts +148 -14
  44. package/dist/theme-editor-preview.d.ts +148 -14
  45. package/dist/theme-editor-preview.js +48 -48
  46. package/dist/theme-editor.d.cts +148 -14
  47. package/dist/theme-editor.d.ts +148 -14
  48. package/dist/voice-runtime.d.cts +6 -2
  49. package/dist/voice-runtime.d.ts +6 -2
  50. package/package.json +1 -1
  51. package/src/ask-user-question-tool.ts +1 -1
  52. package/src/client-history-rest.test.ts +2 -1
  53. package/src/client-visitor-history.test.ts +20 -4
  54. package/src/client.composer-options.test.ts +1 -1
  55. package/src/client.interrupt.test.ts +2 -2
  56. package/src/client.test.ts +477 -915
  57. package/src/client.ts +134 -494
  58. package/src/client.unified-tools.test.ts +76 -0
  59. package/src/components/event-stream-badge-theming.test.ts +3 -0
  60. package/src/components/event-stream-view.test.ts +76 -76
  61. package/src/components/event-stream-view.ts +5 -0
  62. package/src/components/tool-bubble.test.ts +12 -0
  63. package/src/components/tool-bubble.ts +13 -2
  64. package/src/event-stream-view-bundle.test.ts +2 -2
  65. package/src/generated/runtype-openapi-contract.ts +129 -1
  66. package/src/internal/runtype-history-provider.test.ts +2 -2
  67. package/src/session-history.test.ts +1 -1
  68. package/src/session.quote.test.ts +1 -1
  69. package/src/session.resubmit-from.test.ts +1 -1
  70. package/src/session.test.ts +5 -5
  71. package/src/session.ts +95 -36
  72. package/src/suggest-replies-tool.ts +1 -1
  73. package/src/testing/mock-stream.test.ts +37 -16
  74. package/src/testing/mock-stream.ts +14 -9
  75. package/src/types.ts +19 -14
  76. package/src/ui.ask-user-question-plugin.test.ts +7 -7
  77. package/src/ui.composer-defer-one.test.ts +6 -34
  78. package/src/ui.composer-draft-persistence.test.ts +1 -1
  79. package/src/ui.composer-quote.test.ts +1 -1
  80. package/src/ui.message-history-actions.test.ts +1 -1
  81. package/src/ui.streaming-coalescing.test.ts +10 -13
  82. package/src/ui.ts +67 -32
  83. package/src/ui.typing-indicator.test.ts +216 -0
  84. package/src/ui.voice-custom-provider.test.ts +211 -0
  85. package/src/utils/event-stream-buffer.test.ts +13 -13
  86. package/src/utils/event-stream-capture.test.ts +20 -20
  87. package/src/utils/throughput-tracker.test.ts +100 -142
  88. package/src/utils/throughput-tracker.ts +18 -132
  89. package/src/utils/voice-support.ts +18 -1
  90. package/src/voice/voice-turn-tracker.ts +18 -0
  91. package/src/webmcp-bridge.ts +1 -1
  92. package/dist/event-stream-view-FKWDY3UB.js +0 -1
  93. package/src/utils/__fixtures__/unified-translator.oracle.ts +0 -717
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AgentWidgetClient } from "./client";
3
+ import type { AgentWidgetEvent, AgentWidgetMessage } from "./types";
4
+ import type { RuntypeExecutionStreamEvent } from "./generated/runtype-openapi-contract";
5
+
6
+ type Frame = RuntypeExecutionStreamEvent extends infer E
7
+ ? E extends RuntypeExecutionStreamEvent ? Omit<E, "seq" | "executionId"> : never
8
+ : never;
9
+
10
+ async function consume(frames: Frame[]) {
11
+ const events: AgentWidgetEvent[] = [];
12
+ const client = new AgentWidgetClient({
13
+ apiUrl: "https://example.test/dispatch",
14
+ customFetch: async () => new Response(frames.map((frame, seq) =>
15
+ `event: ${frame.type}\ndata: ${JSON.stringify({ executionId: "exec_test", seq, ...frame })}\n\n`
16
+ ).join(""), { headers: { "Content-Type": "text/event-stream" } }),
17
+ });
18
+ await client.dispatch({ messages: [] }, event => events.push(event));
19
+ const messages = new Map<string, AgentWidgetMessage>();
20
+ for (const event of events) if (event.type === "message") messages.set(event.message.id, event.message);
21
+ return { events, messages: [...messages.values()] };
22
+ }
23
+
24
+ const start = (id: string, parameters?: Record<string, unknown>): Frame => ({
25
+ type: "tool_start", toolCallId: id, toolName: "lookup", toolType: "builtin", parameters,
26
+ });
27
+
28
+ describe("canonical unified tool channels", () => {
29
+ it.each([undefined, { query: "provisional" }])("applies final parameters after a provisional start (%j)", async parameters => {
30
+ const { messages } = await consume([
31
+ start("call_a", parameters),
32
+ { type: "tool_input_delta", toolCallId: "call_a", delta: '{"query":"hello"}' },
33
+ { type: "tool_input_complete", toolCallId: "call_a", parameters: { query: "hello" } },
34
+ { type: "tool_complete", toolCallId: "call_a", success: true },
35
+ ]);
36
+ expect(messages[0].toolCall).toMatchObject({ id: "call_a", args: { query: "hello" }, chunks: ['{"query":"hello"}'] });
37
+ });
38
+
39
+ it("accepts empty final parameters", async () => {
40
+ const { messages } = await consume([start("call_a", { stale: true }),
41
+ { type: "tool_input_complete", toolCallId: "call_a", parameters: {} }]);
42
+ expect(messages[0].toolCall?.args).toEqual({});
43
+ });
44
+
45
+ it("keeps continuation calls separate from the last started call", async () => {
46
+ const { messages } = await consume([
47
+ start("call_b"),
48
+ { type: "tool_input_complete", toolCallId: "call_a", toolName: "lookup", parameters: { query: "A" } },
49
+ { type: "tool_output_delta", toolCallId: "call_a", delta: "A output" },
50
+ { type: "tool_complete", toolCallId: "call_a", toolName: "lookup", success: true, result: "A result", executionTime: 42 },
51
+ { type: "tool_complete", toolCallId: "call_b", success: true, result: "B result" },
52
+ ]);
53
+ expect(messages.map(m => m.toolCall?.id).sort()).toEqual(["call_a", "call_b"]);
54
+ expect(messages.find(m => m.toolCall?.id === "call_a")?.toolCall).toMatchObject({ args: { query: "A" }, chunks: ["A output"], result: "A result", duration: 42, durationMs: 42 });
55
+ expect(messages.find(m => m.toolCall?.id === "call_b")?.toolCall?.result).toBe("B result");
56
+ });
57
+
58
+ it("keeps the canonical ID and name on a completion-only continuation", async () => {
59
+ const { messages } = await consume([{ type: "tool_complete", toolCallId: "call_a", toolName: "lookup", success: true }]);
60
+ expect(messages[0].toolCall).toMatchObject({ id: "call_a", name: "lookup" });
61
+ });
62
+
63
+ it("retains a tool failure while the execution continues", async () => {
64
+ const { messages, events } = await consume([
65
+ start("call_a"),
66
+ { type: "tool_complete", toolCallId: "call_a", success: false, error: "Search timed out" },
67
+ { type: "text_start", id: "text_a" },
68
+ { type: "text_delta", id: "text_a", delta: "I can try another search." },
69
+ { type: "text_complete", id: "text_a" },
70
+ { type: "execution_complete", kind: "agent", success: true },
71
+ ]);
72
+ expect(messages[0].toolCall).toMatchObject({ status: "complete", success: false, error: "Search timed out" });
73
+ expect(messages.at(-1)?.content).toBe("I can try another search.");
74
+ expect(events.filter(e => e.type === "error")).toEqual([]);
75
+ });
76
+ });
@@ -112,6 +112,9 @@ describe("event-stream badge theming", () => {
112
112
  });
113
113
 
114
114
  it("routes event types to families: prefixes, reasoning, and execution_error", () => {
115
+ expect(getBadgeColor("execution_start")).toBe(DEFAULT_BADGE_COLORS.flow_);
116
+ expect(getBadgeColor("turn_start")).toBe(DEFAULT_BADGE_COLORS.agent_);
117
+ expect(getBadgeColor("text_delta")).toBe(DEFAULT_BADGE_COLORS.agent_);
115
118
  expect(getBadgeColor("flow_start")).toBe(DEFAULT_BADGE_COLORS.flow_);
116
119
  expect(getBadgeColor("tool_input_delta")).toBe(DEFAULT_BADGE_COLORS.tool_);
117
120
  expect(getBadgeColor("agent_turn_start")).toBe(DEFAULT_BADGE_COLORS.agent_);
@@ -308,7 +308,7 @@ describe("createEventStreamView", () => {
308
308
  describe("header bar", () => {
309
309
  it("should carry the total in the All events option (no title or count badge)", async () => {
310
310
  const { createEventStreamView } = await loadModule();
311
- const events = [makeEvent("step_chunk", 1)];
311
+ const events = [makeEvent("text_delta", 1)];
312
312
  const buffer = createMockBuffer(events);
313
313
  const { element, update } = createEventStreamView({ buffer: buffer as any });
314
314
 
@@ -320,7 +320,7 @@ describe("createEventStreamView", () => {
320
320
  it("should update the total in the All events option when events change", async () => {
321
321
  vi.useFakeTimers();
322
322
  const { createEventStreamView } = await loadModule();
323
- const events = [makeEvent("step_chunk", 1)];
323
+ const events = [makeEvent("text_delta", 1)];
324
324
  const buffer = createMockBuffer(events);
325
325
  const { element, update } = createEventStreamView({ buffer: buffer as any });
326
326
 
@@ -328,7 +328,7 @@ describe("createEventStreamView", () => {
328
328
  expect(getAllEventsOptionText(element)).toBe("All events (1)");
329
329
 
330
330
  vi.advanceTimersByTime(150);
331
- buffer.push(makeEvent("step_chunk", 2));
331
+ buffer.push(makeEvent("text_delta", 2));
332
332
  update();
333
333
 
334
334
  expect(getAllEventsOptionText(element)).toBe("All events (2)");
@@ -340,9 +340,9 @@ describe("createEventStreamView", () => {
340
340
  it("should populate filter options from buffer event types with counts", async () => {
341
341
  const { createEventStreamView } = await loadModule();
342
342
  const events = [
343
- makeEvent("step_chunk", 1),
344
- makeEvent("step_chunk", 2),
345
- makeEvent("flow_complete", 3),
343
+ makeEvent("text_delta", 1),
344
+ makeEvent("text_delta", 2),
345
+ makeEvent("execution_complete", 3),
346
346
  ];
347
347
  const buffer = createMockBuffer(events);
348
348
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -354,14 +354,14 @@ describe("createEventStreamView", () => {
354
354
  // Should have "All events" (with total) + 2 type options
355
355
  expect(filterSelect.options.length).toBe(3);
356
356
  expect(filterSelect.options[0].textContent).toBe("All events (3)");
357
- expect(filterSelect.options[1].textContent).toBe("flow_complete (1)");
358
- expect(filterSelect.options[2].textContent).toBe("step_chunk (2)");
357
+ expect(filterSelect.options[1].textContent).toBe("execution_complete (1)");
358
+ expect(filterSelect.options[2].textContent).toBe("text_delta (2)");
359
359
  });
360
360
 
361
361
  it("should update counts on subsequent update() calls", async () => {
362
362
  vi.useFakeTimers();
363
363
  const { createEventStreamView } = await loadModule();
364
- const events = [makeEvent("step_chunk", 1)];
364
+ const events = [makeEvent("text_delta", 1)];
365
365
  const buffer = createMockBuffer(events);
366
366
  const { element, update } = createEventStreamView({ buffer: buffer as any });
367
367
 
@@ -369,24 +369,24 @@ describe("createEventStreamView", () => {
369
369
 
370
370
  const filterSelect = getFilterSelect(element);
371
371
  expect(filterSelect.options[0].textContent).toBe("All events (1)");
372
- expect(filterSelect.options[1].textContent).toBe("step_chunk (1)");
372
+ expect(filterSelect.options[1].textContent).toBe("text_delta (1)");
373
373
 
374
374
  // Add another event and advance past throttle window
375
- buffer.push(makeEvent("step_chunk", 2));
375
+ buffer.push(makeEvent("text_delta", 2));
376
376
  vi.advanceTimersByTime(150);
377
377
  update();
378
378
 
379
379
  expect(filterSelect.options[0].textContent).toBe("All events (2)");
380
- expect(filterSelect.options[1].textContent).toBe("step_chunk (2)");
380
+ expect(filterSelect.options[1].textContent).toBe("text_delta (2)");
381
381
  vi.useRealTimers();
382
382
  });
383
383
 
384
384
  it("should filter events when a type is selected", async () => {
385
385
  const { createEventStreamView } = await loadModule();
386
386
  const events = [
387
- makeEvent("step_chunk", 1),
388
- makeEvent("flow_complete", 2),
389
- makeEvent("step_chunk", 3),
387
+ makeEvent("text_delta", 1),
388
+ makeEvent("execution_complete", 2),
389
+ makeEvent("text_delta", 3),
390
390
  ];
391
391
  const buffer = createMockBuffer(events);
392
392
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -394,7 +394,7 @@ describe("createEventStreamView", () => {
394
394
  update();
395
395
 
396
396
  const filterSelect = getFilterSelect(element);
397
- filterSelect.value = "step_chunk";
397
+ filterSelect.value = "text_delta";
398
398
  filterSelect.__fireEvent("change");
399
399
 
400
400
  expect(buffer.getAll).toHaveBeenCalled();
@@ -405,7 +405,7 @@ describe("createEventStreamView", () => {
405
405
  it("should debounce search input", async () => {
406
406
  vi.useFakeTimers();
407
407
  const { createEventStreamView } = await loadModule();
408
- const events = [makeEvent("step_chunk", 1, '{"message":"hello world"}')];
408
+ const events = [makeEvent("text_delta", 1, '{"message":"hello world"}')];
409
409
  const buffer = createMockBuffer(events);
410
410
  const { element } = createEventStreamView({ buffer: buffer as any });
411
411
 
@@ -470,7 +470,7 @@ describe("createEventStreamView", () => {
470
470
  it("should show no results message when filters produce empty results", async () => {
471
471
  vi.useFakeTimers();
472
472
  const { createEventStreamView } = await loadModule();
473
- const events = [makeEvent("step_chunk", 1, '{"data":"hello"}')];
473
+ const events = [makeEvent("text_delta", 1, '{"data":"hello"}')];
474
474
  const buffer = createMockBuffer(events);
475
475
  const { element } = createEventStreamView({ buffer: buffer as any });
476
476
 
@@ -494,8 +494,8 @@ describe("createEventStreamView", () => {
494
494
  vi.useFakeTimers();
495
495
  const { createEventStreamView } = await loadModule();
496
496
  const events = [
497
- makeEvent("step_chunk", 1),
498
- makeEvent("flow_complete", 2),
497
+ makeEvent("text_delta", 1),
498
+ makeEvent("execution_complete", 2),
499
499
  ];
500
500
  const buffer = createMockBuffer(events);
501
501
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -509,7 +509,7 @@ describe("createEventStreamView", () => {
509
509
 
510
510
  // Apply type filter
511
511
  const filterSelect = getFilterSelect(element);
512
- filterSelect.value = "step_chunk";
512
+ filterSelect.value = "text_delta";
513
513
  filterSelect.__fireEvent("change");
514
514
 
515
515
  // Should now show "Copy Filtered (1)"
@@ -520,8 +520,8 @@ describe("createEventStreamView", () => {
520
520
  it("should copy filtered events when filters are active", async () => {
521
521
  const { createEventStreamView } = await loadModule();
522
522
  const events = [
523
- makeEvent("step_chunk", 1),
524
- makeEvent("flow_complete", 2),
523
+ makeEvent("text_delta", 1),
524
+ makeEvent("execution_complete", 2),
525
525
  ];
526
526
  const buffer = createMockBuffer(events);
527
527
  const getFullHistory = vi.fn().mockResolvedValue(events);
@@ -534,7 +534,7 @@ describe("createEventStreamView", () => {
534
534
  const copyAllBtn = getCopyAllBtn(element);
535
535
 
536
536
  // Apply type filter
537
- filterSelect.value = "step_chunk";
537
+ filterSelect.value = "text_delta";
538
538
  filterSelect.__fireEvent("change");
539
539
 
540
540
  // Click copy all
@@ -553,8 +553,8 @@ describe("createEventStreamView", () => {
553
553
  it("should copy full history when no filters are active", async () => {
554
554
  const { createEventStreamView } = await loadModule();
555
555
  const events = [
556
- makeEvent("step_chunk", 1),
557
- makeEvent("flow_complete", 2),
556
+ makeEvent("text_delta", 1),
557
+ makeEvent("execution_complete", 2),
558
558
  ];
559
559
  const buffer = createMockBuffer(events);
560
560
  const fullHistory = [...events, makeEvent("old_event", 0)];
@@ -578,8 +578,8 @@ describe("createEventStreamView", () => {
578
578
  it("should fall back to buffer when getFullHistory returns empty", async () => {
579
579
  const { createEventStreamView } = await loadModule();
580
580
  const events = [
581
- makeEvent("step_chunk", 1),
582
- makeEvent("flow_complete", 2),
581
+ makeEvent("text_delta", 1),
582
+ makeEvent("execution_complete", 2),
583
583
  ];
584
584
  const buffer = createMockBuffer(events);
585
585
  const getFullHistory = vi.fn().mockResolvedValue([]);
@@ -681,7 +681,7 @@ describe("createEventStreamView", () => {
681
681
  it("should render rows with relative timestamps", async () => {
682
682
  const { createEventStreamView } = await loadModule();
683
683
  const events = [
684
- { id: "evt-1", type: "flow_start", timestamp: 1000, payload: '{"flowName":"Test"}' },
684
+ { id: "evt-1", type: "execution_start", timestamp: 1000, payload: '{"flowName":"Test"}' },
685
685
  { id: "evt-2", type: "step_start", timestamp: 1361, payload: '{"stepName":"Chatbot 1"}' },
686
686
  ];
687
687
  const buffer = createMockBuffer(events);
@@ -697,7 +697,7 @@ describe("createEventStreamView", () => {
697
697
 
698
698
  it("should render rows with absolute timestamps when configured", async () => {
699
699
  const { createEventStreamView } = await loadModule();
700
- const events = [makeEvent("step_chunk", 1)];
700
+ const events = [makeEvent("text_delta", 1)];
701
701
  const buffer = createMockBuffer(events);
702
702
  const { update } = createEventStreamView({
703
703
  buffer: buffer as any,
@@ -715,7 +715,7 @@ describe("createEventStreamView", () => {
715
715
  it("should extract description from payload fields", async () => {
716
716
  const { createEventStreamView } = await loadModule();
717
717
  const events = [
718
- { id: "evt-1", type: "flow_start", timestamp: 1000, payload: '{"flowName":"My Flow"}' },
718
+ { id: "evt-1", type: "execution_start", timestamp: 1000, payload: '{"flowName":"My Flow"}' },
719
719
  ];
720
720
  const buffer = createMockBuffer(events);
721
721
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -729,7 +729,7 @@ describe("createEventStreamView", () => {
729
729
 
730
730
  it("should hide sequence numbers when showSequenceNumbers is false", async () => {
731
731
  const { createEventStreamView } = await loadModule();
732
- const events = [makeEvent("step_chunk", 1)];
732
+ const events = [makeEvent("text_delta", 1)];
733
733
  const buffer = createMockBuffer(events);
734
734
  const { update } = createEventStreamView({
735
735
  buffer: buffer as any,
@@ -768,7 +768,7 @@ describe("createEventStreamView", () => {
768
768
  describe("expand/collapse", () => {
769
769
  it("should expand row to show inline payload when clicked", async () => {
770
770
  const { createEventStreamView } = await loadModule();
771
- const events = [makeEvent("step_chunk", 1, '{"message":"hello"}')];
771
+ const events = [makeEvent("text_delta", 1, '{"message":"hello"}')];
772
772
  const buffer = createMockBuffer(events);
773
773
  const { element, update } = createEventStreamView({ buffer: buffer as any });
774
774
 
@@ -827,7 +827,7 @@ describe("createEventStreamView", () => {
827
827
  it("should preserve existing row DOM references when new events are appended", async () => {
828
828
  vi.useFakeTimers();
829
829
  const { createEventStreamView } = await loadModule();
830
- const events = [makeEvent("step_chunk", 1), makeEvent("step_chunk", 2)];
830
+ const events = [makeEvent("text_delta", 1), makeEvent("text_delta", 2)];
831
831
  const buffer = createMockBuffer(events);
832
832
  const { element, update } = createEventStreamView({ buffer: buffer as any });
833
833
 
@@ -843,7 +843,7 @@ describe("createEventStreamView", () => {
843
843
 
844
844
  // Add a new event and update (Path C: incremental append)
845
845
  vi.advanceTimersByTime(150);
846
- buffer.push(makeEvent("step_chunk", 3));
846
+ buffer.push(makeEvent("text_delta", 3));
847
847
  update();
848
848
 
849
849
  // Should now have 3 rows
@@ -859,9 +859,9 @@ describe("createEventStreamView", () => {
859
859
  vi.useFakeTimers();
860
860
  const { createEventStreamView } = await loadModule();
861
861
  const events = [
862
- makeEvent("step_chunk", 1, '{"msg":"first"}'),
863
- makeEvent("step_chunk", 2, '{"msg":"second"}'),
864
- makeEvent("step_chunk", 3, '{"msg":"third"}'),
862
+ makeEvent("text_delta", 1, '{"msg":"first"}'),
863
+ makeEvent("text_delta", 2, '{"msg":"second"}'),
864
+ makeEvent("text_delta", 3, '{"msg":"third"}'),
865
865
  ];
866
866
  const buffer = createMockBuffer(events);
867
867
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -895,9 +895,9 @@ describe("createEventStreamView", () => {
895
895
  vi.useFakeTimers();
896
896
  const { createEventStreamView } = await loadModule();
897
897
  const events = [
898
- makeEvent("step_chunk", 1),
899
- makeEvent("flow_complete", 2),
900
- makeEvent("step_chunk", 3),
898
+ makeEvent("text_delta", 1),
899
+ makeEvent("execution_complete", 2),
900
+ makeEvent("text_delta", 3),
901
901
  ];
902
902
  const buffer = createMockBuffer(events);
903
903
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -911,13 +911,13 @@ describe("createEventStreamView", () => {
911
911
  // Change filter
912
912
  vi.advanceTimersByTime(150);
913
913
  const filterSelect = getFilterSelect(element);
914
- filterSelect.value = "step_chunk";
914
+ filterSelect.value = "text_delta";
915
915
  filterSelect.__fireEvent("change");
916
916
 
917
917
  // After filter change, rows are fully rebuilt (Path A)
918
918
  // The first row should be a different DOM reference
919
919
  expect(eventsList.children[0]).not.toBe(originalRow1);
920
- // Should only show filtered events (2 step_chunk events)
920
+ // Should only show filtered events (2 text_delta events)
921
921
  expect(eventsList.children.length).toBe(2);
922
922
  vi.useRealTimers();
923
923
  });
@@ -927,7 +927,7 @@ describe("createEventStreamView", () => {
927
927
  it("uses icon-only arrow-down defaults when paused and new events arrive", async () => {
928
928
  vi.useFakeTimers();
929
929
  const { createEventStreamView } = await loadModule();
930
- const events = [makeEvent("step_chunk", 1)];
930
+ const events = [makeEvent("text_delta", 1)];
931
931
  const buffer = createMockBuffer(events);
932
932
  const { element, update } = createEventStreamView({
933
933
  buffer: buffer as any
@@ -942,7 +942,7 @@ describe("createEventStreamView", () => {
942
942
  eventsList.__fireEvent("wheel", { deltaY: -24 });
943
943
 
944
944
  vi.advanceTimersByTime(150);
945
- buffer.push(makeEvent("step_chunk", 2));
945
+ buffer.push(makeEvent("text_delta", 2));
946
946
  update();
947
947
 
948
948
  const indicator = getScrollIndicator(element);
@@ -955,7 +955,7 @@ describe("createEventStreamView", () => {
955
955
  it("hides the event stream affordance when disabled", async () => {
956
956
  vi.useFakeTimers();
957
957
  const { createEventStreamView } = await loadModule();
958
- const events = [makeEvent("step_chunk", 1)];
958
+ const events = [makeEvent("text_delta", 1)];
959
959
  const buffer = createMockBuffer(events);
960
960
  const { element, update } = createEventStreamView({
961
961
  buffer: buffer as any,
@@ -978,7 +978,7 @@ describe("createEventStreamView", () => {
978
978
  eventsList.__fireEvent("wheel", { deltaY: -24 });
979
979
 
980
980
  vi.advanceTimersByTime(150);
981
- buffer.push(makeEvent("step_chunk", 2));
981
+ buffer.push(makeEvent("text_delta", 2));
982
982
  update();
983
983
 
984
984
  expect(getScrollIndicator(element).style.display).toBe("none");
@@ -988,7 +988,7 @@ describe("createEventStreamView", () => {
988
988
  it("renders the event stream affordance as icon-only when label is empty", async () => {
989
989
  vi.useFakeTimers();
990
990
  const { createEventStreamView } = await loadModule();
991
- const events = [makeEvent("step_chunk", 1)];
991
+ const events = [makeEvent("text_delta", 1)];
992
992
  const buffer = createMockBuffer(events);
993
993
  const { element, update } = createEventStreamView({
994
994
  buffer: buffer as any,
@@ -1013,7 +1013,7 @@ describe("createEventStreamView", () => {
1013
1013
  eventsList.__fireEvent("wheel", { deltaY: -24 });
1014
1014
 
1015
1015
  vi.advanceTimersByTime(150);
1016
- buffer.push(makeEvent("step_chunk", 2));
1016
+ buffer.push(makeEvent("text_delta", 2));
1017
1017
  update();
1018
1018
 
1019
1019
  const indicator = getScrollIndicator(element);
@@ -1026,7 +1026,7 @@ describe("createEventStreamView", () => {
1026
1026
  it("supports a configured label and icon override", async () => {
1027
1027
  vi.useFakeTimers();
1028
1028
  const { createEventStreamView } = await loadModule();
1029
- const events = [makeEvent("step_chunk", 1)];
1029
+ const events = [makeEvent("text_delta", 1)];
1030
1030
  const buffer = createMockBuffer(events);
1031
1031
  const { element, update } = createEventStreamView({
1032
1032
  buffer: buffer as any,
@@ -1051,7 +1051,7 @@ describe("createEventStreamView", () => {
1051
1051
  eventsList.__fireEvent("wheel", { deltaY: -24 });
1052
1052
 
1053
1053
  vi.advanceTimersByTime(150);
1054
- buffer.push(makeEvent("step_chunk", 2));
1054
+ buffer.push(makeEvent("text_delta", 2));
1055
1055
  update();
1056
1056
 
1057
1057
  const indicator = getScrollIndicator(element);
@@ -1065,7 +1065,7 @@ describe("createEventStreamView", () => {
1065
1065
  describe("individual event copy", () => {
1066
1066
  it("should format event as structured JSON with parsed payload", async () => {
1067
1067
  const { createEventStreamView } = await loadModule();
1068
- const events = [makeEvent("step_chunk", 1, '{"message":"hello"}')];
1068
+ const events = [makeEvent("text_delta", 1, '{"message":"hello"}')];
1069
1069
  const buffer = createMockBuffer(events);
1070
1070
  const { update } = createEventStreamView({ buffer: buffer as any });
1071
1071
 
@@ -1080,8 +1080,8 @@ describe("createEventStreamView", () => {
1080
1080
  vi.useFakeTimers();
1081
1081
  const { createEventStreamView } = await loadModule();
1082
1082
  const events = [
1083
- makeEvent("step_chunk", 1),
1084
- makeEvent("flow_complete", 2),
1083
+ makeEvent("text_delta", 1),
1084
+ makeEvent("execution_complete", 2),
1085
1085
  ];
1086
1086
  const buffer = createMockBuffer(events);
1087
1087
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -1106,7 +1106,7 @@ describe("createEventStreamView", () => {
1106
1106
  it("should recover after clear when new events arrive", async () => {
1107
1107
  vi.useFakeTimers();
1108
1108
  const { createEventStreamView } = await loadModule();
1109
- const events = [makeEvent("step_chunk", 1)];
1109
+ const events = [makeEvent("text_delta", 1)];
1110
1110
  const buffer = createMockBuffer(events);
1111
1111
  const { element, update } = createEventStreamView({ buffer: buffer as any });
1112
1112
 
@@ -1133,20 +1133,20 @@ describe("createEventStreamView", () => {
1133
1133
  describe("update throttle", () => {
1134
1134
  it("should render immediately on first update call", async () => {
1135
1135
  const { createEventStreamView } = await loadModule();
1136
- const events = [makeEvent("step_chunk", 1)];
1136
+ const events = [makeEvent("text_delta", 1)];
1137
1137
  const buffer = createMockBuffer(events);
1138
1138
  const { element, update } = createEventStreamView({ buffer: buffer as any });
1139
1139
 
1140
1140
  update();
1141
1141
 
1142
1142
  const filterSelect = getFilterSelect(element);
1143
- expect(filterSelect.options[1].textContent).toBe("step_chunk (1)");
1143
+ expect(filterSelect.options[1].textContent).toBe("text_delta (1)");
1144
1144
  });
1145
1145
 
1146
1146
  it("should throttle rapid update calls within 100ms", async () => {
1147
1147
  vi.useFakeTimers();
1148
1148
  const { createEventStreamView } = await loadModule();
1149
- const events = [makeEvent("step_chunk", 1)];
1149
+ const events = [makeEvent("text_delta", 1)];
1150
1150
  const buffer = createMockBuffer(events);
1151
1151
  const { element, update } = createEventStreamView({ buffer: buffer as any });
1152
1152
 
@@ -1154,30 +1154,30 @@ describe("createEventStreamView", () => {
1154
1154
  update();
1155
1155
 
1156
1156
  const filterSelect = getFilterSelect(element);
1157
- expect(filterSelect.options[1].textContent).toBe("step_chunk (1)");
1157
+ expect(filterSelect.options[1].textContent).toBe("text_delta (1)");
1158
1158
 
1159
1159
  // Add more events and call update rapidly (within throttle window)
1160
- buffer.push(makeEvent("step_chunk", 2));
1161
- buffer.push(makeEvent("step_chunk", 3));
1160
+ buffer.push(makeEvent("text_delta", 2));
1161
+ buffer.push(makeEvent("text_delta", 3));
1162
1162
  update();
1163
1163
  update();
1164
1164
  update();
1165
1165
 
1166
1166
  // Should NOT have rendered yet (within 100ms throttle, rAF pending)
1167
- expect(filterSelect.options[1].textContent).toBe("step_chunk (1)");
1167
+ expect(filterSelect.options[1].textContent).toBe("text_delta (1)");
1168
1168
 
1169
1169
  // Advance time to flush the rAF callback
1170
1170
  vi.advanceTimersByTime(20);
1171
1171
 
1172
1172
  // Now it should have rendered with all 3 events
1173
- expect(filterSelect.options[1].textContent).toBe("step_chunk (3)");
1173
+ expect(filterSelect.options[1].textContent).toBe("text_delta (3)");
1174
1174
  vi.useRealTimers();
1175
1175
  });
1176
1176
 
1177
1177
  it("should render immediately after 100ms has elapsed", async () => {
1178
1178
  vi.useFakeTimers();
1179
1179
  const { createEventStreamView } = await loadModule();
1180
- const events = [makeEvent("step_chunk", 1)];
1180
+ const events = [makeEvent("text_delta", 1)];
1181
1181
  const buffer = createMockBuffer(events);
1182
1182
  const { element, update } = createEventStreamView({ buffer: buffer as any });
1183
1183
 
@@ -1190,17 +1190,17 @@ describe("createEventStreamView", () => {
1190
1190
  vi.advanceTimersByTime(150);
1191
1191
 
1192
1192
  // Add event and update: should render immediately since 150ms > 100ms
1193
- buffer.push(makeEvent("flow_complete", 2));
1193
+ buffer.push(makeEvent("execution_complete", 2));
1194
1194
  update();
1195
1195
 
1196
- expect(filterSelect.options.length).toBe(3); // All events + flow_complete + step_chunk
1196
+ expect(filterSelect.options.length).toBe(3); // All events + execution_complete + text_delta
1197
1197
  vi.useRealTimers();
1198
1198
  });
1199
1199
 
1200
1200
  it("should coalesce multiple rapid updates into a single render via rAF", async () => {
1201
1201
  vi.useFakeTimers();
1202
1202
  const { createEventStreamView } = await loadModule();
1203
- const buffer = createMockBuffer([makeEvent("step_chunk", 1)]);
1203
+ const buffer = createMockBuffer([makeEvent("text_delta", 1)]);
1204
1204
  const { update } = createEventStreamView({ buffer: buffer as any });
1205
1205
 
1206
1206
  // First call: immediate render
@@ -1209,7 +1209,7 @@ describe("createEventStreamView", () => {
1209
1209
 
1210
1210
  // Rapid burst: 10 updates within throttle window
1211
1211
  for (let i = 2; i <= 11; i++) {
1212
- buffer.push(makeEvent("step_chunk", i));
1212
+ buffer.push(makeEvent("text_delta", i));
1213
1213
  update();
1214
1214
  }
1215
1215
 
@@ -1227,8 +1227,8 @@ describe("createEventStreamView", () => {
1227
1227
  it("should render immediately for user-initiated actions (filter change)", async () => {
1228
1228
  const { createEventStreamView } = await loadModule();
1229
1229
  const events = [
1230
- makeEvent("step_chunk", 1),
1231
- makeEvent("flow_complete", 2),
1230
+ makeEvent("text_delta", 1),
1231
+ makeEvent("execution_complete", 2),
1232
1232
  ];
1233
1233
  const buffer = createMockBuffer(events);
1234
1234
  const { element, update } = createEventStreamView({ buffer: buffer as any });
@@ -1240,7 +1240,7 @@ describe("createEventStreamView", () => {
1240
1240
  const copyAllBtn = getCopyAllBtn(element);
1241
1241
 
1242
1242
  // Immediately change filter: this should bypass throttle (uses updateNow internally)
1243
- filterSelect.value = "step_chunk";
1243
+ filterSelect.value = "text_delta";
1244
1244
  filterSelect.__fireEvent("change");
1245
1245
 
1246
1246
  // Should have updated immediately (Copy All title reflects filter)
@@ -1250,14 +1250,14 @@ describe("createEventStreamView", () => {
1250
1250
  it("should cancel pending rAF on destroy", async () => {
1251
1251
  vi.useFakeTimers();
1252
1252
  const { createEventStreamView } = await loadModule();
1253
- const buffer = createMockBuffer([makeEvent("step_chunk", 1)]);
1253
+ const buffer = createMockBuffer([makeEvent("text_delta", 1)]);
1254
1254
  const { update, destroy } = createEventStreamView({ buffer: buffer as any });
1255
1255
 
1256
1256
  // First update: immediate
1257
1257
  update();
1258
1258
 
1259
1259
  // Schedule a throttled update
1260
- buffer.push(makeEvent("step_chunk", 2));
1260
+ buffer.push(makeEvent("text_delta", 2));
1261
1261
  update();
1262
1262
 
1263
1263
  // Destroy before rAF fires: should not throw
@@ -1272,7 +1272,7 @@ describe("createEventStreamView", () => {
1272
1272
  describe("scroll behavior", () => {
1273
1273
  it("should have scroll listener on events list", async () => {
1274
1274
  const { createEventStreamView } = await loadModule();
1275
- const events = [makeEvent("step_chunk", 1)];
1275
+ const events = [makeEvent("text_delta", 1)];
1276
1276
  const buffer = createMockBuffer(events);
1277
1277
  const { element, update } = createEventStreamView({ buffer: buffer as any });
1278
1278
 
@@ -1321,7 +1321,7 @@ describe("createEventStreamView", () => {
1321
1321
  describe("plugin hooks", () => {
1322
1322
  it("should use custom renderEventStreamRow plugin when provided", async () => {
1323
1323
  const { createEventStreamView } = await loadModule();
1324
- const events = [makeEvent("step_chunk", 1)];
1324
+ const events = [makeEvent("text_delta", 1)];
1325
1325
  const buffer = createMockBuffer(events);
1326
1326
  const customRow = createMockElement("div");
1327
1327
  customRow.textContent = "Custom Row";
@@ -1393,7 +1393,7 @@ describe("createEventStreamView", () => {
1393
1393
 
1394
1394
  it("should fall back to default when plugin returns null", async () => {
1395
1395
  const { createEventStreamView } = await loadModule();
1396
- const events = [makeEvent("step_chunk", 1)];
1396
+ const events = [makeEvent("text_delta", 1)];
1397
1397
  const buffer = createMockBuffer(events);
1398
1398
 
1399
1399
  const plugin = {
@@ -48,6 +48,11 @@ export const DEFAULT_BADGE_COLORS: Record<string, EventStreamBadgeColor> = {
48
48
  // The unified error event type is `error`; `execution_error` carries the same
49
49
  // severity and would otherwise fall through to the default pair.
50
50
  DEFAULT_BADGE_COLORS.execution_error = DEFAULT_BADGE_COLORS.error;
51
+ // Reuse the public theme tokens, preserving existing custom themes and colors
52
+ // for persisted events while giving current wire families meaningful badges.
53
+ DEFAULT_BADGE_COLORS.execution_ = DEFAULT_BADGE_COLORS.flow_;
54
+ DEFAULT_BADGE_COLORS.turn_ = DEFAULT_BADGE_COLORS.agent_;
55
+ DEFAULT_BADGE_COLORS.text_ = DEFAULT_BADGE_COLORS.agent_;
51
56
  const DEFAULT_BADGE_COLOR: EventStreamBadgeColor = {
52
57
  bg: "var(--persona-event-badge-default-bg, #f3f4f6)",
53
58
  text: "var(--persona-event-badge-default-fg, #4b5563)",
@@ -37,3 +37,15 @@ describe("createToolBubble shadow", () => {
37
37
  expect(bubble.style.boxShadow).toBe("none");
38
38
  });
39
39
  });
40
+
41
+
42
+ describe("tool failure presentation", () => {
43
+ it("shows the failure in the header and escapes its detail", () => {
44
+ const message = makeMessage();
45
+ message.toolCall = { ...message.toolCall!, success: false, error: "<script>bad()</script> timed out" };
46
+ const bubble = createToolBubble(message);
47
+ expect(bubble.textContent).toContain("Failed");
48
+ expect(bubble.querySelector("[data-persona-tool-error]")?.textContent).toBe(message.toolCall.error);
49
+ expect(bubble.querySelector("script")).toBeNull();
50
+ });
51
+ });