@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.
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-B_JdVK9o.d.cts → types-9Vxm-PPz.d.cts} +136 -6
- package/dist/animations/{types-B_JdVK9o.d.ts → types-9Vxm-PPz.d.ts} +136 -6
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +136 -6
- package/dist/animations-extra.d.ts +136 -6
- package/dist/approval-ui.d.cts +148 -14
- package/dist/approval-ui.d.ts +148 -14
- package/dist/artifacts-ui.d.cts +148 -14
- package/dist/artifacts-ui.d.ts +148 -14
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +148 -14
- package/dist/context-mentions.d.ts +148 -14
- package/dist/event-stream-view-GL3ADRY2.js +1 -0
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +148 -14
- package/dist/event-stream-view.d.ts +148 -14
- package/dist/event-stream-view.js +1 -1
- package/dist/forms-ui.d.cts +161 -23
- package/dist/forms-ui.d.ts +161 -23
- package/dist/history-view.d.cts +136 -6
- package/dist/history-view.d.ts +136 -6
- package/dist/index.cjs +67 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +172 -27
- package/dist/index.d.ts +172 -27
- package/dist/index.global.js +51 -51
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +67 -67
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +148 -14
- package/dist/session-reconnect.d.ts +148 -14
- package/dist/smart-dom-reader.d.cts +148 -14
- package/dist/smart-dom-reader.d.ts +148 -14
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +3 -4
- package/dist/testing.d.ts +3 -4
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +71 -71
- package/dist/theme-editor-preview.d.cts +154 -14
- package/dist/theme-editor-preview.d.ts +154 -14
- package/dist/theme-editor-preview.js +48 -48
- package/dist/theme-editor.d.cts +148 -14
- package/dist/theme-editor.d.ts +148 -14
- package/dist/voice-runtime.d.cts +6 -2
- package/dist/voice-runtime.d.ts +6 -2
- package/package.json +1 -1
- package/src/ask-user-question-tool.ts +1 -1
- package/src/client-history-rest.test.ts +2 -1
- package/src/client-visitor-history.test.ts +106 -4
- package/src/client.composer-options.test.ts +1 -1
- package/src/client.interrupt.test.ts +2 -2
- package/src/client.test.ts +477 -915
- package/src/client.ts +134 -494
- package/src/client.unified-tools.test.ts +76 -0
- package/src/components/event-stream-badge-theming.test.ts +3 -0
- package/src/components/event-stream-view.test.ts +76 -76
- package/src/components/event-stream-view.ts +5 -0
- package/src/components/tool-bubble.test.ts +12 -0
- package/src/components/tool-bubble.ts +13 -2
- package/src/event-stream-view-bundle.test.ts +2 -2
- package/src/generated/runtype-openapi-contract.ts +129 -1
- package/src/internal/runtype-history-provider.test.ts +2 -2
- package/src/launcher-global.ts +1 -1
- package/src/session-history.test.ts +1 -1
- package/src/session.quote.test.ts +1 -1
- package/src/session.resubmit-from.test.ts +1 -1
- package/src/session.test.ts +5 -5
- package/src/session.ts +95 -36
- package/src/suggest-replies-tool.ts +1 -1
- package/src/testing/mock-stream.test.ts +37 -16
- package/src/testing/mock-stream.ts +14 -9
- package/src/types.ts +19 -14
- package/src/ui.ask-user-question-plugin.test.ts +7 -7
- package/src/ui.composer-defer-one.test.ts +6 -34
- package/src/ui.composer-draft-persistence.test.ts +1 -1
- package/src/ui.composer-quote.test.ts +1 -1
- package/src/ui.message-history-actions.test.ts +1 -1
- package/src/ui.streaming-coalescing.test.ts +10 -13
- package/src/ui.ts +80 -32
- package/src/ui.typing-indicator.test.ts +216 -0
- package/src/ui.voice-custom-provider.test.ts +211 -0
- package/src/utils/event-stream-buffer.test.ts +13 -13
- package/src/utils/event-stream-capture.test.ts +20 -20
- package/src/utils/throughput-tracker.test.ts +100 -142
- package/src/utils/throughput-tracker.ts +18 -132
- package/src/utils/voice-support.ts +18 -1
- package/src/voice/voice-turn-tracker.ts +18 -0
- package/src/webmcp-bridge.ts +1 -1
- package/dist/event-stream-view-FKWDY3UB.js +0 -1
- 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("
|
|
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("
|
|
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("
|
|
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("
|
|
344
|
-
makeEvent("
|
|
345
|
-
makeEvent("
|
|
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("
|
|
358
|
-
expect(filterSelect.options[2].textContent).toBe("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
388
|
-
makeEvent("
|
|
389
|
-
makeEvent("
|
|
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 = "
|
|
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("
|
|
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("
|
|
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("
|
|
498
|
-
makeEvent("
|
|
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 = "
|
|
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("
|
|
524
|
-
makeEvent("
|
|
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 = "
|
|
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("
|
|
557
|
-
makeEvent("
|
|
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("
|
|
582
|
-
makeEvent("
|
|
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: "
|
|
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("
|
|
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: "
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
863
|
-
makeEvent("
|
|
864
|
-
makeEvent("
|
|
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("
|
|
899
|
-
makeEvent("
|
|
900
|
-
makeEvent("
|
|
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 = "
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
1084
|
-
makeEvent("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
1161
|
-
buffer.push(makeEvent("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
1193
|
+
buffer.push(makeEvent("execution_complete", 2));
|
|
1194
1194
|
update();
|
|
1195
1195
|
|
|
1196
|
-
expect(filterSelect.options.length).toBe(3); // All events +
|
|
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("
|
|
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("
|
|
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("
|
|
1231
|
-
makeEvent("
|
|
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 = "
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
+
});
|