@runtypelabs/persona 1.48.0 → 2.0.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 (69) hide show
  1. package/README.md +140 -8
  2. package/dist/index.cjs +90 -39
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1055 -24
  5. package/dist/index.d.ts +1055 -24
  6. package/dist/index.global.js +111 -60
  7. package/dist/index.global.js.map +1 -1
  8. package/dist/index.js +90 -39
  9. package/dist/index.js.map +1 -1
  10. package/dist/install.global.js +1 -1
  11. package/dist/install.global.js.map +1 -1
  12. package/dist/widget.css +836 -513
  13. package/package.json +1 -1
  14. package/src/artifacts-session.test.ts +80 -0
  15. package/src/client.test.ts +20 -21
  16. package/src/client.ts +153 -4
  17. package/src/components/approval-bubble.ts +45 -42
  18. package/src/components/artifact-card.ts +91 -0
  19. package/src/components/artifact-pane.ts +501 -0
  20. package/src/components/composer-builder.ts +32 -27
  21. package/src/components/event-stream-view.ts +40 -40
  22. package/src/components/feedback.ts +36 -36
  23. package/src/components/forms.ts +11 -11
  24. package/src/components/header-builder.test.ts +32 -0
  25. package/src/components/header-builder.ts +55 -36
  26. package/src/components/header-layouts.ts +58 -125
  27. package/src/components/launcher.ts +36 -21
  28. package/src/components/message-bubble.ts +92 -65
  29. package/src/components/messages.ts +2 -2
  30. package/src/components/panel.ts +42 -11
  31. package/src/components/reasoning-bubble.ts +23 -23
  32. package/src/components/registry.ts +4 -0
  33. package/src/components/suggestions.ts +1 -1
  34. package/src/components/tool-bubble.ts +32 -32
  35. package/src/defaults.ts +30 -4
  36. package/src/index.ts +80 -2
  37. package/src/install.ts +22 -0
  38. package/src/plugins/types.ts +23 -0
  39. package/src/postprocessors.ts +2 -2
  40. package/src/runtime/host-layout.ts +174 -0
  41. package/src/runtime/init.test.ts +236 -0
  42. package/src/runtime/init.ts +114 -55
  43. package/src/session.ts +135 -2
  44. package/src/styles/tailwind.css +1 -1
  45. package/src/styles/widget.css +836 -513
  46. package/src/types/theme.ts +354 -0
  47. package/src/types.ts +314 -15
  48. package/src/ui.docked.test.ts +104 -0
  49. package/src/ui.ts +940 -227
  50. package/src/utils/artifact-gate.test.ts +255 -0
  51. package/src/utils/artifact-gate.ts +142 -0
  52. package/src/utils/artifact-resize.test.ts +64 -0
  53. package/src/utils/artifact-resize.ts +67 -0
  54. package/src/utils/attachment-manager.ts +10 -10
  55. package/src/utils/code-generators.test.ts +52 -0
  56. package/src/utils/code-generators.ts +40 -36
  57. package/src/utils/dock.ts +17 -0
  58. package/src/utils/dom-context.test.ts +504 -0
  59. package/src/utils/dom-context.ts +896 -0
  60. package/src/utils/dom.ts +12 -1
  61. package/src/utils/message-fingerprint.test.ts +187 -0
  62. package/src/utils/message-fingerprint.ts +105 -0
  63. package/src/utils/migration.ts +179 -0
  64. package/src/utils/morph.ts +1 -1
  65. package/src/utils/plugins.ts +175 -0
  66. package/src/utils/positioning.ts +4 -4
  67. package/src/utils/theme.test.ts +125 -0
  68. package/src/utils/theme.ts +216 -60
  69. package/src/utils/tokens.ts +682 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "1.48.0",
