@webskill/sdk 0.13.0 → 0.15.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 (60) hide show
  1. package/dist/agent.d.ts +3 -3
  2. package/dist/agent.js +2442 -3
  3. package/dist/{dist-ExSQky4C.js → approval-BnLLCOG0.js} +277 -2934
  4. package/dist/browser.d.ts +327 -8
  5. package/dist/browser.js +1017 -51
  6. package/dist/{openUiLibrary-BKXW7Iwx-CWWBVOkE.js → dist-CTsxblSD.js} +496 -28
  7. package/dist/{echarts-De78wXqV.js → echarts-BE7oV_Dl.js} +1 -1
  8. package/dist/{env-8cY40DXB-CGnEVZby.js → env-Bj1MI2Ww.js} +1 -1
  9. package/dist/errors-BDZNpC13.js +22 -0
  10. package/dist/{eventTypes-FllCrX-Z-DNDeHWoG.js → eventTypes-Y07N8IAC.js} +14 -2
  11. package/dist/external-_ZRQe-V9.js +53 -0
  12. package/dist/governance.d.ts +15 -3
  13. package/dist/governance.js +86 -4
  14. package/dist/{index-iBm9tJL_.d.ts → index-CQYo4tcT.d.ts} +469 -10
  15. package/dist/{index-C3XdItd_.d.ts → index-CU2md8R1.d.ts} +26 -18
  16. package/dist/{index-DkvBhJQy.d.ts → index-Oc3H89TJ.d.ts} +204 -4
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +1001 -5
  19. package/dist/kind-DxgS8LM-.js +77 -0
  20. package/dist/linkedDocument-xNXF-z8n.js +94 -0
  21. package/dist/llm-B7lLH0ZI.js +1767 -0
  22. package/dist/mcp.d.ts +56 -3
  23. package/dist/mcp.js +75 -5
  24. package/dist/memoryArtifactStore-D6dLeY55.js +48 -0
  25. package/dist/miniChart-owdCdaw-.js +533 -0
  26. package/dist/node.d.ts +3 -3
  27. package/dist/node.js +110 -13
  28. package/dist/openUiLibrary-CY_ADoe0.js +30 -0
  29. package/dist/openUiSpecLang-DuOCdw9G.js +1849 -0
  30. package/dist/pathSecurity-B1owvJAF.js +65 -0
  31. package/dist/processSandboxEntry.js +5 -1
  32. package/dist/renderResult-D9Q-Vu2x.js +143 -0
  33. package/dist/{rolldown-runtime-BOF7iYI8.js → rolldown-runtime-BHkdefai.js} +1 -2
  34. package/dist/sandboxWorkerEntry.js +5 -1
  35. package/dist/{dist-Bev6i6Ip.js → skill-CAJMsLod.js} +44 -314
  36. package/dist/{skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts} +14 -2
  37. package/dist/surface-DVGiCmwq.js +145 -0
  38. package/dist/testing.d.ts +1 -1
  39. package/dist/testing.js +64 -3
  40. package/dist/{types-DLctJep_-B5G4uk2u.d.ts → types-C3V6lAeO-BIf7UB2e.d.ts} +79 -4
  41. package/dist/types-TUydnxaj.js +78 -0
  42. package/dist/ui-react.d.ts +12 -5
  43. package/dist/ui-react.js +128548 -4729
  44. package/dist/ui-vue.d.ts +1 -1
  45. package/dist/ui-vue.js +11 -2
  46. package/dist/ui.d.ts +3 -3
  47. package/dist/ui.js +1254 -3
  48. package/dist/urlSafety-CiSuCJvX.js +65 -0
  49. package/dist/webSkillApi-DOOyz2G5.js +106 -0
  50. package/dist/{webskillLitCatalog-D_zCqeQF-C9lrvvMr.js → webskillLitCatalog-DSqVFQjK.js} +11 -3
  51. package/package.json +1 -1
  52. package/dist/catalogComponents-BgAJN0p8-CYEXSk45.js +0 -124384
  53. package/dist/client-BCM6Z3yq-qUQNkKrK.js +0 -7787
  54. package/dist/dist-DqcL6jKO.js +0 -3509
  55. package/dist/dist-DxyxmXPU-DGC-dpXf.js +0 -5621
  56. package/dist/dist-GK6dtjRv.js +0 -1493
  57. package/dist/memoryArtifactStore-52Zn9npI-LbCQaqyx.js +0 -86
  58. package/dist/stdio-CFMoANJJ-BxrTeXh7.js +0 -31
  59. package/dist/testing-WPTyXQYt.js +0 -142
  60. package/dist/types-WovEf4ED-CZSDiiBU.js +0 -6215
package/dist/ui.js CHANGED
@@ -1,4 +1,1255 @@
1
- import { et as buildRenderResult } from "./dist-ExSQky4C.js";
2
- import { $ as uiPreset, A as ensureStyles, B as mountViewerComponents, C as applySuggestion, Ct as resolveSurfaceFormTexts, D as collectSpecActions, Dt as uiCatalog, E as collectScopedValues, Et as toEchartsOption, F as fromVercelToolResult, G as renderRenderResult, H as qualifyFieldName, I as gaugePercent, J as toA2uiSurfaceAction, K as resolveColumnWidths, L as interactionToUiSpec, M as fromA2uiSpecAction, N as fromA2uiSurfaceAction, O as collectValues, P as fromUiSurfaceActionDispatch, Q as toVercelToolInvocation, R as loadOpenUiPeers, S as WebFormBridge, St as resolveInteractionTexts, T as collectFormScopes, Tt as shapeInteractionValue, U as renderBlocks, V as normalizeColumnWidths, W as renderMiniChart, X as toOpenUiSpecLang, Y as toJsonRenderSpec, Z as toUiSurfaceActionDispatch, _ as VIEWER_FALLBACK_ATTR, _t as defineUiCatalog, a as A2UI_VERSION, at as DEFAULT_CHART_FONT_SIZES, b as WEBSKILL_STYLES_CSS, bt as renderMiniMarkdown, c as DOCUMENT_COMPONENTS, ct as DEFAULT_SURFACE_HOST_CONTROL_TEXTS, d as SPEC_TABLE_MIN_COLUMN_VAR, dt as UI_CATALOG_PROMPT_BUDGET_BYTES, f as SPEC_TABLE_MIN_COLUMN_WIDTH, ft as WEBSKILL_A2UI_CATALOG_ID, g as VIEWER_COMPONENT_ATTR, gt as chartSpecFromProps, h as VERCEL_INTERACTION_TOOL_NAME, ht as buildA2uiCatalogDefinition, i as A2UI_SURFACE_ACTION, it as CATALOG_SCHEMA_MAX, j as evaluateFieldCondition, k as createUiCatalogToolSource, l as MAX_CONDITION_DEPTH, lt as PLANNED_INCREMENT, m as UI_PRESET_NAMES, mt as a2uiComponentShapes, n as A2UI_SPEC_ACTION, nt as CATALOG_BUDGET_STAGE, o as CHART_PALETTE, ot as DEFAULT_INTERACTION_TEXTS, p as UI_PRESETS, pt as a2uiComponentSchema, q as toA2uiSpecMessages, r as A2UI_SPEC_FORM_PATH, rt as CATALOG_PROMPT_MAX, s as DESCRIBE_UI_PRESET_TOOL, st as DEFAULT_SURFACE_FORM_TEXTS, t as A2UI_BASIC_CATALOG_ID, tt as A2UI_COMMON_TYPES, u as RENDER_UI_TOOL, ut as UI_CATALOG_GROUPS, v as VIEWER_PROPS_ATTR, vt as interactionToFormModel, w as chartToTable, wt as resolveSurfaceHostControlTexts, x as WEBSKILL_SURFACE_ACTION, xt as resolveChartFontSizes, y as VercelUiBridge, yt as mountEchart, z as loadWebSkillLitCatalog } from "./dist-DqcL6jKO.js";
1
+ import { t as WebSkillError } from "./errors-BDZNpC13.js";
2
+ import { t as buildRenderResult } from "./renderResult-D9Q-Vu2x.js";
3
+ import { a as applySuggestion, c as DEFAULT_SURFACE_FORM_TEXTS, d as resolveInteractionTexts, f as resolveSurfaceFormTexts, i as renderMiniMarkdown, l as DEFAULT_SURFACE_HOST_CONTROL_TEXTS, m as shapeInteractionValue, n as chartToTable, o as collectValues, p as resolveSurfaceHostControlTexts, r as renderMiniChart, s as DEFAULT_INTERACTION_TEXTS, t as CHART_PALETTE, u as interactionToFormModel } from "./miniChart-owdCdaw-.js";
4
+ import { C as chartSpecFromProps, E as toEchartsOption, S as DEFAULT_CHART_FONT_SIZES, T as resolveChartFontSizes, _ as CATALOG_BUDGET_STAGE, a as SPEC_TABLE_MIN_COLUMN_WIDTH, b as PLANNED_INCREMENT, c as MAX_CONDITION_DEPTH, d as collectScopedValues, f as qualifyFieldName, g as defineUiCatalog, h as uiCatalog, i as SPEC_TABLE_MIN_COLUMN_VAR, l as evaluateFieldCondition, m as UI_CATALOG_PROMPT_BUDGET_BYTES, n as interactionToUiSpec, o as normalizeColumnWidths, p as UI_CATALOG_GROUPS, r as toJsonRenderSpec, s as resolveColumnWidths, t as toOpenUiSpecLang, u as collectFormScopes, v as CATALOG_PROMPT_MAX, w as mountEchart, x as gaugePercent, y as CATALOG_SCHEMA_MAX } from "./openUiSpecLang-DuOCdw9G.js";
3
5
 
