@runtypelabs/persona 3.29.1 → 3.30.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 +55 -0
- package/dist/codegen.cjs +6 -6
- package/dist/codegen.js +4 -4
- package/dist/index.cjs +47 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -7
- package/dist/index.d.ts +112 -7
- package/dist/index.global.js +59 -59
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +47 -47
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/plugin-kit.cjs +1 -0
- package/dist/plugin-kit.d.cts +98 -0
- package/dist/plugin-kit.d.ts +98 -0
- package/dist/plugin-kit.js +1 -0
- package/dist/smart-dom-reader.d.cts +107 -4
- package/dist/smart-dom-reader.d.ts +107 -4
- package/dist/theme-editor.cjs +38 -38
- package/dist/theme-editor.d.cts +111 -6
- package/dist/theme-editor.d.ts +111 -6
- package/dist/theme-editor.js +38 -38
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +2 -0
- package/dist/theme-reference.d.ts +2 -0
- package/dist/theme-reference.js +1 -1
- package/package.json +8 -2
- package/src/components/approval-bubble.test.ts +268 -0
- package/src/components/approval-bubble.ts +170 -20
- package/src/components/launcher.ts +6 -3
- package/src/components/tool-bubble.test.ts +39 -0
- package/src/components/tool-bubble.ts +4 -0
- package/src/index-core.ts +1 -0
- package/src/plugin-kit.test.ts +230 -0
- package/src/plugin-kit.ts +294 -0
- package/src/plugins/types.ts +49 -2
- package/src/session.test.ts +99 -0
- package/src/session.ts +14 -3
- package/src/theme-editor/preview-utils.test.ts +10 -0
- package/src/theme-editor/preview-utils.ts +29 -1
- package/src/theme-editor/sections.test.ts +17 -0
- package/src/theme-editor/sections.ts +31 -0
- package/src/theme-reference.ts +1 -1
- package/src/types/theme.ts +2 -0
- package/src/types.ts +59 -2
- package/src/ui.approval-plugin.test.ts +204 -0
- package/src/ui.ts +149 -16
- package/src/utils/theme.test.ts +6 -2
- package/src/utils/theme.ts +0 -8
- package/src/utils/tokens.ts +6 -1
- package/src/webmcp-bridge.test.ts +66 -0
- package/src/webmcp-bridge.ts +49 -0
|
@@ -27,6 +27,7 @@ vi.mock("@mcp-b/webmcp-polyfill", () => ({
|
|
|
27
27
|
// Import AFTER vi.mock so the bridge's dynamic import resolves to the mock.
|
|
28
28
|
import {
|
|
29
29
|
WebMcpBridge,
|
|
30
|
+
getWebMcpToolDisplayTitle,
|
|
30
31
|
isWebMcpToolName,
|
|
31
32
|
stripWebMcpPrefix,
|
|
32
33
|
computeClientToolsFingerprint,
|
|
@@ -39,6 +40,7 @@ type MockTool = {
|
|
|
39
40
|
name: string;
|
|
40
41
|
description?: string;
|
|
41
42
|
inputSchema?: object;
|
|
43
|
+
title?: string;
|
|
42
44
|
execute: (args: Record<string, unknown>, client: MockClient) => unknown;
|
|
43
45
|
};
|
|
44
46
|
|
|
@@ -47,10 +49,13 @@ const registry: { tools: MockTool[] } = { tools: [] };
|
|
|
47
49
|
/** A fake `document.modelContext` exposing the strict consumer surface. */
|
|
48
50
|
const makeModelContext = () => ({
|
|
49
51
|
async getTools() {
|
|
52
|
+
// Mirrors the real polyfill's getRegisteredToolInfos(): `title` is always
|
|
53
|
+
// present, "" when the tool didn't declare one; annotations are absent.
|
|
50
54
|
return registry.tools.map((t) => ({
|
|
51
55
|
name: t.name,
|
|
52
56
|
description: t.description ?? `mock ${t.name}`,
|
|
53
57
|
inputSchema: JSON.stringify(t.inputSchema ?? { type: "object" }),
|
|
58
|
+
title: t.title ?? "",
|
|
54
59
|
}));
|
|
55
60
|
},
|
|
56
61
|
async executeTool(
|
|
@@ -199,6 +204,67 @@ describe("WebMcpBridge.snapshotForDispatch", () => {
|
|
|
199
204
|
});
|
|
200
205
|
});
|
|
201
206
|
|
|
207
|
+
describe("WebMCP display titles", () => {
|
|
208
|
+
it("records declared titles on snapshot and exposes them via getWebMcpToolDisplayTitle", async () => {
|
|
209
|
+
registry.tools = [
|
|
210
|
+
fakeTool({ name: "add_to_cart", title: "Add to Cart" }),
|
|
211
|
+
fakeTool({ name: "search_products" }),
|
|
212
|
+
];
|
|
213
|
+
const bridge = new WebMcpBridge({ enabled: true });
|
|
214
|
+
await bridge.snapshotForDispatch();
|
|
215
|
+
expect(getWebMcpToolDisplayTitle("add_to_cart")).toBe("Add to Cart");
|
|
216
|
+
expect(getWebMcpToolDisplayTitle("webmcp:add_to_cart")).toBe("Add to Cart");
|
|
217
|
+
expect(getWebMcpToolDisplayTitle("search_products")).toBeUndefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("evicts a stale title when the tool re-registers without one", async () => {
|
|
221
|
+
registry.tools = [fakeTool({ name: "add_to_cart", title: "Add to Cart" })];
|
|
222
|
+
const bridge = new WebMcpBridge({ enabled: true });
|
|
223
|
+
await bridge.snapshotForDispatch();
|
|
224
|
+
expect(getWebMcpToolDisplayTitle("add_to_cart")).toBe("Add to Cart");
|
|
225
|
+
|
|
226
|
+
registry.tools = [fakeTool({ name: "add_to_cart" })];
|
|
227
|
+
await bridge.snapshotForDispatch();
|
|
228
|
+
expect(getWebMcpToolDisplayTitle("add_to_cart")).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("evicts the title when the tool is removed from the registry entirely", async () => {
|
|
232
|
+
registry.tools = [
|
|
233
|
+
fakeTool({ name: "add_to_cart", title: "Add to Cart" }),
|
|
234
|
+
fakeTool({ name: "search_products", title: "Search the catalog" }),
|
|
235
|
+
];
|
|
236
|
+
const bridge = new WebMcpBridge({ enabled: true });
|
|
237
|
+
await bridge.snapshotForDispatch();
|
|
238
|
+
expect(getWebMcpToolDisplayTitle("add_to_cart")).toBe("Add to Cart");
|
|
239
|
+
|
|
240
|
+
registry.tools = [fakeTool({ name: "search_products", title: "Search the catalog" })];
|
|
241
|
+
await bridge.snapshotForDispatch();
|
|
242
|
+
expect(getWebMcpToolDisplayTitle("add_to_cart")).toBeUndefined();
|
|
243
|
+
expect(getWebMcpToolDisplayTitle("search_products")).toBe("Search the catalog");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("passes the declared title to the confirm gate", async () => {
|
|
247
|
+
registry.tools = [fakeTool({ name: "add_to_cart", title: "Add to Cart" })];
|
|
248
|
+
const onConfirm = vi.fn(async () => true);
|
|
249
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
250
|
+
const r = await bridge.executeToolCall("webmcp:add_to_cart", {});
|
|
251
|
+
expect(r.isError).toBeFalsy();
|
|
252
|
+
expect(onConfirm).toHaveBeenCalledWith(
|
|
253
|
+
expect.objectContaining({ toolName: "add_to_cart", title: "Add to Cart" })
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("omits title from the confirm gate when the tool didn't declare one", async () => {
|
|
258
|
+
registry.tools = [fakeTool({ name: "add_to_cart" })];
|
|
259
|
+
const onConfirm = vi.fn(async () => true);
|
|
260
|
+
const bridge = new WebMcpBridge({ enabled: true, onConfirm });
|
|
261
|
+
await bridge.executeToolCall("webmcp:add_to_cart", {});
|
|
262
|
+
expect(onConfirm).toHaveBeenCalledWith(
|
|
263
|
+
expect.not.objectContaining({ title: expect.anything() })
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
202
268
|
describe("WebMcpBridge.executeToolCall", () => {
|
|
203
269
|
it("returns isError when WebMCP is not enabled", async () => {
|
|
204
270
|
const bridge = new WebMcpBridge({} as AgentWidgetWebMcpConfig);
|
package/src/webmcp-bridge.ts
CHANGED
|
@@ -66,6 +66,14 @@ interface ModelContextToolInfo {
|
|
|
66
66
|
description: string;
|
|
67
67
|
/** JSON-encoded JSON Schema for the tool's input. */
|
|
68
68
|
inputSchema?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Display title declared on the tool (`ToolDescriptor.title` in the WebMCP
|
|
71
|
+
* spec). The polyfill returns `""` when the tool didn't declare one. Note:
|
|
72
|
+
* `annotations` (incl. the legacy `annotations.title`) are NOT exposed on
|
|
73
|
+
* this strict consumer surface — top-level `title` is the only display-name
|
|
74
|
+
* channel available to us.
|
|
75
|
+
*/
|
|
76
|
+
title?: string;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
interface ModelContextCoreLike {
|
|
@@ -77,6 +85,43 @@ interface ModelContextCoreLike {
|
|
|
77
85
|
): Promise<string | null>;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Page-global map of bare tool name → declared display title
|
|
90
|
+
* (`ToolDescriptor.title`). `document.modelContext` is page-global, so a
|
|
91
|
+
* single map shared across widget/bridge instances is semantically correct.
|
|
92
|
+
* Refreshed on every registry read (`snapshotForDispatch` / `executeToolCall`)
|
|
93
|
+
* and consumed by the approval bubble's summary line via
|
|
94
|
+
* `getWebMcpToolDisplayTitle`.
|
|
95
|
+
*/
|
|
96
|
+
const webMcpToolDisplayTitles = new Map<string, string>();
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Record declared display titles from a fresh `getTools()` read. The map is
|
|
100
|
+
* rebuilt from scratch — callers always pass the FULL registry snapshot — so
|
|
101
|
+
* a tool that unregistered or dropped its title can't leave a stale label
|
|
102
|
+
* behind. Exported for tests; production callers are the bridge's registry
|
|
103
|
+
* reads.
|
|
104
|
+
*/
|
|
105
|
+
export const recordWebMcpToolDisplayTitles = (
|
|
106
|
+
infos: ModelContextToolInfo[],
|
|
107
|
+
): void => {
|
|
108
|
+
webMcpToolDisplayTitles.clear();
|
|
109
|
+
for (const info of infos) {
|
|
110
|
+
const title = info.title?.trim();
|
|
111
|
+
if (title) webMcpToolDisplayTitles.set(info.name, title);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Look up the display title a page tool declared via the WebMCP spec's
|
|
117
|
+
* `ToolDescriptor.title`. Accepts wire (`webmcp:add_to_cart`) or bare
|
|
118
|
+
* (`add_to_cart`) names. Returns `undefined` when the tool didn't declare
|
|
119
|
+
* one (callers fall back to humanizing the tool name).
|
|
120
|
+
*/
|
|
121
|
+
export const getWebMcpToolDisplayTitle = (
|
|
122
|
+
toolName: string,
|
|
123
|
+
): string | undefined => webMcpToolDisplayTitles.get(stripWebMcpPrefix(toolName));
|
|
124
|
+
|
|
80
125
|
const log = {
|
|
81
126
|
warn(message: string, ...rest: unknown[]): void {
|
|
82
127
|
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
@@ -220,6 +265,7 @@ export class WebMcpBridge {
|
|
|
220
265
|
log.warn("getTools() threw — shipping an empty WebMCP snapshot.", err);
|
|
221
266
|
return [];
|
|
222
267
|
}
|
|
268
|
+
recordWebMcpToolDisplayTitles(infos);
|
|
223
269
|
|
|
224
270
|
const pageOrigin = typeof location !== "undefined" ? location.origin : "";
|
|
225
271
|
|
|
@@ -295,6 +341,7 @@ export class WebMcpBridge {
|
|
|
295
341
|
const message = err instanceof Error ? err.message : String(err);
|
|
296
342
|
return errorResult(`Failed to read WebMCP registry: ${message}`);
|
|
297
343
|
}
|
|
344
|
+
recordWebMcpToolDisplayTitles(infos);
|
|
298
345
|
const info = infos.find((candidate) => candidate.name === bareName);
|
|
299
346
|
|
|
300
347
|
if (!info) {
|
|
@@ -323,10 +370,12 @@ export class WebMcpBridge {
|
|
|
323
370
|
|
|
324
371
|
// Confirm-by-default gate. Every `webmcp:*` call routes through here,
|
|
325
372
|
// regardless of `annotations.readOnlyHint`.
|
|
373
|
+
const displayTitle = getWebMcpToolDisplayTitle(bareName);
|
|
326
374
|
const gateInfo: WebMcpConfirmInfo = {
|
|
327
375
|
toolName: bareName,
|
|
328
376
|
args,
|
|
329
377
|
description: info.description,
|
|
378
|
+
...(displayTitle ? { title: displayTitle } : {}),
|
|
330
379
|
reason: "gate",
|
|
331
380
|
};
|
|
332
381
|
if (!(await this.requestConfirm(gateInfo))) {
|