@runtypelabs/persona 3.25.0 → 3.27.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 +54 -13
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-BZVr1YOV.d.cts → types-CxvHw0X6.d.cts} +551 -1
- package/dist/animations/{types-BZVr1YOV.d.ts → types-CxvHw0X6.d.ts} +551 -1
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/index.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1230 -3
- package/dist/index.d.ts +1230 -3
- package/dist/index.global.js +52 -52
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +47 -47
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +133 -0
- package/dist/launcher.global.js.map +1 -0
- package/dist/smart-dom-reader.d.cts +551 -1
- package/dist/smart-dom-reader.d.ts +551 -1
- package/dist/theme-editor.cjs +37 -37
- package/dist/theme-editor.d.cts +551 -1
- package/dist/theme-editor.d.ts +551 -1
- package/dist/theme-editor.js +35 -35
- package/package.json +8 -4
- package/src/client.test.ts +58 -9
- package/src/client.ts +18 -0
- package/src/generated/runtype-openapi-contract.ts +1249 -0
- package/src/index-core.ts +19 -0
- package/src/install.test.ts +442 -0
- package/src/install.ts +283 -49
- package/src/launcher-global.ts +115 -0
- package/src/runtime/init.test.ts +71 -0
- package/src/runtime/init.ts +17 -1
- package/src/types.ts +14 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.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",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"access": "public"
|
|
100
100
|
},
|
|
101
101
|
"scripts": {
|
|
102
|
-
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:theme-ref && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:animations",
|
|
102
|
+
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:theme-ref && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:animations",
|
|
103
103
|
"build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
104
104
|
"build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
105
105
|
"build:smart-dom-reader": "tsup src/smart-dom-reader.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
|
|
@@ -108,11 +108,15 @@
|
|
|
108
108
|
"build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
|
|
109
109
|
"build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader \".css=text\" && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
|
|
110
110
|
"build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
|
|
111
|
+
"build:launcher": "tsup src/launcher-global.ts --format iife --global-name AgentWidgetLauncher --minify --sourcemap --splitting false --out-dir dist && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/launcher-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/launcher'+ext);}\"",
|
|
111
112
|
"lint": "eslint . --ext .ts",
|
|
112
|
-
"typecheck": "tsc --noEmit",
|
|
113
|
+
"typecheck": "pnpm run check:runtype-types && tsc --noEmit",
|
|
113
114
|
"test": "vitest",
|
|
114
115
|
"test:ui": "vitest --ui",
|
|
115
116
|
"test:run": "vitest run",
|
|
116
|
-
"size": "size-limit"
|
|
117
|
+
"size": "size-limit",
|
|
118
|
+
"fetch:runtype-openapi": "node scripts/fetch-runtype-openapi.mjs",
|
|
119
|
+
"generate:runtype-types": "pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs",
|
|
120
|
+
"check:runtype-types": "pnpm run fetch:runtype-openapi && node scripts/generate-runtype-openapi-types.mjs --check"
|
|
117
121
|
}
|
|
118
122
|
}
|
package/src/client.test.ts
CHANGED
|
@@ -1633,6 +1633,55 @@ describe('AgentWidgetClient - partId Text/Tool Interleaving', () => {
|
|
|
1633
1633
|
expect(seqTool).toBeLessThan(seq1);
|
|
1634
1634
|
});
|
|
1635
1635
|
|
|
1636
|
+
it('should not emit a whitespace-only assistant bubble before a leading tool call', async () => {
|
|
1637
|
+
const events: AgentWidgetEvent[] = [];
|
|
1638
|
+
|
|
1639
|
+
const encoder = new TextEncoder();
|
|
1640
|
+
global.fetch = vi.fn().mockImplementation(async () => {
|
|
1641
|
+
const stream = new ReadableStream({
|
|
1642
|
+
start(controller) {
|
|
1643
|
+
const e = (eventType: string, data: Record<string, unknown>) =>
|
|
1644
|
+
controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${JSON.stringify({ type: eventType, ...data })}\n\n`));
|
|
1645
|
+
|
|
1646
|
+
e('flow_start', { flowId: 'f1', flowName: 'Test', totalSteps: 1 });
|
|
1647
|
+
e('step_start', { id: 's1', name: 'Prompt', stepType: 'prompt', index: 1, totalSteps: 1 });
|
|
1648
|
+
// Tool UI is the first meaningful output. Some providers still emit
|
|
1649
|
+
// newline-only text lifecycle events around the tool boundary; those
|
|
1650
|
+
// must not become an empty assistant message bubble.
|
|
1651
|
+
e('tool_start', { toolId: 'tc_1', name: 'add_to_cart', toolType: 'local', sequenceIndex: 4 });
|
|
1652
|
+
e('text_start', { partId: 'text_0', messageId: 'msg_s1', seq: 1 });
|
|
1653
|
+
e('step_delta', { id: 's1', text: '\n', partId: 'text_0', messageId: 'msg_s1', seq: 2 });
|
|
1654
|
+
e('text_end', { partId: 'text_0', messageId: 'msg_s1', seq: 3 });
|
|
1655
|
+
e('tool_complete', { toolId: 'tc_1', name: 'add_to_cart', success: true, completedAt: new Date().toISOString(), executionTime: 20, sequenceIndex: 5 });
|
|
1656
|
+
e('flow_complete', { success: true });
|
|
1657
|
+
controller.close();
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
return { ok: true, body: stream };
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
const client = new AgentWidgetClient({ apiUrl: 'http://localhost:8000' });
|
|
1664
|
+
await client.dispatch(
|
|
1665
|
+
{ messages: [{ id: 'usr_1', role: 'user', content: 'Add to cart', createdAt: new Date().toISOString() }] },
|
|
1666
|
+
(event) => events.push(event)
|
|
1667
|
+
);
|
|
1668
|
+
|
|
1669
|
+
const messageEvents = events.filter(e => e.type === 'message');
|
|
1670
|
+
const messagesById = new Map<string, AgentWidgetMessage>();
|
|
1671
|
+
for (const event of messageEvents) {
|
|
1672
|
+
if (event.type === 'message') messagesById.set(event.message.id, event.message);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const allMessages = Array.from(messagesById.values());
|
|
1676
|
+
const assistantTexts = allMessages.filter(m => m.role === 'assistant' && !m.variant);
|
|
1677
|
+
const toolMsgs = allMessages.filter(m => m.variant === 'tool');
|
|
1678
|
+
|
|
1679
|
+
expect(assistantTexts).toHaveLength(0);
|
|
1680
|
+
expect(toolMsgs).toHaveLength(1);
|
|
1681
|
+
expect(toolMsgs[0].toolCall?.name).toBe('add_to_cart');
|
|
1682
|
+
expect(toolMsgs[0].toolCall?.status).toBe('complete');
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1636
1685
|
it('should not split when partId is absent (backward compatible)', async () => {
|
|
1637
1686
|
const events: AgentWidgetEvent[] = [];
|
|
1638
1687
|
|
|
@@ -3097,7 +3146,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3097
3146
|
|
|
3098
3147
|
const client = new AgentWidgetClient({
|
|
3099
3148
|
clientToken: 'ct_live_demo',
|
|
3100
|
-
apiUrl: 'https://api.runtype
|
|
3149
|
+
apiUrl: 'https://api.runtype.com',
|
|
3101
3150
|
});
|
|
3102
3151
|
// Simulate an initialized client session (resumeFlow reads sessionId off it).
|
|
3103
3152
|
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
@@ -3108,7 +3157,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3108
3157
|
await client.resumeFlow('exec_xyz', { toolu_A: { ok: true } });
|
|
3109
3158
|
|
|
3110
3159
|
// Session-authed sibling of /v1/client/chat — no Bearer key, sessionId in body.
|
|
3111
|
-
expect(capturedUrl).toBe('https://api.runtype
|
|
3160
|
+
expect(capturedUrl).toBe('https://api.runtype.com/v1/client/resume');
|
|
3112
3161
|
expect(capturedBody).toEqual({
|
|
3113
3162
|
executionId: 'exec_xyz',
|
|
3114
3163
|
toolOutputs: { toolu_A: { ok: true } },
|
|
@@ -3127,7 +3176,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3127
3176
|
|
|
3128
3177
|
const client = new AgentWidgetClient({
|
|
3129
3178
|
clientToken: 'ct_live_demo',
|
|
3130
|
-
apiUrl: 'https://api.runtype
|
|
3179
|
+
apiUrl: 'https://api.runtype.com/v1/dispatch',
|
|
3131
3180
|
});
|
|
3132
3181
|
// A live session so initSession() short-circuits instead of fetching /init.
|
|
3133
3182
|
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
@@ -3136,7 +3185,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3136
3185
|
};
|
|
3137
3186
|
await client.resumeFlow('exec_abc', {});
|
|
3138
3187
|
|
|
3139
|
-
expect(capturedUrl).toBe('https://api.runtype
|
|
3188
|
+
expect(capturedUrl).toBe('https://api.runtype.com/v1/client/resume');
|
|
3140
3189
|
});
|
|
3141
3190
|
|
|
3142
3191
|
it('refreshes the session via initSession() before resuming when stale (BugBot r3367875360)', async () => {
|
|
@@ -3148,7 +3197,7 @@ describe('AgentWidgetClient.resumeFlow', () => {
|
|
|
3148
3197
|
|
|
3149
3198
|
const client = new AgentWidgetClient({
|
|
3150
3199
|
clientToken: 'ct_live_demo',
|
|
3151
|
-
apiUrl: 'https://api.runtype
|
|
3200
|
+
apiUrl: 'https://api.runtype.com',
|
|
3152
3201
|
});
|
|
3153
3202
|
// A stale session that has already expired — a long WebMCP approval wait can
|
|
3154
3203
|
// outlive it, so resumeFlow must not trust this.clientSession directly.
|
|
@@ -3797,7 +3846,7 @@ describe('AgentWidgetClient - diff-only clientTools (client-token)', () => {
|
|
|
3797
3846
|
function makeClient(tools: unknown[]) {
|
|
3798
3847
|
const client = new AgentWidgetClient({
|
|
3799
3848
|
clientToken: 'ct_live_demo',
|
|
3800
|
-
apiUrl: 'https://api.runtype
|
|
3849
|
+
apiUrl: 'https://api.runtype.com',
|
|
3801
3850
|
});
|
|
3802
3851
|
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
3803
3852
|
sessionId: 'cs_diff_1',
|
|
@@ -3855,7 +3904,7 @@ describe('AgentWidgetClient - diff-only clientTools (client-token)', () => {
|
|
|
3855
3904
|
const live = [...TOOLS];
|
|
3856
3905
|
const client = new AgentWidgetClient({
|
|
3857
3906
|
clientToken: 'ct_live_demo',
|
|
3858
|
-
apiUrl: 'https://api.runtype
|
|
3907
|
+
apiUrl: 'https://api.runtype.com',
|
|
3859
3908
|
});
|
|
3860
3909
|
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
3861
3910
|
sessionId: 'cs_diff_1',
|
|
@@ -3882,7 +3931,7 @@ describe('AgentWidgetClient - diff-only clientTools (client-token)', () => {
|
|
|
3882
3931
|
];
|
|
3883
3932
|
const client = new AgentWidgetClient({
|
|
3884
3933
|
clientToken: 'ct_live_demo',
|
|
3885
|
-
apiUrl: 'https://api.runtype
|
|
3934
|
+
apiUrl: 'https://api.runtype.com',
|
|
3886
3935
|
});
|
|
3887
3936
|
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
3888
3937
|
sessionId: 'cs_diff_1',
|
|
@@ -3978,7 +4027,7 @@ describe('AgentWidgetClient - diff-only clientTools (client-token)', () => {
|
|
|
3978
4027
|
});
|
|
3979
4028
|
const client = new AgentWidgetClient({
|
|
3980
4029
|
clientToken: 'ct_live_demo',
|
|
3981
|
-
apiUrl: 'https://api.runtype
|
|
4030
|
+
apiUrl: 'https://api.runtype.com',
|
|
3982
4031
|
});
|
|
3983
4032
|
// Pre-seed a stale fingerprint bound to a prior session (no live session, so
|
|
3984
4033
|
// initSession() takes the mint path and fetches /client/init).
|
package/src/client.ts
CHANGED
|
@@ -1297,7 +1297,25 @@ export class AgentWidgetClient {
|
|
|
1297
1297
|
};
|
|
1298
1298
|
};
|
|
1299
1299
|
|
|
1300
|
+
const shouldEmitMessage = (msg: AgentWidgetMessage): boolean => {
|
|
1301
|
+
if (msg.role !== "assistant" || msg.variant) return true;
|
|
1302
|
+
|
|
1303
|
+
const hasContentParts =
|
|
1304
|
+
Array.isArray(msg.contentParts) && msg.contentParts.length > 0;
|
|
1305
|
+
const hasRawContent =
|
|
1306
|
+
typeof msg.rawContent === "string" && msg.rawContent.trim() !== "";
|
|
1307
|
+
const hasVisibleText =
|
|
1308
|
+
typeof msg.content === "string" && msg.content.trim() !== "";
|
|
1309
|
+
|
|
1310
|
+
// Do not surface assistant text bubbles that only contain whitespace.
|
|
1311
|
+
// Some providers emit newline-only text parts around a leading tool call;
|
|
1312
|
+
// rendering those as normal messages creates an empty bubble above the
|
|
1313
|
+
// tool card. Keep media/component/stop-reason messages renderable.
|
|
1314
|
+
return hasVisibleText || hasContentParts || hasRawContent || Boolean(msg.stopReason);
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1300
1317
|
const emitMessage = (msg: AgentWidgetMessage) => {
|
|
1318
|
+
if (!shouldEmitMessage(msg)) return;
|
|
1301
1319
|
onEvent({
|
|
1302
1320
|
type: "message",
|
|
1303
1321
|
message: cloneMessage(msg)
|