3
+ "version": "2.0.0",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { AgentWidgetSession } from "./session";
3
+
4
+ describe("AgentWidgetSession artifacts", () => {
5
+ it("merges artifact_start, delta, complete into markdown state", () => {
6
+ const onArtifactsState = vi.fn();
7
+ const session = new AgentWidgetSession(
8
+ {},
9
+ {
10
+ onMessagesChanged: () => {},
11
+ onStatusChanged: () => {},
12
+ onStreamingChanged: () => {},
13
+ onArtifactsState
14
+ }
15
+ );
16
+
17
+ session.injectTestEvent({
18
+ type: "artifact_start",
19
+ id: "a1",
20
+ artifactType: "markdown",
21
+ title: "Doc"
22
+ });
23
+ expect(onArtifactsState.mock.calls.length).toBe(1);
24
+ expect(onArtifactsState.mock.calls[0][0].artifacts[0].markdown).toBe("");
25
+
26
+ session.injectTestEvent({
27
+ type: "artifact_delta",
28
+ id: "a1",
29
+ artDelta: "# Hello"
30
+ });
31
+ expect(onArtifactsState.mock.calls[1][0].artifacts[0].markdown).toBe("# Hello");
32
+
33
+ session.injectTestEvent({ type: "artifact_complete", id: "a1" });
34
+ expect(onArtifactsState.mock.calls[2][0].artifacts[0].status).toBe("complete");
35
+ });
36
+
37
+ it("clearMessages clears artifacts", () => {
38
+ const onArtifactsState = vi.fn();
39
+ const session = new AgentWidgetSession(
40
+ {},
41
+ {
42
+ onMessagesChanged: () => {},
43
+ onStatusChanged: () => {},
44
+ onStreamingChanged: () => {},
45
+ onArtifactsState
46
+ }
47
+ );
48
+ session.injectTestEvent({
49
+ type: "artifact_start",
50
+ id: "x",
51
+ artifactType: "markdown"
52
+ });
53
+ session.injectTestEvent({
54
+ type: "artifact_delta",
55
+ id: "x",
56
+ artDelta: "Hi"
57
+ });
58
+ expect(session.getArtifacts().length).toBe(1);
59
+ session.clearMessages();
60
+ expect(session.getArtifacts().length).toBe(0);
61
+ const last = onArtifactsState.mock.calls.pop()?.[0];
62
+ expect(last?.artifacts.length).toBe(0);
63
+ });
64
+
65
+ it("upsertArtifact adds record", () => {
66
+ const onArtifactsState = vi.fn();
67
+ const session = new AgentWidgetSession(
68
+ {},
69
+ {
70
+ onMessagesChanged: () => {},
71
+ onStatusChanged: () => {},
72
+ onStreamingChanged: () => {},
73
+ onArtifactsState
74
+ }
75
+ );
76
+ session.upsertArtifact({ artifactType: "markdown", content: "C" });
77
+ expect(session.getArtifacts()).toHaveLength(1);
78
+ expect(session.getArtifacts()[0].markdown).toBe("C");
79
+ });
80
+ });
@@ -653,8 +653,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
653
653
  systemPrompt: 'You are a test assistant.',
654
654
  temperature: 0.7,
655
655
  loopConfig: {
656
- maxIterations: 3,
657
- stopCondition: 'auto',
656
+ maxTurns: 3,
658
657
  },
659
658
  },