4
- export { A2UI_BASIC_CATALOG_ID, A2UI_COMMON_TYPES, A2UI_SPEC_ACTION, A2UI_SPEC_FORM_PATH, A2UI_SURFACE_ACTION, A2UI_VERSION, CATALOG_BUDGET_STAGE, CATALOG_PROMPT_MAX, CATALOG_SCHEMA_MAX, CHART_PALETTE, DEFAULT_CHART_FONT_SIZES, DEFAULT_INTERACTION_TEXTS, DEFAULT_SURFACE_FORM_TEXTS, DEFAULT_SURFACE_HOST_CONTROL_TEXTS, DESCRIBE_UI_PRESET_TOOL, DOCUMENT_COMPONENTS, MAX_CONDITION_DEPTH, PLANNED_INCREMENT, RENDER_UI_TOOL, SPEC_TABLE_MIN_COLUMN_VAR, SPEC_TABLE_MIN_COLUMN_WIDTH, UI_CATALOG_GROUPS, UI_CATALOG_PROMPT_BUDGET_BYTES, UI_PRESETS, UI_PRESET_NAMES, VERCEL_INTERACTION_TOOL_NAME, VIEWER_COMPONENT_ATTR, VIEWER_FALLBACK_ATTR, VIEWER_PROPS_ATTR, VercelUiBridge, WEBSKILL_A2UI_CATALOG_ID, WEBSKILL_STYLES_CSS, WEBSKILL_SURFACE_ACTION, WebFormBridge, a2uiComponentSchema, a2uiComponentShapes, applySuggestion, buildA2uiCatalogDefinition, buildRenderResult, chartSpecFromProps, chartToTable, collectFormScopes, collectScopedValues, collectSpecActions, collectValues, createUiCatalogToolSource, defineUiCatalog, ensureStyles, evaluateFieldCondition, fromA2uiSpecAction, fromA2uiSurfaceAction, fromUiSurfaceActionDispatch, fromVercelToolResult, gaugePercent, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, mountEchart, mountViewerComponents, normalizeColumnWidths, qualifyFieldName, renderBlocks, renderMiniChart, renderMiniMarkdown, renderRenderResult, resolveChartFontSizes, resolveColumnWidths, resolveInteractionTexts, resolveSurfaceFormTexts, resolveSurfaceHostControlTexts, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toEchartsOption, toJsonRenderSpec, toOpenUiSpecLang, toUiSurfaceActionDispatch, toVercelToolInvocation, uiCatalog, uiPreset };
6
+ //#region ../ui/src/web/resultRenderer.ts
7
+ /** RenderBlock → DOM(markdown/json/table/image/file/chart 六种) */
8
+ function renderBlocks(container, blocks, doc) {
9
+ for (const block of blocks) switch (block.type) {
10
+ case "chart":
11
+ container.appendChild(renderMiniChart(block.chart, doc));
12
+ break;
13
+ case "markdown":
14
+ container.appendChild(renderMiniMarkdown(block.text, doc));
15
+ break;
16
+ case "json": {
17
+ const pre = doc.createElement("pre");
18
+ pre.className = "webskill-result__json";
19
+ pre.textContent = JSON.stringify(block.data, null, 2);
20
+ container.appendChild(pre);
21
+ break;
22
+ }
23
+ case "table": {
24
+ const table = doc.createElement("table");
25
+ const thead = doc.createElement("thead");
26
+ const headRow = doc.createElement("tr");
27
+ for (const column of block.columns) {
28
+ const th = doc.createElement("th");
29
+ th.textContent = column;
30
+ headRow.appendChild(th);
31
+ }
32
+ thead.appendChild(headRow);
33
+ table.appendChild(thead);
34
+ const tbody = doc.createElement("tbody");
35
+ for (const row of block.rows) {
36
+ const tr = doc.createElement("tr");
37
+ for (const cell of row) {
38
+ const td = doc.createElement("td");
39
+ td.textContent = typeof cell === "object" ? JSON.stringify(cell) : String(cell ?? "");
40
+ tr.appendChild(td);
41
+ }
42
+ tbody.appendChild(tr);
43
+ }
44
+ table.appendChild(tbody);
45
+ container.appendChild(table);
46
+ break;
47
+ }
48
+ case "image": {
49
+ const img = doc.createElement("img");
50
+ img.src = block.url;
51
+ img.alt = block.alt ?? "";
52
+ container.appendChild(img);
53
+ break;
54
+ }
55
+ case "file": {
56
+ const div = doc.createElement("div");
57
+ div.className = "webskill-result__file";
58
+ const meta = [block.mimeType, block.size !== void 0 ? `${block.size} bytes` : void 0].filter(Boolean).join(", ");
59
+ div.textContent = meta ? `${block.path} (${meta})` : block.path;
60
+ container.appendChild(div);
61
+ break;
62
+ }
63
+ }
64
+ }
65
+ /** 完整 RenderResultRequest → DOM(summary 头 + blocks) */
66
+ function renderRenderResult(request, doc) {
67
+ const root = doc.createElement("div");
68
+ root.className = "webskill-result";
69
+ if (request.title) {
70
+ const title = doc.createElement("div");
71
+ title.className = "webskill-form__title";
72
+ title.textContent = request.title;
73
+ root.appendChild(title);
74
+ }
75
+ if (request.summary) {
76
+ const summary = doc.createElement("div");
77
+ summary.className = "webskill-result__summary";
78
+ summary.textContent = request.summary;
79
+ root.appendChild(summary);
80
+ }
81
+ renderBlocks(root, request.blocks, doc);
82
+ return root;
83
+ }
84
+
85
+ //#endregion
86
+ //#region ../ui/src/viewer/presetComponents.ts
87
+ /**
88
+ * viewer 的宿主预置组件(0.11.0 分册 18 FR-18.5 / 分册 19)。
89
+ *
90
+ * 技能**不写 JS**:文档 HTML 里只留占位,由这里挂载真实组件。
91
+ * 交互只落在图表上——表格排序的例子已作废(分册 29 §2 登记 #13)。
92
+ *
93
+ * 三条硬约束:
94
+ * - props 用 **catalog 自己的 zod schema** 解析,同形因此是机制上成立的,
95
+ * 不是靠一句「记得同步」的注释(FR-19.2);
96
+ * - 值一律经 `textContent` 落地,**永不** `innerHTML`(FR-19.4 / AC-19.5);
97
+ * - 白名单外的名字只渲染降级提示,**不触发任何按名 import**(FR-19.3)。
98
+ */
99
+ /** 占位元素的属性名,模板与挂载方共用(FR-19.1 的契约) */
100
+ const VIEWER_COMPONENT_ATTR = "data-webskill-component";
101
+ const VIEWER_PROPS_ATTR = "data-webskill-props";
102
+ /** 降级提示的标记;模板 CSS 与判据都认这个属性,不认某个 class 名 */
103
+ const VIEWER_FALLBACK_ATTR = "data-webskill-fallback";
104
+ /** 文档面可用的组件白名单(FR-19.3)。字面量数组——不接受任何按名动态加载 */
105
+ const DOCUMENT_COMPONENTS = [
106
+ "Chart",
107
+ "Table",
108
+ "Metric",
109
+ "Gauge",
110
+ "KeyValue"
111
+ ];
112
+ const WHITELIST = new Set(DOCUMENT_COMPONENTS);
113
+ /**
114
+ * 扫描 `root` 下的占位并挂载预置组件。
115
+ *
116
+ * 逐个占位独立处理:一个坏占位只降级它自己,不会让整篇文档变空白。
117
+ */
118
+ function mountViewerComponents(root, options) {
119
+ const handles = [];
120
+ for (const node of Array.from(root.querySelectorAll(`[${VIEWER_COMPONENT_ATTR}]`))) {
121
+ const element = node;
122
+ const name = element.getAttribute("data-webskill-component") ?? "";
123
+ if (!WHITELIST.has(name)) {
124
+ fallback(element, `Unsupported component "${name}"`);
125
+ continue;
126
+ }
127
+ const props = readProps(element, name);
128
+ if (!props) continue;
129
+ const handle = render(element, name, props, options?.fontSizes);
130
+ if (handle) handles.push(handle);
131
+ }
132
+ return { dispose: () => {
133
+ for (const handle of handles) handle.dispose();
134
+ handles.length = 0;
135
+ } };
136
+ }
137
+ /** 用 catalog 的 schema 解析:多余字段被丢弃,缺省值与 catalog 完全一致 */
138
+ function readProps(element, name) {
139
+ const raw = element.getAttribute(VIEWER_PROPS_ATTR);
140
+ if (raw === null || raw === "") {
141
+ fallback(element, `Component "${name}" is missing ${VIEWER_PROPS_ATTR}`);
142
+ return;
143
+ }
144
+ let parsed;
145
+ try {
146
+ parsed = JSON.parse(raw);
147
+ } catch {
148
+ fallback(element, `Component "${name}" has invalid props JSON`);
149
+ return;
150
+ }
151
+ const schema = uiCatalog.component(name)?.props;
152
+ if (!schema) {
153
+ fallback(element, `Component "${name}" is not declared in the catalog`);
154
+ return;
155
+ }
156
+ const result = schema.safeParse(parsed);
157
+ if (!result.success) {
158
+ fallback(element, `Component "${name}" has props the catalog rejects`);
159
+ return;
160
+ }
161
+ return result.data;
162
+ }
163
+ function render(element, name, props, fontSizes) {
164
+ const doc = element.ownerDocument;
165
+ element.replaceChildren();
166
+ switch (name) {
167
+ case "Chart": {
168
+ const handle = mountEchart(element);
169
+ handle.setChart(chartSpecFromProps(props), fontSizes);
170
+ return handle;
171
+ }
172
+ case "Table":
173
+ renderBlocks(element, [{
174
+ type: "table",
175
+ columns: props["columns"],
176
+ rows: props["rows"]
177
+ }], doc);
178
+ return;
179
+ case "Metric": {
180
+ append(doc, element, "span", String(props["label"]), { "data-webskill-metric": "label" });
181
+ append(doc, element, "strong", String(props["value"]), { "data-webskill-metric": "value" });
182
+ const change = props["change"];
183
+ if (typeof change === "string") append(doc, element, "span", change, {
184
+ "data-webskill-metric": "change",
185
+ "data-trend": String(props["trend"] ?? "neutral")
186
+ });
187
+ return;
188
+ }
189
+ case "Gauge": {
190
+ const value = props["value"];
191
+ const min = props["min"];
192
+ const max = props["max"];
193
+ const label = typeof props["label"] === "string" ? props["label"] : "";
194
+ element.setAttribute("data-tone", String(props["tone"]));
195
+ if (label !== "") append(doc, element, "span", label, { "data-webskill-gauge": "label" });
196
+ const fill = append(doc, append(doc, element, "div", "", {
197
+ role: "meter",
198
+ "aria-label": label === "" ? "Gauge" : label,
199
+ "aria-valuenow": String(value),
200
+ "aria-valuemin": String(min),
201
+ "aria-valuemax": String(max),
202
+ "data-webskill-gauge": "track"
203
+ }), "div", "", { "data-webskill-gauge": "fill" });
204
+ fill.style.width = `${gaugePercent(value, min, max)}%`;
205
+ append(doc, element, "span", String(value), { "data-webskill-gauge": "value" });
206
+ return;
207
+ }
208
+ case "KeyValue": {
209
+ const dl = append(doc, element, "dl", "", {});
210
+ for (const item of props["items"]) {
211
+ append(doc, dl, "dt", item.label, {});
212
+ append(doc, dl, "dd", item.value, {});
213
+ }
214
+ return;
215
+ }
216
+ }
217
+ }
218
+ function append(doc, parent, tag, text, attrs) {
219
+ const el = doc.createElement(tag);
220
+ for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, value);
221
+ if (text !== "") el.textContent = text;
222
+ parent.appendChild(el);
223
+ return el;
224
+ }
225
+ /**
226
+ * 可见降级(AC-19.4)。
227
+ *
228
+ * 静默留空是最坏的结果:文档看着「渲染成功」,作者却不知道自己写错了组件名。
229
+ * 原有内容整体替换,避免半截渲染与提示并存。
230
+ */
231
+ function fallback(element, reason) {
232
+ element.setAttribute(VIEWER_FALLBACK_ATTR, "true");
233
+ element.textContent = reason;
234
+ }
235
+
236
+ //#endregion
237
+ //#region ../ui/src/web/styles.ts
238
+ /** 最小样式(BEM class,应用可整体覆写) */
239
+ const WEBSKILL_STYLES_CSS = `
240
+ .webskill-form { display: block; padding: 12px; border: 1px solid #d0d0d0; border-radius: 8px; font-family: system-ui, sans-serif; max-width: 420px; }
241
+ .webskill-form__title { margin: 0 0 8px; font-size: 15px; font-weight: 600; }
242
+ .webskill-form__message { margin: 0 0 10px; font-size: 14px; }
243
+ .webskill-form__control { margin-bottom: 10px; display: flex; flex-direction: column; gap: 4px; }
244
+ .webskill-form__control--invalid .webskill-form__input { border-color: #d33; }
245
+ .webskill-form__label { font-size: 13px; font-weight: 500; }
246
+ .webskill-form__label-required::after { content: ' *'; color: #d33; }
247
+ .webskill-form__description { font-size: 12px; color: #666; }
248
+ .webskill-form__error { font-size: 12px; color: #d33; display: none; }
249
+ .webskill-form__control--invalid .webskill-form__error { display: block; }
250
+ .webskill-form__input { padding: 6px 8px; border: 1px solid #bbb; border-radius: 4px; font-size: 14px; font-family: inherit; }
251
+ .webskill-form__suggestion { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #666; }
252
+ .webskill-form__suggestion-use { padding: 1px 8px; border-radius: 4px; border: 1px solid #bbb; background: #fff; cursor: pointer; font-size: 12px; }
253
+ .webskill-form__actions { display: flex; gap: 8px; margin-top: 12px; }
254
+ .webskill-form__button { padding: 6px 14px; border-radius: 4px; border: 1px solid #bbb; background: #fff; cursor: pointer; font-size: 14px; }
255
+ .webskill-form__button--primary { background: #2563eb; border-color: #2563eb; color: #fff; }
256
+ .webskill-form__button--ghost { background: transparent; }
257
+ .webskill-result { font-family: system-ui, sans-serif; font-size: 14px; }
258
+ .webskill-result__summary { font-size: 12px; color: #666; margin-bottom: 6px; }
259
+ .webskill-result__json { background: #f5f5f5; padding: 8px; border-radius: 4px; overflow-x: auto; }
260
+ .webskill-result__file { padding: 4px 0; font-size: 13px; }
261
+ .webskill-md pre { background: #f5f5f5; padding: 8px; border-radius: 4px; overflow-x: auto; }
262
+ .webskill-md h1, .webskill-md h2, .webskill-md h3 { margin: 8px 0 4px; }
263
+ .webskill-md p { margin: 4px 0; }
264
+ .webskill-md ul { margin: 4px 0; padding-left: 20px; }
265
+ `;
266
+ /** 每个 document 只注入一次 */
267
+ function ensureStyles(doc) {
268
+ if (doc.querySelector("style[data-webskill]")) return;
269
+ const style = doc.createElement("style");
270
+ style.setAttribute("data-webskill", "");
271
+ style.textContent = WEBSKILL_STYLES_CSS;
272
+ doc.head.appendChild(style);
273
+ }
274
+
275
+ //#endregion
276
+ //#region ../ui/src/web/webFormBridge.ts
277
+ const CONTROL_TAG = "data-webskill-control";
278
+ /**
279
+ * 框架无关原生 DOM 的 UiBridge:request 渲染表单并返回 Promise
280
+ * (提交 resolve / 取消 {cancelled:true}),完成后卸载 DOM。
281
+ * document 可注入(jsdom 测试);BEM class 样式可覆写。
282
+ */
283
+ var WebFormBridge = class {
284
+ #mount;
285
+ #doc;
286
+ #styles;
287
+ #progressEl;
288
+ #texts;
289
+ #interactionTexts;
290
+ /** 等待中的 request 取消句柄(id → 触发 cancelled 并卸载 DOM) */
291
+ #pending = /* @__PURE__ */ new Map();
292
+ constructor(options) {
293
+ this.#mount = options.mount;
294
+ this.#doc = options.document ?? options.mount.ownerDocument;
295
+ this.#styles = options.styles ?? true;
296
+ this.#interactionTexts = options.texts;
297
+ this.#texts = resolveSurfaceFormTexts(options.texts);
298
+ }
299
+ request(input) {
300
+ if (this.#styles) ensureStyles(this.#doc);
301
+ this.#clearProgress();
302
+ const model = interactionToFormModel(input, this.#interactionTexts);
303
+ const { form, cleanup } = this.#renderForm(model);
304
+ return new Promise((resolve) => {
305
+ const cancel = () => {
306
+ cleanup();
307
+ this.#pending.delete(input.id);
308
+ resolve({
309
+ id: input.id,
310
+ cancelled: true
311
+ });
312
+ };
313
+ this.#pending.set(input.id, cancel);
314
+ form.addEventListener("submit", (event) => {
315
+ event.preventDefault();
316
+ const { values, missingRequired } = collectValues(model.controls, form);
317
+ if (missingRequired.length > 0) {
318
+ for (const name of missingRequired) {
319
+ const wrapper = form.querySelector(`[${CONTROL_TAG}="${name}"]`)?.closest(".webskill-form__control");
320
+ wrapper?.classList.add("webskill-form__control--invalid");
321
+ wrapper?.querySelector(".webskill-form__input")?.setAttribute("aria-invalid", "true");
322
+ }
323
+ return;
324
+ }
325
+ this.#pending.delete(input.id);
326
+ cleanup();
327
+ resolve({
328
+ id: input.id,
329
+ value: shapeInteractionValue(model, values)
330
+ });
331
+ });
332
+ form.querySelector("[data-webskill-cancel]")?.addEventListener("click", (event) => {
333
+ event.preventDefault();
334
+ cancel();
335
+ });
336
+ this.#mount.appendChild(form);
337
+ });
338
+ }
339
+ /** 尽力取消等待中的 request(超时后清理遗留表单) */
340
+ cancel(id) {
341
+ this.#pending.get(id)?.();
342
+ }
343
+ async renderResult(input) {
344
+ if (this.#styles) ensureStyles(this.#doc);
345
+ this.#clearProgress();
346
+ this.#mount.querySelector(".webskill-result")?.remove();
347
+ this.#mount.appendChild(renderRenderResult(input, this.#doc));
348
+ }
349
+ async progress(input) {
350
+ if (!this.#progressEl) {
351
+ this.#progressEl = this.#doc.createElement("div");
352
+ this.#progressEl.className = "webskill-result__summary";
353
+ this.#mount.appendChild(this.#progressEl);
354
+ }
355
+ const pct = input.value !== void 0 ? ` (${Math.round(input.value * 100)}%)` : "";
356
+ this.#progressEl.textContent = `${input.message}${pct}`;
357
+ }
358
+ /** 新一轮交互/结果渲染开始时移除遗留 progress 元素 */
359
+ #clearProgress() {
360
+ this.#progressEl?.remove();
361
+ this.#progressEl = void 0;
362
+ }
363
+ #renderForm(model) {
364
+ const doc = this.#doc;
365
+ const form = doc.createElement("form");
366
+ form.className = "webskill-form";
367
+ form.noValidate = true;
368
+ if (model.title) {
369
+ const title = doc.createElement("div");
370
+ title.className = "webskill-form__title";
371
+ title.textContent = model.title;
372
+ form.appendChild(title);
373
+ }
374
+ if (model.message && model.kind !== "confirm" && model.kind !== "ask" && model.kind !== "select") {
375
+ const message = doc.createElement("p");
376
+ message.className = "webskill-form__message";
377
+ message.textContent = model.message;
378
+ form.appendChild(message);
379
+ }
380
+ for (const control of model.controls) form.appendChild(this.#renderControl(control));
381
+ const actions = doc.createElement("div");
382
+ actions.className = "webskill-form__actions";
383
+ const submit = doc.createElement("button");
384
+ submit.type = "submit";
385
+ submit.className = "webskill-form__button webskill-form__button--primary";
386
+ submit.textContent = model.submitLabel;
387
+ actions.appendChild(submit);
388
+ if (model.cancelLabel) {
389
+ const cancel = doc.createElement("button");
390
+ cancel.type = "button";
391
+ cancel.className = "webskill-form__button webskill-form__button--ghost";
392
+ cancel.textContent = model.cancelLabel;
393
+ cancel.setAttribute("data-webskill-cancel", "");
394
+ actions.appendChild(cancel);
395
+ }
396
+ form.appendChild(actions);
397
+ return {
398
+ form,
399
+ cleanup: () => form.remove()
400
+ };
401
+ }
402
+ #renderControl(control) {
403
+ const doc = this.#doc;
404
+ const wrapper = doc.createElement("div");
405
+ wrapper.className = "webskill-form__control";
406
+ const controlId = `webskill-field-${control.name}`;
407
+ const errorId = `webskill-error-${control.name}`;
408
+ const label = doc.createElement("label");
409
+ label.className = control.required ? "webskill-form__label webskill-form__label-required" : "webskill-form__label";
410
+ label.textContent = control.label;
411
+ label.htmlFor = controlId;
412
+ wrapper.appendChild(label);
413
+ if (control.description) {
414
+ const desc = doc.createElement("div");
415
+ desc.className = "webskill-form__description";
416
+ desc.textContent = control.description;
417
+ wrapper.appendChild(desc);
418
+ }
419
+ let input;
420
+ if (control.control === "select") {
421
+ const select = doc.createElement("select");
422
+ select.className = "webskill-form__input";
423
+ for (const option of control.options ?? []) {
424
+ const opt = doc.createElement("option");
425
+ opt.value = JSON.stringify(option.value);
426
+ opt.textContent = option.label;
427
+ if (control.defaultValue !== void 0 && JSON.stringify(control.defaultValue) === opt.value) opt.selected = true;
428
+ select.appendChild(opt);
429
+ }
430
+ input = select;
431
+ } else if (control.control === "boolean") {
432
+ const checkbox = doc.createElement("input");
433
+ checkbox.type = "checkbox";
434
+ checkbox.className = "webskill-form__input";
435
+ checkbox.checked = control.defaultValue === true;
436
+ input = checkbox;
437
+ } else if (control.control === "textarea") {
438
+ const textarea = doc.createElement("textarea");
439
+ textarea.className = "webskill-form__input";
440
+ textarea.value = control.defaultValue !== void 0 ? String(control.defaultValue) : "";
441
+ input = textarea;
442
+ } else {
443
+ const textInput = doc.createElement("input");
444
+ textInput.type = control.control === "number" ? "number" : control.control === "date" ? "date" : "text";
445
+ textInput.className = "webskill-form__input";
446
+ if (control.defaultValue !== void 0) textInput.value = String(control.defaultValue);
447
+ input = textInput;
448
+ }
449
+ input.setAttribute(CONTROL_TAG, control.name);
450
+ input.id = controlId;
451
+ input.setAttribute("aria-describedby", errorId);
452
+ wrapper.appendChild(input);
453
+ if (control.suggestion !== void 0) wrapper.appendChild(this.#renderSuggestion(control, wrapper));
454
+ const error = doc.createElement("div");
455
+ error.className = "webskill-form__error";
456
+ error.id = errorId;
457
+ error.setAttribute("role", "alert");
458
+ error.textContent = this.#texts.required;
459
+ wrapper.appendChild(error);
460
+ return wrapper;
461
+ }
462
+ /** 建议值以**建议**形态出现(FR-19.4):控件初始值仍为空,点「使用」才写入 */
463
+ #renderSuggestion(control, container) {
464
+ const doc = this.#doc;
465
+ const row = doc.createElement("div");
466
+ row.className = "webskill-form__suggestion";
467
+ row.setAttribute("data-webskill-suggestion", control.name);
468
+ const preview = doc.createElement("span");
469
+ preview.className = "webskill-form__suggestion-value";
470
+ preview.textContent = `Suggested: ${String(control.suggestion?.value)}`;
471
+ row.appendChild(preview);
472
+ const reason = control.suggestion?.reason;
473
+ if (reason !== void 0 && reason !== "") {
474
+ const why = doc.createElement("span");
475
+ why.className = "webskill-form__suggestion-reason";
476
+ why.textContent = `(${reason})`;
477
+ row.appendChild(why);
478
+ }
479
+ const use = doc.createElement("button");
480
+ use.type = "button";
481
+ use.className = "webskill-form__suggestion-use";
482
+ use.textContent = "Use";
483
+ use.setAttribute("data-webskill-suggestion-use", control.name);
484
+ use.addEventListener("click", () => applySuggestion(control, container));
485
+ row.appendChild(use);
486
+ return row;
487
+ }
488
+ };
489
+
490
+ //#endregion
491
+ //#region ../ui/src/catalog/presets.ts
492
+ const charts = {
493
+ name: "charts",
494
+ summary: "One question, one chart, with the takeaway spelled out above it.",
495
+ guidance: [
496
+ "Layout: `Card` → `Heading` (the takeaway, not \"Chart\") → `Chart` → optional `Text` for the caveat.",
497
+ "Pick the chart type from the question: `line` / `area` for time, `bar` for comparison, `pie` for shares.",
498
+ "Every series needs a name; labels and values must be the same length.",
499
+ "Never repeat the same numbers in a `Table` right under the chart."
500
+ ].join("\n"),
501
+ example: {
502
+ component: "Card",
503
+ props: { title: "Revenue" },
504
+ children: [
505
+ {
506
+ component: "Heading",
507
+ props: {
508
+ level: 3,
509
+ text: "Revenue grew 27% in Q4"
510
+ }
511
+ },
512
+ {
513
+ component: "Chart",
514
+ props: {
515
+ type: "line",
516
+ labels: [
517
+ "Q1",
518
+ "Q2",
519
+ "Q3",
520
+ "Q4"
521
+ ],
522
+ series: [{
523
+ name: "Revenue",
524
+ values: [
525
+ 8,
526
+ 9,
527
+ 9.8,
528
+ 12.4
529
+ ]
530
+ }]
531
+ }
532
+ },
533
+ {
534
+ component: "Text",
535
+ props: {
536
+ text: "Figures in millions, unaudited.",
537
+ tone: "muted"
538
+ }
539
+ }
540
+ ]
541
+ }
542
+ };
543
+ const cards = {
544
+ name: "cards",
545
+ summary: "A short list of comparable items, one `Card` each.",
546
+ guidance: [
547
+ "Layout: `Grid` (2 or 3 columns) → one `Card` per item.",
548
+ "Keep every card the same shape: title, one `Metric` or `Text`, at most one `Badge` for status.",
549
+ "Use `Grid` rather than a vertical `Stack` as soon as there are three or more items.",
550
+ "Put an action on a card only when the user can act on that single item."
551
+ ].join("\n"),
552
+ example: {
553
+ component: "Grid",
554
+ props: { columns: 2 },
555
+ children: [{
556
+ component: "Card",
557
+ props: { title: "Checkout" },
558
+ children: [{
559
+ component: "Metric",
560
+ props: {
561
+ label: "Errors",
562
+ value: 12,
563
+ trend: "down"
564
+ }
565
+ }, {
566
+ component: "Badge",
567
+ props: {
568
+ text: "healthy",
569
+ tone: "success"
570
+ }
571
+ }]
572
+ }, {
573
+ component: "Card",
574
+ props: { title: "Search" },
575
+ children: [{
576
+ component: "Metric",
577
+ props: {
578
+ label: "Errors",
579
+ value: 143,
580
+ trend: "up"
581
+ }
582
+ }, {
583
+ component: "Badge",
584
+ props: {
585
+ text: "degraded",
586
+ tone: "warning"
587
+ }
588
+ }]
589
+ }]
590
+ }
591
+ };
592
+ const dashboards = {
593
+ name: "dashboards",
594
+ summary: "Headline numbers first, then detail, optionally split across `Tabs`.",
595
+ guidance: [
596
+ "Layout: `Stack` → `Grid` of `Metric` cards → `Chart` or `Table` for the detail.",
597
+ "Use `Tabs` when the detail splits into independent groups (filters, exports, alerts).",
598
+ "Each `Tabs` panel and each `Grid` cell is its own form container: at most one `Form` inside.",
599
+ "Show at most four headline metrics; anything more belongs in a `Table`."
600
+ ].join("\n"),
601
+ example: {
602
+ component: "Stack",
603
+ children: [{
604
+ component: "Grid",
605
+ props: { columns: 2 },
606
+ children: [{
607
+ component: "Card",
608
+ props: { title: "MRR" },
609
+ children: [{
610
+ component: "Metric",
611
+ props: {
612
+ label: "This month",
613
+ value: "12,400",
614
+ trend: "up"
615
+ }
616
+ }]
617
+ }, {
618
+ component: "Card",
619
+ props: { title: "Churn" },
620
+ children: [{
621
+ component: "Metric",
622
+ props: {
623
+ label: "This month",
624
+ value: "2.1%",
625
+ trend: "down"
626
+ }
627
+ }]
628
+ }]
629
+ }, {
630
+ component: "Tabs",
631
+ props: { labels: ["Trend", "Breakdown"] },
632
+ children: [{
633
+ component: "Stack",
634
+ children: [{
635
+ component: "Chart",
636
+ props: {
637
+ type: "bar",
638
+ labels: ["Q3", "Q4"],
639
+ series: [{
640
+ name: "Revenue",
641
+ values: [9800, 12400]
642
+ }]
643
+ }
644
+ }]
645
+ }, {
646
+ component: "Stack",
647
+ children: [{
648
+ component: "Table",
649
+ props: {
650
+ columns: ["Region", "Revenue"],
651
+ rows: [["EMEA", 5400], ["AMER", 7e3]]
652
+ }
653
+ }]
654
+ }]
655
+ }]
656
+ }
657
+ };
658
+ const slides = {
659
+ name: "slides",
660
+ summary: "One idea per `Tabs` panel, headline plus at most three supporting lines.",
661
+ guidance: [
662
+ "Layout: `Tabs` → one `Stack` per slide, labelled with the slide title.",
663
+ "Each slide: one `Heading` (level 2) stating the point, then a `Chart`, `Metric` or short `Markdown` list.",
664
+ "Never put more than one idea on a slide; add a panel instead.",
665
+ "No `Form` inside slides — a deck is for reading, not for input."
666
+ ].join("\n"),
667
+ example: {
668
+ component: "Tabs",
669
+ props: { labels: ["Where we are", "What we do next"] },
670
+ children: [{
671
+ component: "Stack",
672
+ children: [{
673
+ component: "Heading",
674
+ props: {
675
+ level: 2,
676
+ text: "Revenue grew 27% in Q4"
677
+ }
678
+ }, {
679
+ component: "Metric",
680
+ props: {
681
+ label: "Q4 revenue",
682
+ value: "12,400",
683
+ trend: "up"
684
+ }
685
+ }]
686
+ }, {
687
+ component: "Stack",
688
+ children: [{
689
+ component: "Heading",
690
+ props: {
691
+ level: 2,
692
+ text: "Double down on EMEA"
693
+ }
694
+ }, {
695
+ component: "Markdown",
696
+ props: { text: "- hire two AEs\n- localise pricing" }
697
+ }]
698
+ }]
699
+ }
700
+ };
701
+ const reports = {
702
+ name: "reports",
703
+ summary: "A long-form document: sections, tables and an optional `Timeline`.",
704
+ guidance: [
705
+ "Layout: `Stack` → `Heading` (level 2) → body → `Separator` between sections.",
706
+ "Use `Table` for figures, `Timeline` for what happened when, `Markdown` for prose.",
707
+ "Open with a one-paragraph summary before any detail.",
708
+ "Attach generated files with `FileLink` at the end rather than inlining raw data."
709
+ ].join("\n"),
710
+ example: {
711
+ component: "Stack",
712
+ children: [
713
+ {
714
+ component: "Heading",
715
+ props: {
716
+ level: 2,
717
+ text: "Q4 incident review"
718
+ }
719
+ },
720
+ {
721
+ component: "Text",
722
+ props: { text: "Three incidents, all recovered within the hour." }
723
+ },
724
+ { component: "Separator" },
725
+ {
726
+ component: "Timeline",
727
+ props: { items: [{
728
+ title: "Search latency spike",
729
+ time: "12 Nov",
730
+ state: "done"
731
+ }, {
732
+ title: "Checkout timeouts",
733
+ time: "03 Dec",
734
+ state: "done"
735
+ }] }
736
+ },
737
+ {
738
+ component: "FileLink",
739
+ props: {
740
+ path: "q4-incidents.csv",
741
+ label: "Full log",
742
+ size: 24117
743
+ }
744
+ }
745
+ ]
746
+ }
747
+ };
748
+ /** 五类场景预设(FR-6.4)。@experimental */
749
+ const UI_PRESETS = [
750
+ charts,
751
+ cards,
752
+ dashboards,
753
+ slides,
754
+ reports
755
+ ];
756
+ const UI_PRESET_NAMES = UI_PRESETS.map((preset) => preset.name);
757
+ /** Returns undefined for names outside the catalog presets. @experimental */
758
+ function uiPreset(name) {
759
+ return UI_PRESETS.find((preset) => preset.name === name);
760
+ }
761
+
762
+ //#endregion
763
+ //#region ../ui/src/catalog/uiCatalogToolSource.ts
764
+ const RENDER_UI_TOOL = "render_ui";
765
+ const DESCRIBE_UI_PRESET_TOOL = "describe_ui_preset";
766
+ const defaultId = () => `ui-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
767
+ /**
768
+ * 工具描述只留一句指针:catalog 本体走 system 消息(C4)。
769
+ * 工具 `description` 的长度上限与截断在各 provider 上不一致且是静默的,
770
+ * 近 5000 字符的 catalog 放在那里,被截断时模型拿到的是半份规格而请求照样成功。
771
+ */
772
+ const RENDER_UI_DESCRIPTION = [
773
+ "Render a UI for the answer instead of prose.",
774
+ "Use it for forms, tables, charts, metrics or file downloads.",
775
+ "The component catalog is in the system prompt."
776
+ ].join(" ");
777
+ const INTENTS = /* @__PURE__ */ new Set([
778
+ "submit",
779
+ "cancel",
780
+ "select",
781
+ "download",
782
+ "refresh"
783
+ ]);
784
+ /** 去重靠指纹兜底,但重复调用本身也浪费一轮,所以先在提示词里劝阻(FR-21.5) */
785
+ const NO_DUPLICATE_RENDER = "If a skill has already rendered a surface for this request, do not call render_ui again with the same content.";
786
+ function isIntent(value) {
787
+ return typeof value === "string" && INTENTS.has(value);
788
+ }
789
+ /**
790
+ * 从节点树抽出 action 能力表。能力表走事件外壳而非树内:
791
+ * 树是模型可写的,nonce 放进去就等于让模型自己签发能力。
792
+ * action id 取节点 id;Button 未声明 id 时退回 intent 名(同 intent 只留一条)。
793
+ * 表单的提交/取消取自作用域表:容器级放宽后一张 surface 可能有多个表单。
794
+ */
795
+ function collectActions(node, out, scopes) {
796
+ if (node.component === "Button" && isIntent(node.props?.["action"])) {
797
+ const intent = node.props["action"];
798
+ const id = node.id ?? intent;
799
+ if (!out.has(id)) out.set(id, {
800
+ id,
801
+ intent,
802
+ ...intent === "submit" ? { awaitResponse: true } : {}
803
+ });
804
+ }
805
+ const scope = scopes.get(node);
806
+ if (scope) {
807
+ if (!out.has(scope.submitActionId)) out.set(scope.submitActionId, {
808
+ id: scope.submitActionId,
809
+ intent: "submit",
810
+ awaitResponse: true
811
+ });
812
+ if (typeof node.props?.["cancelLabel"] === "string" && !out.has(scope.cancelActionId)) out.set(scope.cancelActionId, {
813
+ id: scope.cancelActionId,
814
+ intent: "cancel"
815
+ });
816
+ }
817
+ for (const child of node.children ?? []) collectActions(child, out, scopes);
818
+ }
819
+ /** 节点树声明的 action 能力表(renderer 据此生成按钮,runtime 据此签发 nonce)。@experimental */
820
+ function collectSpecActions(spec) {
821
+ const actions = /* @__PURE__ */ new Map();
822
+ collectActions(spec, actions, new Map(collectFormScopes(spec).map((scope) => [scope.form, scope])));
823
+ return [...actions.values()];
824
+ }
825
+ /**
826
+ * 把 catalog 变成一个工具的入参 Schema,让模型直接生成声明式 UI(21 号文档 §3):
827
+ * agentLoop / UiBridge / extractUiSpecEvents 全部零改动,装配处只多一个 externalTools 条目。
828
+ */
829
+ function createUiCatalogToolSource(options = {}) {
830
+ const catalog = options.catalog ?? uiCatalog;
831
+ const newSurfaceId = options.newSurfaceId ?? defaultId;
832
+ const presets = options.presets ? options.presets.flatMap((name) => uiPreset(name) ?? []) : UI_PRESETS;
833
+ const presetChoices = presets.map((preset) => `${preset.name} — ${preset.summary}`).join("\n");
834
+ const presetTool = () => ({
835
+ name: DESCRIBE_UI_PRESET_TOOL,
836
+ description: `Fetch the layout guidance for one scenario preset before calling ${RENDER_UI_TOOL}.\n${presetChoices}`,
837
+ inputSchema: {
838
+ type: "object",
839
+ properties: { preset: {
840
+ type: "string",
841
+ enum: presets.map((preset) => preset.name)
842
+ } },
843
+ required: ["preset"],
844
+ additionalProperties: false
845
+ }
846
+ });
847
+ const renderTool = () => ({
848
+ name: RENDER_UI_TOOL,
849
+ description: RENDER_UI_DESCRIPTION,
850
+ inputSchema: {
851
+ type: "object",
852
+ properties: {
853
+ spec: catalog.toJsonSchema(),
854
+ ...presets.length > 0 ? { preset: {
855
+ type: "string",
856
+ enum: presets.map((preset) => preset.name),
857
+ description: `Scenario preset this surface follows; call ${DESCRIBE_UI_PRESET_TOOL} first for its layout guidance.`
858
+ } } : {}
859
+ },
860
+ required: ["spec"],
861
+ additionalProperties: false
862
+ }
863
+ });
864
+ return {
865
+ kind: "ui-catalog",
866
+ systemPrompt: () => Promise.resolve(`${catalog.toPrompt()}\n\n${NO_DUPLICATE_RENDER}`),
867
+ listToolSpecs: () => Promise.resolve(presets.length > 0 ? [renderTool(), presetTool()] : [renderTool()]),
868
+ canHandle: (name) => name === "render_ui" || presets.length > 0 && name === "describe_ui_preset",
869
+ argCaptureTrust: (name) => name === "render_ui" || name === "describe_ui_preset" ? { tier: "reviewed" } : void 0,
870
+ call: (name, args) => {
871
+ if (name === "describe_ui_preset") {
872
+ const requested = args["preset"];
873
+ const preset = presets.find((candidate) => candidate.name === requested);
874
+ if (!preset) return Promise.resolve({
875
+ ok: false,
876
+ content: [],
877
+ error: {
878
+ code: "VALIDATION_FAILED",
879
+ message: `Unknown UI preset "${String(requested)}"; available presets: ${presets.map((candidate) => candidate.name).join(", ")}`
880
+ }
881
+ });
882
+ return Promise.resolve({
883
+ ok: true,
884
+ content: [{
885
+ type: "text",
886
+ text: `${preset.guidance}\n\nExample spec:\n${JSON.stringify(preset.example)}`
887
+ }]
888
+ });
889
+ }
890
+ const spec = args["spec"];
891
+ const result = catalog.validate(spec);
892
+ if (!result.ok) return Promise.resolve({
893
+ ok: false,
894
+ content: [],
895
+ error: {
896
+ code: "UI_SPEC_INVALID",
897
+ message: `The UI spec does not match the catalog: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`
898
+ }
899
+ });
900
+ const node = spec;
901
+ const surfaceId = newSurfaceId();
902
+ const actions = collectSpecActions(node);
903
+ return Promise.resolve({
904
+ ok: true,
905
+ content: [{
906
+ type: "json",
907
+ data: { $surface: {
908
+ type: "open",
909
+ id: surfaceId,
910
+ node,
911
+ ...actions.length > 0 ? { actions } : {}
912
+ } }
913
+ }, {
914
+ type: "text",
915
+ text: `Rendered surface ${surfaceId}.`
916
+ }]
917
+ });
918
+ }
919
+ };
920
+ }
921
+
922
+ //#endregion
923
+ //#region ../ui/src/adapters/vercel/vercelPayload.ts
924
+ const VERCEL_INTERACTION_TOOL_NAME = "webskill_interaction";
925
+ /** InteractionRequest → SDK tool UI 结构(应用聊天 UI 直接消费) */
926
+ function toVercelToolInvocation(request) {
927
+ return {
928
+ type: "tool-call",
929
+ toolCallId: request.id,
930
+ toolName: VERCEL_INTERACTION_TOOL_NAME,
931
+ input: request
932
+ };
933
+ }
934
+ /** SDK 回传(tool result / output)→ InteractionResponse */
935
+ function fromVercelToolResult(part) {
936
+ const p = part ?? {};
937
+ const id = String(p["toolCallId"] ?? p["id"] ?? "");
938
+ if (p["cancelled"] === true) return {
939
+ id,
940
+ cancelled: true
941
+ };
942
+ return {
943
+ id,
944
+ value: p["output"] ?? p["result"] ?? p["value"]
945
+ };
946
+ }
947
+
948
+ //#endregion
949
+ //#region ../ui/src/adapters/vercel/vercelUiBridge.ts
950
+ /**
951
+ * 薄桥接类:request → SDK tool UI 结构 → 应用的聊天 UI(sendInteraction 回调实现)
952
+ * → SDK 回传 → InteractionResponse。
953
+ */
954
+ var VercelUiBridge = class {
955
+ #sendInteraction;
956
+ constructor(deps) {
957
+ this.#sendInteraction = deps.sendInteraction;
958
+ }
959
+ async request(input) {
960
+ return fromVercelToolResult(await this.#sendInteraction(toVercelToolInvocation(input)));
961
+ }
962
+ };
963
+
964
+ //#endregion
965
+ //#region ../ui/src/adapters/surface/surfaceDescriptor.ts
966
+ const WEBSKILL_SURFACE_ACTION = "webskill:surface-action";
967
+ function toUiSurfaceActionDispatch(snapshot, action, value, scopeId) {
968
+ return {
969
+ ...snapshot.runId ? { runId: snapshot.runId } : {},
970
+ surfaceId: snapshot.id,
971
+ actionId: action.id,
972
+ intent: action.intent,
973
+ ...action.nonce ? { nonce: action.nonce } : {},
974
+ ...scopeId ? { scopeId } : {},
975
+ ...value ? { value } : {}
976
+ };
977
+ }
978
+ /** Returns undefined for malformed or non-WebSkill renderer events. @experimental */
979
+ function fromUiSurfaceActionDispatch(value) {
980
+ if (typeof value !== "object" || value === null) return void 0;
981
+ const event = value;
982
+ if (typeof event["surfaceId"] !== "string" || typeof event["actionId"] !== "string" || !isSurfaceActionIntent(event["intent"])) return;
983
+ return {
984
+ ...typeof event["runId"] === "string" ? { runId: event["runId"] } : {},
985
+ surfaceId: event["surfaceId"],
986
+ actionId: event["actionId"],
987
+ intent: event["intent"],
988
+ ...typeof event["nonce"] === "string" ? { nonce: event["nonce"] } : {},
989
+ ...typeof event["scopeId"] === "string" ? { scopeId: event["scopeId"] } : {},
990
+ ...isRecord(event["value"]) ? { value: event["value"] } : {},
991
+ ...event["cancelled"] === true ? { cancelled: true } : {}
992
+ };
993
+ }
994
+ function isSurfaceActionIntent(value) {
995
+ return value === "submit" || value === "cancel" || value === "select" || value === "download" || value === "refresh";
996
+ }
997
+ function isRecord(value) {
998
+ return typeof value === "object" && value !== null && !Array.isArray(value);
999
+ }
1000
+
1001
+ //#endregion
1002
+ //#region ../ui/src/adapters/a2ui/a2uiMessages.ts
1003
+ /**
1004
+ * A2UI 协议常量与 surface 动作编解码(v0.9.1)。
1005
+ * 依据 https://a2ui.org/specification/v0.9-a2ui/:
1006
+ * - 信封 {version, createSurface|updateComponents|updateDataModel|deleteSurface}
1007
+ * - 组件为扁平邻接表(id 引用),root 必需
1008
+ * - 输入组件与数据模型经 JSON Pointer 双向绑定;action.context 回传
1009
+ *
1010
+ * 声明树 → A2UI 消息的转换在 `specToA2ui.ts`(四档共用的单一渲染路径)。
1011
+ */
1012
+ const A2UI_VERSION = "v0.9.1";
1013
+ const A2UI_BASIC_CATALOG_ID = "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json";
1014
+ const A2UI_SURFACE_ACTION = WEBSKILL_SURFACE_ACTION;
1015
+ /** Decodes a nonce-bound action emitted by a WebSkill A2UI extension host. @experimental */
1016
+ function fromA2uiSurfaceAction(event) {
1017
+ const action = event;
1018
+ if (action?.name !== A2UI_SURFACE_ACTION) return void 0;
1019
+ return fromUiSurfaceActionDispatch(action.context);
1020
+ }
1021
+ /** Builds the A2UI event payload an extension host sends after a user action. @experimental */
1022
+ function toA2uiSurfaceAction(snapshot, action, value, scopeId) {
1023
+ return {
1024
+ name: A2UI_SURFACE_ACTION,
1025
+ context: toUiSurfaceActionDispatch(snapshot, action, value, scopeId)
1026
+ };
1027
+ }
1028
+
1029
+ //#endregion
1030
+ //#region ../ui/src/adapters/a2ui/a2uiComponentShape.ts
1031
+ /** A2UI 协议公共类型(与 @a2ui/web_core 的 common-types 同一套定义) */
1032
+ const A2UI_COMMON_TYPES = "https://a2ui.org/specification/v0_9/common_types.json";
1033
+ const CHILD_LIST_REF = `${A2UI_COMMON_TYPES}#/$defs/ChildList`;
1034
+ const ACTION_REF = `${A2UI_COMMON_TYPES}#/$defs/Action`;
1035
+ const DYNAMIC_VALUE_REF = `${A2UI_COMMON_TYPES}#/$defs/DynamicValue`;
1036
+ /** catalog 里承载 action 名的 props 字段 */
1037
+ const ACTION_PROP = "action";
1038
+ /** 表单控件的标识字段;有它才谈得上把值写进数据模型 */
1039
+ const NAME_PROP = "name";
1040
+ const SUBMIT_LABEL_PROP = "submitLabel";
1041
+ const CANCEL_LABEL_PROP = "cancelLabel";
1042
+ /** A2UI 线上形状里表单控件的取值字段 */
1043
+ const A2UI_VALUE_PROP = "value";
1044
+ /** A2UI 线上形状里的子节点字段 */
1045
+ const A2UI_CHILDREN_PROP = "children";
1046
+ /** catalog → 每个组件的 A2UI 线上形状 */
1047
+ function a2uiComponentShapes(catalog) {
1048
+ const branches = catalog.toJsonSchema()["oneOf"] ?? [];
1049
+ const byName = /* @__PURE__ */ new Map();
1050
+ for (const branch of branches) {
1051
+ const name = (branch.properties?.["component"])?.["const"];
1052
+ if (typeof name === "string") byName.set(name, branch);
1053
+ }
1054
+ return catalog.components.map((def) => {
1055
+ const branch = byName.get(def.name);
1056
+ const props = branch?.properties?.["props"] ?? { type: "object" };
1057
+ const keys = Object.keys(props.properties ?? {});
1058
+ const childrenDescription = (branch?.properties?.["children"])?.description;
1059
+ return {
1060
+ name: def.name,
1061
+ description: def.description,
1062
+ props,
1063
+ staticProps: keys.filter((key) => key !== ACTION_PROP),
1064
+ childList: def.children !== void 0,
1065
+ ...childrenDescription ? { childListDescription: childrenDescription } : {},
1066
+ actionProps: keys.filter((key) => key === ACTION_PROP),
1067
+ valueProps: def.group === "input" && def.children === void 0 && keys.includes(NAME_PROP) ? [A2UI_VALUE_PROP] : [],
1068
+ ...keys.includes(SUBMIT_LABEL_PROP) ? { submitLabelProp: SUBMIT_LABEL_PROP } : {},
1069
+ ...keys.includes(CANCEL_LABEL_PROP) ? { cancelLabelProp: CANCEL_LABEL_PROP } : {}
1070
+ };
1071
+ });
1072
+ }
1073
+ /** 单个组件的 A2UI props JSON Schema(协议要求每个组件是一份 schema) */
1074
+ function a2uiComponentSchema(shape) {
1075
+ const properties = { ...shape.props.properties ?? {} };
1076
+ for (const key of shape.actionProps) properties[key] = {
1077
+ $ref: ACTION_REF,
1078
+ description: "What happens when the user activates this component."
1079
+ };
1080
+ for (const key of shape.valueProps) properties[key] = {
1081
+ $ref: DYNAMIC_VALUE_REF,
1082
+ description: "Two-way binding to the surface data model."
1083
+ };
1084
+ if (shape.childList) properties[A2UI_CHILDREN_PROP] = {
1085
+ $ref: CHILD_LIST_REF,
1086
+ description: shape.childListDescription ? `${shape.childListDescription}. Children are referenced by id, never inlined.` : "Child component ids, in render order."
1087
+ };
1088
+ return {
1089
+ ...shape.props,
1090
+ description: shape.description,
1091
+ properties
1092
+ };
1093
+ }
1094
+
1095
+ //#endregion
1096
+ //#region ../ui/src/adapters/a2ui/webskillCatalog.ts
1097
+ /** 自定义 catalog 的协议 id(对齐 https://a2ui.org/specification/v1_0/catalog_definition.json) */
1098
+ const WEBSKILL_A2UI_CATALOG_ID = "https://webskill.dev/a2ui/catalogs/v1/catalog.json";
1099
+ /**
1100
+ * WebSkill catalog → A2UI `catalog_definition.json`。
1101
+ * 组件 schema 由 catalog 的 JSON Schema 投影经 `a2uiComponentShapes` 改写成 A2UI 线上形状
1102
+ * (嵌套变 id 引用、action 名变协议信封),不手写第二份词汇表——
1103
+ * Lit 元素的 zod API 也由同一份 shape 派生。
1104
+ */
1105
+ function buildA2uiCatalogDefinition(catalog) {
1106
+ const components = {};
1107
+ for (const shape of a2uiComponentShapes(catalog)) components[shape.name] = a2uiComponentSchema(shape);
1108
+ return {
1109
+ catalogId: WEBSKILL_A2UI_CATALOG_ID,
1110
+ title: catalog.title,
1111
+ description: `${catalog.title} ${catalog.version}`,
1112
+ instructions: catalog.instructions,
1113
+ components,
1114
+ functions: Object.fromEntries(catalog.actions.map((action) => [action.name, { description: action.description }]))
1115
+ };
1116
+ }
1117
+
1118
+ //#endregion
1119
+ //#region ../ui/src/adapters/a2ui/specToA2ui.ts
1120
+ /** 声明树里按钮触发的事件名(宿主据 context.actionId 回传 surface action) */
1121
+ const A2UI_SPEC_ACTION = "webskill:spec-action";
1122
+ /** 表单值在 A2UI 数据模型里的根路径 */
1123
+ const A2UI_SPEC_FORM_PATH = "/form";
1124
+ /**
1125
+ * catalog 声明树 → A2UI 消息序列。
1126
+ *
1127
+ * 协议要求组件是扁平邻接表且根节点 id 为 `root`,因此这里做树 → 邻接表的展开。
1128
+ * 三处改写全部由 `a2uiComponentShapes` 决定,本函数不认识任何具体组件名:
1129
+ * - 子节点变成 props.children 里的 id 引用
1130
+ * - 表单控件的值经 JSON Pointer 与数据模型双向绑定
1131
+ * - 自带提交/取消标签的组件(A2UI 没有表单提交事件)把按钮物化成 Button 子节点,
1132
+ * 与 `toA2uiMessages` 对交互请求的处理同一手法
1133
+ */
1134
+ function toA2uiSpecMessages(catalog, spec, options) {
1135
+ const shapes = new Map(a2uiComponentShapes(catalog).map((shape) => [shape.name, shape]));
1136
+ const components = [];
1137
+ const defaults = {};
1138
+ let counter = 0;
1139
+ const event = (actionId) => ({ event: {
1140
+ name: A2UI_SPEC_ACTION,
1141
+ context: {
1142
+ ...options.context,
1143
+ actionId
1144
+ }
1145
+ } });
1146
+ const button = (id, text, actionId) => {
1147
+ components.push({
1148
+ id,
1149
+ component: "Button",
1150
+ label: text,
1151
+ action: event(actionId)
1152
+ });
1153
+ return id;
1154
+ };
1155
+ const walk = (node, id) => {
1156
+ const props = { ...node.props };
1157
+ const shape = shapes.get(node.component);
1158
+ const children = (node.children ?? []).map((child) => walk(child, child.id ?? `n${counter++}`));
1159
+ for (const key of shape?.valueProps ?? []) {
1160
+ const name = typeof props["name"] === "string" ? props["name"] : id;
1161
+ if (props["defaultValue"] !== void 0) defaults[name] = props["defaultValue"];
1162
+ props[key] = { path: `${A2UI_SPEC_FORM_PATH}/${name}` };
1163
+ }
1164
+ for (const key of shape?.actionProps ?? []) props[key] = event(typeof props[key] === "string" ? props[key] : "select");
1165
+ if (shape?.submitLabelProp) {
1166
+ const text = props[shape.submitLabelProp];
1167
+ children.push(button(`${id}-submit`, typeof text === "string" ? text : "Submit", "submit"));
1168
+ }
1169
+ if (shape?.cancelLabelProp && typeof props[shape.cancelLabelProp] === "string") children.push(button(`${id}-cancel`, props[shape.cancelLabelProp], "cancel"));
1170
+ if (children.length > 0) props[A2UI_CHILDREN_PROP] = children;
1171
+ components.push({
1172
+ ...props,
1173
+ id,
1174
+ component: node.component
1175
+ });
1176
+ return id;
1177
+ };
1178
+ walk(spec, "root");
1179
+ const messages = [{
1180
+ version: A2UI_VERSION,
1181
+ createSurface: {
1182
+ surfaceId: options.surfaceId,
1183
+ catalogId: options.catalogId ?? "https://webskill.dev/a2ui/catalogs/v1/catalog.json",
1184
+ sendDataModel: true
1185
+ }
1186
+ }, {
1187
+ version: A2UI_VERSION,
1188
+ updateComponents: {
1189
+ surfaceId: options.surfaceId,
1190
+ components
1191
+ }
1192
+ }];
1193
+ if (Object.keys(defaults).length > 0) messages.push({
1194
+ version: A2UI_VERSION,
1195
+ updateDataModel: {
1196
+ surfaceId: options.surfaceId,
1197
+ path: A2UI_SPEC_FORM_PATH,
1198
+ value: defaults
1199
+ }
1200
+ });
1201
+ return messages;
1202
+ }
1203
+ /** A2UI action 事件 → 声明树 action(非本 catalog 的事件返回 undefined) */
1204
+ function fromA2uiSpecAction(event) {
1205
+ const action = event;
1206
+ if (action?.name !== "webskill:spec-action") return void 0;
1207
+ const context = action.context ?? {};
1208
+ const actionId = context["actionId"];
1209
+ if (typeof actionId !== "string") return void 0;
1210
+ return {
1211
+ actionId,
1212
+ context
1213
+ };
1214
+ }
1215
+
1216
+ //#endregion
1217
+ //#region ../ui/src/a2uiRuntime/loadLitCatalog.ts
1218
+ /**
1219
+ * BYOC Lit 元素的加载入口。
1220
+ *
1221
+ * 元素模块静态依赖 `lit` 与 `@a2ui/*`(三者都是 optional peer),
1222
+ * 因此只能经动态 import 进来:未用 A2UI 档的应用不为这段体积买单,
1223
+ * 缺依赖时给出带安装提示的 `UI_UNAVAILABLE`,而不是一个空白 surface。
1224
+ */
1225
+ async function loadWebSkillLitCatalog() {
1226
+ try {
1227
+ const { webskillLitCatalog } = await import("./webskillLitCatalog-DSqVFQjK.js");
1228
+ return webskillLitCatalog();
1229
+ } catch (cause) {
1230
+ throw new WebSkillError("UI_UNAVAILABLE", "The WebSkill A2UI catalog is unavailable; install @a2ui/lit, @a2ui/web_core and lit to render catalog surfaces with A2UI", cause);
1231
+ }
1232
+ }
1233
+
1234
+ //#endregion
1235
+ //#region ../ui/src/optionalPeers/openUiRuntime.ts
1236
+ /**
1237
+ * Loads the optional OpenUI peers (@openuidev/react-lang and zod) for the OpenUI
1238
+ * interaction/surface paths. Throws a WebSkillError with code UI_UNAVAILABLE and an
1239
+ * install hint when either peer is missing, so callers can fall back to native
1240
+ * rendering instead of leaving a blank surface.
1241
+ */
1242
+ async function loadOpenUiPeers() {
1243
+ try {
1244
+ const [openuiModule, zodModule] = await Promise.all([import("./dist-CTsxblSD.js"), import("zod")]);
1245
+ return {
1246
+ openui: openuiModule,
1247
+ z: zodModule.z
1248
+ };
1249
+ } catch (cause) {
1250
+ throw new WebSkillError("UI_UNAVAILABLE", "The OpenUI renderer is unavailable; install @openuidev/react-lang and zod to render surfaces and interactions with the OpenUI track", cause);
1251
+ }
1252
+ }
1253
+
1254
+ //#endregion
1255
+ export { A2UI_BASIC_CATALOG_ID, A2UI_COMMON_TYPES, A2UI_SPEC_ACTION, A2UI_SPEC_FORM_PATH, A2UI_SURFACE_ACTION, A2UI_VERSION, CATALOG_BUDGET_STAGE, CATALOG_PROMPT_MAX, CATALOG_SCHEMA_MAX, CHART_PALETTE, DEFAULT_CHART_FONT_SIZES, DEFAULT_INTERACTION_TEXTS, DEFAULT_SURFACE_FORM_TEXTS, DEFAULT_SURFACE_HOST_CONTROL_TEXTS, DESCRIBE_UI_PRESET_TOOL, DOCUMENT_COMPONENTS, MAX_CONDITION_DEPTH, PLANNED_INCREMENT, RENDER_UI_TOOL, SPEC_TABLE_MIN_COLUMN_VAR, SPEC_TABLE_MIN_COLUMN_WIDTH, UI_CATALOG_GROUPS, UI_CATALOG_PROMPT_BUDGET_BYTES, UI_PRESETS, UI_PRESET_NAMES, VERCEL_INTERACTION_TOOL_NAME, VIEWER_COMPONENT_ATTR, VIEWER_FALLBACK_ATTR, VIEWER_PROPS_ATTR, VercelUiBridge, WEBSKILL_A2UI_CATALOG_ID, WEBSKILL_STYLES_CSS, WEBSKILL_SURFACE_ACTION, WebFormBridge, a2uiComponentSchema, a2uiComponentShapes, applySuggestion, buildA2uiCatalogDefinition, buildRenderResult, chartSpecFromProps, chartToTable, collectFormScopes, collectScopedValues, collectSpecActions, collectValues, createUiCatalogToolSource, defineUiCatalog, ensureStyles, evaluateFieldCondition, fromA2uiSpecAction, fromA2uiSurfaceAction, fromUiSurfaceActionDispatch, fromVercelToolResult, gaugePercent, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, mountEchart, mountViewerComponents, normalizeColumnWidths, qualifyFieldName, renderBlocks, renderMiniChart, renderMiniMarkdown, renderRenderResult, resolveChartFontSizes, resolveColumnWidths, resolveInteractionTexts, resolveSurfaceFormTexts, resolveSurfaceHostControlTexts, shapeInteractionValue, A2UI_CHILDREN_PROP as t, toA2uiSpecMessages, toA2uiSurfaceAction, toEchartsOption, toJsonRenderSpec, toOpenUiSpecLang, toUiSurfaceActionDispatch, toVercelToolInvocation, uiCatalog, uiPreset };