@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.
- package/README.md +140 -8
- package/dist/index.cjs +90 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1055 -24
- package/dist/index.d.ts +1055 -24
- package/dist/index.global.js +111 -60
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +90 -39
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/widget.css +836 -513
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +80 -0
- package/src/client.test.ts +20 -21
- package/src/client.ts +153 -4
- package/src/components/approval-bubble.ts +45 -42
- package/src/components/artifact-card.ts +91 -0
- package/src/components/artifact-pane.ts +501 -0
- package/src/components/composer-builder.ts +32 -27
- package/src/components/event-stream-view.ts +40 -40
- package/src/components/feedback.ts +36 -36
- package/src/components/forms.ts +11 -11
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +55 -36
- package/src/components/header-layouts.ts +58 -125
- package/src/components/launcher.ts +36 -21
- package/src/components/message-bubble.ts +92 -65
- package/src/components/messages.ts +2 -2
- package/src/components/panel.ts +42 -11
- package/src/components/reasoning-bubble.ts +23 -23
- package/src/components/registry.ts +4 -0
- package/src/components/suggestions.ts +1 -1
- package/src/components/tool-bubble.ts +32 -32
- package/src/defaults.ts +30 -4
- package/src/index.ts +80 -2
- package/src/install.ts +22 -0
- package/src/plugins/types.ts +23 -0
- package/src/postprocessors.ts +2 -2
- package/src/runtime/host-layout.ts +174 -0
- package/src/runtime/init.test.ts +236 -0
- package/src/runtime/init.ts +114 -55
- package/src/session.ts +135 -2
- package/src/styles/tailwind.css +1 -1
- package/src/styles/widget.css +836 -513
- package/src/types/theme.ts +354 -0
- package/src/types.ts +314 -15
- package/src/ui.docked.test.ts +104 -0
- package/src/ui.ts +940 -227
- package/src/utils/artifact-gate.test.ts +255 -0
- package/src/utils/artifact-gate.ts +142 -0
- package/src/utils/artifact-resize.test.ts +64 -0
- package/src/utils/artifact-resize.ts +67 -0
- package/src/utils/attachment-manager.ts +10 -10
- package/src/utils/code-generators.test.ts +52 -0
- package/src/utils/code-generators.ts +40 -36
- package/src/utils/dock.ts +17 -0
- package/src/utils/dom-context.test.ts +504 -0
- package/src/utils/dom-context.ts +896 -0
- package/src/utils/dom.ts +12 -1
- package/src/utils/message-fingerprint.test.ts +187 -0
- package/src/utils/message-fingerprint.ts +105 -0
- package/src/utils/migration.ts +179 -0
- package/src/utils/morph.ts +1 -1
- package/src/utils/plugins.ts +175 -0
- package/src/utils/positioning.ts +4 -4
- package/src/utils/theme.test.ts +125 -0
- package/src/utils/theme.ts +216 -60
- package/src/utils/tokens.ts +682 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "
|
|
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
|
+
});
|
package/src/client.test.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
737
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
739
738
|
}),
|
|
740
739
|
sseEvent('agent_iteration_start', {
|
|
741
|
-
executionId: execId, iteration: 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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
893
|
+
maxTurns: 2, startedAt: new Date().toISOString(), seq: 1,
|
|
895
894
|
}),
|
|
896
895
|
sseEvent('agent_iteration_start', {
|
|
897
|
-
executionId: execId, iteration: 1,
|
|
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,
|
|
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
|
-
|
|
956
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
958
957
|
}),
|
|
959
958
|
sseEvent('agent_iteration_start', {
|
|
960
|
-
executionId: execId, iteration: 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
|
-
|
|
1028
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
1030
1029
|
}),
|
|
1031
1030
|
sseEvent('agent_iteration_start', {
|
|
1032
|
-
executionId: execId, iteration: 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1302
|
+
maxTurns: 1, startedAt: new Date().toISOString(), seq: 1,
|
|
1304
1303
|
}),
|
|
1305
1304
|
sseEvent('agent_iteration_start', {
|
|
1306
|
-
executionId: execId, iteration: 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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 = "
|
|
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 = "
|
|
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 = "
|
|
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
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
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 —
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
"
|
|
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", "
|
|
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", "
|
|
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", "
|
|
129
|
-
const title = createElement("span", "
|
|
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", "
|
|
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.
|
|
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.
|
|
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.
|
|
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", "
|
|
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
|
-
"
|
|
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", "
|
|
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", "
|
|
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 ?? "#
|
|
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", "
|
|
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
|
|
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);
|