660
659
  agentOptions: {
@@ -679,7 +678,7 @@ describe('AgentWidgetClient - Agent Payload Building', () => {
679
678
  expect(capturedPayload.agent.name).toBe('Test Agent');
680
679
  expect(capturedPayload.agent.model).toBe('openai:gpt-4o-mini');
681
680
  expect(capturedPayload.agent.systemPrompt).toBe('You are a test assistant.');
682
- expect(capturedPayload.agent.loopConfig.maxIterations).toBe(3);
681
+ expect(capturedPayload.agent.loopConfig.maxTurns).toBe(3);
683
682
  expect(capturedPayload.messages).toHaveLength(1);
684
683
  expect(capturedPayload.messages[0].content).toBe('Hello agent');
685
684
  expect(capturedPayload.options.streamResponse).toBe(true);
@@ -735,10 +734,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
735
734
  global.fetch = createAgentStreamFetch([
736
735
  sseEvent('agent_start', {
737
736
  executionId: execId, agentId: 'virtual', agentName: 'Test',
738
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
737
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
739
738
  }),
740
739
  sseEvent('agent_iteration_start', {
741
- executionId: execId, iteration: 1, maxIterations: 1,
740
+ executionId: execId, iteration: 1, maxTurns: 1,
742
741
  startedAt: new Date().toISOString(), seq: 2,
743
742
  }),
744
743
  sseEvent('agent_turn_start', {
@@ -801,11 +800,11 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
801
800
  global.fetch = createAgentStreamFetch([
802
801
  sseEvent('agent_start', {
803
802
  executionId: execId, agentId: 'virtual', agentName: 'Test',
804
- maxIterations: 2, startedAt: new Date().toISOString(), seq: 1,
803
+ maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
805
804
  }),
806
805
  // Iteration 1
807
806
  sseEvent('agent_iteration_start', {
808
- executionId: execId, iteration: 1, maxIterations: 2,
807
+ executionId: execId, iteration: 1, maxTurns: 2,
809
808
  startedAt: new Date().toISOString(), seq: 2,
810
809
  }),
811
810
  sseEvent('agent_turn_start', {
@@ -826,7 +825,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
826
825
  }),
827
826
  // Iteration 2
828
827
  sseEvent('agent_iteration_start', {
829
- executionId: execId, iteration: 2, maxIterations: 2,
828
+ executionId: execId, iteration: 2, maxTurns: 2,
830
829
  startedAt: new Date().toISOString(), seq: 7,
831
830
  }),
832
831
  sseEvent('agent_turn_start', {
@@ -891,10 +890,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
891
890
  global.fetch = createAgentStreamFetch([
892
891
  sseEvent('agent_start', {
893
892
  executionId: execId, agentId: 'virtual', agentName: 'Test',
894
- maxIterations: 2, startedAt: new Date().toISOString(), seq: 1,
893
+ maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
895
894
  }),
896
895
  sseEvent('agent_iteration_start', {
897
- executionId: execId, iteration: 1, maxIterations: 2,
896
+ executionId: execId, iteration: 1, maxTurns: 2,
898
897
  startedAt: new Date().toISOString(), seq: 2,
899
898
  }),
900
899
  sseEvent('agent_turn_delta', {
@@ -906,7 +905,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
906
905
  stopConditionMet: false, completedAt: new Date().toISOString(), seq: 4,
907
906
  }),
908
907
  sseEvent('agent_iteration_start', {
909
- executionId: execId, iteration: 2, maxIterations: 2,
908
+ executionId: execId, iteration: 2, maxTurns: 2,
910
909
  startedAt: new Date().toISOString(), seq: 5,
911
910
  }),
912
911
  sseEvent('agent_turn_delta', {
@@ -954,10 +953,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
954
953
  global.fetch = createAgentStreamFetch([
955
954
  sseEvent('agent_start', {
956
955
  executionId: execId, agentId: 'virtual', agentName: 'Test',
957
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
956
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
958
957
  }),
959
958
  sseEvent('agent_iteration_start', {
960
- executionId: execId, iteration: 1, maxIterations: 1,
959
+ executionId: execId, iteration: 1, maxTurns: 1,
961
960
  startedAt: new Date().toISOString(), seq: 2,
962
961
  }),
963
962
  sseEvent('agent_tool_start', {
@@ -1026,10 +1025,10 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1026
1025
  global.fetch = createAgentStreamFetch([
1027
1026
  sseEvent('agent_start', {
1028
1027
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1029
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
1028
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1030
1029
  }),
1031
1030
  sseEvent('agent_iteration_start', {
1032
- executionId: execId, iteration: 1, maxIterations: 1,
1031
+ executionId: execId, iteration: 1, maxTurns: 1,
1033
1032
  startedAt: new Date().toISOString(), seq: 2,
1034
1033
  }),
1035
1034
  sseEvent('agent_turn_delta', {
@@ -1090,7 +1089,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1090
1089
  global.fetch = createAgentStreamFetch([
1091
1090
  sseEvent('agent_start', {
1092
1091
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1093
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
1092
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1094
1093
  }),
1095
1094
  sseEvent('agent_error', {
1096
1095
  executionId: execId, iteration: 1,
@@ -1129,7 +1128,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1129
1128
  global.fetch = createAgentStreamFetch([
1130
1129
  sseEvent('agent_start', {
1131
1130
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1132
- maxIterations: 2, startedAt: new Date().toISOString(), seq: 1,
1131
+ maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
1133
1132
  }),
1134
1133
  sseEvent('agent_reflection', {
1135
1134
  executionId: execId, iteration: 1,
@@ -1175,7 +1174,7 @@ describe('AgentWidgetClient - Agent Event Streaming', () => {
1175
1174
  global.fetch = createAgentStreamFetch([
1176
1175
  sseEvent('agent_start', {
1177
1176
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1178
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
1177
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1179
1178
  }),
1180
1179
  sseEvent('agent_ping', {
1181
1180
  executionId: execId, timestamp: new Date().toISOString(), seq: 2,
@@ -1300,10 +1299,10 @@ describe('AgentWidgetClient - Unified Event Names', () => {
1300
1299
  global.fetch = createAgentStreamFetch([
1301
1300
  sseEvent('agent_start', {
1302
1301
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1303
- maxIterations: 1, startedAt: new Date().toISOString(), seq: 1,
1302
+ maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
1304
1303
  }),
1305
1304
  sseEvent('agent_iteration_start', {
1306
- executionId: execId, iteration: 1, maxIterations: 1,
1305
+ executionId: execId, iteration: 1, maxTurns: 1,
1307
1306
  startedAt: new Date().toISOString(), seq: 2,
1308
1307
  }),
1309
1308
  sseEvent('tool_start', {
@@ -1368,7 +1367,7 @@ describe('AgentWidgetClient - Unified Event Names', () => {
1368
1367
  global.fetch = createAgentStreamFetch([
1369
1368
  sseEvent('agent_start', {
1370
1369
  executionId: execId, agentId: 'virtual', agentName: 'Test',
1371
- maxIterations: 2, startedAt: new Date().toISOString(), seq: 1,
1370
+ maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
1372
1371
  }),
1373
1372
  sseEvent('agent_reflect', {
1374
1373
  executionId: execId, iteration: 1,
package/src/client.ts CHANGED
@@ -16,7 +16,8 @@ import {
16
16
  ClientInitResponse,
17
17
  ClientChatRequest,
18
18
  ClientFeedbackRequest,
19
- ClientFeedbackType
19
+ ClientFeedbackType,
20
+ PersonaArtifactKind
20
21
  } from "./types";
21
22
  import {
22
23
  extractTextFromJson,
@@ -25,6 +26,8 @@ import {
25
26
  createRegexJsonParser,
26
27
  createXmlParser
27
28
  } from "./utils/formatting";
29
+ // artifactsSidebarEnabled is used in ui.ts to gate the sidebar pane rendering;
30
+ // artifact events are always processed here regardless of config.
28
31
 
29
32
  type DispatchOptions = {
30
33
  messages: AgentWidgetMessage[];
@@ -468,6 +471,7 @@ export class AgentWidgetClient {
468
471
  ...(options.assistantMessageId && { assistantMessageId: options.assistantMessageId }),
469
472
  // Include metadata/context from middleware if present (excluding sessionId)
470
473
  ...(sanitizedMetadata && Object.keys(sanitizedMetadata).length > 0 && { metadata: sanitizedMetadata }),
474
+ ...(basePayload.inputs && Object.keys(basePayload.inputs).length > 0 && { inputs: basePayload.inputs }),
471
475
  ...(basePayload.context && { context: basePayload.context }),
472
476
  };
473
477
 
@@ -1110,6 +1114,20 @@ export class AgentWidgetClient {
1110
1114
  }
1111
1115
  };
1112
1116
 
1117
+ // Track tool call IDs for artifact emit tools so we can suppress their UI
1118
+ const artifactToolCallIds = new Set<string>();
1119
+ // Track artifact reference card messages so we can update them on artifact_complete
1120
+ const artifactCardMessages = new Map<string, AgentWidgetMessage>();
1121
+ // Track artifact IDs that already have a reference card (from auto-creation or transcript_insert)
1122
+ const artifactIdsWithCards = new Set<string>();
1123
+ // Accumulate artifact markdown content for embedding in card props on complete
1124
+ const artifactContent = new Map<string, { markdown: string; title?: string }>();
1125
+ const isArtifactEmitToolName = (name: string | undefined): boolean => {
1126
+ if (!name) return false;
1127
+ const normalized = name.replace(/_+/g, "_").replace(/^_|_$/g, "");
1128
+ return normalized === "emit_artifact_markdown" || normalized === "emit_artifact_component";
1129
+ };
1130
+
1113
1131
  const resolveToolId = (
1114
1132
  payload: Record<string, any>,
1115
1133
  allowCreate: boolean
@@ -1346,12 +1364,18 @@ export class AgentWidgetClient {
1346
1364
  } else if (payloadType === "tool_start") {
1347
1365
  const toolId =
1348
1366
  resolveToolId(payload, true) ?? `tool-${nextSequence()}`;
1367
+ const toolName = payload.toolName ?? payload.name;
1368
+ // Suppress tool UI for artifact emit tools — artifacts are handled via artifact_* events
1369
+ if (isArtifactEmitToolName(toolName)) {
1370
+ artifactToolCallIds.add(toolId);
1371
+ continue;
1372
+ }
1349
1373
  const toolMessage = ensureToolMessage(toolId);
1350
1374
  const tool = toolMessage.toolCall ?? {
1351
1375
  id: toolId,
1352
1376
  status: "pending"
1353
1377
  };
1354
- tool.name = payload.toolName ?? tool.name;
1378
+ tool.name = toolName ?? tool.name;
1355
1379
  tool.status = "running";
1356
1380
  if (payload.args !== undefined) {
1357
1381
  tool.args = payload.args;
@@ -1378,6 +1402,7 @@ export class AgentWidgetClient {
1378
1402
  resolveToolId(payload, false) ??
1379
1403
  resolveToolId(payload, true) ??
1380
1404
  `tool-${nextSequence()}`;
1405
+ if (artifactToolCallIds.has(toolId)) continue;
1381
1406
  const toolMessage = ensureToolMessage(toolId);
1382
1407
  const tool = toolMessage.toolCall ?? {
1383
1408
  id: toolId,
@@ -1408,6 +1433,10 @@ export class AgentWidgetClient {
1408
1433
  resolveToolId(payload, false) ??
1409
1434
  resolveToolId(payload, true) ??
1410
1435
  `tool-${nextSequence()}`;
1436
+ if (artifactToolCallIds.has(toolId)) {
1437
+ artifactToolCallIds.delete(toolId);
1438
+ continue;
1439
+ }
1411
1440
  const toolMessage = ensureToolMessage(toolId);
1412
1441
  const tool = toolMessage.toolCall ?? {
1413
1442
  id: toolId,
@@ -1842,7 +1871,7 @@ export class AgentWidgetClient {
1842
1871
  agentName: payload.agentName ?? '',
1843
1872
  status: 'running',
1844
1873
  currentIteration: 0,
1845
- maxIterations: payload.maxIterations ?? 1,
1874
+ maxTurns: payload.maxTurns ?? 1,
1846
1875
  startedAt: resolveTimestamp(payload.startedAt)
1847
1876
  };
1848
1877
  } else if (payloadType === "agent_iteration_start") {
@@ -1931,7 +1960,7 @@ export class AgentWidgetClient {
1931
1960
  result: undefined, duration: undefined, startedAt: undefined,
1932
1961
  completedAt: undefined, durationMs: undefined
1933
1962
  };
1934
- tool.name = payload.toolName ?? tool.name;
1963
+ tool.name = payload.toolName ?? payload.name ?? tool.name;
1935
1964
  tool.status = "running";
1936
1965
  if (payload.parameters !== undefined) {
1937
1966
  tool.args = payload.parameters;
@@ -2106,6 +2135,126 @@ export class AgentWidgetClient {
2106
2135
  };
2107
2136
  emitMessage(existingMessage);
2108
2137
  }
2138
+ } else if (
2139
+ payloadType === "artifact_start" ||
2140
+ payloadType === "artifact_delta" ||
2141
+ payloadType === "artifact_update" ||
2142
+ payloadType === "artifact_complete"
2143
+ ) {
2144
+ if (payloadType === "artifact_start") {
2145
+ const at = payload.artifactType as PersonaArtifactKind;
2146
+ const artId = String(payload.id);
2147
+ const artTitle = typeof payload.title === "string" ? payload.title : undefined;
2148
+ onEvent({
2149
+ type: "artifact_start",
2150
+ id: artId,
2151
+ artifactType: at,
2152
+ title: artTitle,
2153
+ component: typeof payload.component === "string" ? payload.component : undefined
2154
+ });
2155
+ artifactContent.set(artId, { markdown: "", title: artTitle });
2156
+ // Insert inline artifact reference card (skip if already present from transcript_insert)
2157
+ if (!artifactIdsWithCards.has(artId)) {
2158
+ artifactIdsWithCards.add(artId);
2159
+ const cardMsg: AgentWidgetMessage = {
2160
+ id: `artifact-ref-${artId}`,
2161
+ role: "assistant",
2162
+ content: "",
2163
+ createdAt: new Date().toISOString(),
2164
+ streaming: true,
2165
+ sequence: nextSequence(),
2166
+ rawContent: JSON.stringify({
2167
+ component: "PersonaArtifactCard",
2168
+ props: { artifactId: artId, title: artTitle, artifactType: at, status: "streaming" },
2169
+ }),
2170
+ };
2171
+ artifactCardMessages.set(artId, cardMsg);
2172
+ emitMessage(cardMsg);
2173
+ }
2174
+ } else if (payloadType === "artifact_delta") {
2175
+ const deltaId = String(payload.id);
2176
+ const deltaText = typeof payload.delta === "string" ? payload.delta : String(payload.delta ?? "");
2177
+ onEvent({
2178
+ type: "artifact_delta",
2179
+ id: deltaId,
2180
+ artDelta: deltaText
2181
+ });
2182
+ const acc = artifactContent.get(deltaId);
2183
+ if (acc) acc.markdown += deltaText;
2184
+ } else if (payloadType === "artifact_update") {
2185
+ const props =
2186
+ payload.props && typeof payload.props === "object" && !Array.isArray(payload.props)
2187
+ ? (payload.props as Record<string, unknown>)
2188
+ : {};
2189
+ onEvent({
2190
+ type: "artifact_update",
2191
+ id: String(payload.id),
2192
+ props,
2193
+ component: typeof payload.component === "string" ? payload.component : undefined
2194
+ });
2195
+ } else if (payloadType === "artifact_complete") {
2196
+ const artCompleteId = String(payload.id);
2197
+ onEvent({ type: "artifact_complete", id: artCompleteId });
2198
+ // Update the inline card to show completed state
2199
+ const refMsg = artifactCardMessages.get(artCompleteId);
2200
+ if (refMsg) {
2201
+ refMsg.streaming = false;
2202
+ try {
2203
+ const parsed = JSON.parse(refMsg.rawContent ?? "{}");
2204
+ if (parsed.props) {
2205
+ parsed.props.status = "complete";
2206
+ // Store markdown content in card props so download works after page refresh
2207
+ const acc = artifactContent.get(artCompleteId);
2208
+ if (acc?.markdown) {
2209
+ parsed.props.markdown = acc.markdown;
2210
+ }
2211
+ }
2212
+ refMsg.rawContent = JSON.stringify(parsed);
2213
+ } catch { /* ignore parse errors */ }
2214
+ artifactContent.delete(artCompleteId);
2215
+ emitMessage(refMsg);
2216
+ artifactCardMessages.delete(artCompleteId);
2217
+ }
2218
+ }
2219
+ } else if (payloadType === "transcript_insert") {
2220
+ const m = payload.message as Record<string, unknown> | undefined;
2221
+ if (!m || typeof m !== "object") {
2222
+ continue;
2223
+ }
2224
+ const id = String(m.id ?? `msg-${nextSequence()}`);
2225
+ const roleRaw = m.role;
2226
+ const role =
2227
+ roleRaw === "user" ? "user" : roleRaw === "system" ? "system" : "assistant";
2228
+ const msg: AgentWidgetMessage = {
2229
+ id,
2230
+ role,
2231
+ content: typeof m.content === "string" ? m.content : "",
2232
+ rawContent: typeof m.rawContent === "string" ? m.rawContent : undefined,
2233
+ createdAt:
2234
+ typeof m.createdAt === "string" ? m.createdAt : new Date().toISOString(),
2235
+ streaming: m.streaming === true,
2236
+ // Omit variant unless the stream specifies it. Do not default to `"assistant"`:
2237
+ // that value is truthy and skips the component-directive branch (`!message.variant` in ui.ts).
2238
+ ...(typeof m.variant === "string"
2239
+ ? { variant: m.variant as AgentWidgetMessage["variant"] }
2240
+ : {}),
2241
+ sequence: nextSequence()
2242
+ };
2243
+ emitMessage(msg);
2244
+ // Detect artifact references in transcript_insert to prevent duplicate auto-cards
2245
+ if (msg.rawContent) {
2246
+ try {
2247
+ const parsed = JSON.parse(msg.rawContent);
2248
+ const refArtId = parsed?.props?.artifactId;
2249
+ if (typeof refArtId === "string") {
2250
+ artifactIdsWithCards.add(refArtId);
2251
+ }
2252
+ } catch { /* not JSON or no artifactId */ }
2253
+ }
2254
+ assistantMessage = null;
2255
+ assistantMessageRef.current = null;
2256
+ streamParsers.delete(id);
2257
+ rawContentBuffers.delete(id);
2109
2258
  } else if (payloadType === "error" && payload.error) {
2110
2259
  onEvent({
2111
2260
  type: "error",
@@ -26,11 +26,17 @@ export const updateApprovalBubbleUI = (
26
26
 
27
27
  // Update badge color
28
28
  if (approval.status === "approved") {
29
- statusBadge.className = "tvw-inline-flex tvw-items-center tvw-px-2 tvw-py-0.5 tvw-rounded-full tvw-text-xs tvw-font-medium tvw-approval-badge-approved";
29
+ statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
30
+ statusBadge.style.backgroundColor = "var(--persona-palette-colors-success-100, #dcfce7)";
31
+ statusBadge.style.color = "var(--persona-palette-colors-success-700, #15803d)";
30
32
  } else if (approval.status === "denied") {
31
- statusBadge.className = "tvw-inline-flex tvw-items-center tvw-px-2 tvw-py-0.5 tvw-rounded-full tvw-text-xs tvw-font-medium tvw-approval-badge-denied";
33
+ statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
34
+ statusBadge.style.backgroundColor = "var(--persona-palette-colors-error-100, #fee2e2)";
35
+ statusBadge.style.color = "var(--persona-palette-colors-error-700, #b91c1c)";
32
36
  } else if (approval.status === "timeout") {
33
- statusBadge.className = "tvw-inline-flex tvw-items-center tvw-px-2 tvw-py-0.5 tvw-rounded-full tvw-text-xs tvw-font-medium tvw-approval-badge-timeout";
37
+ statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
38
+ statusBadge.style.backgroundColor = "var(--persona-palette-colors-warning-100, #fef3c7)";
39
+ statusBadge.style.color = "var(--persona-palette-colors-warning-700, #b45309)";
34
40
  }
35
41
  statusBadge.setAttribute("data-approval-status", approval.status);
36
42
  }
@@ -42,9 +48,9 @@ export const updateApprovalBubbleUI = (
42
48
  const iconName = approval.status === "denied" ? "shield-x"
43
49
  : approval.status === "timeout" ? "shield-alert"
44
50
  : "shield-check";
45
- const iconColor = approval.status === "approved" ? "#16a34a"
46
- : approval.status === "denied" ? "#dc2626"
47
- : approval.status === "timeout" ? "#ca8a04"
51
+ const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, #16a34a)"
52
+ : approval.status === "denied" ? "var(--persona-feedback-error, #dc2626)"
53
+ : approval.status === "timeout" ? "var(--persona-feedback-warning, #ca8a04)"
48
54
  : (approvalConfig?.titleColor ?? "currentColor");
49
55
  const icon = renderLucideIcon(iconName, 20, iconColor, 2);
50
56
  if (icon) {
@@ -73,14 +79,12 @@ export const createApprovalBubble = (
73
79
  const bubble = createElement(
74
80
  "div",
75
81
  [
76
- "vanilla-message-bubble",
77
- "vanilla-approval-bubble",
78
- "tvw-w-full",
79
- "tvw-max-w-[85%]",
80
- "tvw-rounded-2xl",
81
- "tvw-border",
82
- "tvw-shadow-sm",
83
- "tvw-overflow-hidden",
82
+ "persona-w-full",
83
+ "persona-max-w-[85%]",
84
+ "persona-rounded-2xl",
85
+ "persona-border",
86
+ "persona-shadow-sm",
87
+ "persona-overflow-hidden",
84
88
  ].join(" ")
85
89
  );
86
90
 
@@ -88,13 +92,9 @@ export const createApprovalBubble = (
88
92
  bubble.id = `bubble-${message.id}`;
89
93
  bubble.setAttribute("data-message-id", message.id);
90
94
 
91
- // Apply styling — only set inline styles when config overrides exist (CSS defaults handle the rest)
92
- if (approvalConfig?.backgroundColor) {
93
- bubble.style.backgroundColor = approvalConfig.backgroundColor;
94
- }
95
- if (approvalConfig?.borderColor) {
96
- bubble.style.borderColor = approvalConfig.borderColor;
97
- }
95
+ // Apply styling — use semantic tokens with config overrides
96
+ bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg, #fefce8)";
97
+ bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border, #fef08a)";
98
98
 
99
99
  if (!approval) {
100
100
  return bubble;
@@ -103,18 +103,18 @@ export const createApprovalBubble = (
103
103
  // Header section with icon, title, and status badge
104
104
  const header = createElement(
105
105
  "div",
106
- "tvw-flex tvw-items-start tvw-gap-3 tvw-px-4 tvw-py-3"
106
+ "persona-flex persona-items-start persona-gap-3 persona-px-4 persona-py-3"
107
107
  );
108
108
 
109
109
  // Icon container
110
- const iconContainer = createElement("div", "tvw-flex-shrink-0 tvw-mt-0.5");
110
+ const iconContainer = createElement("div", "persona-flex-shrink-0 persona-mt-0.5");
111
111
  iconContainer.setAttribute("data-approval-icon", "true");
112
112
  const iconName = approval.status === "denied" ? "shield-x"
113
113
  : approval.status === "timeout" ? "shield-alert"
114
114
  : "shield-check";
115
- const iconColor = approval.status === "approved" ? "#16a34a"
116
- : approval.status === "denied" ? "#dc2626"
117
- : approval.status === "timeout" ? "#ca8a04"
115
+ const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, #16a34a)"
116
+ : approval.status === "denied" ? "var(--persona-feedback-error, #dc2626)"
117
+ : approval.status === "timeout" ? "var(--persona-feedback-warning, #ca8a04)"
118
118
  : (approvalConfig?.titleColor ?? "currentColor");
119
119
  const icon = renderLucideIcon(iconName, 20, iconColor, 2);
120
120
  if (icon) {
@@ -122,11 +122,11 @@ export const createApprovalBubble = (
122
122
  }
123
123
 
124
124
  // Content area
125
- const content = createElement("div", "tvw-flex-1 tvw-min-w-0");
125
+ const content = createElement("div", "persona-flex-1 persona-min-w-0");
126
126
 
127
127
  // Title row with status badge
128
- const titleRow = createElement("div", "tvw-flex tvw-items-center tvw-gap-2");
129
- const title = createElement("span", "tvw-text-sm tvw-font-medium tvw-text-cw-primary");
128
+ const titleRow = createElement("div", "persona-flex persona-items-center persona-gap-2");
129
+ const title = createElement("span", "persona-text-sm persona-font-medium persona-text-persona-primary");
130
130
  if (approvalConfig?.titleColor) {
131
131
  title.style.color = approvalConfig.titleColor;
132
132
  }
@@ -135,16 +135,19 @@ export const createApprovalBubble = (
135
135
 
136
136
  // Status badge (shown when resolved)
137
137
  if (!isPending) {
138
- const badge = createElement("span", "tvw-inline-flex tvw-items-center tvw-px-2 tvw-py-0.5 tvw-rounded-full tvw-text-xs tvw-font-medium");
138
+ const badge = createElement("span", "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium");
139
139
  badge.setAttribute("data-approval-status", approval.status);
140
140
  if (approval.status === "approved") {
141
- badge.className += " tvw-approval-badge-approved";
141
+ badge.style.backgroundColor = "var(--persona-palette-colors-success-100, #dcfce7)";
142
+ badge.style.color = "var(--persona-palette-colors-success-700, #15803d)";
142
143
  badge.textContent = "Approved";
143
144
  } else if (approval.status === "denied") {
144
- badge.className += " tvw-approval-badge-denied";
145
+ badge.style.backgroundColor = "var(--persona-palette-colors-error-100, #fee2e2)";
146
+ badge.style.color = "var(--persona-palette-colors-error-700, #b91c1c)";
145
147
  badge.textContent = "Denied";
146
148
  } else if (approval.status === "timeout") {
147
- badge.className += " tvw-approval-badge-timeout";
149
+ badge.style.backgroundColor = "var(--persona-palette-colors-warning-100, #fef3c7)";
150
+ badge.style.color = "var(--persona-palette-colors-warning-700, #b45309)";
148
151
  badge.textContent = "Timeout";
149
152
  }
150
153
  titleRow.appendChild(badge);
@@ -153,7 +156,7 @@ export const createApprovalBubble = (
153
156
  content.appendChild(titleRow);
154
157
 
155
158
  // Description
156
- const description = createElement("p", "tvw-text-sm tvw-mt-0.5 tvw-text-cw-muted");
159
+ const description = createElement("p", "persona-text-sm persona-mt-0.5 persona-text-persona-muted");
157
160
  if (approvalConfig?.descriptionColor) {
158
161
  description.style.color = approvalConfig.descriptionColor;
159
162
  }
@@ -164,7 +167,7 @@ export const createApprovalBubble = (
164
167
  if (approval.parameters) {
165
168
  const paramsPre = createElement(
166
169
  "pre",
167
- "tvw-mt-2 tvw-text-xs tvw-p-2 tvw-rounded tvw-overflow-x-auto tvw-max-h-32 tvw-bg-cw-container tvw-text-cw-primary"
170
+ "persona-mt-2 persona-text-xs persona-p-2 persona-rounded persona-overflow-x-auto persona-max-h-32 persona-bg-persona-container persona-text-persona-primary"
168
171
  );
169
172
  if (approvalConfig?.parameterBackgroundColor) {
170
173
  paramsPre.style.backgroundColor = approvalConfig.parameterBackgroundColor;
@@ -180,13 +183,13 @@ export const createApprovalBubble = (
180
183
 
181
184
  // Action buttons (only shown when pending)
182
185
  if (isPending) {
183
- const buttonsContainer = createElement("div", "tvw-flex tvw-gap-2 tvw-mt-2");
186
+ const buttonsContainer = createElement("div", "persona-flex persona-gap-2 persona-mt-2");
184
187
  buttonsContainer.setAttribute("data-approval-buttons", "true");
185
188
 
186
189
  // Approve button
187
- const approveBtn = createElement("button", "tvw-inline-flex tvw-items-center tvw-px-3 tvw-py-1.5 tvw-rounded-md tvw-text-xs tvw-font-medium tvw-border-none tvw-cursor-pointer") as HTMLButtonElement;
190
+ const approveBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-border-none persona-cursor-pointer") as HTMLButtonElement;
188
191
  approveBtn.type = "button";
189
- approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "#16a34a";
192
+ approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "var(--persona-approval-approve-bg, #22c55e)";
190
193
  approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? "#ffffff";
191
194
  approveBtn.setAttribute("data-approval-action", "approve");
192
195
  const approveIcon = renderLucideIcon("shield-check", 14, approvalConfig?.approveButtonTextColor ?? "#ffffff", 2);
@@ -198,13 +201,13 @@ export const createApprovalBubble = (
198
201
  approveBtn.appendChild(approveLabel);
199
202
 
200
203
  // Deny button
201
- const denyBtn = createElement("button", "tvw-inline-flex tvw-items-center tvw-px-3 tvw-py-1.5 tvw-rounded-md tvw-text-xs tvw-font-medium tvw-cursor-pointer") as HTMLButtonElement;
204
+ const denyBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-cursor-pointer") as HTMLButtonElement;
202
205
  denyBtn.type = "button";
203
206
  denyBtn.style.backgroundColor = approvalConfig?.denyButtonColor ?? "transparent";
204
- denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "#dc2626";
205
- denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ?? "#fca5a5"}`;
207
+ denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, #dc2626)";
208
+ denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : "var(--persona-palette-colors-error-200, #fca5a5)"}`;
206
209
  denyBtn.setAttribute("data-approval-action", "deny");
207
- const denyIcon = renderLucideIcon("shield-x", 14, approvalConfig?.denyButtonTextColor ?? "#dc2626", 2);
210
+ const denyIcon = renderLucideIcon("shield-x", 14, approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, #dc2626)", 2);
208
211
  if (denyIcon) {
209
212
  denyIcon.style.marginRight = "4px";
210
213
  denyBtn.appendChild(denyIcon);