@webskill/sdk 0.9.0 → 0.11.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 (36) hide show
  1. package/dist/agent.d.ts +3 -2
  2. package/dist/agent.js +3 -1098
  3. package/dist/browser.d.ts +260 -11
  4. package/dist/browser.js +772 -52
  5. package/dist/{catalogComponents-DfxxfUvn-T7Ic8QFV.js → catalogComponents-BFoqpT1v-CjUBZ3bc.js} +1604 -426
  6. package/dist/{dist-CFmkV45C.js → dist-B-cOu08W.js} +874 -107
  7. package/dist/{dist-BQe1uglQ.js → dist-DTHZS2k1.js} +524 -30
  8. package/dist/dist-qnlI2Iup.js +1280 -0
  9. package/dist/{eventTypes-DbOpAECr-BjcjZVms.js → eventTypes-FllCrX-Z-DNDeHWoG.js} +9 -2
  10. package/dist/governance.d.ts +33 -6
  11. package/dist/governance.js +45 -7
  12. package/dist/{index-znZjobkr.d.ts → index-D3mONFHD.d.ts} +281 -11
  13. package/dist/{index-DXNTIa-6.d.ts → index-DACk2_XZ.d.ts} +114 -7
  14. package/dist/{index-lLcCpHE-.d.ts → index-DWbs58LF.d.ts} +234 -14
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +3 -3
  17. package/dist/mcp.d.ts +49 -6
  18. package/dist/mcp.js +146 -38
  19. package/dist/node.d.ts +3 -3
  20. package/dist/node.js +52 -2
  21. package/dist/{openUiLibrary-DURlAxjk-Do_yqg3u.js → openUiLibrary-D5u8oIvx-BLOAQCho.js} +3 -3
  22. package/dist/processSandboxEntry.js +6 -0
  23. package/dist/sandboxWorkerEntry.js +6 -0
  24. package/dist/{skillVersionStore-Bl-ElD45-dMJ8Ybhb.d.ts → skillVersionStore-D-qHk9ZE-BcmFLykd.d.ts} +13 -5
  25. package/dist/{testing-Csg5ljNm.js → testing-WPTyXQYt.js} +26 -3
  26. package/dist/testing.d.ts +1 -1
  27. package/dist/testing.js +1 -1
  28. package/dist/{types-B3n0cMZu-BDhheIhX.d.ts → types-C26b05fW-CdrRCRDb.d.ts} +55 -6
  29. package/dist/ui-react.d.ts +11 -2
  30. package/dist/ui-react.js +141 -42
  31. package/dist/ui-vue.d.ts +1 -1
  32. package/dist/ui-vue.js +2 -2
  33. package/dist/ui.d.ts +4 -4
  34. package/dist/ui.js +3 -3
  35. package/dist/{webskillLitCatalog-DwTwSBFt-BG7kL-Es.js → webskillLitCatalog-DME6PBkV-CmYNLlIT.js} +135 -16
  36. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { h as WebSkillError } from "./dist-Bev6i6Ip.js";
2
2
  import { toJSONSchema, z } from "zod";
3
3
 
