@runtypelabs/persona 4.2.0 → 4.3.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 (43) hide show
  1. package/README.md +22 -6
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-B_xbfvR0.d.cts → types-C6tFDxKy.d.cts} +1 -1
  5. package/dist/animations/{types-B_xbfvR0.d.ts → types-C6tFDxKy.d.ts} +1 -1
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +6 -6
  9. package/dist/codegen.js +9 -9
  10. package/dist/index.cjs +44 -44
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +101 -3
  13. package/dist/index.d.ts +101 -3
  14. package/dist/index.global.js +33 -33
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +44 -44
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js.map +1 -1
  21. package/dist/smart-dom-reader.d.cts +76 -1
  22. package/dist/smart-dom-reader.d.ts +76 -1
  23. package/dist/theme-editor-preview.cjs +35 -35
  24. package/dist/theme-editor-preview.d.cts +76 -1
  25. package/dist/theme-editor-preview.d.ts +76 -1
  26. package/dist/theme-editor-preview.js +37 -37
  27. package/dist/theme-editor.cjs +1 -1
  28. package/dist/theme-editor.d.cts +76 -1
  29. package/dist/theme-editor.d.ts +76 -1
  30. package/dist/theme-editor.js +1 -1
  31. package/package.json +1 -1
  32. package/src/client.test.ts +349 -29
  33. package/src/client.ts +96 -24
  34. package/src/defaults.ts +2 -0
  35. package/src/index-core.ts +2 -0
  36. package/src/install.ts +9 -1
  37. package/src/session.ts +6 -3
  38. package/src/session.voice.test.ts +65 -0
  39. package/src/types.ts +57 -2
  40. package/src/utils/__fixtures__/unified-translator.oracle.ts +3 -3
  41. package/src/utils/code-generators.ts +10 -0
  42. package/src/utils/target.test.ts +51 -0
  43. package/src/utils/target.ts +90 -0