4
- //#region ../ui/dist/webskillCatalog-CKM0bu47.js
4
+ //#region ../ui/dist/webskillCatalog-DrmnCbsR.js
5
5
  const DEFAULT_INTERACTION_TEXTS = {
6
6
  submit: "Submit",
7
7
  cancel: "Cancel",
@@ -29,7 +29,9 @@ const DEFAULT_SURFACE_FORM_TEXTS = {
29
29
  addItem: "Add",
30
30
  removeItem: "Remove",
31
31
  arrayFirstItemOnly: "This renderer shows only the first item of a repeatable group.",
32
- readOnlySnapshot: "This is a saved record — its actions are no longer available."
32
+ readOnlySnapshot: "This is a saved record — its actions are no longer available.",
33
+ suggested: DEFAULT_INTERACTION_TEXTS.suggested,
34
+ useSuggestion: DEFAULT_INTERACTION_TEXTS.useSuggestion
33
35
  };
34
36
  function resolveSurfaceFormTexts(texts) {
35
37
  return texts ? {
@@ -322,6 +324,74 @@ function mountEchart(container) {
322
324
  }
323
325
  };
324
326
  }
327
+ /**
328
+ * catalog 请求体积上限(0.11.0 分册 14 / AC-G20)的**数字单一来源**。
329
+ *
330
+ * 度量与报错格式在 `scripts/catalogBudget.mjs`(`check:contracts` 与 vitest 共用);
331
+ * 那边不重复这些数字,一律从本模块读——两处各写一份阈值就等于没有阈值。
332
+ *
333
+ * catalog 进入每一次 LLM 请求的有两处:系统提示词 `toPrompt()` 与
334
+ * `render_ui` 入参 `toJsonSchema()`。只量前者会漏掉约 2/3 的成本。
335
+ *
336
+ * **超阈值的处置是砍组件或收窄枚举取值,不是抬高这里的数字。**
337
+ */
338
+ /**
339
+ * 预算阶段。分册 12 是本版最后一处 catalog 增量(分册 11 只把 `date` 加进
340
+ * 已有的 `Field.type`,分册 23 不碰 catalog),故在此切到 `'final'`:
341
+ * 阈值收紧到实测终态 + 余量,且余量小于一个平均组件的成本
342
+ * ——余量大于一个组件,等于默许下一版无声塞进一个。
343
+ */
344
+ const CATALOG_BUDGET_STAGE = "final";
345
+ /**
346
+ * 阈值 = **已落地实测** + 余量,余量须小于一个平均组件的成本。
347
+ *
348
+ * 2026-08-14 实测终态(分册 12 落地后):prompt 12 744 / schema 21 470,30 个组件。
349
+ * 平均组件成本 prompt 425 / schema 716。
350
+ * 当前余量 prompt 256 / schema 330,均小于一个平均组件——再加一个组件必红。
351
+ *
352
+ * ⚠️ **本版这两个数字被上调过两次**,两次都由需求方裁决并改写了需求文档
353
+ * (分册 29 §2 范围订正 第 7 条与 §2.2),不是实现阶段自行放宽:
354
+ *
355
+ * 1. 11 200 → 12 200 / 20 500 → 21 200:分册 13 组件描述由短改长。
356
+ * `example` 是既有护栏的硬要求(`catalog.test.ts` 与 `catalogMatrix.test.tsx`
357
+ * 逐个组件校验并四档渲染),靠省掉示例腾出的空间收不回来。
358
+ * 2. 12 200 → 13 000 / 21 200 → 21 800:分册 12 的 `Split` 实测 490 + 694,
359
+ * 超出计划增量(403 + 1 047)的 prompt 侧。需求方裁定
360
+ * 「描述要长,可用性大于预算阈值」——收紧措辞可省回 158 字节,
361
+ * 但砍的正是各组件「何时改用 X」的辨析句,即选型准确率本身。
362
+ *
363
+ * 两次上调合计把单请求成本从 21.7 KB 抬到约 34.2 KB。
364
+ * **AC-G20 原文「超了砍组件、不抬阈值」已被推翻,但仅限本版;
365
+ * 后续再撞顶仍须回到需求方,不得沿用本版作为先例。**
366
+ */
367
+ const CATALOG_PROMPT_MAX = 13e3;
368
+ const CATALOG_SCHEMA_MAX = 21800;
369
+ /**
370
+ * **尚未落地**的计划增量。本版已无剩余 catalog 增量,故归零;
371
+ * `CATALOG_BUDGET_STAGE` 同时切到 `'final'`,余量改由「小于一个平均组件」判定。
372
+ */
373
+ const PLANNED_INCREMENT = {
374
+ prompt: 0,
375
+ schema: 0
376
+ };
377
+ /** 码与人话之间的分隔符。放在消息最前面,`codeOfIssue` 才能从 zod 的 issue 里认出它。 */
378
+ const SEPARATOR = ": ";
379
+ /**
380
+ * 给 zod 的 `error` 文案打上稳定码。
381
+ *
382
+ * 组件自己在 schema 里声明,sanitize 只负责把码搬到 `UiSpecDegradation.code` 上——
383
+ * 这样新增一条策略性约束不需要去 sanitize 里加组件名分支(写死清单是本仓反复出问题的地方)。
384
+ */
385
+ function codedError(code, message) {
386
+ return `${code}${SEPARATOR}${message}`;
387
+ }
388
+ /** 从 zod issue 的消息里取回码;没有码就返回 undefined(普通类型错误就是这种)。 */
389
+ function codeOfIssue(message) {
390
+ const index = message.indexOf(SEPARATOR);
391
+ if (index <= 0) return void 0;
392
+ const head = message.slice(0, index);
393
+ return /^[A-Z][A-Z0-9_]*$/.test(head) ? head : void 0;
394
+ }
325
395
  function isRecord$1$1(value) {
326
396
  return typeof value === "object" && value !== null && !Array.isArray(value);
327
397
  }
@@ -369,24 +439,36 @@ function sanitizeNode(catalog, byName, node, path, degradations) {
369
439
  const parsed = def.props.safeParse(rawProps);
370
440
  if (!parsed.success) {
371
441
  const offending = new Set(parsed.error.issues.map((issue) => String(issue.path[0] ?? "")).filter((key) => key !== ""));
442
+ const codes = /* @__PURE__ */ new Map();
443
+ for (const issue of parsed.error.issues) {
444
+ const key = String(issue.path[0] ?? "");
445
+ const code = codeOfIssue(issue.message);
446
+ if (key !== "" && code !== void 0) codes.set(key, code);
447
+ }
372
448
  const kept = {};
373
449
  for (const [key, value] of Object.entries(rawProps)) if (!offending.has(key)) kept[key] = value;
374
450
  const retry = def.props.safeParse(kept);
375
451
  if (!retry.success) {
452
+ const carried = [...codes.values()].find((code) => code !== void 0);
376
453
  degradations.push({
377
454
  path,
378
455
  kind: "node-dropped",
379
456
  component: name,
380
- detail: `Props cannot be repaired by dropping invalid keys: ${retry.error.issues.map((i) => i.message).join("; ")}`
457
+ detail: `Props cannot be repaired by dropping invalid keys: ${retry.error.issues.map((i) => i.message).join("; ")}`,
458
+ ...carried === void 0 ? {} : { code: carried }
381
459
  });
382
460
  return;
383
461
  }
384
- for (const key of offending) degradations.push({
385
- path: `${path}.props.${key}`,
386
- kind: "prop-dropped",
387
- component: name,
388
- detail: `Dropped invalid prop "${key}".`
389
- });
462
+ for (const key of offending) {
463
+ const code = codes.get(key);
464
+ degradations.push({
465
+ path: `${path}.props.${key}`,
466
+ kind: "prop-dropped",
467
+ component: name,
468
+ detail: `Dropped invalid prop "${key}".`,
469
+ ...code === void 0 ? {} : { code }
470
+ });
471
+ }
390
472
  props = kept;
391
473
  }
392
474
  const sanitized = {
@@ -636,6 +718,56 @@ const tone = z.enum([
636
718
  "info",
637
719
  "destructive"
638
720
  ]);
721
+ /**
722
+ * 装饰色。与 `tone` 语义**不重叠**:tone 表状态(success 是「成功」),
723
+ * palette 表美观(「行程第三天」用绿色不代表它成功了)。
724
+ * 取值是色板名不是颜色值——必须映射到 design token,深浅色主题才能自动跟随。
725
+ */
726
+ const palette = z.enum([
727
+ "sky",
728
+ "violet",
729
+ "amber",
730
+ "rose",
731
+ "emerald"
732
+ ]);
733
+ /**
734
+ * surface 级视觉档(FR-13.4)。只在**根节点**上有意义,切的是一组 token
735
+ * (圆角/阴影/底色),不是逐组件改样式;作用域限于该 surface 子树。
736
+ * `office` 是缺省,与本版之前逐字节一致——它是回归基线。
737
+ */
738
+ const surfaceTheme = z.enum([
739
+ "office",
740
+ "vivid",
741
+ "dashboard-dark"
742
+ ]);
743
+ /** 图标白名单:任意名字会让模型产出渲染不出来的节点,且枚举长度直接计入每次请求 */
744
+ const iconName = z.enum([
745
+ "info",
746
+ "check",
747
+ "alert",
748
+ "x",
749
+ "clock",
750
+ "calendar",
751
+ "user",
752
+ "star",
753
+ "search",
754
+ "settings",
755
+ "download",
756
+ "link",
757
+ "chart",
758
+ "file",
759
+ "folder",
760
+ "mail"
761
+ ]);
762
+ /**
763
+ * `Image.src` 的同源约束(AC-13.4)。**判定放在 schema 上,`validate` 与 `sanitize` 两条路径就都覆盖**;
764
+ * 只写进组件描述等于把安全交给模型自觉——提示注入可以用 `src` 把数据带到任意主机上去。
765
+ *
766
+ * 同源按**语法**判:`data:image/` 与相对路径放行,任何带 scheme 的绝对 URL
767
+ * 以及协议相对的 `//host/...` 一律拒。这样判定不需要知道当前 origin,
768
+ * `@webskill/ui` 得以保持环境无关。
769
+ */
770
+ const imageSrc = z.string().refine((src) => /^data:image\//i.test(src) || !/^([a-z][a-z0-9+.-]*:|\/\/)/i.test(src), { error: codedError("IMAGE_SRC_NOT_ALLOWED", "Image.src must be a same-origin path or a data:image URL; remote URLs are rejected by policy.") });
639
771
  const actionRef = z.object({
640
772
  /** catalog actions 里声明过的名字 */
641
773
  action: z.string().describe("Action name declared by the catalog"),
@@ -709,7 +841,7 @@ const uiCatalog = defineUiCatalog({
709
841
  "- The root node of a surface must be `Stack` or `Card`.",
710
842
  "- Ask for user input with `Form` + `Field`; never describe an input in `Text`.",
711
843
  "- A `Button`'s `action` must be one of the catalog actions.",
712
- "- Never emit HTML, scripts or remote images.",
844
+ "- Never emit HTML or scripts. `Image.src` must be same-origin or a data URL.",
713
845
  "- Prefer `Table` for many rows, `Chart` for trends, `Metric` for a single number,",
714
846
  " `Timeline` for ordered steps and `Progress` for how far a long task has come.",
715
847
  "- Match `Field.type` to the question instead of using `text` for everything:",
@@ -735,7 +867,8 @@ const uiCatalog = defineUiCatalog({
735
867
  "center",
736
868
  "end",
737
869
  "stretch"
738
- ]).optional()
870
+ ]).optional(),
871
+ theme: surfaceTheme.optional()
739
872
  }),
740
873
  children: "any",
741
874
  example: {
@@ -756,7 +889,15 @@ const uiCatalog = defineUiCatalog({
756
889
  description: "A titled section that groups related content.",
757
890
  props: z.object({
758
891
  title: z.string(),
759
- description: z.string().optional()
892
+ description: z.string().optional(),
893
+ palette: palette.optional(),
894
+ theme: surfaceTheme.optional(),
895
+ span: z.union([z.literal(1), z.literal(2)]).default(1),
896
+ density: z.enum([
897
+ "compact",
898
+ "default",
899
+ "comfortable"
900
+ ]).default("default")
760
901
  }),
761
902
  children: "any",
762
903
  example: {
@@ -803,15 +944,46 @@ const uiCatalog = defineUiCatalog({
803
944
  }]
804
945
  }
805
946
  },
947
+ {
948
+ name: "Split",
949
+ group: "layout",
950
+ description: "Two panes side by side at a fixed ratio — a chart next to its takeaways, a form next to its help text, a summary next to the detail it summarises. Takes exactly two children and stacks them vertically when the container is narrow, so the main pane always comes first. Use `Grid` when the cells are peers rather than a main/side pair.",
951
+ props: z.object({ ratio: z.enum([
952
+ "1:1",
953
+ "2:1",
954
+ "3:1",
955
+ "1:2"
956
+ ]).default("1:1") }),
957
+ children: PANEL,
958
+ example: {
959
+ component: "Split",
960
+ props: { ratio: "2:1" },
961
+ children: [{
962
+ component: "Card",
963
+ props: { title: "Trend" }
964
+ }, {
965
+ component: "Card",
966
+ props: { title: "Notes" }
967
+ }]
968
+ }
969
+ },
806
970
  {
807
971
  name: "Grid",
808
972
  group: "layout",
809
- description: "Dashboard grid. Each child is one cell and its own form container.",
810
- props: z.object({ columns: z.union([
811
- z.literal(2),
812
- z.literal(3),
813
- z.literal(4)
814
- ]).default(2) }),
973
+ description: "Dashboard grid: several tiles, metrics or charts the reader scans in parallel. Each child is one cell and its own form container. Prefer `columns: 'auto'` with a `minColumnWidth` — it fits as many cells as the container allows and reflows when narrow, which a fixed count cannot do. Set a fixed count only when the layout really needs exactly N.",
974
+ props: z.object({
975
+ columns: z.union([
976
+ z.literal(2),
977
+ z.literal(3),
978
+ z.literal(4),
979
+ z.literal("auto")
980
+ ]).default(2),
981
+ minColumnWidth: z.enum([
982
+ "sm",
983
+ "md",
984
+ "lg"
985
+ ]).default("md")
986
+ }),
815
987
  children: PANEL,
816
988
  example: {
817
989
  component: "Grid",
@@ -849,7 +1021,8 @@ const uiCatalog = defineUiCatalog({
849
1021
  z.literal(3),
850
1022
  z.literal(4)
851
1023
  ]).default(3),
852
- text: z.string()
1024
+ text: z.string(),
1025
+ palette: palette.optional()
853
1026
  }),
854
1027
  example: {
855
1028
  component: "Heading",
@@ -889,7 +1062,8 @@ const uiCatalog = defineUiCatalog({
889
1062
  description: "A short status label.",
890
1063
  props: z.object({
891
1064
  text: z.string(),
892
- tone: tone.default("neutral")
1065
+ tone: tone.default("neutral"),
1066
+ palette: palette.optional()
893
1067
  }),
894
1068
  example: {
895
1069
  component: "Badge",
@@ -950,13 +1124,16 @@ const uiCatalog = defineUiCatalog({
950
1124
  {
951
1125
  name: "Chart",
952
1126
  group: "data",
953
- description: "A bar, line, area or pie chart over labelled series.",
1127
+ description: "A bar, line, area, pie, scatter, stacked or dual-axis chart over labelled series.",
954
1128
  props: z.object({
955
1129
  type: z.enum([
956
1130
  "bar",
957
1131
  "line",
958
1132
  "area",
959
- "pie"
1133
+ "pie",
1134
+ "scatter",
1135
+ "stacked-bar",
1136
+ "dual-axis"
960
1137
  ]),
961
1138
  title: z.string().optional(),
962
1139
  labels: z.array(z.string()).min(1),
@@ -1007,7 +1184,7 @@ const uiCatalog = defineUiCatalog({
1007
1184
  {
1008
1185
  name: "FileLink",
1009
1186
  group: "data",
1010
- description: "A download entry for a run artifact.",
1187
+ description: "A download entry for a file. The host automatically renders every artifact the run produced as a download card, so never create a FileLink for those; use it only for files that are not run artifacts.",
1011
1188
  props: z.object({
1012
1189
  path: z.string(),
1013
1190
  label: z.string().optional(),
@@ -1158,6 +1335,160 @@ const uiCatalog = defineUiCatalog({
1158
1335
  }
1159
1336
  }
1160
1337
  },
1338
+ {
1339
+ name: "Icon",
1340
+ group: "content",
1341
+ description: "A small semantic icon, picked from the catalog whitelist. Use it to mark an item inside a list or next to a heading, never on its own as decoration. Names outside the whitelist are dropped by the renderer, so choose the closest listed name instead of inventing one.",
1342
+ props: z.object({
1343
+ name: iconName,
1344
+ palette: palette.optional()
1345
+ }),
1346
+ example: {
1347
+ component: "Icon",
1348
+ props: {
1349
+ name: "check",
1350
+ palette: "emerald"
1351
+ }
1352
+ }
1353
+ },
1354
+ {
1355
+ name: "Image",
1356
+ group: "content",
1357
+ description: "An image the host already serves. `src` must be same-origin or a data URL — remote URLs are rejected by policy, so never link to an image you found on the web. `alt` is required: it is what a screen reader and a later model read instead of the pixels.",
1358
+ props: z.object({
1359
+ src: imageSrc,
1360
+ alt: z.string(),
1361
+ ratio: z.enum([
1362
+ "auto",
1363
+ "1:1",
1364
+ "4:3",
1365
+ "16:9"
1366
+ ]).default("auto")
1367
+ }),
1368
+ example: {
1369
+ component: "Image",
1370
+ props: {
1371
+ src: "/chart.png",
1372
+ alt: "Revenue by region",
1373
+ ratio: "16:9"
1374
+ }
1375
+ }
1376
+ },
1377
+ {
1378
+ name: "Quote",
1379
+ group: "content",
1380
+ description: "Someone else's words, set apart from your own. Use it when you are repeating a customer comment, a policy sentence or a source document, so the reader can tell it apart from your summary. Do not use it for emphasis.",
1381
+ props: z.object({
1382
+ text: z.string(),
1383
+ by: z.string().optional()
1384
+ }),
1385
+ example: {
1386
+ component: "Quote",
1387
+ props: {
1388
+ text: "Delivery was two days late.",
1389
+ by: "Order A-1002"
1390
+ }
1391
+ }
1392
+ },
1393
+ {
1394
+ name: "Callout",
1395
+ group: "content",
1396
+ description: "A side note the reader may want but does not have to act on — background, a caveat, a definition. Use `Alert` instead when it is a state they must react to, and plain `Text` when it belongs in the flow of the answer.",
1397
+ props: z.object({
1398
+ title: z.string().optional(),
1399
+ text: z.string(),
1400
+ palette: palette.optional()
1401
+ }),
1402
+ example: {
1403
+ component: "Callout",
1404
+ props: {
1405
+ title: "How this is counted",
1406
+ text: "Refunds are excluded."
1407
+ }
1408
+ }
1409
+ },
1410
+ {
1411
+ name: "KeyValue",
1412
+ group: "data",
1413
+ description: "Facts about one thing, as label/value pairs. Use it when a `Table` would have a single row, for example the details of one order. For several comparable rows use `Table`, and for one headline number use `Metric`.",
1414
+ props: z.object({ items: z.array(z.object({
1415
+ label: z.string(),
1416
+ value: z.string()
1417
+ })).min(1) }),
1418
+ example: {
1419
+ component: "KeyValue",
1420
+ props: { items: [{
1421
+ label: "Status",
1422
+ value: "Shipped"
1423
+ }, {
1424
+ label: "Carrier",
1425
+ value: "DHL"
1426
+ }] }
1427
+ }
1428
+ },
1429
+ {
1430
+ name: "Gauge",
1431
+ group: "data",
1432
+ description: "One value read against a range — capacity used, budget spent, a score out of a maximum. Give `min` and `max` when the range is not 0-100. Use `Progress` for how far a task has come, and `Metric` when there is no range to compare against.",
1433
+ props: z.object({
1434
+ label: z.string().optional(),
1435
+ value: z.number(),
1436
+ min: z.number().default(0),
1437
+ max: z.number().default(100),
1438
+ tone: z.enum([
1439
+ "neutral",
1440
+ "success",
1441
+ "warning"
1442
+ ]).default("neutral")
1443
+ }),
1444
+ example: {
1445
+ component: "Gauge",
1446
+ props: {
1447
+ label: "Disk used",
1448
+ value: 82,
1449
+ tone: "warning"
1450
+ }
1451
+ }
1452
+ },
1453
+ {
1454
+ name: "Accordion",
1455
+ group: "layout",
1456
+ description: "Sections the reader opens one at a time. Use it for a long report where most readers only need one part, so the answer stays scannable instead of becoming a wall of text. Use `Tabs` when the sections are alternatives rather than a sequence, and plain `Card`s when there are only two or three short ones.",
1457
+ props: z.object({ items: z.array(z.object({
1458
+ title: z.string(),
1459
+ text: z.string()
1460
+ })).min(1) }),
1461
+ example: {
1462
+ component: "Accordion",
1463
+ props: { items: [{
1464
+ title: "Method",
1465
+ text: "Sampled hourly."
1466
+ }, {
1467
+ title: "Caveats",
1468
+ text: "Week 12 is partial."
1469
+ }] }
1470
+ }
1471
+ },
1472
+ {
1473
+ name: "Carousel",
1474
+ group: "layout",
1475
+ description: "Slides the reader scrolls through sideways — an itinerary day by day, a set of options, a small gallery. Each slide carries its own title and optional image, and only one is in view at a time. Use `Grid` when the reader should compare the items side by side instead of one at a time.",
1476
+ props: z.object({ items: z.array(z.object({
1477
+ title: z.string(),
1478
+ text: z.string().optional(),
1479
+ src: z.string().optional()
1480
+ })).min(1) }),
1481
+ example: {
1482
+ component: "Carousel",
1483
+ props: { items: [{
1484
+ title: "Day 1",
1485
+ text: "Arrival"
1486
+ }, {
1487
+ title: "Day 2",
1488
+ text: "Old town"
1489
+ }] }
1490
+ }
1491
+ },
1161
1492
  {
1162
1493
  name: "Alert",
1163
1494
  group: "feedback",
@@ -1228,10 +1559,12 @@ const UI_CATALOG_GROUPS = {
1228
1559
  };
1229
1560
  /**
1230
1561
  * catalog 系统提示的体积上限(UTF-8 字节,FR-6.6)。
1231
- * 描述每次请求都要重发,所以上限必须是可判定的数字而不是「尽量精简」。
1232
- * 要改这个数字,先想清楚是否值得让每次请求都多付这些 token。
1562
+ *
1563
+ * **0.11.0 起只是 `CATALOG_PROMPT_MAX` 的别名**:旧值 20 480 相对实际 7 788 宽了 2.6 倍,
1564
+ * 拦不住任何东西;且它只量提示词,漏掉了 `render_ui` 入参 Schema 那约两分之一的成本。
1565
+ * @deprecated 用 `CATALOG_PROMPT_MAX` / `CATALOG_SCHEMA_MAX`(`./budget`)。
1233
1566
  */
1234
- const UI_CATALOG_PROMPT_BUDGET_BYTES = 20480;
1567
+ const UI_CATALOG_PROMPT_BUDGET_BYTES = CATALOG_PROMPT_MAX;
1235
1568
  /** A2UI 协议公共类型(与 @a2ui/web_core 的 common-types 同一套定义) */
1236
1569
  const A2UI_COMMON_TYPES = "https://a2ui.org/specification/v0_9/common_types.json";
1237
1570
  const CHILD_LIST_REF = `${A2UI_COMMON_TYPES}#/$defs/ChildList`;
@@ -1604,6 +1937,18 @@ function chartToTable(chart) {
1604
1937
  rows: chart.labels.map((label, li) => [label, ...chart.series.map((s) => s.data[li] ?? null)])
1605
1938
  };
1606
1939
  }
1940
+ /**
1941
+ * `Gauge` 的百分比换算(0.11.0 分册 19 FR-19.2)。
1942
+ *
1943
+ * catalog 的 React `SpecGauge` 与文档面的预置组件都读这一份:
1944
+ * 换算里有两个容易各写各的判断——`max === min` 时不能除零、越界值要夹紧——
1945
+ * 两处各写一遍就会在边界值上给出不同的条。
1946
+ */
1947
+ function gaugePercent(value, min, max) {
1948
+ const span = max - min;
1949
+ if (span <= 0) return 0;
1950
+ return Math.max(0, Math.min(100, (value - min) / span * 100));
1951
+ }
1607
1952
  /** RenderBlock → DOM(markdown/json/table/image/file/chart 六种) */
1608
1953
  function renderBlocks(container, blocks, doc) {
1609
1954
  for (const block of blocks) switch (block.type) {
@@ -1681,6 +2026,154 @@ function renderRenderResult(request, doc) {
1681
2026
  renderBlocks(root, request.blocks, doc);
1682
2027
  return root;
1683
2028
  }
2029
+ /**
2030
+ * viewer 的宿主预置组件(0.11.0 分册 18 FR-18.5 / 分册 19)。
2031
+ *
2032
+ * 技能**不写 JS**:文档 HTML 里只留占位,由这里挂载真实组件。
2033
+ * 交互只落在图表上——表格排序的例子已作废(分册 29 §2 登记 #13)。
2034
+ *
2035
+ * 三条硬约束:
2036
+ * - props 用 **catalog 自己的 zod schema** 解析,同形因此是机制上成立的,
2037
+ * 不是靠一句「记得同步」的注释(FR-19.2);
2038
+ * - 值一律经 `textContent` 落地,**永不** `innerHTML`(FR-19.4 / AC-19.5);
2039
+ * - 白名单外的名字只渲染降级提示,**不触发任何按名 import**(FR-19.3)。
2040
+ */
2041
+ /** 占位元素的属性名,模板与挂载方共用(FR-19.1 的契约) */
2042
+ const VIEWER_COMPONENT_ATTR = "data-webskill-component";
2043
+ const VIEWER_PROPS_ATTR = "data-webskill-props";
2044
+ /** 降级提示的标记;模板 CSS 与判据都认这个属性,不认某个 class 名 */
2045
+ const VIEWER_FALLBACK_ATTR = "data-webskill-fallback";
2046
+ /** 文档面可用的组件白名单(FR-19.3)。字面量数组——不接受任何按名动态加载 */
2047
+ const DOCUMENT_COMPONENTS = [
2048
+ "Chart",
2049
+ "Table",
2050
+ "Metric",
2051
+ "Gauge",
2052
+ "KeyValue"
2053
+ ];
2054
+ const WHITELIST = new Set(DOCUMENT_COMPONENTS);
2055
+ /**
2056
+ * 扫描 `root` 下的占位并挂载预置组件。
2057
+ *
2058
+ * 逐个占位独立处理:一个坏占位只降级它自己,不会让整篇文档变空白。
2059
+ */
2060
+ function mountViewerComponents(root) {
2061
+ const handles = [];
2062
+ for (const node of Array.from(root.querySelectorAll(`[${VIEWER_COMPONENT_ATTR}]`))) {
2063
+ const element = node;
2064
+ const name = element.getAttribute("data-webskill-component") ?? "";
2065
+ if (!WHITELIST.has(name)) {
2066
+ fallback(element, `Unsupported component "${name}"`);
2067
+ continue;
2068
+ }
2069
+ const props = readProps(element, name);
2070
+ if (!props) continue;
2071
+ const handle = render(element, name, props);
2072
+ if (handle) handles.push(handle);
2073
+ }
2074
+ return { dispose: () => {
2075
+ for (const handle of handles) handle.dispose();
2076
+ handles.length = 0;
2077
+ } };
2078
+ }
2079
+ /** 用 catalog 的 schema 解析:多余字段被丢弃,缺省值与 catalog 完全一致 */
2080
+ function readProps(element, name) {
2081
+ const raw = element.getAttribute(VIEWER_PROPS_ATTR);
2082
+ if (raw === null || raw === "") {
2083
+ fallback(element, `Component "${name}" is missing ${VIEWER_PROPS_ATTR}`);
2084
+ return;
2085
+ }
2086
+ let parsed;
2087
+ try {
2088
+ parsed = JSON.parse(raw);
2089
+ } catch {
2090
+ fallback(element, `Component "${name}" has invalid props JSON`);
2091
+ return;
2092
+ }
2093
+ const schema = uiCatalog.component(name)?.props;
2094
+ if (!schema) {
2095
+ fallback(element, `Component "${name}" is not declared in the catalog`);
2096
+ return;
2097
+ }
2098
+ const result = schema.safeParse(parsed);
2099
+ if (!result.success) {
2100
+ fallback(element, `Component "${name}" has props the catalog rejects`);
2101
+ return;
2102
+ }
2103
+ return result.data;
2104
+ }
2105
+ function render(element, name, props) {
2106
+ const doc = element.ownerDocument;
2107
+ element.replaceChildren();
2108
+ switch (name) {
2109
+ case "Chart": {
2110
+ const handle = mountEchart(element);
2111
+ handle.setChart(chartSpecFromProps(props));
2112
+ return handle;
2113
+ }
2114
+ case "Table":
2115
+ renderBlocks(element, [{
2116
+ type: "table",
2117
+ columns: props["columns"],
2118
+ rows: props["rows"]
2119
+ }], doc);
2120
+ return;
2121
+ case "Metric": {
2122
+ append(doc, element, "span", String(props["label"]), { "data-webskill-metric": "label" });
2123
+ append(doc, element, "strong", String(props["value"]), { "data-webskill-metric": "value" });
2124
+ const change = props["change"];
2125
+ if (typeof change === "string") append(doc, element, "span", change, {
2126
+ "data-webskill-metric": "change",
2127
+ "data-trend": String(props["trend"] ?? "neutral")
2128
+ });
2129
+ return;
2130
+ }
2131
+ case "Gauge": {
2132
+ const value = props["value"];
2133
+ const min = props["min"];
2134
+ const max = props["max"];
2135
+ const label = typeof props["label"] === "string" ? props["label"] : "";
2136
+ element.setAttribute("data-tone", String(props["tone"]));
2137
+ if (label !== "") append(doc, element, "span", label, { "data-webskill-gauge": "label" });
2138
+ const fill = append(doc, append(doc, element, "div", "", {
2139
+ role: "meter",
2140
+ "aria-label": label === "" ? "Gauge" : label,
2141
+ "aria-valuenow": String(value),
2142
+ "aria-valuemin": String(min),
2143
+ "aria-valuemax": String(max),
2144
+ "data-webskill-gauge": "track"
2145
+ }), "div", "", { "data-webskill-gauge": "fill" });
2146
+ fill.style.width = `${gaugePercent(value, min, max)}%`;
2147
+ append(doc, element, "span", String(value), { "data-webskill-gauge": "value" });
2148
+ return;
2149
+ }
2150
+ case "KeyValue": {
2151
+ const dl = append(doc, element, "dl", "", {});
2152
+ for (const item of props["items"]) {
2153
+ append(doc, dl, "dt", item.label, {});
2154
+ append(doc, dl, "dd", item.value, {});
2155
+ }
2156
+ return;
2157
+ }
2158
+ }
2159
+ }
2160
+ function append(doc, parent, tag, text, attrs) {
2161
+ const el = doc.createElement(tag);
2162
+ for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, value);
2163
+ if (text !== "") el.textContent = text;
2164
+ parent.appendChild(el);
2165
+ return el;
2166
+ }
2167
+ /**
2168
+ * 可见降级(AC-19.4)。
2169
+ *
2170
+ * 静默留空是最坏的结果:文档看着「渲染成功」,作者却不知道自己写错了组件名。
2171
+ * 原有内容整体替换,避免半截渲染与提示并存。
2172
+ */
2173
+ function fallback(element, reason) {
2174
+ element.setAttribute(VIEWER_FALLBACK_ATTR, "true");
2175
+ element.textContent = reason;
2176
+ }
1684
2177
  /** 最小样式(BEM class,应用可整体覆写) */
1685
2178
  const WEBSKILL_STYLES_CSS = `
1686
2179
  .webskill-form { display: block; padding: 12px; border: 1px solid #d0d0d0; border-radius: 8px; font-family: system-ui, sans-serif; max-width: 420px; }
@@ -1884,7 +2377,7 @@ var WebFormBridge = class {
1884
2377
  input = textarea;
1885
2378
  } else {
1886
2379
  const textInput = doc.createElement("input");
1887
- textInput.type = control.control === "number" ? "number" : "text";
2380
+ textInput.type = control.control === "number" ? "number" : control.control === "date" ? "date" : "text";
1888
2381
  textInput.className = "webskill-form__input";
1889
2382
  if (control.defaultValue !== void 0) textInput.value = String(control.defaultValue);
1890
2383
  input = textInput;
@@ -2431,7 +2924,8 @@ const FIELD_TYPE = {
2431
2924
  select: "select",
2432
2925
  textarea: "textarea",
2433
2926
  file: "file",
2434
- password: "password"
2927
+ password: "password",
2928
+ date: "date"
2435
2929
  };
2436
2930
  function toOptions(options) {
2437
2931
  return (options ?? []).map((option) => ({
@@ -2871,7 +3365,7 @@ function fromA2uiSpecAction(event) {
2871
3365
  */
2872
3366
  async function loadWebSkillLitCatalog() {
2873
3367
  try {
2874
- const { webskillLitCatalog } = await import("./webskillLitCatalog-DwTwSBFt-BG7kL-Es.js");
3368
+ const { webskillLitCatalog } = await import("./webskillLitCatalog-DME6PBkV-CmYNLlIT.js");
2875
3369
  return webskillLitCatalog();
2876
3370
  } catch (cause) {
2877
3371
  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);
@@ -2896,4 +3390,4 @@ async function loadOpenUiPeers() {
2896
3390
  }
2897
3391
 
2898
3392
  //#endregion
2899
- export { UI_CATALOG_PROMPT_BUDGET_BYTES as $, fromUiSurfaceActionDispatch as A, resolveColumnWidths as B, collectSpecActions as C, evaluateFieldCondition as D, ensureStyles as E, normalizeColumnWidths as F, toUiSurfaceActionDispatch as G, toA2uiSurfaceAction as H, qualifyFieldName as I, A2UI_CHILDREN_PROP as J, toVercelToolInvocation as K, renderBlocks as L, interactionToUiSpec as M, loadOpenUiPeers as N, fromA2uiSpecAction as O, loadWebSkillLitCatalog as P, UI_CATALOG_GROUPS as Q, renderMiniChart as R, collectScopedValues as S, createUiCatalogToolSource as T, toJsonRenderSpec as U, toA2uiSpecMessages as V, toOpenUiSpecLang as W, DEFAULT_INTERACTION_TEXTS as X, A2UI_COMMON_TYPES as Y, DEFAULT_SURFACE_FORM_TEXTS as Z, WEBSKILL_SURFACE_ACTION as _, A2UI_VERSION as a, defineUiCatalog as at, chartToTable as b, MAX_CONDITION_DEPTH as c, renderMiniMarkdown as ct, SPEC_TABLE_MIN_COLUMN_WIDTH as d, shapeInteractionValue as dt, WEBSKILL_A2UI_CATALOG_ID as et, UI_PRESETS as f, uiCatalog as ft, WEBSKILL_STYLES_CSS as g, VercelUiBridge as h, A2UI_SURFACE_ACTION as i, chartSpecFromProps as it, fromVercelToolResult as j, fromA2uiSurfaceAction as k, RENDER_UI_TOOL as l, resolveInteractionTexts as lt, VERCEL_INTERACTION_TOOL_NAME as m, A2UI_SPEC_ACTION as n, a2uiComponentShapes as nt, CHART_PALETTE as o, interactionToFormModel as ot, UI_PRESET_NAMES as p, uiPreset as q, A2UI_SPEC_FORM_PATH as r, buildA2uiCatalogDefinition as rt, DESCRIBE_UI_PRESET_TOOL as s, mountEchart as st, A2UI_BASIC_CATALOG_ID as t, a2uiComponentSchema as tt, SPEC_TABLE_MIN_COLUMN_VAR as u, resolveSurfaceFormTexts as ut, WebFormBridge as v, collectValues as w, collectFormScopes as x, applySuggestion as y, renderRenderResult as z };
3393
+ export { uiPreset as $, ensureStyles as A, mountViewerComponents as B, applySuggestion as C, collectSpecActions as D, collectScopedValues as E, fromVercelToolResult as F, renderRenderResult as G, qualifyFieldName as H, gaugePercent as I, toA2uiSurfaceAction as J, resolveColumnWidths as K, interactionToUiSpec as L, fromA2uiSpecAction as M, fromA2uiSurfaceAction as N, collectValues as O, fromUiSurfaceActionDispatch as P, toVercelToolInvocation as Q, loadOpenUiPeers as R, WebFormBridge as S, uiCatalog as St, collectFormScopes as T, renderBlocks as U, normalizeColumnWidths as V, renderMiniChart as W, toOpenUiSpecLang as X, toJsonRenderSpec as Y, toUiSurfaceActionDispatch as Z, VIEWER_FALLBACK_ATTR as _, mountEchart as _t, A2UI_VERSION as a, DEFAULT_INTERACTION_TEXTS as at, WEBSKILL_STYLES_CSS as b, resolveSurfaceFormTexts as bt, DOCUMENT_COMPONENTS as c, UI_CATALOG_GROUPS as ct, SPEC_TABLE_MIN_COLUMN_VAR as d, a2uiComponentSchema as dt, A2UI_CHILDREN_PROP as et, SPEC_TABLE_MIN_COLUMN_WIDTH as f, a2uiComponentShapes as ft, VIEWER_COMPONENT_ATTR as g, interactionToFormModel as gt, VERCEL_INTERACTION_TOOL_NAME as h, defineUiCatalog as ht, A2UI_SURFACE_ACTION as i, CATALOG_SCHEMA_MAX as it, evaluateFieldCondition as j, createUiCatalogToolSource as k, MAX_CONDITION_DEPTH as l, UI_CATALOG_PROMPT_BUDGET_BYTES as lt, UI_PRESET_NAMES as m, chartSpecFromProps as mt, A2UI_SPEC_ACTION as n, CATALOG_BUDGET_STAGE as nt, CHART_PALETTE as o, DEFAULT_SURFACE_FORM_TEXTS as ot, UI_PRESETS as p, buildA2uiCatalogDefinition as pt, toA2uiSpecMessages as q, A2UI_SPEC_FORM_PATH as r, CATALOG_PROMPT_MAX as rt, DESCRIBE_UI_PRESET_TOOL as s, PLANNED_INCREMENT as st, A2UI_BASIC_CATALOG_ID as t, A2UI_COMMON_TYPES as tt, RENDER_UI_TOOL as u, WEBSKILL_A2UI_CATALOG_ID as ut, VIEWER_PROPS_ATTR as v, renderMiniMarkdown as vt, chartToTable as w, WEBSKILL_SURFACE_ACTION as x, shapeInteractionValue as xt, VercelUiBridge as y, resolveInteractionTexts as yt, loadWebSkillLitCatalog as z };