@@ -469,12 +469,12 @@ describe('AgentWidgetClient - JSON Streaming', () => {
469
469
  'data: {"type":"flow_complete","flowId":"flow_01k9pfnztzfag9tfz4t65c9c5q","success":true,"duration":2968,"completedAt":"2025-11-12T23:47:42.234Z","totalTokensUsed":0}'
470
470
  ];
471
471
 
472
- // Route the legacy step_chunk fixtures through the oracle as the 4.0 unified
473
- // wire (step_chunk → step_delta → text_delta), exercising the structured
474
- // JSON parser on the unified flow path: incremental text extraction, never
472
+ // Route the legacy step_chunk fixtures through the oracle as the 4.0 wire
473
+ // (step_chunk → step_delta → text_delta), exercising the structured
474
+ // JSON parser on the wire flow path: incremental text extraction, never
475
475
  // showing raw JSON, with the assembled response reconciled at step_complete.
476
476
  global.fetch = createRawStreamFetch(
477
- toUnifiedFrames(
477
+ legacyToWireFrames(
478
478
  sseEvents
479
479
  .filter((f) => f.startsWith('data:'))
480
480
  .map((f) => f.replace('"type":"step_chunk"', '"type":"step_delta"') + '\n\n')
@@ -581,13 +581,13 @@ function sseEvent(eventType: string, data: Record<string, unknown>): string {
581
581
 
582
582
  /**
583
583
  * Re-encode legacy `agent_*` / `flow_*` / `step_*` / `tool_*` SSE frames into the
584
- * neutral UNIFIED wire the 4.0 API now emits, using the same encoder the API uses
584
+ * Persona wire the 4.0 API now emits, using the same encoder the API uses
585
585
  * (the vendored `createUnifiedEventWrite` oracle). The 4.0 widget only consumes the
586
- * unified vocabulary, so these handler tests author the rendering intent in the
586
+ * wire vocabulary, so these handler tests author the rendering intent in the
587
587
  * (more readable) legacy frames and inject exactly what the client sees off the
588
588
  * wire — the bridge translates it straight back before the dispatch chain renders.
589
589
  */
590
- function toUnifiedFrames(legacyFrames: string[]): string[] {
590
+ function legacyToWireFrames(legacyFrames: string[]): string[] {
591
591
  const out: string[] = [];
592
592
  const write = createUnifiedEventWrite((chunk) => out.push(chunk));
593
593
  for (const frame of legacyFrames) write(frame);
@@ -595,7 +595,7 @@ function toUnifiedFrames(legacyFrames: string[]): string[] {
595
595
  }
596
596
 
597
597
  /** Stream pre-built SSE frames verbatim (no re-encode) — for fixtures already in
598
- * the unified vocabulary. */
598
+ * the wire vocabulary. */
599
599
  function createRawStreamFetch(frames: string[]) {
600
600
  return vi.fn().mockImplementation(async (_url?: string, _options?: any) => {
601
601
  const encoder = new TextEncoder();
@@ -610,10 +610,10 @@ function createRawStreamFetch(frames: string[]) {
610
610
  }
611
611
 
612
612
  /**
613
- * Mock fetch that streams the given legacy events as the 4.0 unified wire.
613
+ * Mock fetch that streams the given legacy events as the 4.0 wire.
614
614
  */
615
615
  function createAgentStreamFetch(events: string[]) {
616
- return createRawStreamFetch(toUnifiedFrames(events));
616
+ return createRawStreamFetch(legacyToWireFrames(events));
617
617
  }
618
618
 
619
619
  describe('AgentWidgetClient - Agent Mode Detection', () => {
@@ -638,7 +638,199 @@ describe('AgentWidgetClient - Agent Mode Detection', () => {
638
638
  });
639
639
  });
640
640
 
641
+ describe('AgentWidgetClient - target routing', () => {
642
+ const userMessage = (): AgentWidgetMessage[] => [
643
+ { id: 'u1', role: 'user', content: 'hi', createdAt: '2025-01-01T00:00:00.000Z' },
644
+ ];
645
+
646
+ it('routes a Runtype agent TypeID target through agent mode', async () => {
647
+ let captured: any = null;
648
+ global.fetch = vi.fn().mockImplementation(async (_url: string, options: any) => {
649
+ captured = JSON.parse(options.body);
650
+ const encoder = new TextEncoder();
651
+ return {
652
+ ok: true,
653
+ body: new ReadableStream({
654
+ start(controller) {
655
+ controller.enqueue(encoder.encode(sseEvent('agent_complete', {
656
+ executionId: 'exec_1', agentId: 'agent_123', success: true, iterations: 1,
657
+ completedAt: new Date().toISOString(), seq: 1,
658
+ })));
659
+ controller.close();
660
+ },
661
+ }),
662
+ };
663
+ });
664
+
665
+ const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000', target: 'agent_123' });
666
+ expect(client.isAgentMode()).toBe(true);
667
+ await client.dispatch({ messages: userMessage() }, () => {});
668
+
669
+ expect(captured.agent).toEqual({ agentId: 'agent_123' });
670
+ });
671
+
672
+ it('routes a Runtype flow TypeID target through flow dispatch', async () => {
673
+ let captured: any = null;
674
+ global.fetch = vi.fn().mockImplementation(async (_url: string, options: any) => {
675
+ captured = JSON.parse(options.body);
676
+ const encoder = new TextEncoder();
677
+ return {
678
+ ok: true,
679
+ body: new ReadableStream({
680
+ start(controller) {
681
+ controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
682
+ controller.close();
683
+ },
684
+ }),
685
+ };
686
+ });
687
+
688
+ const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000', target: 'flow_123' });
689
+ expect(client.isAgentMode()).toBe(false);
690
+ await client.dispatch({ messages: userMessage() }, () => {});
691
+
692
+ expect(captured.flowId).toBe('flow_123');
693
+ expect(captured.agent).toBeUndefined();
694
+ });
695
+
696
+ it('spreads a custom provider target payload into the proxy dispatch body', async () => {
697
+ let captured: any = null;
698
+ global.fetch = vi.fn().mockImplementation(async (_url: string, options: any) => {
699
+ captured = JSON.parse(options.body);
700
+ const encoder = new TextEncoder();
701
+ return {
702
+ ok: true,
703
+ body: new ReadableStream({
704
+ start(controller) {
705
+ controller.enqueue(encoder.encode('data: {"type":"flow_complete","success":true}\n\n'));
706
+ controller.close();
707
+ },
708
+ }),
709
+ };
710
+ });
711
+
712
+ const client = new AgentWidgetClient({
713
+ apiUrl: 'http://localhost:8000',
714
+ target: 'eve:support',
715
+ targetProviders: { eve: (id) => ({ payload: { assistant: id } }) },
716
+ });
717
+ expect(client.isAgentMode()).toBe(false);
718
+ await client.dispatch({ messages: userMessage() }, () => {});
719
+
720
+ expect(captured.assistant).toBe('support');
721
+ expect(Array.isArray(captured.messages)).toBe(true);
722
+ });
723
+
724
+ it('throws when target is combined with agentId', () => {
725
+ expect(
726
+ () => new AgentWidgetClient({ apiUrl: 'http://localhost:8000', target: 'agent_1', agentId: 'agent_2' }),
727
+ ).toThrow(/mutually exclusive/i);
728
+ });
729
+ });
730
+
641
731
  describe('AgentWidgetClient - Agent Payload Building', () => {
732
+ it('should build a saved agent-id payload from top-level agentId', async () => {
733
+ let capturedPayload: any = null;
734
+ global.fetch = vi.fn().mockImplementation(async (_url: string, options: any) => {
735
+ capturedPayload = JSON.parse(options.body);
736
+ const encoder = new TextEncoder();
737
+ const stream = new ReadableStream({
738
+ start(controller) {
739
+ controller.enqueue(encoder.encode(sseEvent('agent_complete', {
740
+ executionId: 'exec_1',
741
+ agentId: 'agent_123',
742
+ success: true,
743
+ iterations: 1,
744
+ completedAt: new Date().toISOString(),
745
+ seq: 1,
746
+ })));
747
+ controller.close();
748
+ }
749
+ });
750
+ return { ok: true, body: stream };
751
+ });
752
+
753
+ const client = new AgentWidgetClient({
754
+ apiUrl: 'http://localhost:8000',
755
+ agentId: 'agent_123',
756
+ });
757
+
758
+ await client.dispatch({
759
+ messages: [{
760
+ id: 'usr_1',
761
+ role: 'user',
762
+ content: 'Hello saved agent',
763
+ createdAt: '2025-01-01T00:00:00.000Z',
764
+ }],
765
+ }, () => {});
766
+
767
+ expect(capturedPayload).toBeDefined();
768
+ expect(capturedPayload.agent).toEqual({ agentId: 'agent_123' });
769
+ expect(capturedPayload.flowId).toBeUndefined();
770
+ expect(capturedPayload.messages).toHaveLength(1);
771
+ expect(capturedPayload.messages[0].content).toBe('Hello saved agent');
772
+ expect(capturedPayload.options.streamResponse).toBe(true);
773
+ expect(capturedPayload.options.recordMode).toBe('virtual');
774
+ });
775
+
776
+ it('uses top-level agentId as the client-token session target', async () => {
777
+ const requests: Array<{ url: string; body: Record<string, unknown> }> = [];
778
+ global.fetch = vi.fn().mockImplementation(async (url: string, options: any) => {
779
+ requests.push({ url, body: JSON.parse(options.body) });
780
+ if (url.endsWith('/v1/client/init')) {
781
+ return {
782
+ ok: true,
783
+ json: async () => ({
784
+ sessionId: 'sess_agent',
785
+ expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
786
+ config: {},
787
+ }),
788
+ };
789
+ }
790
+ const encoder = new TextEncoder();
791
+ return {
792
+ ok: true,
793
+ body: new ReadableStream({
794
+ start(controller) {
795
+ controller.enqueue(encoder.encode(sseEvent('agent_complete', {
796
+ executionId: 'exec_1',
797
+ agentId: 'agent_123',
798
+ success: true,
799
+ iterations: 1,
800
+ completedAt: new Date().toISOString(),
801
+ seq: 1,
802
+ })));
803
+ controller.close();
804
+ },
805
+ }),
806
+ };
807
+ });
808
+
809
+ const client = new AgentWidgetClient({
810
+ apiUrl: 'https://api.runtype.com',
811
+ clientToken: 'ct_live_demo',
812
+ agentId: 'agent_123',
813
+ });
814
+
815
+ await client.dispatch({
816
+ messages: [{
817
+ id: 'usr_1',
818
+ role: 'user',
819
+ content: 'Hello agent token',
820
+ createdAt: '2025-01-01T00:00:00.000Z',
821
+ }],
822
+ }, () => {});
823
+
824
+ expect(requests[0]).toMatchObject({
825
+ url: 'https://api.runtype.com/v1/client/init',
826
+ body: { token: 'ct_live_demo', flowId: 'agent_123' },
827
+ });
828
+ expect(requests[1]).toMatchObject({
829
+ url: 'https://api.runtype.com/v1/client/chat',
830
+ body: { sessionId: 'sess_agent' },
831
+ });
832
+ });
833
+
642
834
  it('should build agent payload with agent config', async () => {
643
835
  let capturedPayload: any = null;
644
836
  global.fetch = vi.fn().mockImplementation(async (_url: string, options: any) => {
@@ -1239,7 +1431,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1239
1431
  }
1240
1432
  }
1241
1433
 
1242
- // Reflection now folds into a loop-scoped reasoning bubble (unified spec):
1434
+ // Reflection now folds into a loop-scoped reasoning bubble (wire spec):
1243
1435
  // `agent_reflection` → reasoning_start{scope:"loop"} + reasoning_complete{text}.
1244
1436
  const reflectionMessages = Array.from(messagesById.values())
1245
1437
  .filter(m => m.variant === 'reasoning' && m.reasoning?.scope === 'loop');
@@ -1296,7 +1488,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1296
1488
  });
1297
1489
 
1298
1490
  // ============================================================================
1299
- // Unified Event Name Support (chunk → delta, agent_tool_* → tool_* with agentContext)
1491
+ // Wire event name support (chunk → delta, agent_tool_* → tool_* with agentContext)
1300
1492
  // ============================================================================
1301
1493
 
1302
1494
  // ============================================================================
@@ -1590,7 +1782,7 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1590
1782
  expect(assistantTexts.length).toBe(2);
1591
1783
  // First message uses the provided ID
1592
1784
  expect(assistantTexts[0].id).toBe('ast_pre_generated_id');
1593
- // Second message composes baseId + the unified text block id for traceability
1785
+ // Second message composes baseId + the wire text block id for traceability
1594
1786
  expect(assistantTexts[1].id).toBe('ast_pre_generated_id_text_2');
1595
1787
  // Content is correct per segment
1596
1788
  expect(assistantTexts[0].content).toBe('Before tool.');
@@ -1600,7 +1792,7 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1600
1792
  it('should not overwrite last segment content with the full step response (flow)', async () => {
1601
1793
  const events: AgentWidgetEvent[] = [];
1602
1794
 
1603
- // Unified wire: two flow text segments split by a tool, each its own block
1795
+ // Wire: two flow text segments split by a tool, each its own block
1604
1796
  // (sealed by text_end → text_complete). The step's full structured response
1605
1797
  // (`step_complete.result.response`) reconciles rawContent without clobbering
1606
1798
  // either sealed bubble's displayed content.
@@ -1715,10 +1907,10 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1715
1907
  };
1716
1908
  };
1717
1909
 
1718
- // Unified wire (via the oracle): a single flow text block carrying partial
1910
+ // Wire (via the oracle): a single flow text block carrying partial
1719
1911
  // structured JSON, sealed by text_end, then the authoritative final structured
1720
1912
  // response on step_complete. Exercises the async structured-content parser +
1721
- // sealed-segment reconciliation on the unified flow path.
1913
+ // sealed-segment reconciliation on the wire flow path.
1722
1914
  global.fetch = createAgentStreamFetch([
1723
1915
  sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 }),
1724
1916
  sseEvent('tool_start', { toolId: 'tc_1', name: 'test_tool', toolType: 'custom', startedAt: new Date().toISOString() }),
@@ -1784,9 +1976,137 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
1784
1976
  });
1785
1977
  });
1786
1978
 
1979
+ describe('AgentWidgetClient - flow continuation stream without execution_start', () => {
1980
+ // A tool-driven `/resume` continues a flow on a brand-new stream that does NOT
1981
+ // re-emit `execution_start` (`flow_start`). Each stream resolves its execution
1982
+ // kind independently and defaults to `"agent"`, so the continuation used to
1983
+ // mis-route the final prompt-step finalization: the streamed text block was
1984
+ // sealed via the agent path (which never records it as the sealed flow bubble),
1985
+ // then `step_complete.result.response` re-rendered the same text as a SECOND
1986
+ // bubble. The client now recovers the flow kind from the leading `step_*`
1987
+ // frame (a `stepType` is flow-only), so the finalization reconciles in place.
1988
+ function collectAssistantTexts(events: AgentWidgetEvent[]) {
1989
+ const messagesById = new Map<string, AgentWidgetMessage>();
1990
+ for (const event of events) {
1991
+ if (event.type === 'message') messagesById.set(event.message.id, event.message);
1992
+ }
1993
+ return Array.from(messagesById.values())
1994
+ .filter(m => m.role === 'assistant' && !m.variant)
1995
+ .sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
1996
+ }
1997
+
1998
+ it('does not duplicate the final message when a flow resumes without execution_start', async () => {
1999
+ const events: AgentWidgetEvent[] = [];
2000
+
2001
+ // No `flow_start`/`execution_start`: this is exactly the wire a tool-driven
2002
+ // `/resume` continuation delivers.
2003
+ global.fetch = createAgentStreamFetch([
2004
+ sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
2005
+ sseEvent('text_start', { messageId: 'msg_s1' }),
2006
+ sseEvent('step_delta', { id: 's1', text: 'Done! Added to your cart.' }),
2007
+ sseEvent('text_end', { messageId: 'msg_s1' }),
2008
+ // The authoritative final response mirrors the streamed text.
2009
+ sseEvent('step_complete', { id: 's1', name: 'Prompt', stepType: 'prompt', success: true, result: { response: 'Done! Added to your cart.' }, executionTime: 500 }),
2010
+ sseEvent('flow_complete', { success: true }),
2011
+ ]);
2012
+
2013
+ const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
2014
+ await client.dispatch(
2015
+ { messages: [{ id: 'usr_1', role: 'user', content: 'add it to my cart', createdAt: new Date().toISOString() }] },
2016
+ (event) => events.push(event)
2017
+ );
2018
+
2019
+ const assistantTexts = collectAssistantTexts(events);
2020
+
2021
+ // Exactly ONE assistant bubble — not duplicated by the step_complete finalization.
2022
+ expect(assistantTexts.length).toBe(1);
2023
+ expect(assistantTexts[0].content).toBe('Done! Added to your cart.');
2024
+ expect(assistantTexts[0].streaming).toBe(false);
2025
+ });
2026
+
2027
+ it('still reconciles in place when execution_start IS present (initial flow stream)', async () => {
2028
+ const events: AgentWidgetEvent[] = [];
2029
+
2030
+ // Same shape but WITH flow_start — the pre-existing, already-correct path.
2031
+ global.fetch = createAgentStreamFetch([
2032
+ sseEvent('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1, executionId: 'exec_f1' }),
2033
+ sseEvent('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 0, totalSteps: 1 }),
2034
+ sseEvent('text_start', { messageId: 'msg_s1' }),
2035
+ sseEvent('step_delta', { id: 's1', text: 'Done! Added to your cart.' }),
2036
+ sseEvent('text_end', { messageId: 'msg_s1' }),
2037
+ sseEvent('step_complete', { id: 's1', name: 'Prompt', stepType: 'prompt', success: true, result: { response: 'Done! Added to your cart.' }, executionTime: 500 }),
2038
+ sseEvent('flow_complete', { success: true }),
2039
+ ]);
2040
+
2041
+ const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
2042
+ await client.dispatch(
2043
+ { messages: [{ id: 'usr_1', role: 'user', content: 'add it to my cart', createdAt: new Date().toISOString() }] },
2044
+ (event) => events.push(event)
2045
+ );
2046
+
2047
+ const assistantTexts = collectAssistantTexts(events);
2048
+ expect(assistantTexts.length).toBe(1);
2049
+ expect(assistantTexts[0].content).toBe('Done! Added to your cart.');
2050
+ expect(assistantTexts[0].streaming).toBe(false);
2051
+ });
2052
+
2053
+ it('does not misclassify an agent stream as a flow (no stepType present)', async () => {
2054
+ const events: AgentWidgetEvent[] = [];
2055
+ const execId = 'exec_a1';
2056
+
2057
+ // Agent loops use `agent_*`/turn_* frames and never carry a `stepType`, so the
2058
+ // flow recovery must not engage. A single streamed turn yields exactly one
2059
+ // bubble (regression guard so the recovery can't over-fire on agents).
2060
+ global.fetch = createAgentStreamFetch([
2061
+ sseEvent('agent_start', {
2062
+ executionId: execId, agentId: 'virtual', agentName: 'Test',
2063
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
2064
+ }),
2065
+ sseEvent('agent_iteration_start', {
2066
+ executionId: execId, iteration: 1, maxTurns: 1,
2067
+ startedAt: new Date().toISOString(), seq: 2,
2068
+ }),
2069
+ sseEvent('agent_turn_start', {
2070
+ executionId: execId, iteration: 1, turnIndex: 0,
2071
+ role: 'assistant', turnId: 'turn_1', seq: 3,
2072
+ }),
2073
+ sseEvent('agent_turn_delta', {
2074
+ executionId: execId, iteration: 1, delta: 'Hi there!',
2075
+ contentType: 'text', turnId: 'turn_1', seq: 4,
2076
+ }),
2077
+ sseEvent('agent_turn_complete', {
2078
+ executionId: execId, iteration: 1, role: 'assistant',
2079
+ turnId: 'turn_1', completedAt: new Date().toISOString(), seq: 5,
2080
+ }),
2081
+ sseEvent('agent_iteration_complete', {
2082
+ executionId: execId, iteration: 1, toolCallsMade: 0,
2083
+ stopConditionMet: false, completedAt: new Date().toISOString(), seq: 6,
2084
+ }),
2085
+ sseEvent('agent_complete', {
2086
+ executionId: execId, agentId: 'virtual', success: true,
2087
+ iterations: 1, stopReason: 'max_iterations',
2088
+ completedAt: new Date().toISOString(), seq: 7,
2089
+ }),
2090
+ ]);
2091
+
2092
+ const client = new AgentWidgetClient({
2093
+ apiUrl: 'http://localhost:8000',
2094
+ agent: { name: 'Test', model: 'openai:gpt-4o-mini', systemPrompt: 'test' },
2095
+ });
2096
+ await client.dispatch(
2097
+ { messages: [{ id: 'usr_1', role: 'user', content: 'hi', createdAt: new Date().toISOString() }] },
2098
+ (event) => events.push(event)
2099
+ );
2100
+
2101
+ const assistantTexts = collectAssistantTexts(events);
2102
+ expect(assistantTexts.length).toBe(1);
2103
+ expect(assistantTexts[0].content).toBe('Hi there!');
2104
+ });
2105
+ });
2106
+
1787
2107
  describe('AgentWidgetClient - nested flow-as-tool (parentToolCallId)', () => {
1788
2108
  // PR #4602: a flow running as a tool enriches its streamed text/reasoning with
1789
- // toolContext.toolId; the unified wire surfaces it as text_start/reasoning_start
2109
+ // toolContext.toolId; the wire surfaces it as text_start/reasoning_start
1790
2110
  // .parentToolCallId, and the widget routes that block into the parent tool's row.
1791
2111
  it('routes nested flow text into the parent tool row, not the top-level assistant', async () => {
1792
2112
  const events: AgentWidgetEvent[] = [];
@@ -2334,9 +2654,9 @@ describe('AgentWidgetClient - agent_turn text/tool interleaving', () => {
2334
2654
  // ============================================================================
2335
2655
 
2336
2656
  describe('AgentWidgetClient: step_await parsing', () => {
2337
- // Unified collapses the legacy flow `step_await` (a `local_tool_required` pause)
2657
+ // Wire collapses the legacy flow `step_await` (a `local_tool_required` pause)
2338
2658
  // into the neutral `await` event the native handler consumes.
2339
- const buildUnifiedAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2659
+ const buildAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2340
2660
  const encoder = new TextEncoder();
2341
2661
  const body = `event: await\ndata: ${JSON.stringify({ type: 'await', ...payload })}\n\n`;
2342
2662
  return new ReadableStream({
@@ -2362,7 +2682,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2362
2682
  it('emits a complete tool message with awaitingLocalTool=true for local_tool_required', async () => {
2363
2683
  global.fetch = vi.fn().mockResolvedValue({
2364
2684
  ok: true,
2365
- body: buildUnifiedAwaitStream({
2685
+ body: buildAwaitStream({
2366
2686
  awaitReason: 'local_tool_required',
2367
2687
  id: 'step-1',
2368
2688
  name: 'Test Step',
@@ -2402,7 +2722,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2402
2722
  it('emits a running tool message for WebMCP local_tool_required until the browser tool resolves', async () => {
2403
2723
  global.fetch = vi.fn().mockResolvedValue({
2404
2724
  ok: true,
2405
- body: buildUnifiedAwaitStream({
2725
+ body: buildAwaitStream({
2406
2726
  awaitReason: 'local_tool_required',
2407
2727
  id: 'step-1',
2408
2728
  name: 'Test Step',
@@ -2473,7 +2793,7 @@ describe('AgentWidgetClient: step_await parsing', () => {
2473
2793
  // ============================================================================
2474
2794
 
2475
2795
  describe('AgentWidgetClient: agent_await parsing', () => {
2476
- // Unified collapses the legacy `step_await`/`agent_await` pair into one `await`
2796
+ // Wire collapses the legacy `step_await`/`agent_await` pair into one `await`
2477
2797
  // event; the dispatch origin survives as the `origin` field on the payload.
2478
2798
  const buildAgentAwaitStream = (payload: Record<string, unknown>): ReadableStream<Uint8Array> => {
2479
2799
  const encoder = new TextEncoder();
@@ -2899,7 +3219,7 @@ describe('AgentWidgetClient - agent_media events', () => {
2899
3219
  (e) => events.push(e)
2900
3220
  );
2901
3221
 
2902
- // Unified streams each media item as its own block (media_start/complete),
3222
+ // Wire streams each media item as its own block (media_start/complete),
2903
3223
  // so each renders as its own synthetic message with a single content part.
2904
3224
  const mediaMessages = collectMediaMessages(events);
2905
3225
  expect(mediaMessages).toHaveLength(3);
@@ -3660,8 +3980,8 @@ describe('AgentWidgetClient - version header', () => {
3660
3980
  });
3661
3981
  });
3662
3982
 
3663
- describe('AgentWidgetClient - Unified event vocabulary (default in 4.0)', () => {
3664
- const unifiedTextStream = (execId: string) => [
3983
+ describe('AgentWidgetClient - Wire event vocabulary (default in 4.0)', () => {
3984
+ const sampleTextStream = (execId: string) => [
3665
3985
  sseEvent('execution_start', { kind: 'agent', executionId: execId, agentId: 'virtual', agentName: 'Test', maxTurns: 1, startedAt: new Date().toISOString(), seq: 1 }),
3666
3986
  sseEvent('turn_start', { executionId: execId, id: 'turn_1', iteration: 1, role: 'assistant', seq: 2 }),
3667
3987
  sseEvent('text_start', { executionId: execId, id: 'text_1', role: 'assistant', seq: 3 }),
@@ -3672,14 +3992,14 @@ describe('AgentWidgetClient - Unified event vocabulary (default in 4.0)', () =>
3672
3992
  sseEvent('execution_complete', { kind: 'agent', executionId: execId, success: true, completedAt: new Date().toISOString(), seq: 8 }),
3673
3993
  ];
3674
3994
 
3675
- it('does not append an events param to the dispatch URL (unified is the default wire)', async () => {
3995
+ it('does not append an events param to the dispatch URL (the wire is the only format)', async () => {
3676
3996
  let capturedUrl = '';
3677
3997
  global.fetch = vi.fn().mockImplementation(async (url: string) => {
3678
3998
  capturedUrl = url;
3679
3999
  const encoder = new TextEncoder();
3680
4000
  const stream = new ReadableStream({
3681
4001
  start(c) {
3682
- for (const e of unifiedTextStream('exec_u')) c.enqueue(encoder.encode(e));
4002
+ for (const e of sampleTextStream('exec_u')) c.enqueue(encoder.encode(e));
3683
4003
  c.close();
3684
4004
  },
3685
4005
  });
@@ -3697,10 +4017,10 @@ describe('AgentWidgetClient - Unified event vocabulary (default in 4.0)', () =>
3697
4017
  expect(capturedUrl).not.toContain('events=');
3698
4018
  });
3699
4019
 
3700
- it('renders a unified stream through the internal handlers with no config flag', async () => {
4020
+ it('renders a wire stream through the internal handlers with no config flag', async () => {
3701
4021
  const events: AgentWidgetEvent[] = [];
3702
4022
  const execId = 'exec_u1';
3703
- global.fetch = createRawStreamFetch(unifiedTextStream(execId));
4023
+ global.fetch = createRawStreamFetch(sampleTextStream(execId));
3704
4024
 
3705
4025
  const client = new AgentWidgetClient({
3706
4026
  apiUrl: 'http://localhost:8000',