@webskill/sdk 0.21.0 → 0.23.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 (135) hide show
  1. package/dist/abnfDiagram-O67JEVCF-DtYFjfqO.js +109 -0
  2. package/dist/agent.d.ts +3 -3
  3. package/dist/agent.js +1174 -169
  4. package/dist/{approval-DwN2o2QG.js → approval-7CuZS3Z4.js} +104 -18
  5. package/dist/arc-8U_6NQWm.js +130 -0
  6. package/dist/architecture-7GRP2DOG-RTw1wpAq.js +4 -0
  7. package/dist/architectureDiagram-NJMV4G6O-XmmXrJAK.js +7473 -0
  8. package/dist/array-DQfpQNY8.js +8 -0
  9. package/dist/blockDiagram-BEXU5L5S-D4yjnCD1.js +2582 -0
  10. package/dist/browser.d.ts +455 -49
  11. package/dist/browser.js +2134 -138
  12. package/dist/c4Diagram-YGBWAQC7-BLmA_JSd.js +3668 -0
  13. package/dist/channel-CZxxLrG7.js +9 -0
  14. package/dist/chunk-2Q5K7J3B-Ck03gkFR.js +21 -0
  15. package/dist/chunk-3FUC2YCW-tr6jmbQf.js +530 -0
  16. package/dist/chunk-5DYCD2WN-Bt97YGTd.js +58 -0
  17. package/dist/chunk-5VM5RSS4-BBxa48HL.js +21 -0
  18. package/dist/chunk-6AEJRKK7-JXK89I-O.js +14 -0
  19. package/dist/chunk-742MDFTN-CPmgCGwC.js +466 -0
  20. package/dist/chunk-7INBJB4K-C9jq9o66.js +6552 -0
  21. package/dist/chunk-7M6MHVWA-BrKCWcR1.js +4894 -0
  22. package/dist/chunk-7PRAP22T-BHED6GVs.js +95 -0
  23. package/dist/chunk-DUW6YSOI-DoZj78PO.js +19 -0
  24. package/dist/chunk-FOHPRMQF-Dkbp819z.js +27189 -0
  25. package/dist/chunk-GNY47TPC-Bpvq1T49.js +87 -0
  26. package/dist/chunk-GTNCS2PH-CLmh4I2c.js +686 -0
  27. package/dist/chunk-GWA4HPMP-B6vN3fkL.js +95 -0
  28. package/dist/chunk-J5ZVWO5B-Bi_Pcobw.js +26 -0
  29. package/dist/chunk-JWPE2WC7-D1rC79GS.js +12 -0
  30. package/dist/chunk-LNGE3PJU-D5OIoe3Z.js +2582 -0
  31. package/dist/chunk-MBY4JIJT-C34L7a4f.js +2276 -0
  32. package/dist/chunk-NETBCI7D-Cg84k8N8.js +321 -0
  33. package/dist/chunk-O7XYJQB3-B6K0KOSL.js +8846 -0
  34. package/dist/chunk-UA2S7LBM-DxHM29_K.js +612 -0
  35. package/dist/chunk-WEXAMYUT-Ba6jFWhB.js +33 -0
  36. package/dist/chunk-XXDRQBXY-DPbSy4kg.js +12 -0
  37. package/dist/chunk-Y2CYZVJY-U3QOMqrW.js +15 -0
  38. package/dist/chunk-Z7XXMR3K-oLg1h8F3.js +1038 -0
  39. package/dist/chunk-ZIGJFQKS-DqEFBuHR.js +2215 -0
  40. package/dist/classDiagram-v2-NBCMYWYE-BqnOWIUt.js +3845 -0
  41. package/dist/cose-bilkent-JH36ORCC-C0dTAHVK.js +4270 -0
  42. package/dist/cynefin-OW5HDTMX-B2QvgEQj.js +4 -0
  43. package/dist/cynefinDiagram-VND7K2PF-yAWc6Z3W.js +479 -0
  44. package/dist/cytoscape.esm-C64mRh9B.js +28325 -0
  45. package/dist/dagre-6A5THRUB-D0w2W27v.js +4816 -0
  46. package/dist/defaultLocale-B_EoOkSF.js +249 -0
  47. package/dist/diagram-22UHCM2B-DvgzhUzP.js +6115 -0
  48. package/dist/diagram-3UASUU5V-DWgUB-ef.js +795 -0
  49. package/dist/diagram-ATOU4E4O-P1olqwIs.js +553 -0
  50. package/dist/diagram-CDSNMT55-BUlZuLUe.js +402 -0
  51. package/dist/diagram-MLGK6HIB-6w5qmkGt.js +186 -0
  52. package/dist/diagram-MPIPVDR6-DEFiSo8o.js +289 -0
  53. package/dist/dist-UajCDt3n.js +92 -0
  54. package/dist/ebnfDiagram-ZINNZB2B-CI8liCfR.js +123 -0
  55. package/dist/elk-276RUBZZ-3SvW6OAK.js +90034 -0
  56. package/dist/erDiagram-OPXOYQCR-CPTbKhwP.js +2301 -0
  57. package/dist/eventmodeling-NTZA5JFV-BlyuqGp0.js +4 -0
  58. package/dist/flowDiagram-KWPJA3E3-CcUJ6HbV.js +18 -0
  59. package/dist/ganttDiagram-FUAMR5RP-31UjY0KK.js +3939 -0
  60. package/dist/{toolSource-C9oNuKmr.js → geometry-CRhuySKV.js} +169 -12
  61. package/dist/gitGraph-4MIJSDKK-ChdVlrk7.js +4 -0
  62. package/dist/gitGraphDiagram-X574FWY7-DKuxJymB.js +1153 -0
  63. package/dist/governance.d.ts +22 -4
  64. package/dist/governance.js +100 -10
  65. package/dist/graphlib-tk9gQYuN.js +4194 -0
  66. package/dist/{index-DHopiH_m.d.ts → index-7No55dRb.d.ts} +161 -11
  67. package/dist/{index-B8Tgmpwh.d.ts → index-ShvCB5I9.d.ts} +2 -2
  68. package/dist/{index-CYLt5u4L.d.ts → index-V76_OFj2.d.ts} +789 -21
  69. package/dist/index.d.ts +4 -4
  70. package/dist/index.js +11 -9
  71. package/dist/info-A6RAGUB7-CxRAt9S9.js +4 -0
  72. package/dist/infoDiagram-VRGFBTTK-pRspGfjZ.js +25 -0
  73. package/dist/init-CMOIWdg-.js +16 -0
  74. package/dist/ishikawaDiagram-OU5B5YK6-DKerAPAg.js +975 -0
  75. package/dist/journeyDiagram-ZHPQQLJL-C4MK-YO5.js +1227 -0
  76. package/dist/kanban-definition-PNTS6WVX-BqsZpMXx.js +1244 -0
  77. package/dist/katex-BcRRvyh_.js +26465 -0
  78. package/dist/{kind-DaKqLX2F.js → kind-Dc8x0HWz.js} +54 -7
  79. package/dist/line-LgV8iVMV.js +48 -0
  80. package/dist/linear-BWKR0Tqd.js +393 -0
  81. package/dist/linkedDocument-CZZOl9cO.js +456 -0
  82. package/dist/mcp.d.ts +2 -2
  83. package/dist/{memoryArtifactStore-D6dLeY55.js → memoryArtifactStore-Bs7glPqm.js} +14 -0
  84. package/dist/mermaid-parser.core-pYR5siSx.js +754 -0
  85. package/dist/mermaid.core-DMtwGCHy.js +1756 -0
  86. package/dist/mindmap-definition-NLK3R4M7-CXbD-myG.js +1312 -0
  87. package/dist/node.d.ts +3 -3
  88. package/dist/node.js +12 -5
  89. package/dist/{openUiLibrary-b0i7F_oZ.js → openUiLibrary-B7j3rLrm.js} +1 -1
  90. package/dist/{openUiSpecLang-CLXJEeRU.js → openUiSpecLang-BSYYnjay.js} +1 -1
  91. package/dist/ordinal-CxA6GHQS.js +84 -0
  92. package/dist/packet-AYTQ26CC-BOm08cxl.js +4 -0
  93. package/dist/path-C27Z2Bjv.js +106 -0
  94. package/dist/{pathSecurity-B1owvJAF.js → pathSecurity-CirkQwWP.js} +12 -1
  95. package/dist/pegDiagram-GJSIUBJH-BLGJOewQ.js +115 -0
  96. package/dist/pie-WAS4IAKB-Dvg7glpk.js +4 -0
  97. package/dist/pieDiagram-5QR66LMP-laoaV_PB.js +298 -0
  98. package/dist/quadrantDiagram-O4NWA36T-Db_wFBkT.js +2236 -0
  99. package/dist/radar-RG4KPBEZ-BZDaTu06.js +4 -0
  100. package/dist/railroad-74A4TZTK-C-UhjkOy.js +4 -0
  101. package/dist/railroad-abnf-HS5TGJTU-dUKVA4OS.js +4 -0
  102. package/dist/railroad-ebnf-LZEXJU2U-DzQumk18.js +4 -0
  103. package/dist/railroad-peg-WCYAUIDC-B3vvTWed.js +4 -0
  104. package/dist/railroadDiagram-XR7U4H2S-BY0sTM4X.js +89 -0
  105. package/dist/requirementDiagram-PLB6GJNP-3rkQfEhs.js +2474 -0
  106. package/dist/rough.cjs-AmDd_NQk.js +1395 -0
  107. package/dist/sankeyDiagram-IPEJSGJF-CGAJhyG2.js +3578 -0
  108. package/dist/sequenceDiagram-PO4LG4MO-9FJjFX06.js +5522 -0
  109. package/dist/sizeCapture-INFHLROL-D8PWxdOn.js +56 -0
  110. package/dist/{skill-CAJMsLod.js → skill-WSUDp6A1.js} +1 -1
  111. package/dist/{skillVersionStore-B24Jjjry-BkOk9wI_.d.ts → skillVersionStore-S4_HJ_Fl-Cvy9LqVu.d.ts} +35 -4
  112. package/dist/src-NkhI3UMl.js +2653 -0
  113. package/dist/stateDiagram-v2-GCMORJYK-B0O3wc1-.js +2715 -0
  114. package/dist/swimlanes-2SLR337P-D4EOnrpu.js +6637 -0
  115. package/dist/swimlanesDiagram-TC7HE7FX-BUx29EJ9.js +30 -0
  116. package/dist/testing.d.ts +2 -1
  117. package/dist/testing.js +1 -1
  118. package/dist/timeline-definition-EJHVYXUP-CrgiiB4C.js +1513 -0
  119. package/dist/treeView-Q6P3EWNA-C1by-Gzc.js +4 -0
  120. package/dist/treemap-WGGIJYW6-mC2NZ6ad.js +4 -0
  121. package/dist/{types-BvTV_05--BW8zDuEk.d.ts → types-CpDRZ0rA-BiZBXGm4.d.ts} +66 -8
  122. package/dist/ui-react.d.ts +3 -3
  123. package/dist/ui-react.js +3746 -96
  124. package/dist/ui-vue.d.ts +1 -1
  125. package/dist/ui.d.ts +3 -3
  126. package/dist/ui.js +2 -2
  127. package/dist/usecaseDiagram-POWQR4AR-BUt_TQOY.js +14397 -0
  128. package/dist/vennDiagram-UO4OBE2U-CwXr0wjA.js +2808 -0
  129. package/dist/wardley-WFR3VGLG-CVSYjq_S.js +4 -0
  130. package/dist/wardleyDiagram-VNRHLVJA-Cpfd47dQ.js +874 -0
  131. package/dist/{webSkillApi-Cib6G-94.js → webSkillApi-D5VMUnrv.js} +2 -2
  132. package/dist/{webskillLitCatalog-W06npwej.js → webskillLitCatalog-D5BCiMCU.js} +1 -1
  133. package/dist/xychartDiagram-PMCCYNJV-e1TeVoc6.js +2704 -0
  134. package/package.json +1 -1
  135. package/dist/linkedDocument-C0gj1sMq.js +0 -245
package/dist/agent.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
2
- import { b as parseSkillMarkdown, y as isValidSkillName } from "./skill-CAJMsLod.js";
2
+ import { b as parseSkillMarkdown, y as isValidSkillName } from "./skill-WSUDp6A1.js";
3
3
  import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
4
4
  import { n as sanitizeUntrustedLine, t as UNTRUSTED_LINE_LIMIT } from "./untrustedText-BIaRvPZK.js";
5
- import { c as formatAttachmentText, o as XLSX_MIME, s as classifyAttachment } from "./kind-DaKqLX2F.js";
6
- import { c as SPREADSHEET_EXTENSION, f as DEFAULT_MAX_DATA_SOURCE_BYTES, l as SPREADSHEET_LIMITS, o as toBase64, s as encodeSpreadsheet, u as SPREADSHEET_MIME_TYPE } from "./linkedDocument-C0gj1sMq.js";
7
- import { _ as frameSteps, a as defersImage, c as isPdfTextTrustworthy, d as DOCUMENT_READ_BUDGET, f as PAGE_ACTION_KINDS, g as frameLabel, h as toFrameScopes, i as asReadMode, l as measurePdfTextQuality, m as toActionFrameScopes, n as READ_DOCUMENT_TOOL, o as takesImage, p as PAGE_DOCUMENT_REF, r as createDocumentToolSource, s as PDF_GARBLED_RATIO, t as PDF_LARGE_IMAGE_RATIO, u as sniffDocumentFormat } from "./toolSource-C9oNuKmr.js";
5
+ import { d as extractedDocumentFormat, f as formatAttachmentText, u as classifyAttachment } from "./kind-Dc8x0HWz.js";
6
+ import { c as toBase64, d as SPREADSHEET_LIMITS, f as SPREADSHEET_MIME_TYPE, l as encodeSpreadsheet, m as DEFAULT_MAX_DATA_SOURCE_BYTES, u as SPREADSHEET_EXTENSION } from "./linkedDocument-CZZOl9cO.js";
7
+ import { A as frameSteps, C as DOCUMENT_READ_BUDGET, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, S as sniffDocumentFormat, T as PAGE_DOCUMENT_REF, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, g as asReadMode, h as createDocumentToolSource, i as MAX_DIGIT_WIDTH, k as frameLabel, l as parseCellAddress, m as READ_DOCUMENT_TOOL, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage, w as PAGE_ACTION_KINDS, x as measurePdfTextQuality, y as PDF_GARBLED_RATIO } from "./geometry-CRhuySKV.js";
8
8
 
9
9
  //#region ../agent/src/todo/store.ts
10
10
  const STATUSES = [
@@ -66,13 +66,18 @@ var TodoStore = class {
66
66
  /**
67
67
  * 更新单条。置为 `in-progress` 时其余进行中条目降级为 `pending`,
68
68
  * 每条降级各发一次 `todo.updated`,事件序列与状态变化一一对应。
69
+ *
70
+ * `options.concurrent` 由**编排器**在并发委派时传(0.23.0 分册 19 FR-19.9):
71
+ * 几个子 agent 真的在同时跑,把别人降级回 `pending` 是在界面上说谎。
72
+ * 模型经 `manage_todo` 的写入不传它,仍维持「同时只有一条进行中」——
73
+ * 那条约束针对的是模型乱标(名义并行、实则一件事),不是真并发。
69
74
  */
70
- update(id, patch) {
75
+ update(id, patch, options) {
71
76
  const target = this.#items.find((item) => item.id === id);
72
77
  if (!target) throw new WebSkillError("TODO_ITEM_NOT_FOUND", `Todo item "${id}" does not exist in the current list`);
73
78
  if (patch.status !== void 0 && !STATUSES.includes(patch.status)) throw new WebSkillError("TODO_LIST_INVALID", `Todo item "${id}" has an unknown status; expected one of ${STATUSES.join(", ")}`);
74
79
  const demoted = [];
75
- if (patch.status === "in-progress") for (const item of this.#items) {
80
+ if (patch.status === "in-progress" && options?.concurrent !== true) for (const item of this.#items) {
76
81
  if (item.id === id || item.status !== "in-progress") continue;
77
82
  item.status = "pending";
78
83
  demoted.push(item);
@@ -130,7 +135,7 @@ const STATUS_ENUM = [
130
135
  "completed"
131
136
  ];
132
137
  const TODO_TOOL_DESCRIPTION = "Maintain the visible task list for a multi-step request. The rules are in the system prompt.";
133
- const INPUT_SCHEMA$5 = {
138
+ const INPUT_SCHEMA$6 = {
134
139
  type: "object",
135
140
  properties: {
136
141
  action: {
@@ -183,7 +188,7 @@ const INPUT_SCHEMA$5 = {
183
188
  required: ["action"],
184
189
  additionalProperties: false
185
190
  };
186
- function toolError$7(code, message) {
191
+ function toolError$8(code, message) {
187
192
  return {
188
193
  ok: false,
189
194
  content: [],
@@ -232,15 +237,15 @@ function createTodoToolSource(options = {}) {
232
237
  try {
233
238
  if (action === "create") {
234
239
  const items = parseItems(args["items"]);
235
- if (typeof items === "string") return Promise.resolve(toolError$7("VALIDATION_FAILED", items));
240
+ if (typeof items === "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", items));
236
241
  const list = store.create(items);
237
242
  return Promise.resolve(ok(events, progressText(list.items)));
238
243
  }
239
244
  if (action === "update") {
240
245
  const id = args["id"];
241
246
  const status = args["status"];
242
- if (typeof id !== "string") return Promise.resolve(toolError$7("VALIDATION_FAILED", "The \"id\" argument is required for action \"update\""));
243
- if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$7("VALIDATION_FAILED", "The \"status\" argument must be a string"));
247
+ if (typeof id !== "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"id\" argument is required for action \"update\""));
248
+ if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"status\" argument must be a string"));
244
249
  const title = args["title"];
245
250
  const list = store.update(id, {
246
251
  ...status !== void 0 ? { status } : {},
@@ -252,10 +257,10 @@ function createTodoToolSource(options = {}) {
252
257
  store.clear();
253
258
  return Promise.resolve(ok(events, progressText([])));
254
259
  }
255
- return Promise.resolve(toolError$7("VALIDATION_FAILED", "The \"action\" argument must be one of create, update, clear"));
260
+ return Promise.resolve(toolError$8("VALIDATION_FAILED", "The \"action\" argument must be one of create, update, clear"));
256
261
  } catch (e) {
257
262
  const code = e instanceof Error && "code" in e ? String(e.code) : "VALIDATION_FAILED";
258
- return Promise.resolve(toolError$7(code, e instanceof Error ? e.message : String(e)));
263
+ return Promise.resolve(toolError$8(code, e instanceof Error ? e.message : String(e)));
259
264
  } finally {
260
265
  unsubscribe();
261
266
  }
@@ -267,7 +272,7 @@ function createTodoToolSource(options = {}) {
267
272
  listToolSpecs: () => Promise.resolve([{
268
273
  name: MANAGE_TODO_TOOL,
269
274
  description: TODO_TOOL_DESCRIPTION,
270
- inputSchema: INPUT_SCHEMA$5
275
+ inputSchema: INPUT_SCHEMA$6
271
276
  }]),
272
277
  canHandle: (name) => name === MANAGE_TODO_TOOL,
273
278
  argCaptureTrust: (name) => name === "manage_todo" ? { tier: "reviewed" } : void 0,
@@ -291,6 +296,9 @@ function ok(events, text) {
291
296
  //#endregion
292
297
  //#region ../agent/src/skillGeneration/generator.ts
293
298
  const DEFAULT_MAX_PER_SESSION = 3;
299
+ /** 缺省体积闸门(0.23.0 分册 20 FR-20.12):宿主可经 policy 覆盖 */
300
+ const DEFAULT_MAX_BINARY_BYTES = 10 * 1024 * 1024;
301
+ const DEFAULT_MAX_TOTAL_BINARY_BYTES = 25 * 1024 * 1024;
294
302
  const SCRIPT_EXTENSIONS = [".ts", ".js"];
295
303
  /** 缺省英文模板:与 0.9.0 之前硬编码的正文逐字一致 */
296
304
  function defaultConfirmSkill(draft) {
@@ -322,11 +330,66 @@ function validateDraft(draft) {
322
330
  }
323
331
  }
324
332
  /** 确认面板正文:完整 SKILL.md + 附带文件,供用户逐字检查(FR-9.4 风险缓解) */
325
- function previewOf(draft) {
333
+ function previewOf(draft, binaries) {
326
334
  const sections = [`SKILL.md\n${"─".repeat(32)}\n${draft.content}`];
327
- for (const file of draft.files ?? []) sections.push(`${file.path}\n${"─".repeat(32)}\n${file.content}`);
335
+ for (const file of draft.files ?? []) {
336
+ const binary = binaries.get(file.path);
337
+ const body = binary === void 0 ? file.content : `[binary ${binary.size} bytes from "${binary.sourceName}"]\nsha256: ${binary.sha256}`;
338
+ sections.push(`${file.path}\n${"─".repeat(32)}\n${body}`);
339
+ }
328
340
  return sections.join("\n\n");
329
341
  }
342
+ /** 环境无关 sha256(WebCrypto;Node ≥17 与浏览器均有 globalThis.crypto.subtle) */
343
+ async function sha256Hex(bytes) {
344
+ const copy = new ArrayBuffer(bytes.byteLength);
345
+ new Uint8Array(copy).set(bytes);
346
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", copy);
347
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
348
+ }
349
+ /**
350
+ * 把模型指认的附件变成字节(0.23.0 分册 20 §2.4)。
351
+ *
352
+ * 排在用户确认**之前**:「那个附件取不到」「超出上限」这类失败与用户当时正在做的事
353
+ * 直接相关,拖到提交时才报,用户已经点完同意、等完一轮审核了。
354
+ * 上限也在这里判——**拒绝必须发生在写盘之前**,先复制再检查会把超限的字节先落一遍磁盘。
355
+ */
356
+ async function materializeBinaries(draft, options) {
357
+ const claimed = (draft.files ?? []).filter((file) => file.attachmentId !== void 0);
358
+ const out = /* @__PURE__ */ new Map();
359
+ if (claimed.length === 0) return out;
360
+ const resolve = options.resolveAttachment;
361
+ if (resolve === void 0) fail("This host cannot put uploaded files into a skill. Remove \"attachmentId\" from the file entries and write their contents instead.");
362
+ const maxOne = options.policy?.maxBinaryBytes ?? DEFAULT_MAX_BINARY_BYTES;
363
+ const maxTotal = options.policy?.maxTotalBinaryBytes ?? DEFAULT_MAX_TOTAL_BINARY_BYTES;
364
+ let total = 0;
365
+ for (const file of claimed) {
366
+ const id = file.attachmentId;
367
+ const attachment = await resolve(id);
368
+ if (attachment === void 0) fail(`No file with attachment id "${id}" was uploaded in this conversation, so "${file.path}" cannot be added to the skill.`, {
369
+ attachmentId: id,
370
+ path: file.path
371
+ });
372
+ const size = attachment.bytes.byteLength;
373
+ if (size > maxOne) fail(`The attached file "${attachment.name}" is ${size} bytes, above the ${maxOne} byte limit for one file.`, {
374
+ path: file.path,
375
+ size,
376
+ limit: maxOne
377
+ });
378
+ total += size;
379
+ if (total > maxTotal) fail(`The attached files add up to ${total} bytes, above the ${maxTotal} byte limit for one skill.`, {
380
+ path: file.path,
381
+ total,
382
+ limit: maxTotal
383
+ });
384
+ out.set(file.path, {
385
+ bytes: attachment.bytes,
386
+ size,
387
+ sha256: await sha256Hex(attachment.bytes),
388
+ sourceName: attachment.name
389
+ });
390
+ }
391
+ return out;
392
+ }
330
393
  /**
331
394
  * 工具名硬校(FR-30.4 / AC-30.8)。
332
395
  *
@@ -422,6 +485,7 @@ var SkillGenerator = class {
422
485
  assertStepsAreNotTruncated(draft, trace.records);
423
486
  }
424
487
  this.#used += 1;
488
+ const binaries = await materializeBinaries(draft, this.#options);
425
489
  const evidence = trace.available ? buildTraceEvidence(draft, trace.records) : void 0;
426
490
  this.#requestSeq += 1;
427
491
  const response = await this.#options.ui.request({
@@ -430,7 +494,7 @@ var SkillGenerator = class {
430
494
  capability: "confirm",
431
495
  message: this.#options.messages?.confirmSkill?.(draft) ?? defaultConfirmSkill(draft),
432
496
  details: {
433
- preview: previewOf(draft),
497
+ preview: previewOf(draft, binaries),
434
498
  ...evidence !== void 0 ? { traceEvidence: evidence } : {}
435
499
  }
436
500
  });
@@ -439,7 +503,8 @@ var SkillGenerator = class {
439
503
  const { id } = await sink.submit({
440
504
  draft,
441
505
  confirmedAt,
442
- ...sessionId !== void 0 ? { sessionId } : {}
506
+ ...sessionId !== void 0 ? { sessionId } : {},
507
+ ...binaries.size > 0 ? { binaries } : {}
443
508
  });
444
509
  return {
445
510
  status: "submitted",
@@ -491,6 +556,10 @@ const SKILL_GENERATION_SYSTEM_PROMPT = [
491
556
  "- `content` must be a complete SKILL.md: YAML frontmatter with `name` and `description`,",
492
557
  " then the instructions as Markdown. The frontmatter `name` must equal the `name` argument.",
493
558
  "- Put helper scripts under `scripts/` and use the `.ts` or `.js` extension.",
559
+ "- A file shipped with the skill (a template spreadsheet, a reference document) is addressed",
560
+ " from inside SKILL.md as `skill:<the skill name>/<the path you gave it>`, for example",
561
+ " `skill:monthly-report/assets/template.xlsx`. Write that, never the attachment id of this",
562
+ " conversation — that id is gone by the time anyone uses the skill.",
494
563
  "- Never copy credentials, tokens or personal data into the skill.",
495
564
  "- The user has to confirm every candidate, and the candidate stays unusable until a reviewer",
496
565
  " approves it. Say so instead of promising the skill is ready."
@@ -500,7 +569,7 @@ const SKILL_GENERATION_SYSTEM_PROMPT = [
500
569
  //#region ../agent/src/skillGeneration/toolSource.ts
501
570
  const GENERATE_SKILL_TOOL = "generate_skill";
502
571
  const TOOL_DESCRIPTION = "Turn the procedure just demonstrated in this conversation into a skill candidate. The user must confirm it, and a reviewer must approve it before anyone can use it.";
503
- const INPUT_SCHEMA$4 = {
572
+ const INPUT_SCHEMA$5 = {
504
573
  type: "object",
505
574
  properties: {
506
575
  name: {
@@ -517,17 +586,24 @@ const INPUT_SCHEMA$4 = {
517
586
  },
518
587
  files: {
519
588
  type: "array",
520
- description: "Optional helper scripts or reference files shipped with the skill",
589
+ description: "Optional helper scripts or reference files shipped with the skill. To ship a file the user uploaded in this conversation (a spreadsheet or document used as a template), give its attachment id instead of \"content\" — the skill then carries the real file and the user never has to upload it again. Do not retype an uploaded file's contents: you only ever saw text extracted from it, so what you write would not be the same file.",
521
590
  items: {
522
591
  type: "object",
523
592
  properties: {
524
593
  path: {
525
594
  type: "string",
526
- description: "Relative path, e.g. \"scripts/check.ts\""
595
+ description: "Relative path, e.g. \"scripts/check.ts\" or \"references/template.xlsx\""
527
596
  },
528
- content: { type: "string" }
597
+ content: {
598
+ type: "string",
599
+ description: "The file contents. Omit when \"attachmentId\" is given."
600
+ },
601
+ attachmentId: {
602
+ type: "string",
603
+ description: "Id of a file uploaded in this conversation. Its bytes are copied into the skill as-is. Omit when \"content\" is given. This id exists only in this conversation, so SKILL.md must address the shipped file as \"skill:<the skill name>/<its path in this list>\" instead."
604
+ }
529
605
  },
530
- required: ["path", "content"],
606
+ required: ["path"],
531
607
  additionalProperties: false
532
608
  }
533
609
  },
@@ -558,7 +634,7 @@ const INPUT_SCHEMA$4 = {
558
634
  ],
559
635
  additionalProperties: false
560
636
  };
561
- function toolError$6(code, message) {
637
+ function toolError$7(code, message) {
562
638
  return {
563
639
  ok: false,
564
640
  content: [],
@@ -585,10 +661,22 @@ function readDraft(args) {
585
661
  for (const entry of rawFiles) {
586
662
  if (typeof entry !== "object" || entry === null) return "Every entry in \"files\" must be an object";
587
663
  const record = entry;
588
- if (typeof record["path"] !== "string" || typeof record["content"] !== "string") return "Every attached file needs string \"path\" and \"content\"";
664
+ if (typeof record["path"] !== "string") return "Every attached file needs a string \"path\"";
665
+ const attachmentId = record["attachmentId"];
666
+ if (attachmentId !== void 0 && (typeof attachmentId !== "string" || attachmentId.trim() === "")) return "An attached file's \"attachmentId\" must be a non-empty string";
667
+ if (attachmentId === void 0) {
668
+ if (typeof record["content"] !== "string") return "Every attached file needs string \"content\", or an \"attachmentId\" pointing at an uploaded file";
669
+ files.push({
670
+ path: record["path"],
671
+ content: record["content"]
672
+ });
673
+ continue;
674
+ }
675
+ if (typeof record["content"] === "string" && record["content"] !== "") return "An attached file takes either \"content\" or \"attachmentId\", not both";
589
676
  files.push({
590
677
  path: record["path"],
591
- content: record["content"]
678
+ content: "",
679
+ attachmentId
592
680
  });
593
681
  }
594
682
  return {
@@ -627,7 +715,7 @@ function createSkillGenerationToolSource(options) {
627
715
  const generator = new SkillGenerator(options);
628
716
  const call = async (_name, args) => {
629
717
  const draft = readDraft(args);
630
- if (typeof draft === "string") return toolError$6("SKILL_GENERATION_VALIDATION_FAILED", draft);
718
+ if (typeof draft === "string") return toolError$7("SKILL_GENERATION_VALIDATION_FAILED", draft);
631
719
  try {
632
720
  const outcome = await generator.generate(draft);
633
721
  if (outcome.status === "declined") return {
@@ -651,7 +739,7 @@ function createSkillGenerationToolSource(options) {
651
739
  }]
652
740
  };
653
741
  } catch (e) {
654
- return toolError$6(e instanceof Error && "code" in e ? String(e.code) : "SKILL_GENERATION_VALIDATION_FAILED", e instanceof Error ? e.message : String(e));
742
+ return toolError$7(e instanceof Error && "code" in e ? String(e.code) : "SKILL_GENERATION_VALIDATION_FAILED", e instanceof Error ? e.message : String(e));
655
743
  }
656
744
  };
657
745
  return {
@@ -660,7 +748,7 @@ function createSkillGenerationToolSource(options) {
660
748
  listToolSpecs: () => Promise.resolve([{
661
749
  name: GENERATE_SKILL_TOOL,
662
750
  description: TOOL_DESCRIPTION,
663
- inputSchema: INPUT_SCHEMA$4
751
+ inputSchema: INPUT_SCHEMA$5
664
752
  }]),
665
753
  canHandle: (name) => name === GENERATE_SKILL_TOOL,
666
754
  argCaptureTrust: (name) => name === "generate_skill" ? { tier: "reviewed" } : void 0,
@@ -670,13 +758,12 @@ function createSkillGenerationToolSource(options) {
670
758
 
671
759
  //#endregion
672
760
  //#region ../agent/src/delegation/orchestrator.ts
673
- const DEFAULT_MAX_TURNS = 8;
674
761
  /**
675
- * 串行委派编排器(设计 03 §13–§16)。
762
+ * 委派编排器(设计 03 §13–§16,并发改造见 0.23.0 分册 19)。
676
763
  *
677
- * 严格串行由**内部互斥**保证而不是调用方自觉:并发委派会产生并发待决交互,
678
- * 而当前交互模型是单一待决 nonce,届时表现为「点了没反应」。
679
- * 放开并发必须显式改这里的守卫,不会悄悄发生。
764
+ * 并发放开的前提是**并行的是执行、不是提问**:待决交互仍然一次只呈现一张,
765
+ * `ReactBridgeState` / `specInteractionChannel` 的队列保证。
766
+ * 若要在别处复用本编排器,那两处的排队语义是它的前置条件。
680
767
  *
681
768
  * 上下文隔离靠回值形状达成:只有 `summary` 回到父 agent,
682
769
  * 子 agent 的消息历史根本不经过本对象。
@@ -684,34 +771,62 @@ const DEFAULT_MAX_TURNS = 8;
684
771
  */
685
772
  var DelegationOrchestrator = class {
686
773
  #options;
687
- #running;
774
+ /** 正在跑的子 run:id → 任务描述 */
775
+ #running = /* @__PURE__ */ new Map();
776
+ /** 等名额的委派,先进先出 */
777
+ #waiting = [];
778
+ /**
779
+ * 此刻在跑的子 run 已领走的轮数之和(FR-19.7)。
780
+ * 父级的 `remainingTurns` 不会因子 run 而减少(子 run 跑在独立 runtime 上),
781
+ * 不自己记账的话两个并发委派会各自读到同一个剩余值,把同一份额度花两遍。
782
+ */
783
+ #claimedTurns = 0;
784
+ #seq = 0;
688
785
  constructor(options) {
689
786
  this.#options = options;
690
787
  }
691
- /** 当前正在执行的委派任务;无则 undefined */
788
+ /** 正在执行的委派任务之一;全部空闲则 undefined。仅供诊断 */
692
789
  get running() {
693
- return this.#running;
790
+ for (const request of this.#running.values()) return request.task;
791
+ }
792
+ #limit() {
793
+ const raw = this.#options.concurrencyLimit?.() ?? 1;
794
+ return Number.isFinite(raw) && raw >= 1 ? Math.floor(raw) : 1;
795
+ }
796
+ /** 让出一个名额并唤醒队首。必须在 finally 里调用,否则一次异常会把名额永久吞掉 */
797
+ #release(id, claimed) {
798
+ this.#running.delete(id);
799
+ this.#claimedTurns = Math.max(0, this.#claimedTurns - claimed);
800
+ this.#waiting.shift()?.();
694
801
  }
695
802
  async delegate(request) {
696
803
  const runner = this.#options.runner;
697
804
  if (!runner) throw new WebSkillError("DELEGATION_UNAVAILABLE", "Delegation is enabled but this host did not provide a sub-agent runner");
698
- if (this.#running !== void 0) throw new WebSkillError("DELEGATION_IN_PROGRESS", `Only one sub-agent may run at a time; "${this.#running}" has not finished yet`);
699
805
  if (request.task.trim() === "") throw new WebSkillError("DELEGATION_UNAVAILABLE", "A delegated task must not be empty");
806
+ while (this.#running.size >= this.#limit()) await new Promise((resolve) => this.#waiting.push(resolve));
807
+ const id = `d${this.#seq += 1}`;
700
808
  const parent = this.#options.parentBudget();
809
+ const availableTurns = Math.max(0, parent.remainingTurns - this.#claimedTurns);
810
+ const idleSlots = Math.max(1, this.#limit() - this.#running.size);
811
+ const share = Math.floor(availableTurns / idleSlots);
701
812
  const budget = {
702
- maxTurns: Math.min(this.#options.policy?.maxTurns ?? DEFAULT_MAX_TURNS, parent.remainingTurns),
813
+ maxTurns: Math.min(this.#options.policy?.maxTurns ?? Number.POSITIVE_INFINITY, share),
703
814
  timeoutMs: Math.min(this.#options.policy?.timeoutMs ?? Number.POSITIVE_INFINITY, parent.remainingTimeoutMs)
704
815
  };
705
- if (budget.maxTurns < 1 || budget.timeoutMs <= 0) return {
706
- todoId: request.todoId,
707
- outcome: "failed",
708
- summary: `The parent run has no budget left to delegate "${request.task}" (${parent.remainingTurns} turns and ${parent.remainingTimeoutMs} ms remaining).`
709
- };
710
- this.#running = request.task;
816
+ if (budget.maxTurns < 1 || budget.timeoutMs <= 0) {
817
+ this.#waiting.shift()?.();
818
+ return {
819
+ todoId: request.todoId,
820
+ outcome: "failed",
821
+ summary: `The parent run has no budget left to delegate "${request.task}" (${availableTurns} turns and ${parent.remainingTimeoutMs} ms remaining).`
822
+ };
823
+ }
824
+ this.#running.set(id, request);
825
+ this.#claimedTurns += budget.maxTurns;
711
826
  this.#options.todos?.update(request.todoId, {
712
827
  status: "in-progress",
713
828
  delegatedTo: request.task
714
- });
829
+ }, { concurrent: true });
715
830
  const controller = new AbortController();
716
831
  let timedOut = false;
717
832
  const timer = setTimeout(() => {
@@ -744,7 +859,7 @@ var DelegationOrchestrator = class {
744
859
  };
745
860
  } finally {
746
861
  clearTimeout(timer);
747
- this.#running = void 0;
862
+ this.#release(id, budget.maxTurns);
748
863
  }
749
864
  }
750
865
  };
@@ -752,25 +867,27 @@ var DelegationOrchestrator = class {
752
867
  //#endregion
753
868
  //#region ../agent/src/prompts/delegation.ts
754
869
  /**
755
- * 串行委派的策略提示词(设计 03 §13)。
870
+ * 委派的策略提示词(设计 03 §13;并发改造见 0.23.0 分册 19)。
756
871
  *
757
- * 「一次只能有一个」是编排器的硬约束,这里写进提示词是为了让模型不去
758
- * 尝试并行委派——被拒绝的调用会浪费一轮,而不是产生并发。
872
+ * 并发数那两句是**建议**而非约束:真正拦住数量的是编排器的排队与宿主硬上限。
873
+ * 写在这里是为了让模型按任务真实需要给值,而不是一口气派出一大批。
759
874
  */
760
875
  const DELEGATION_SYSTEM_PROMPT = [
761
876
  "Delegating sub-tasks:",
762
877
  "- Delegate a task only when it is self-contained and its details do not need to stay in this conversation.",
763
- "- Delegate one task at a time and wait for the result. A second delegation while one is running is rejected.",
878
+ "- Several delegations may run at the same time. Only delegate work that is genuinely independent: sub-agents cannot see each other and cannot exchange results.",
879
+ "- Keep the number of parallel sub-agents small. Use \"suggestedConcurrency\" to state how many this task really needs; the user picks the final number and the host may cap it lower.",
764
880
  "- Every delegation must reference an existing task-list item via \"todoId\".",
765
881
  "- You receive only the sub-agent summary, never its intermediate steps. If you need details, ask for them in the task description.",
766
- "- If a delegation fails, decide yourself whether to retry with a narrower task or to continue without it."
882
+ "- If a delegation fails, decide yourself whether to retry with a narrower task or to continue without it.",
883
+ "- If the user declines delegation, do the work yourself in this conversation and do not delegate again."
767
884
  ].join("\n");
768
885
 
769
886
  //#endregion
770
887
  //#region ../agent/src/delegation/toolSource.ts
771
888
  const DELEGATE_TASK_TOOL = "delegate_task";
772
- const DELEGATE_TOOL_DESCRIPTION = "Hand one self-contained sub-task to a sub-agent and wait for its summary. One delegation at a time.";
773
- const INPUT_SCHEMA$3 = {
889
+ const DELEGATE_TOOL_DESCRIPTION = "Hand one self-contained sub-task to a sub-agent and wait for its summary. Several delegations may run in parallel, up to a limit the user picks.";
890
+ const INPUT_SCHEMA$4 = {
774
891
  type: "object",
775
892
  properties: {
776
893
  todoId: {
@@ -785,12 +902,17 @@ const INPUT_SCHEMA$3 = {
785
902
  type: "array",
786
903
  description: "Optional subset of tool names the sub-agent may use; omit to inherit the current set",
787
904
  items: { type: "string" }
905
+ },
906
+ suggestedConcurrency: {
907
+ type: "integer",
908
+ minimum: 1,
909
+ description: "How many sub-agents this task genuinely needs to run at once. The user confirms the final number and the host may cap it lower; omit if one at a time is enough"
788
910
  }
789
911
  },
790
912
  required: ["todoId", "task"],
791
913
  additionalProperties: false
792
914
  };
793
- function toolError$5(code, message) {
915
+ function toolError$6(code, message) {
794
916
  return {
795
917
  ok: false,
796
918
  content: [],
@@ -825,12 +947,12 @@ function createDelegationToolSource(options) {
825
947
  const call = async (_name, args) => {
826
948
  const todoId = args["todoId"];
827
949
  const task = args["task"];
828
- if (typeof todoId !== "string" || todoId === "") return toolError$5("VALIDATION_FAILED", "The \"todoId\" argument is required and must be a string");
829
- if (typeof task !== "string" || task.trim() === "") return toolError$5("VALIDATION_FAILED", "The \"task\" argument is required and must be a non-empty string");
950
+ if (typeof todoId !== "string" || todoId === "") return toolError$6("VALIDATION_FAILED", "The \"todoId\" argument is required and must be a string");
951
+ if (typeof task !== "string" || task.trim() === "") return toolError$6("VALIDATION_FAILED", "The \"task\" argument is required and must be a non-empty string");
830
952
  const rawTools = args["allowedTools"];
831
953
  let allowedTools;
832
954
  if (rawTools !== void 0) {
833
- if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$5("VALIDATION_FAILED", "The \"allowedTools\" argument must be an array of tool names");
955
+ if (!Array.isArray(rawTools) || rawTools.some((entry) => typeof entry !== "string")) return toolError$6("VALIDATION_FAILED", "The \"allowedTools\" argument must be an array of tool names");
834
956
  allowedTools = rawTools;
835
957
  }
836
958
  try {
@@ -862,7 +984,7 @@ function createDelegationToolSource(options) {
862
984
  }]
863
985
  };
864
986
  } catch (e) {
865
- return toolError$5(e instanceof Error && "code" in e ? String(e.code) : "DELEGATION_FAILED", e instanceof Error ? e.message : String(e));
987
+ return toolError$6(e instanceof Error && "code" in e ? String(e.code) : "DELEGATION_FAILED", e instanceof Error ? e.message : String(e));
866
988
  }
867
989
  };
868
990
  return {
@@ -872,7 +994,7 @@ function createDelegationToolSource(options) {
872
994
  listToolSpecs: () => Promise.resolve([{
873
995
  name: DELEGATE_TASK_TOOL,
874
996
  description: DELEGATE_TOOL_DESCRIPTION,
875
- inputSchema: INPUT_SCHEMA$3
997
+ inputSchema: INPUT_SCHEMA$4
876
998
  }]),
877
999
  canHandle: (name) => name === DELEGATE_TASK_TOOL,
878
1000
  argCaptureTrust: (name) => name === "delegate_task" ? { tier: "reviewed" } : void 0,
@@ -1275,7 +1397,7 @@ const PERCEPTION_SYSTEM_PROMPT = [
1275
1397
  //#endregion
1276
1398
  //#region ../agent/src/perception/toolSource.ts
1277
1399
  const PERCEIVE_PAGE_TOOL = "perceive_page";
1278
- const DESCRIPTION$2 = "Read the parts of the current page the host made readable, as a structured accessibility outline. Read-only: it cannot click, type, submit or navigate.";
1400
+ const DESCRIPTION$4 = "Read the parts of the current page the host made readable, as a structured accessibility outline. Read-only: it cannot click, type, submit or navigate.";
1279
1401
  /**
1280
1402
  * 两个参数,且它们都扩不出一寸新范围(AC-10.8 的判据由 0.14.0 分册 22 §3 修订)。
1281
1403
  *
@@ -1288,7 +1410,7 @@ const DESCRIPTION$2 = "Read the parts of the current page the host made readable
1288
1410
  * 「模型看到的工具契约」随宿主配置漂移,同一段对话里换个设置就变一次形状,
1289
1411
  * 而模型的历史消息里还留着旧形状。宁可多一个恒定存在、关闭时明确拒绝的参数。
1290
1412
  */
1291
- const INPUT_SCHEMA$2 = {
1413
+ const INPUT_SCHEMA$3 = {
1292
1414
  type: "object",
1293
1415
  properties: {
1294
1416
  cursor: {
@@ -1309,7 +1431,7 @@ const SUBTREE_CODES = /* @__PURE__ */ new Set([
1309
1431
  "PERCEPTION_SUBTREE_OUT_OF_SCOPE",
1310
1432
  "PERCEPTION_SUBTREE_UNKNOWN_REF"
1311
1433
  ]);
1312
- function toolError$4(code, message) {
1434
+ function toolError$5(code, message) {
1313
1435
  return {
1314
1436
  ok: false,
1315
1437
  content: [],
@@ -1401,8 +1523,8 @@ function createPagePerceptionToolSource(options) {
1401
1523
  kind: "page-perception",
1402
1524
  listToolSpecs: () => Promise.resolve(policy.enabled ? [{
1403
1525
  name: PERCEIVE_PAGE_TOOL,
1404
- description: DESCRIPTION$2,
1405
- inputSchema: INPUT_SCHEMA$2
1526
+ description: DESCRIPTION$4,
1527
+ inputSchema: INPUT_SCHEMA$3
1406
1528
  }] : []),
1407
1529
  systemPrompt: () => Promise.resolve(policy.enabled ? PERCEPTION_SYSTEM_PROMPT : void 0),
1408
1530
  canHandle: (name) => name === PERCEIVE_PAGE_TOOL,
@@ -1416,11 +1538,11 @@ function createPagePerceptionToolSource(options) {
1416
1538
  };
1417
1539
  const cursor = args["cursor"];
1418
1540
  const ref = args["ref"];
1419
- if (ref !== void 0 && typeof ref !== "string") return toolError$4("PERCEPTION_REF_INVALID", "perceive_page needs \"ref\" to be a string when it is present.");
1541
+ if (ref !== void 0 && typeof ref !== "string") return toolError$5("PERCEPTION_REF_INVALID", "perceive_page needs \"ref\" to be a string when it is present.");
1420
1542
  if (cursor !== void 0) {
1421
1543
  const index = typeof cursor === "string" ? pending?.cursors.get(cursor) : void 0;
1422
- if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$4("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
1423
- if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$4("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
1544
+ if (!paged || pending === void 0 || index === void 0 || pending.runId !== context?.runId) return toolError$5("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
1545
+ if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$5("PERCEPTION_CURSOR_INVALID", CURSOR_REJECTED);
1424
1546
  return resultOf(pending, index);
1425
1547
  }
1426
1548
  try {
@@ -1482,8 +1604,8 @@ function createPagePerceptionToolSource(options) {
1482
1604
  return resultOf(pending, 0);
1483
1605
  } catch (e) {
1484
1606
  const code = e instanceof WebSkillError ? e.code : void 0;
1485
- if (code !== void 0 && SUBTREE_CODES.has(code)) return toolError$4(code, messageOf(e));
1486
- return toolError$4("PERCEPTION_FAILED", `perceive_page failed: ${messageOf(e)}`);
1607
+ if (code !== void 0 && SUBTREE_CODES.has(code)) return toolError$5(code, messageOf(e));
1608
+ return toolError$5("PERCEPTION_FAILED", `perceive_page failed: ${messageOf(e)}`);
1487
1609
  }
1488
1610
  }
1489
1611
  };
@@ -1656,7 +1778,7 @@ const RECORD_LIMIT$1 = 50;
1656
1778
  function sanitizeDescription(value) {
1657
1779
  return sanitizeUntrustedLine(value, DESCRIPTION_LIMIT);
1658
1780
  }
1659
- function isRecord$1(value) {
1781
+ function isRecord$2(value) {
1660
1782
  return typeof value === "object" && value !== null;
1661
1783
  }
1662
1784
  /**
@@ -1666,7 +1788,7 @@ function isRecord$1(value) {
1666
1788
  * 标成后者会让读代码的人以为它已经被验证过了。
1667
1789
  */
1668
1790
  function validate(raw, provenance) {
1669
- if (!isRecord$1(raw)) return void 0;
1791
+ if (!isRecord$2(raw)) return void 0;
1670
1792
  const { id, kind, description, target } = raw;
1671
1793
  if (typeof id !== "string" || !ID_PATTERN.test(id)) return void 0;
1672
1794
  if (typeof kind !== "string" || !OFFERABLE_KINDS.includes(kind)) return void 0;
@@ -1867,6 +1989,7 @@ function clampDownloadWaitMs(ms) {
1867
1989
 
1868
1990
  //#endregion
1869
1991
  //#region ../agent/src/pageAction/policy.ts
1992
+ const scrollDirectionOf = (value) => SCROLL_DIRECTIONS.includes(value) ? value : "down";
1870
1993
  const declineReason = "Page action was declined by the user.";
1871
1994
  /** 目标记忆的上限;它在 `act()` 之后立刻就被读走,容量只为防无界增长 */
1872
1995
  const TARGET_MEMO_LIMIT = 200;
@@ -2001,7 +2124,7 @@ var PageActionPolicy = class {
2001
2124
  const where = target.frame === void 0 ? "on the page" : `in the embedded frame "${target.frame}"`;
2002
2125
  const scopeNote = target.elevated === true ? " This dialog is outside the usual allowlist; it was opened by this task." : "";
2003
2126
  const scopeLabel = rememberable ? this.#options.consent?.describeScope?.(target, request.action) : void 0;
2004
- const message = request.action === "back" ? `Allow the assistant to go back to the previous page${target.name === void 0 ? "" : ` on "${target.name}"`}?${scopeNote}` : request.action === "scroll" ? `Allow the assistant to scroll ${request.value === "up" ? "up" : "down"} the area around "${target.name ?? target.role}" ${where}?${scopeNote}` : `Allow the assistant to ${request.action} "${target.name ?? target.role}" ${where}?${scopeNote}`;
2127
+ const message = request.action === "back" ? `Allow the assistant to go back to the previous page${target.name === void 0 ? "" : ` on "${target.name}"`}?${scopeNote}` : request.action === "scroll" ? `Allow the assistant to scroll ${scrollDirectionOf(request.value)} the area around "${target.name ?? target.role}" ${where}?${scopeNote}` : `Allow the assistant to ${request.action} "${target.name ?? target.role}" ${where}?${scopeNote}`;
2005
2128
  const response = await this.#options.ui.request({
2006
2129
  type: "authorize",
2007
2130
  id: `page-action-${this.#seq}`,
@@ -2018,7 +2141,7 @@ var PageActionPolicy = class {
2018
2141
  ...rememberable ? { rememberable: true } : {},
2019
2142
  ...scopeLabel !== void 0 ? { rememberLabel: scopeLabel } : {},
2020
2143
  ...request.action === "fill" && request.value !== void 0 ? { value: target.secret === true ? "••••••" : request.value } : {},
2021
- ...request.action === "scroll" ? { direction: request.value === "up" ? "up" : "down" } : {}
2144
+ ...request.action === "scroll" ? { direction: scrollDirectionOf(request.value) } : {}
2022
2145
  }
2023
2146
  });
2024
2147
  if (response.cancelled === true || response.value === false) return false;
@@ -2100,19 +2223,21 @@ const PAGE_ACTION_SYSTEM_PROMPT = [
2100
2223
  "A reference expires as soon as the page is perceived again; perceive first, then act.",
2101
2224
  "After an action, the result tells you where it led. \"openedTab\" means a new page opened — switch to it with focus_page_tab to read it. \"navigated\" means this page itself moved to a new address. \"elevatedModal\" means a dialog opened on this page. If none of the three appear, the page did not change.",
2102
2225
  "Use the \"back\" action to return to the previous page in this page's history. It takes no \"ref\" and no \"value\", and it only affects the page you are currently on. After going back, always perceive_page again: every reference from before the action is dead.",
2103
- "Use the \"scroll\" action only when the page itself says there is more than you can see (for example a list that says it has 20 rows but shows 13). Pass the \"ref\" of any element inside the region you want to scroll and a \"value\" of \"down\" or \"up\"; the region is found for you, so do not look for a reference to the scrollbar or the container. The result carries \"scrolled\": when \"atEnd\" is true there is nothing further in that direction — stop scrolling and answer with what you have. Perceive the page again after each scroll: rows that left the screen no longer exist and their references are dead."
2226
+ "Use the \"scroll\" action only when the page itself says there is more than you can see (for example a list that says it has 20 rows but shows 13). Pass the \"ref\" of any element inside the region you want to scroll and a \"value\" of \"down\" or \"up\"; the region is found for you, so do not look for a reference to the scrollbar or the container. The result carries \"scrolled\": when \"atEnd\" is true there is nothing further in that direction — stop scrolling and answer with what you have. Perceive the page again after each scroll: rows that left the screen no longer exist and their references are dead.",
2227
+ "Use the \"hover\" action when a menu, tooltip or icon only appears while the pointer rests on something — a dropdown that ignores clicks, or a table header whose filter icon is invisible until hovered. Pass the \"ref\" of the thing to hover over and no \"value\". Then perceive_page again: whatever appeared is new and has no reference until you read it. If the result says \"noop\": true, nothing on the page reacted, and hovering again will not help — some pages reveal those panels through a pure CSS effect that only a real pointer can trigger. Say so and offer another route instead of retrying.",
2228
+ "Use the \"drag\" action to drag one element onto another — reordering a list by its drag handle, for example. Pass the \"ref\" of what you are dragging and a \"value\" holding the ref of where it should land. Both must come from a perceive_page result, so perceive first and make sure the destination is actually on screen. You can only express a start and an end; there is no path, speed or pause in between."
2104
2229
  ].join("\n");
2105
2230
 
2106
2231
  //#endregion
2107
2232
  //#region ../agent/src/pageAction/toolSource.ts
2108
2233
  const PAGE_ACTION_TOOL = "act_on_page";
2109
- const DESCRIPTION$1 = "Act on one element of the current page. The target must be a \"ref\" from a fresh perceive_page result; selectors are rejected. Each call asks the user to confirm before anything happens.";
2234
+ const DESCRIPTION$3 = "Act on one element of the current page. The target must be a \"ref\" from a fresh perceive_page result; selectors are rejected. Each call asks the user to confirm before anything happens.";
2110
2235
  /**
2111
2236
  * 入参里**没有** `scope` / `selector` / `include` / `frame` 一类字段(AC-23.2):
2112
2237
  * 工具一旦接受它们,「能操作哪里」就有了一条从模型输出流进来的路径。
2113
2238
  * 模型能表达的只有「在哪个句柄上做哪个动作」。
2114
2239
  */
2115
- const INPUT_SCHEMA$1 = {
2240
+ const INPUT_SCHEMA$2 = {
2116
2241
  type: "object",
2117
2242
  properties: {
2118
2243
  ref: {
@@ -2125,13 +2250,13 @@ const INPUT_SCHEMA$1 = {
2125
2250
  },
2126
2251
  value: {
2127
2252
  type: "string",
2128
- description: "Text for \"fill\", the option name for \"select\", \"true\"/\"false\" for \"set\", or \"down\"/\"up\" for \"scroll\""
2253
+ description: "Text for \"fill\", the option name for \"select\", \"true\"/\"false\" for \"set\", one of \"down\"/\"up\"/\"left\"/\"right\" for \"scroll\", or the destination ref for \"drag\". Not used by \"hover\" or \"attach\". After a scroll, call perceive_page again: content that was outside the viewport may not have existed in the page at all until you scrolled to it."
2129
2254
  }
2130
2255
  },
2131
2256
  required: ["action"],
2132
2257
  additionalProperties: false
2133
2258
  };
2134
- function toolError$3(code, message) {
2259
+ function toolError$4(code, message) {
2135
2260
  return {
2136
2261
  ok: false,
2137
2262
  content: [],
@@ -2154,8 +2279,8 @@ function createPageActionToolSource(options) {
2154
2279
  kind: "page-action",
2155
2280
  listToolSpecs: () => Promise.resolve(policy.enabled ? [{
2156
2281
  name: PAGE_ACTION_TOOL,
2157
- description: DESCRIPTION$1,
2158
- inputSchema: INPUT_SCHEMA$1
2282
+ description: DESCRIPTION$3,
2283
+ inputSchema: INPUT_SCHEMA$2
2159
2284
  }] : []),
2160
2285
  systemPrompt: () => Promise.resolve(policy.enabled ? PAGE_ACTION_SYSTEM_PROMPT : void 0),
2161
2286
  canHandle: (name) => name === PAGE_ACTION_TOOL,
@@ -2181,7 +2306,7 @@ function createPageActionToolSource(options) {
2181
2306
  },
2182
2307
  call: async (_name, args, context) => {
2183
2308
  const action = args["action"];
2184
- if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$3("VALIDATION_FAILED", `act_on_page needs one of ${PAGE_ACTION_KINDS.join(" / ")}.`);
2309
+ if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return toolError$4("VALIDATION_FAILED", `act_on_page needs one of ${PAGE_ACTION_KINDS.join(" / ")}.`);
2185
2310
  const runContext = { ...context?.runId !== void 0 ? { runId: context.runId } : {} };
2186
2311
  const run = async (request) => {
2187
2312
  try {
@@ -2198,25 +2323,25 @@ function createPageActionToolSource(options) {
2198
2323
  } }
2199
2324
  };
2200
2325
  } catch (e) {
2201
- return toolError$3(e instanceof WebSkillError ? e.code : "PAGE_ACTION_FAILED", messageOf(e));
2326
+ return toolError$4(e instanceof WebSkillError ? e.code : "PAGE_ACTION_FAILED", messageOf(e));
2202
2327
  }
2203
2328
  };
2204
2329
  if (action === "back") {
2205
- if ("ref" in args || "value" in args) return toolError$3("VALIDATION_FAILED", "act_on_page with \"back\" takes no \"ref\" and no \"value\".");
2330
+ if ("ref" in args || "value" in args) return toolError$4("VALIDATION_FAILED", "act_on_page with \"back\" takes no \"ref\" and no \"value\".");
2206
2331
  return await run({
2207
2332
  ref: PAGE_DOCUMENT_REF,
2208
2333
  action: "back"
2209
2334
  });
2210
2335
  }
2211
2336
  const ref = args["ref"];
2212
- if (typeof ref !== "string") return toolError$3("VALIDATION_FAILED", `act_on_page needs a "ref" string when the action is "${action}".`);
2337
+ if (typeof ref !== "string") return toolError$4("VALIDATION_FAILED", `act_on_page needs a "ref" string when the action is "${action}".`);
2213
2338
  const value = args["value"];
2214
2339
  if ([
2215
2340
  "fill",
2216
2341
  "select",
2217
2342
  "set"
2218
- ].includes(action) && typeof value !== "string") return toolError$3("VALIDATION_FAILED", `act_on_page needs a "value" string when the action is "${action}".`);
2219
- if (action === "scroll" && value !== "down" && value !== "up") return toolError$3("VALIDATION_FAILED", "act_on_page with \"scroll\" needs a \"value\" of \"down\" or \"up\".");
2343
+ ].includes(action) && typeof value !== "string") return toolError$4("VALIDATION_FAILED", `act_on_page needs a "value" string when the action is "${action}".`);
2344
+ if (action === "scroll" && !SCROLL_DIRECTIONS.includes(value)) return toolError$4("VALIDATION_FAILED", `act_on_page with "scroll" needs a "value" of ${SCROLL_DIRECTIONS.map((d) => `"${d}"`).join(" / ")}.`);
2220
2345
  return await run({
2221
2346
  ref,
2222
2347
  action,
@@ -2458,7 +2583,7 @@ const SPECS = [
2458
2583
  inputSchema: TAB_SCHEMA
2459
2584
  }
2460
2585
  ];
2461
- function toolError$2(code, message) {
2586
+ function toolError$3(code, message) {
2462
2587
  return {
2463
2588
  ok: false,
2464
2589
  content: [],
@@ -2493,7 +2618,7 @@ function createTabWorksetToolSource(options) {
2493
2618
  }]
2494
2619
  };
2495
2620
  const handle = args["tab"];
2496
- if (typeof handle !== "string" || handle === "") return toolError$2("VALIDATION_FAILED", `${name} needs a "tab" handle from list_page_tabs.`);
2621
+ if (typeof handle !== "string" || handle === "") return toolError$3("VALIDATION_FAILED", `${name} needs a "tab" handle from list_page_tabs.`);
2497
2622
  return {
2498
2623
  ok: true,
2499
2624
  content: [{
@@ -2502,7 +2627,7 @@ function createTabWorksetToolSource(options) {
2502
2627
  }]
2503
2628
  };
2504
2629
  } catch (e) {
2505
- return toolError$2(e instanceof WebSkillError ? e.code : "PAGE_TAB_FAILED", messageOf(e));
2630
+ return toolError$3(e instanceof WebSkillError ? e.code : "PAGE_TAB_FAILED", messageOf(e));
2506
2631
  }
2507
2632
  }
2508
2633
  };
@@ -2518,12 +2643,12 @@ function formatSize(bytes) {
2518
2643
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
2519
2644
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
2520
2645
  }
2521
- /** 两种待抽取格式共用 `document-text` 这一种 kind,选抽取器时要按格式再分一次 */
2522
- function isSpreadsheet(contentType, name) {
2523
- const type = contentType.toLowerCase();
2524
- if (type !== "") return type === XLSX_MIME;
2525
- return name.toLowerCase().endsWith(".xlsx");
2526
- }
2646
+ /** `document-text` 下的三种格式各自的抽取器与称呼 */
2647
+ const EXTRACTED_LABEL = {
2648
+ docx: "Word documents",
2649
+ xlsx: "Excel workbooks",
2650
+ pptx: "PowerPoint presentations"
2651
+ };
2527
2652
  /**
2528
2653
  * 读取本机下载文件的策略(0.14.0 分册 20)。
2529
2654
  *
@@ -2715,13 +2840,19 @@ var DownloadedFilePolicy = class {
2715
2840
  };
2716
2841
  }
2717
2842
  #extract(contentType, name) {
2718
- const extract = isSpreadsheet(contentType, name) ? this.#options.xlsxExtractor : this.#options.docxExtractor;
2843
+ const extract = this.#extractorFor(contentType, name);
2719
2844
  if (extract === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", this.#noExtractorReason(contentType, name));
2720
2845
  return extract;
2721
2846
  }
2847
+ #extractorFor(contentType, name) {
2848
+ const format = extractedDocumentFormat(contentType, name);
2849
+ if (format === "xlsx") return this.#options.xlsxExtractor;
2850
+ if (format === "pptx") return this.#options.pptxExtractor;
2851
+ return this.#options.docxExtractor;
2852
+ }
2722
2853
  #noExtractorReason(contentType, name) {
2723
- const excel = isSpreadsheet(contentType, name);
2724
- return `${excel ? "Excel workbooks" : "Word documents"} cannot be read in this environment: no ${excel ? "xlsx" : "docx"} text extractor is wired up.`;
2854
+ const format = extractedDocumentFormat(contentType, name);
2855
+ return `${EXTRACTED_LABEL[format]} cannot be read in this environment: no ${format} text extractor is wired up.`;
2725
2856
  }
2726
2857
  /** 可读性判定与附件分类器同源(AC-20.17);返回 `undefined` 即可读 */
2727
2858
  async #unreadableReason(contentType, name) {
@@ -2729,7 +2860,7 @@ var DownloadedFilePolicy = class {
2729
2860
  if (kind === void 0) return `Its type "${contentType === "" ? "unknown" : contentType}" cannot be read. ${READABLE_TYPES}`;
2730
2861
  if (kind === "image" && await this.#options.imageCapable?.() !== true) return IMAGE_UNREADABLE;
2731
2862
  if (kind === "document-text") {
2732
- if ((isSpreadsheet(contentType, name) ? this.#options.xlsxExtractor : this.#options.docxExtractor) === void 0) return this.#noExtractorReason(contentType, name);
2863
+ if (this.#extractorFor(contentType, name) === void 0) return this.#noExtractorReason(contentType, name);
2733
2864
  }
2734
2865
  }
2735
2866
  #unreadable(entry, reason) {
@@ -2825,8 +2956,8 @@ const READ_INPUT_SCHEMA = {
2825
2956
  required: ["id"],
2826
2957
  additionalProperties: false
2827
2958
  };
2828
- const SYSTEM_PROMPT$1 = "You can list and read files the user downloaded in this browser with list_downloaded_files and read_downloaded_file. Every call asks the user for permission, so only use them when the task actually needs a local file. Never guess an id: always list first, and never treat the listing as evidence about files you were not shown.";
2829
- function toolError$1(e) {
2959
+ const SYSTEM_PROMPT$3 = "You can list and read files the user downloaded in this browser with list_downloaded_files and read_downloaded_file. They cover the browser download folder and nothing else. A file the user attached to a message is already in this conversation and is reachable by the id given with it, so never go looking for an attachment here. Every call asks the user for permission, so only use them when the task actually needs a local file. Never guess an id: always list first, and never treat the listing as evidence about files you were not shown.";
2960
+ function toolError$2(e) {
2830
2961
  return {
2831
2962
  ok: false,
2832
2963
  content: [],
@@ -2856,7 +2987,7 @@ function createDownloadedFileToolSource(options) {
2856
2987
  description: READ_DESCRIPTION,
2857
2988
  inputSchema: READ_INPUT_SCHEMA
2858
2989
  }] : []),
2859
- systemPrompt: () => Promise.resolve(policy.enabled ? SYSTEM_PROMPT$1 : void 0),
2990
+ systemPrompt: () => Promise.resolve(policy.enabled ? SYSTEM_PROMPT$3 : void 0),
2860
2991
  canHandle: (name) => name === "list_downloaded_files" || name === "read_downloaded_file",
2861
2992
  argCaptureTrust: (name) => name === "list_downloaded_files" || name === "read_downloaded_file" ? { tier: "reviewed" } : void 0,
2862
2993
  call: async (name, args) => {
@@ -2875,7 +3006,7 @@ function createDownloadedFileToolSource(options) {
2875
3006
  content: readContent(await policy.read(id))
2876
3007
  };
2877
3008
  } catch (e) {
2878
- return toolError$1(e);
3009
+ return toolError$2(e);
2879
3010
  }
2880
3011
  }
2881
3012
  };
@@ -2918,63 +3049,124 @@ function readContent(result) {
2918
3049
  //#endregion
2919
3050
  //#region ../agent/src/spreadsheet/toolSource.ts
2920
3051
  const EXPORT_SPREADSHEET_TOOL = "export_spreadsheet";
2921
- const DESCRIPTION = "Write tabular data to an Excel (.xlsx) file the user can download.";
2922
- const INPUT_SCHEMA = {
2923
- type: "object",
2924
- properties: {
2925
- fileName: {
2926
- type: "string",
2927
- description: "Name for the workbook, for example \"sales-2026.xlsx\". A missing .xlsx suffix is added for you."
3052
+ const DESCRIPTION$2 = "Write tabular data — and, when they belong in the sheet, pictures — to an Excel (.xlsx) file the user can download.";
3053
+ const CELL_SCHEMA = {
3054
+ anyOf: [
3055
+ { type: "string" },
3056
+ { type: "number" },
3057
+ { type: "boolean" },
3058
+ { type: "null" }
3059
+ ],
3060
+ description: "Text is never re-parsed as a number or a date: \"0123\" stays \"0123\". Use null for an empty cell."
3061
+ };
3062
+ /**
3063
+ * 表里的图。只在宿主接了 `resolveImage` 时才并进 schema——
3064
+ * 接不了字节却把字段给出去,等于邀请模型填一个填了也不算数的东西。
3065
+ */
3066
+ const IMAGES_SCHEMA = {
3067
+ type: "array",
3068
+ maxItems: SPREADSHEET_LIMITS.imagesPerSheet,
3069
+ description: "Pictures placed on this sheet. Use this instead of telling the user to paste images in themselves, and instead of handing out download links for them.",
3070
+ items: {
3071
+ type: "object",
3072
+ properties: {
3073
+ ref: {
3074
+ type: "string",
3075
+ description: "Where the picture comes from: \"artifact:<file name>\" for something produced earlier in this conversation, or \"upload:<id or file name>\" for a picture the user attached. Never base64."
3076
+ },
3077
+ anchor: {
3078
+ type: "object",
3079
+ description: "Top-left cell the picture sits on. Both numbers are 1-based, so row 1 is the header row.",
3080
+ properties: {
3081
+ row: {
3082
+ type: "integer",
3083
+ minimum: 1
3084
+ },
3085
+ column: {
3086
+ type: "integer",
3087
+ minimum: 1
3088
+ }
3089
+ },
3090
+ required: ["row", "column"],
3091
+ additionalProperties: false
3092
+ },
3093
+ width: {
3094
+ type: "integer",
3095
+ minimum: 1,
3096
+ description: "Displayed width in pixels."
3097
+ },
3098
+ height: {
3099
+ type: "integer",
3100
+ minimum: 1,
3101
+ description: "Displayed height in pixels."
3102
+ },
3103
+ alt: {
3104
+ type: "string",
3105
+ description: "Short caption, used as the picture name inside the workbook."
3106
+ }
2928
3107
  },
2929
- spec: {
2930
- type: "object",
2931
- properties: { sheets: {
2932
- type: "array",
2933
- minItems: 1,
2934
- maxItems: SPREADSHEET_LIMITS.sheets,
2935
- items: {
2936
- type: "object",
2937
- properties: {
2938
- name: {
2939
- type: "string",
2940
- description: "Sheet name: at most 31 characters, and none of : \\ / ? * [ ]"
2941
- },
2942
- columns: {
2943
- type: "array",
2944
- items: { type: "string" },
2945
- description: "Optional header row. When given it becomes the first, bold row."
2946
- },
2947
- rows: {
2948
- type: "array",
2949
- maxItems: SPREADSHEET_LIMITS.rowsPerSheet,
2950
- items: {
3108
+ required: [
3109
+ "ref",
3110
+ "anchor",
3111
+ "width",
3112
+ "height"
3113
+ ],
3114
+ additionalProperties: false
3115
+ }
3116
+ };
3117
+ function inputSchema(withImages) {
3118
+ return {
3119
+ type: "object",
3120
+ properties: {
3121
+ fileName: {
3122
+ type: "string",
3123
+ description: "Name for the workbook, for example \"sales-2026.xlsx\". A missing .xlsx suffix is added for you."
3124
+ },
3125
+ spec: {
3126
+ type: "object",
3127
+ properties: { sheets: {
3128
+ type: "array",
3129
+ minItems: 1,
3130
+ maxItems: SPREADSHEET_LIMITS.sheets,
3131
+ items: {
3132
+ type: "object",
3133
+ properties: {
3134
+ name: {
3135
+ type: "string",
3136
+ description: "Sheet name: at most 31 characters, and none of : \\ / ? * [ ]"
3137
+ },
3138
+ columns: {
3139
+ type: "array",
3140
+ items: { type: "string" },
3141
+ description: "Optional header row. When given it becomes the first, bold row."
3142
+ },
3143
+ rows: {
2951
3144
  type: "array",
2952
- maxItems: SPREADSHEET_LIMITS.columnsPerSheet,
3145
+ maxItems: SPREADSHEET_LIMITS.rowsPerSheet,
2953
3146
  items: {
2954
- anyOf: [
2955
- { type: "string" },
2956
- { type: "number" },
2957
- { type: "boolean" },
2958
- { type: "null" }
2959
- ],
2960
- description: "Text is never re-parsed as a number or a date: \"0123\" stays \"0123\". Use null for an empty cell."
3147
+ type: "array",
3148
+ maxItems: SPREADSHEET_LIMITS.columnsPerSheet,
3149
+ items: CELL_SCHEMA
2961
3150
  }
2962
- }
2963
- }
2964
- },
2965
- required: ["name", "rows"],
2966
- additionalProperties: false
2967
- }
2968
- } },
2969
- required: ["sheets"],
2970
- additionalProperties: false
2971
- }
2972
- },
2973
- required: ["fileName", "spec"],
2974
- additionalProperties: false
2975
- };
2976
- const SYSTEM_PROMPT = "Use export_spreadsheet when the user asks for a downloadable Excel file. The tool returns only a summary; the file itself is delivered to the user as a card. Do not re-read the file you just wrote. Cell values are written literally — the tool never turns text into a number or a date, and it writes no styling, formulas, merged cells or charts.";
2977
- function toolError(e) {
3151
+ },
3152
+ ...withImages ? { images: IMAGES_SCHEMA } : {}
3153
+ },
3154
+ required: ["name", "rows"],
3155
+ additionalProperties: false
3156
+ }
3157
+ } },
3158
+ required: ["sheets"],
3159
+ additionalProperties: false
3160
+ }
3161
+ },
3162
+ required: ["fileName", "spec"],
3163
+ additionalProperties: false
3164
+ };
3165
+ }
3166
+ function systemPrompt$1(withImages) {
3167
+ return "Use export_spreadsheet when the user asks for a downloadable Excel file. The tool returns only a summary; the file itself is delivered to the user as a card. Do not re-read the file you just wrote. Cell values are written literally — the tool never turns text into a number or a date, and it writes no styling, formulas, merged cells or charts." + (withImages ? " Pictures do go in: put them in the sheet's \"images\" with a ref and an anchor cell. A picture the user attached to a message is already in this conversation — reference it as \"upload:<id or file name>\" and do not go looking for it on the page or in the download list first. When the user asks for a workbook of pictures, put the pictures in it — do not deliver the images separately and ask the user to insert them." : "");
3168
+ }
3169
+ function toolError$1(e) {
2978
3170
  return {
2979
3171
  ok: false,
2980
3172
  content: [],
@@ -2984,7 +3176,7 @@ function toolError(e) {
2984
3176
  }
2985
3177
  };
2986
3178
  }
2987
- const isRecord = (v) => typeof v === "object" && v !== null;
3179
+ const isRecord$1 = (v) => typeof v === "object" && v !== null;
2988
3180
  /**
2989
3181
  * FR-10.3a 的两条分支。忘写后缀是模型的高频行为,为它打断一整轮不划算;
2990
3182
  * 写成 `.csv` 则是**意图冲突**而不是疏漏,必须拒。
@@ -2992,7 +3184,7 @@ const isRecord = (v) => typeof v === "object" && v !== null;
2992
3184
  * 路径安全不在这里做:`writeArtifact` 那一条(`resolveInsideRoot`)是唯一的一份,
2993
3185
  * FR-10.3b 明确禁止另写一份。
2994
3186
  */
2995
- function resolveFileName(value) {
3187
+ function resolveFileName$1(value) {
2996
3188
  if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", "export_spreadsheet requires a non-empty \"fileName\".");
2997
3189
  const fileName = value.trim();
2998
3190
  const lastSegment = fileName.split(/[\\/]/).pop() ?? "";
@@ -3004,9 +3196,11 @@ function resolveFileName(value) {
3004
3196
  }
3005
3197
  function summarize(spec) {
3006
3198
  const sheets = spec.sheets;
3199
+ const imageCount = sheets.reduce((total, sheet) => total + (sheet.images?.length ?? 0), 0);
3007
3200
  return {
3008
3201
  sheetCount: sheets.length,
3009
- rowCount: sheets.reduce((total, sheet) => total + sheet.rows.length, 0)
3202
+ rowCount: sheets.reduce((total, sheet) => total + sheet.rows.length, 0),
3203
+ ...imageCount > 0 ? { imageCount } : {}
3010
3204
  };
3011
3205
  }
3012
3206
  /**
@@ -3017,20 +3211,23 @@ function summarize(spec) {
3017
3211
  * @experimental
3018
3212
  */
3019
3213
  function createSpreadsheetToolSource(options) {
3020
- const { writeArtifact } = options;
3214
+ const { writeArtifact, resolveImage } = options;
3215
+ const withImages = resolveImage !== void 0;
3216
+ const schema = inputSchema(withImages);
3217
+ const prompt = systemPrompt$1(withImages);
3021
3218
  return {
3022
3219
  kind: "spreadsheet",
3023
3220
  listToolSpecs: () => Promise.resolve(writeArtifact ? [{
3024
3221
  name: EXPORT_SPREADSHEET_TOOL,
3025
- description: DESCRIPTION,
3026
- inputSchema: INPUT_SCHEMA
3222
+ description: DESCRIPTION$2,
3223
+ inputSchema: schema
3027
3224
  }] : []),
3028
- systemPrompt: () => Promise.resolve(writeArtifact ? SYSTEM_PROMPT : void 0),
3225
+ systemPrompt: () => Promise.resolve(writeArtifact ? prompt : void 0),
3029
3226
  canHandle: (name) => name === EXPORT_SPREADSHEET_TOOL,
3030
3227
  captureArgs: (_name, args) => {
3031
3228
  const spec = args["spec"];
3032
- const sheets = isRecord(spec) && Array.isArray(spec["sheets"]) ? spec["sheets"] : [];
3033
- const rowCount = sheets.reduce((total, sheet) => total + (isRecord(sheet) && Array.isArray(sheet["rows"]) ? sheet["rows"].length : 0), 0);
3229
+ const sheets = isRecord$1(spec) && Array.isArray(spec["sheets"]) ? spec["sheets"] : [];
3230
+ const rowCount = sheets.reduce((total, sheet) => total + (isRecord$1(sheet) && Array.isArray(sheet["rows"]) ? sheet["rows"].length : 0), 0);
3034
3231
  return {
3035
3232
  ...typeof args["fileName"] === "string" ? { fileName: args["fileName"] } : {},
3036
3233
  sheetCount: sheets.length,
@@ -3040,9 +3237,9 @@ function createSpreadsheetToolSource(options) {
3040
3237
  call: async (name, args, context) => {
3041
3238
  try {
3042
3239
  if (name !== "export_spreadsheet" || !writeArtifact) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
3043
- const fileName = resolveFileName(args["fileName"]);
3240
+ const fileName = resolveFileName$1(args["fileName"]);
3044
3241
  const spec = args["spec"];
3045
- const bytes = await encodeSpreadsheet(spec);
3242
+ const bytes = await encodeSpreadsheet(spec, resolveImage ? (ref) => resolveImage(ref, context) : void 0);
3046
3243
  const artifact = await writeArtifact(fileName, bytes, SPREADSHEET_MIME_TYPE, context);
3047
3244
  return {
3048
3245
  ok: true,
@@ -3055,12 +3252,820 @@ function createSpreadsheetToolSource(options) {
3055
3252
  }
3056
3253
  }]
3057
3254
  };
3255
+ } catch (e) {
3256
+ return toolError$1(e);
3257
+ }
3258
+ }
3259
+ };
3260
+ }
3261
+
3262
+ //#endregion
3263
+ //#region ../agent/src/template/toolSource.ts
3264
+ /** @experimental */
3265
+ const INSPECT_TEMPLATE_TOOL = "inspect_template";
3266
+ /** @experimental */
3267
+ const FILL_TEMPLATE_TOOL = "fill_template";
3268
+ const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
3269
+ const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
3270
+ /**
3271
+ * 骨架预算。模板不是文档,格子数是有限的(实测四份模板 32~56 个),
3272
+ * 但没人能保证用户不传一份两千行的台账进来——超了就截断并明说截了。
3273
+ * @experimental
3274
+ */
3275
+ const TEMPLATE_INSPECT_BUDGET = {
3276
+ cells: 400,
3277
+ chars: 2e4
3278
+ };
3279
+ const INSPECT_DESCRIPTION = "Look inside an Excel (.xlsx) or Word (.docx) template the user attached, and report where things are: sheet names, cell addresses and their current text, merged regions; for Word, paragraph indexes and table/row/column coordinates. A cell reported with a \"formula\" is worked out by the template itself; fill the cells it reads instead. Call this before fill_template — you cannot guess a template's layout.";
3280
+ const FILL_DESCRIPTION = "Fill an Excel (.xlsx) or Word (.docx) template the user attached and hand the result back as a downloadable file. Everything the template already contains — styling, column widths, merged cells, formulas, headers and footers — is kept exactly as it was; only the places you name are written. The one exception is VBA macros: if the template is a macro-enabled workbook or document, the result is a plain .xlsx or .docx and its macros are dropped. Say so when you hand the file over.";
3281
+ const TEMPLATE_ID = "The attachment id, or the id from list_downloaded_files.";
3282
+ function inspectSchema(idHint) {
3283
+ return {
3284
+ type: "object",
3285
+ properties: {
3286
+ template: {
3287
+ type: "string",
3288
+ description: templateIdDescription(idHint)
3289
+ },
3290
+ sheet: {
3291
+ type: "string",
3292
+ description: "Excel only: inspect just this sheet. Omit to inspect every sheet."
3293
+ }
3294
+ },
3295
+ required: ["template"],
3296
+ additionalProperties: false
3297
+ };
3298
+ }
3299
+ /**
3300
+ * 取件号的可用形式由**宿主接了什么**决定,所以多出来的那一句由宿主给。
3301
+ * 无条件写死「还可以传在线文档」的后果是:没接那条通路的宿主里,模型会反复尝试一个永远读不到的号。
3302
+ */
3303
+ function templateIdDescription(idHint) {
3304
+ return idHint === void 0 || idHint === "" ? TEMPLATE_ID : `${TEMPLATE_ID} ${idHint}`;
3305
+ }
3306
+ const IMAGE_REF = {
3307
+ type: "string",
3308
+ description: "Where the picture comes from: \"artifact:<file name>\" for something produced earlier in this conversation, or \"upload:<id or file name>\" for a picture the user attached. Never base64."
3309
+ };
3310
+ function fillSchema(withImages, idHint) {
3311
+ return {
3312
+ type: "object",
3313
+ properties: {
3314
+ template: {
3315
+ type: "string",
3316
+ description: templateIdDescription(idHint)
3317
+ },
3318
+ fileName: {
3319
+ type: "string",
3320
+ description: "Name for the file you are producing. The template itself is never modified."
3321
+ },
3322
+ cells: {
3323
+ type: "array",
3324
+ description: "Excel only. Each entry writes one cell. Never write to a cell that inspect_template reported with a \"formula\" — an amount or a total is the template's own calculation, and overwriting it produces a sheet that no longer adds up when anything changes.",
3325
+ items: {
3326
+ type: "object",
3327
+ properties: {
3328
+ sheet: {
3329
+ type: "string",
3330
+ description: "Sheet name. Omit for the first sheet."
3331
+ },
3332
+ address: {
3333
+ type: "string",
3334
+ description: "A1-style address, for example \"B7\"."
3335
+ },
3336
+ value: {
3337
+ type: "string",
3338
+ description: "A plain number such as \"2\" or \"-1.5\" is stored as a number, so formulas and totals in the template add it up. Anything else is stored as text, including \"0123\", \"1,200\" and \"12 kg\" — write amounts as bare digits with no thousands separator, currency sign or unit, or the template's SUM will skip them. Everything is written literally: \"0123\" stays \"0123\". Use \"\" to clear the cell while keeping its formatting."
3339
+ }
3340
+ },
3341
+ required: ["address", "value"],
3342
+ additionalProperties: false
3343
+ }
3344
+ },
3345
+ rows: {
3346
+ type: "array",
3347
+ description: "Excel only. Insert blank rows before writing cells, when the template has fewer detail rows than you need. Formulas, merged regions and the rows below move down with them; addresses in \"cells\" are the positions after the insert.",
3348
+ items: {
3349
+ type: "object",
3350
+ properties: {
3351
+ sheet: { type: "string" },
3352
+ at: {
3353
+ type: "integer",
3354
+ minimum: 1,
3355
+ description: "1-based row number the new rows are inserted before."
3356
+ },
3357
+ count: {
3358
+ type: "integer",
3359
+ minimum: 1
3360
+ },
3361
+ copyStyleFrom: {
3362
+ type: "integer",
3363
+ minimum: 1,
3364
+ description: "Row whose formatting the new rows copy. Defaults to the row just above \"at\"."
3365
+ }
3366
+ },
3367
+ required: ["at", "count"],
3368
+ additionalProperties: false
3369
+ }
3370
+ },
3371
+ texts: {
3372
+ type: "array",
3373
+ description: "Word only. Each entry replaces the WHOLE text of one paragraph or one table cell — not part of it. A header line often holds several fields at once (for example \"No.: ABC-01 Serial: \"), and writing only the field you are changing deletes the others. Copy the text inspect_template reported and change just the part that differs, keeping the spacing that lines the fields up.",
3374
+ items: {
3375
+ type: "object",
3376
+ properties: {
3377
+ paragraph: {
3378
+ type: "integer",
3379
+ minimum: 0,
3380
+ description: "0-based body paragraph index."
3381
+ },
3382
+ table: {
3383
+ type: "integer",
3384
+ minimum: 0,
3385
+ description: "0-based table index."
3386
+ },
3387
+ row: {
3388
+ type: "integer",
3389
+ minimum: 0
3390
+ },
3391
+ column: {
3392
+ type: "integer",
3393
+ minimum: 0
3394
+ },
3395
+ value: {
3396
+ type: "string",
3397
+ description: "Written literally, and it becomes the entire content of the target. Use \"\" to clear the target."
3398
+ }
3399
+ },
3400
+ required: ["value"],
3401
+ additionalProperties: false
3402
+ }
3403
+ },
3404
+ ...withImages ? { images: {
3405
+ type: "array",
3406
+ description: "Pictures to place in the template. Use this instead of telling the user to paste them in themselves. Each picture is scaled down to fit the cell it lands on and never overflows it.",
3407
+ items: {
3408
+ type: "object",
3409
+ properties: {
3410
+ ref: IMAGE_REF,
3411
+ sheet: {
3412
+ type: "string",
3413
+ description: "Excel only."
3414
+ },
3415
+ address: {
3416
+ type: "string",
3417
+ description: "Excel only: A1-style cell the picture sits on."
3418
+ },
3419
+ paragraph: {
3420
+ type: "integer",
3421
+ minimum: 0,
3422
+ description: "Word only."
3423
+ },
3424
+ table: {
3425
+ type: "integer",
3426
+ minimum: 0,
3427
+ description: "Word only."
3428
+ },
3429
+ row: {
3430
+ type: "integer",
3431
+ minimum: 0,
3432
+ description: "Word only."
3433
+ },
3434
+ column: {
3435
+ type: "integer",
3436
+ minimum: 0,
3437
+ description: "Word only."
3438
+ }
3439
+ },
3440
+ required: ["ref"],
3441
+ additionalProperties: false
3442
+ }
3443
+ } } : {}
3444
+ },
3445
+ required: ["template", "fileName"],
3446
+ additionalProperties: false
3447
+ };
3448
+ }
3449
+ function systemPrompt(withImages) {
3450
+ return "When the user gives you a form, a table or a report template and asks you to produce a filled-in copy, fill that template — do not build a new file from scratch and do not describe what to type where. Always call inspect_template first: the addresses and coordinates come from it, never from guesswork, and a wrong address is a wrong file. fill_template returns only a summary; the file is delivered to the user as a card, so do not read it back. The template is left untouched — everything it already contains is preserved, and you only name the places that change. When the template has too few detail rows for the data, insert rows instead of overwriting the rows below." + (withImages ? " A template that reserves space for a photo takes one: reference the picture as \"artifact:<name>\" or \"upload:<id or name>\" and give the cell it belongs in. Do not hand the picture over separately and ask the user to paste it." : "");
3451
+ }
3452
+ function toolError(e) {
3453
+ return {
3454
+ ok: false,
3455
+ content: [],
3456
+ error: {
3457
+ code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
3458
+ message: messageOf(e)
3459
+ }
3460
+ };
3461
+ }
3462
+ const isRecord = (v) => typeof v === "object" && v !== null;
3463
+ /**
3464
+ * 照着模板出文件(0.22.0 分册 42)。
3465
+ *
3466
+ * 两个工具而不是一个:模型得先看见模板的坐标,才谈得上决定往哪儿写。
3467
+ * 合成一个工具就只剩两条路——要么让模型盲填,要么每次把整份骨架塞进填值的返回里。
3468
+ * @experimental
3469
+ */
3470
+ function createTemplateToolSource(options) {
3471
+ const { files, xlsx, docx, writeArtifact, resolveImage } = options;
3472
+ const withImages = resolveImage !== void 0;
3473
+ const inspectInput = inspectSchema(options.idHint);
3474
+ const schema = fillSchema(withImages, options.idHint);
3475
+ const prompt = systemPrompt(withImages);
3476
+ const available = () => xlsx !== void 0 || docx !== void 0;
3477
+ const budgetOf = () => options.budget?.() ?? TEMPLATE_INSPECT_BUDGET;
3478
+ return {
3479
+ kind: "office-template",
3480
+ listToolSpecs: () => {
3481
+ if (!available()) return Promise.resolve([]);
3482
+ const specs = [{
3483
+ name: INSPECT_TEMPLATE_TOOL,
3484
+ description: INSPECT_DESCRIPTION,
3485
+ inputSchema: inspectInput
3486
+ }];
3487
+ if (writeArtifact !== void 0) specs.push({
3488
+ name: FILL_TEMPLATE_TOOL,
3489
+ description: FILL_DESCRIPTION,
3490
+ inputSchema: schema
3491
+ });
3492
+ return Promise.resolve(specs);
3493
+ },
3494
+ systemPrompt: () => Promise.resolve(available() && writeArtifact !== void 0 ? prompt : void 0),
3495
+ canHandle: (name) => name === "inspect_template" || name === "fill_template",
3496
+ argCaptureTrust: (name) => name === "inspect_template" ? { tier: "reviewed" } : void 0,
3497
+ captureArgs: (name, args) => {
3498
+ if (name === "inspect_template") return args;
3499
+ const count = (key) => Array.isArray(args[key]) ? args[key].length : 0;
3500
+ return {
3501
+ ...typeof args["fileName"] === "string" ? { fileName: args["fileName"] } : {},
3502
+ filledCount: count("cells") + count("texts"),
3503
+ insertedRows: (Array.isArray(args["rows"]) ? args["rows"] : []).reduce((total, row) => total + (isRecord(row) && typeof row["count"] === "number" ? row["count"] : 0), 0),
3504
+ imageCount: count("images")
3505
+ };
3506
+ },
3507
+ call: async (name, args, context) => {
3508
+ try {
3509
+ if (name === "inspect_template") return await inspect(args, context);
3510
+ if (name === "fill_template") return await fill(args, context);
3511
+ throw new WebSkillError("TOOL_NOT_FOUND", `Unknown tool ${name}.`);
3058
3512
  } catch (e) {
3059
3513
  return toolError(e);
3060
3514
  }
3061
3515
  }
3062
3516
  };
3517
+ async function load(raw, tool, context) {
3518
+ if (typeof raw !== "string" || raw === "") throw new WebSkillError("TOOL_UNSUPPORTED", `${tool} requires the "template" id of an attachment or download.`);
3519
+ const file = await files.read(raw, context);
3520
+ if (file === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", `No readable file with id "${raw}". It is either not an attachment of the current message, or a download id that has since expired — list the downloads again to get a fresh one.`);
3521
+ const format = sniffDocumentFormat(file.bytes, file.name);
3522
+ if (format !== "xlsx" && format !== "docx") throw new WebSkillError("TEMPLATE_UNSUPPORTED", `"${file.name}" is not an Excel (.xlsx) or Word (.docx) file, so it cannot be used as a template.`);
3523
+ if ((format === "xlsx" ? xlsx : docx) === void 0) throw new WebSkillError("TEMPLATE_UNSUPPORTED", `This environment cannot fill ${format} templates.`);
3524
+ return {
3525
+ name: file.name,
3526
+ bytes: file.bytes,
3527
+ format
3528
+ };
3529
+ }
3530
+ async function inspect(args, context) {
3531
+ const template = await load(args["template"], INSPECT_TEMPLATE_TOOL, context);
3532
+ const budget = budgetOf();
3533
+ if (template.format === "xlsx") {
3534
+ const skeleton = await xlsx.read(template.bytes);
3535
+ const only = typeof args["sheet"] === "string" ? args["sheet"] : void 0;
3536
+ const sheets = only === void 0 ? skeleton.sheets : skeleton.sheets.filter((sheet) => sheet.name === only);
3537
+ if (only !== void 0 && sheets.length === 0) throw new WebSkillError("TEMPLATE_TARGET_MISSING", `The workbook has no sheet named "${only}". It has: ${skeleton.sheets.map((s) => s.name).join(", ")}.`);
3538
+ let left = budget.cells;
3539
+ let truncated = false;
3540
+ const described = sheets.map((sheet) => {
3541
+ const cells = sheet.cells.slice(0, Math.max(left, 0));
3542
+ if (cells.length < sheet.cells.length) truncated = true;
3543
+ left -= cells.length;
3544
+ return {
3545
+ name: sheet.name,
3546
+ range: sheet.range,
3547
+ cells: cells.map((cell) => ({
3548
+ address: cell.address,
3549
+ text: clip(cell.text, budget.chars),
3550
+ ...cell.formula === void 0 ? {} : { formula: cell.formula }
3551
+ })),
3552
+ ...sheet.merges.length > 0 ? { merges: [...sheet.merges] } : {},
3553
+ ...cells.length < sheet.cells.length ? { omittedCells: sheet.cells.length - cells.length } : {}
3554
+ };
3555
+ });
3556
+ return json({
3557
+ file: template.name,
3558
+ format: "xlsx",
3559
+ sheets: described,
3560
+ ...truncated ? { truncated: `Only the first ${budget.cells} filled cells are listed; inspect one sheet at a time.` } : {}
3561
+ });
3562
+ }
3563
+ const skeleton = await docx.read(template.bytes);
3564
+ let left = budget.cells;
3565
+ const paragraphs = skeleton.paragraphs.filter((paragraph) => paragraph.text !== "").slice(0, left);
3566
+ left -= paragraphs.length;
3567
+ const cells = skeleton.cells.slice(0, Math.max(left, 0));
3568
+ const tableCount = skeleton.cells.reduce((max, cell) => Math.max(max, cell.table + 1), 0);
3569
+ const omitted = skeleton.cells.length - cells.length;
3570
+ return json({
3571
+ file: template.name,
3572
+ format: "docx",
3573
+ paragraphs: paragraphs.map((paragraph) => ({
3574
+ index: paragraph.index,
3575
+ text: clip(paragraph.text, budget.chars)
3576
+ })),
3577
+ tableCount,
3578
+ cells: cells.map((cell) => ({
3579
+ table: cell.table,
3580
+ row: cell.row,
3581
+ column: cell.column,
3582
+ text: clip(cell.text, budget.chars),
3583
+ ...cell.gridSpan === void 0 ? {} : { gridSpan: cell.gridSpan },
3584
+ ...cell.vMerge === void 0 ? {} : { vMerge: cell.vMerge }
3585
+ })),
3586
+ ...omitted > 0 ? { truncated: `${omitted} further table cell(s) are not listed; the template is larger than the budget.` } : {}
3587
+ });
3588
+ }
3589
+ async function fill(args, context) {
3590
+ if (writeArtifact === void 0) throw new WebSkillError("TOOL_UNSUPPORTED", `${FILL_TEMPLATE_TOOL} is not available in this host.`);
3591
+ const template = await load(args["template"], FILL_TEMPLATE_TOOL, context);
3592
+ const fileName = resolveFileName(args["fileName"], template.format);
3593
+ const images = await resolveImages(args["images"], context);
3594
+ if (template.format === "xlsx") {
3595
+ const spec = {
3596
+ ...Array.isArray(args["rows"]) ? { rows: args["rows"] } : {},
3597
+ ...Array.isArray(args["cells"]) ? { cells: args["cells"] } : {},
3598
+ ...images.length > 0 ? { images: images.map((image) => {
3599
+ const address = image.placement["address"];
3600
+ if (typeof address !== "string" || address === "") throw new WebSkillError("TEMPLATE_ADDRESS_INVALID", `The picture "${image.ref}" needs an "address" telling which cell it goes on.`);
3601
+ const sheet = image.placement["sheet"];
3602
+ return {
3603
+ address,
3604
+ ...typeof sheet === "string" ? { sheet } : {},
3605
+ ...image.bytes
3606
+ };
3607
+ }) } : {}
3608
+ };
3609
+ const bytes = await xlsx.fill(template.bytes, spec);
3610
+ const artifact = await writeArtifact(fileName, bytes, XLSX_MIME, context);
3611
+ return json({
3612
+ path: artifact.path,
3613
+ size: artifact.size,
3614
+ filledCount: spec.cells?.length ?? 0,
3615
+ insertedRows: (spec.rows ?? []).reduce((total, row) => total + row.count, 0),
3616
+ ...images.length > 0 ? { imageCount: images.length } : {}
3617
+ });
3618
+ }
3619
+ const spec = {
3620
+ ...Array.isArray(args["texts"]) ? { texts: args["texts"] } : {},
3621
+ ...images.length > 0 ? { images: images.map((image) => ({
3622
+ ...pickCoordinates(image.placement),
3623
+ ...image.bytes
3624
+ })) } : {}
3625
+ };
3626
+ const bytes = await docx.fill(template.bytes, spec);
3627
+ const artifact = await writeArtifact(fileName, bytes, DOCX_MIME, context);
3628
+ return json({
3629
+ path: artifact.path,
3630
+ size: artifact.size,
3631
+ filledCount: spec.texts?.length ?? 0,
3632
+ ...images.length > 0 ? { imageCount: images.length } : {}
3633
+ });
3634
+ }
3635
+ async function resolveImages(raw, context) {
3636
+ if (!Array.isArray(raw) || raw.length === 0) return [];
3637
+ if (resolveImage === void 0) throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", "This host cannot resolve picture references.");
3638
+ const out = [];
3639
+ for (const entry of raw) {
3640
+ if (!isRecord(entry) || typeof entry["ref"] !== "string") throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", "Every picture needs a \"ref\".");
3641
+ const ref = entry["ref"];
3642
+ const bytes = await resolveImage(ref, context);
3643
+ if (bytes === void 0) throw new WebSkillError("TEMPLATE_IMAGE_UNRESOLVED", `Nothing in this conversation matches the picture reference "${ref}".`);
3644
+ out.push({
3645
+ ref,
3646
+ bytes,
3647
+ placement: entry
3648
+ });
3649
+ }
3650
+ return out;
3651
+ }
3652
+ }
3653
+ function pickCoordinates(placement) {
3654
+ const out = {};
3655
+ for (const key of [
3656
+ "paragraph",
3657
+ "table",
3658
+ "row",
3659
+ "column"
3660
+ ]) {
3661
+ const value = placement[key];
3662
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) out[key] = value;
3663
+ }
3664
+ return out;
3665
+ }
3666
+ /** 后缀必须与模板同格式:拿 xlsx 模板出 .docx 是意图冲突,不是笔误 */
3667
+ function resolveFileName(value, format) {
3668
+ if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${FILL_TEMPLATE_TOOL} requires a non-empty "fileName".`);
3669
+ const fileName = value.trim();
3670
+ const lastSegment = fileName.split(/[\\/]/).pop() ?? "";
3671
+ const dot = lastSegment.lastIndexOf(".");
3672
+ const extension = dot <= 0 ? "" : lastSegment.slice(dot + 1).toLowerCase();
3673
+ if (extension === "") return `${fileName}.${format}`;
3674
+ if (extension === format) return fileName;
3675
+ throw new WebSkillError("VALIDATION_FAILED", `The template is a .${format} file, so the result is a .${format} file; "${fileName}" asks for .${extension}.`);
3676
+ }
3677
+ function clip(text, limit) {
3678
+ const max = Math.max(Math.floor(limit / 40), 40);
3679
+ return text.length <= max ? text : `${text.slice(0, max)}…`;
3680
+ }
3681
+ function json(data) {
3682
+ return {
3683
+ ok: true,
3684
+ content: [{
3685
+ type: "json",
3686
+ data
3687
+ }]
3688
+ };
3689
+ }
3690
+
3691
+ //#endregion
3692
+ //#region ../agent/src/pageImage/toolSource.ts
3693
+ const CAPTURE_PAGE_IMAGE_TOOL = "capture_page_image";
3694
+ const DESCRIPTION$1 = "Save a picture that is already on the page as a run artifact, so a skill can place it in a document.";
3695
+ const INPUT_SCHEMA$1 = {
3696
+ type: "object",
3697
+ properties: {
3698
+ ref: {
3699
+ type: "string",
3700
+ description: "Element handle from perceive_page. Only nodes marked \"capturable\" can be captured — those are the <img>, <canvas> and <svg> elements."
3701
+ },
3702
+ note: {
3703
+ type: "string",
3704
+ description: "Why you need this picture. It is shown to the user when asking for permission."
3705
+ }
3706
+ },
3707
+ required: ["ref"],
3708
+ additionalProperties: false
3709
+ };
3710
+ const SYSTEM_PROMPT$2 = "Use capture_page_image when a picture on the current page belongs in a document you are about to produce. It returns a reference, never the picture itself — pass that reference to the skill, do not try to read it back. Capturing does not tell you what the picture shows; ask for a page perception with images if you need that. Capture one picture per call, and only for nodes the page perception marked as capturable. Do not list the captured references back to the user. They are working material for the document, not a deliverable; say how many pictures went in and leave it at that.";
3711
+ const isFailure$1 = (result) => "reason" in result;
3712
+ /**
3713
+ * 页面取像工具(0.22.0 分册 12)。
3714
+ *
3715
+ * 与 `export_spreadsheet` 同一套 `ExternalToolSource` 范式(FR-12.1),
3716
+ * 不另造第二套注册机制。
3717
+ * @experimental
3718
+ */
3719
+ function createPageImageToolSource(options) {
3720
+ const { capture } = options;
3721
+ return {
3722
+ kind: "page-image",
3723
+ listToolSpecs: () => Promise.resolve(capture ? [{
3724
+ name: CAPTURE_PAGE_IMAGE_TOOL,
3725
+ description: DESCRIPTION$1,
3726
+ inputSchema: INPUT_SCHEMA$1
3727
+ }] : []),
3728
+ systemPrompt: () => Promise.resolve(capture ? SYSTEM_PROMPT$2 : void 0),
3729
+ canHandle: (name) => name === CAPTURE_PAGE_IMAGE_TOOL,
3730
+ captureArgs: (_name, args) => typeof args["ref"] === "string" ? { ref: args["ref"] } : {},
3731
+ call: async (name, args, context) => {
3732
+ try {
3733
+ if (name !== "capture_page_image" || !capture) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
3734
+ const ref = args["ref"];
3735
+ if (typeof ref !== "string" || ref.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${CAPTURE_PAGE_IMAGE_TOOL} requires a non-empty "ref" taken from a page perception.`);
3736
+ const note = typeof args["note"] === "string" && args["note"].trim() !== "" ? args["note"].trim() : void 0;
3737
+ const result = await capture({
3738
+ ref: ref.trim(),
3739
+ ...note === void 0 ? {} : { note }
3740
+ }, context);
3741
+ if (isFailure$1(result)) {
3742
+ const levels = result.triedLevels ?? [];
3743
+ return {
3744
+ ok: false,
3745
+ content: [],
3746
+ error: {
3747
+ code: "TOOL_EXECUTION_FAILED",
3748
+ message: levels.length > 0 ? `${result.reason} (tried: ${levels.join(", ")})` : result.reason
3749
+ }
3750
+ };
3751
+ }
3752
+ return {
3753
+ ok: true,
3754
+ content: [{
3755
+ type: "json",
3756
+ data: {
3757
+ ref: ref.trim(),
3758
+ artifact: result.artifact,
3759
+ width: result.width,
3760
+ height: result.height,
3761
+ bytes: result.bytes
3762
+ }
3763
+ }]
3764
+ };
3765
+ } catch (e) {
3766
+ return {
3767
+ ok: false,
3768
+ content: [],
3769
+ error: {
3770
+ code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
3771
+ message: messageOf(e)
3772
+ }
3773
+ };
3774
+ }
3775
+ }
3776
+ };
3777
+ }
3778
+
3779
+ //#endregion
3780
+ //#region ../agent/src/remoteImage/toolSource.ts
3781
+ const FETCH_REMOTE_IMAGE_TOOL = "fetch_remote_image";
3782
+ const DESCRIPTION = "Download an image from a public URL and save it as a run artifact, so a skill can place it in a document.";
3783
+ const INPUT_SCHEMA = {
3784
+ type: "object",
3785
+ properties: {
3786
+ url: {
3787
+ type: "string",
3788
+ description: "Absolute https:// or http:// address of the image. Addresses on private networks are refused."
3789
+ },
3790
+ note: {
3791
+ type: "string",
3792
+ description: "Why you need this image. It is shown to the user when asking for permission."
3793
+ },
3794
+ describe: {
3795
+ type: "boolean",
3796
+ description: "Attach the image itself to the result so you can look at it. Set it only when the user asked what the picture shows; leave it out when you are collecting images for a document."
3797
+ }
3798
+ },
3799
+ required: ["url"],
3800
+ additionalProperties: false
3801
+ };
3802
+ const SYSTEM_PROMPT$1 = "Use fetch_remote_image when a document you are about to produce needs an image that lives at a public URL. To just show a public image in the conversation, write plain markdown — ![alt](https://…) — and do not call this tool. It returns a reference to pass on to the skill: a reference is not a path and cannot be read back. Set \"describe\" to true only when the user asked what a picture shows — it attaches the image so you can look at it, which costs extra, so leave it out while you are only collecting images for a document. Without \"describe\" you have not seen the image: say you cannot see it rather than guessing. Never infer the subject from the URL, the file name or the site it came from. The user is asked for permission before the first download, because fetching reveals their IP address to that site. Fetch one image per call, and only from addresses you got from the page or from the user — never guess a URL. Do not list the fetched references back to the user; say how many images went into the document instead.";
3803
+ const isFailure = (result) => "reason" in result;
3804
+ /**
3805
+ * 外链图片抓取工具(0.22.0 分册 13)。
3806
+ *
3807
+ * 与 `capture_page_image` 同一套范式:模型给出来源、宿主拿字节、回给模型的只有引用。
3808
+ * 三种图片来源(上传、页面、外链)因此在技能那一侧长得一模一样(AC-G1)。
3809
+ * @experimental
3810
+ */
3811
+ function createRemoteImageToolSource(options) {
3812
+ const { fetchImage } = options;
3813
+ return {
3814
+ kind: "remote-image",
3815
+ listToolSpecs: () => Promise.resolve(fetchImage ? [{
3816
+ name: FETCH_REMOTE_IMAGE_TOOL,
3817
+ description: DESCRIPTION,
3818
+ inputSchema: INPUT_SCHEMA
3819
+ }] : []),
3820
+ systemPrompt: () => Promise.resolve(fetchImage ? SYSTEM_PROMPT$1 : void 0),
3821
+ canHandle: (name) => name === FETCH_REMOTE_IMAGE_TOOL,
3822
+ captureArgs: (_name, args) => typeof args["url"] === "string" ? { url: args["url"] } : {},
3823
+ call: async (name, args, context) => {
3824
+ try {
3825
+ if (name !== "fetch_remote_image" || !fetchImage) throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
3826
+ const url = args["url"];
3827
+ if (typeof url !== "string" || url.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${FETCH_REMOTE_IMAGE_TOOL} requires a non-empty "url" pointing at an image.`);
3828
+ const note = typeof args["note"] === "string" && args["note"].trim() !== "" ? args["note"].trim() : void 0;
3829
+ const describe = args["describe"] === true;
3830
+ const result = await fetchImage({
3831
+ url: url.trim(),
3832
+ ...note === void 0 ? {} : { note },
3833
+ ...describe ? { describe } : {}
3834
+ }, context);
3835
+ if (isFailure(result)) return {
3836
+ ok: false,
3837
+ content: [],
3838
+ error: {
3839
+ code: "TOOL_EXECUTION_FAILED",
3840
+ message: result.kind === void 0 ? result.reason : `${result.reason} [${result.kind}]`
3841
+ }
3842
+ };
3843
+ const data = describe ? result.data : void 0;
3844
+ const mimeType = describe ? result.mimeType : void 0;
3845
+ const seen = data !== void 0 && mimeType !== void 0;
3846
+ const content = [{
3847
+ type: "json",
3848
+ data: {
3849
+ url: url.trim(),
3850
+ artifact: result.artifact,
3851
+ width: result.width,
3852
+ height: result.height,
3853
+ bytes: result.bytes,
3854
+ ...seen ? { note: "The image in the next message is the picture downloaded from this URL." } : {}
3855
+ }
3856
+ }];
3857
+ if (data !== void 0 && mimeType !== void 0) content.push({
3858
+ type: "image",
3859
+ id: result.artifact.replace(/^artifact:/, ""),
3860
+ mimeType,
3861
+ data
3862
+ });
3863
+ return {
3864
+ ok: true,
3865
+ content
3866
+ };
3867
+ } catch (e) {
3868
+ return {
3869
+ ok: false,
3870
+ content: [],
3871
+ error: {
3872
+ code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
3873
+ message: messageOf(e)
3874
+ }
3875
+ };
3876
+ }
3877
+ }
3878
+ };
3879
+ }
3880
+
3881
+ //#endregion
3882
+ //#region ../agent/src/dwg/views.ts
3883
+ /**
3884
+ * 视图取用(0.22.0 分册 23 FR-23.5 / FR-23.7)。
3885
+ *
3886
+ * 单独成文件是因为 diff、导出、投放面三边都要用同一套「哪些视图、按什么配对」,
3887
+ * 各写一份迟早会在某一边把空布局漏掉。
3888
+ */
3889
+ /** 模型空间视图的固定 id。两份图纸靠它配对 */
3890
+ const DWG_MODEL_VIEW_ID = "Model";
3891
+ /**
3892
+ * 文档的视图列表。
3893
+ *
3894
+ * 分册 23 之前解析出的文档没有 `views`,给它补一个模型空间视图:
3895
+ * 那时的 `entities` 本来就是模型空间的全部内容,语义对得上。
3896
+ */
3897
+ function dwgViewsOf(doc) {
3898
+ const views = doc.views;
3899
+ if (views && views.length > 0) return views;
3900
+ return [{
3901
+ id: DWG_MODEL_VIEW_ID,
3902
+ name: DWG_MODEL_VIEW_ID,
3903
+ kind: "model",
3904
+ entities: doc.entities,
3905
+ bounds: doc.bounds,
3906
+ viewports: []
3907
+ }];
3908
+ }
3909
+ /** 按 id 取视图;取不到就退回第一个,而不是什么都没有 */
3910
+ function dwgPickView(doc, viewId) {
3911
+ const views = dwgViewsOf(doc);
3912
+ return views.find((v) => v.id === viewId) ?? views[0];
3913
+ }
3914
+
3915
+ //#endregion
3916
+ //#region ../agent/src/dwg/toolSource.ts
3917
+ const VIEW_DWG_TOOL = "view_dwg";
3918
+ /** 图层汇总进上下文的条数上限:图纸动辄几十上百个图层,全给等于把预算烧在噪声上 */
3919
+ const MAX_LAYER_ROWS = 25;
3920
+ /** 叠加的硬上限(FR-35.1)。三份叠在一起没有颜色方案能让人分得清 */
3921
+ const MAX_DRAWINGS = 2;
3922
+ const VIEW_SCHEMA = {
3923
+ type: "object",
3924
+ properties: { files: {
3925
+ type: "array",
3926
+ items: {
3927
+ type: "string",
3928
+ description: "The id of a .dwg. It can come from the download list, from a file the user attached, or from the id that read_linked_document returns for a drawing on the page. The download listing marks .dwg as unreadable because it has no text to extract — pass the id here anyway."
3929
+ },
3930
+ minItems: 1,
3931
+ maxItems: MAX_DRAWINGS,
3932
+ description: "One drawing id, or two to stack them on top of each other. Two drawings are overlaid in the same coordinate system so the user can see where they differ and measure it — the first id becomes the top layer. Pass two only when the user asked to look at two drawings."
3933
+ } },
3934
+ required: ["files"],
3935
+ additionalProperties: false
3936
+ };
3937
+ const SYSTEM_PROMPT = "Use view_dwg to show a CAD drawing. Drawings reach you as ids, never as content: from the download list, from a file the user attached, or from read_linked_document when the drawing is linked on the page. Those three all hand you an id and nothing else — a drawing is far too large to put in front of you, so an id with no readable content is the expected outcome, not a failed read. list_downloaded_files reports .dwg as unreadable — that verdict is about extracting text, and does not apply here: hand the id to this tool and it will read the drawing itself. It opens a viewer for the user and returns only a summary to you — the drawing geometry never comes back as data, so do not ask for entity coordinates and do not try to describe the drawing from the tool result alone. The user drives the viewer: they switch between sheets and model space, turn layers on and off, and measure distances, areas and angles there. Pass two ids to stack two drawings in one viewer. That is for the user to look at, not for you to compare: you never receive the geometry, so you cannot say what changed between them. Tell the user to look and to measure, and offer to explain what the viewer controls do. The \"approximated\" count is geometry drawn imprecisely and the \"notDrawn\" count is geometry missing from the viewer entirely — say which one applies rather than merging them.";
3938
+ function omissionNote(doc) {
3939
+ return doc.omissions.map((o) => {
3940
+ switch (o.reason) {
3941
+ case "acis-not-tessellated": return `${o.count} ${o.kind} solid(s) cannot be displayed: they are ACIS solids, which this reader does not tessellate.`;
3942
+ case "pattern-approximated": return `${o.count} ${o.kind} entity(ies) are drawn with an approximated fill pattern, not the exact hatch definition.`;
3943
+ case "pattern-too-dense": return `${o.count} hatch(es) have a pattern too dense to draw; their outline is shown without the fill.`;
3944
+ case "linetype-unavailable": return `${o.count} entity(ies) reference a line type whose dash definition is unavailable; they are drawn solid.`;
3945
+ case "unsupported-entity": return `${o.count} ${o.kind} entity(ies) are not supported and were not drawn.`;
3946
+ }
3947
+ });
3948
+ }
3949
+ function summariseDocument(doc) {
3950
+ const views = dwgViewsOf(doc);
3951
+ const tally = (severity) => doc.omissions.filter((o) => o.severity === severity).reduce((total, o) => total + o.count, 0);
3952
+ return {
3953
+ version: doc.version,
3954
+ entities: views.reduce((total, v) => total + v.entities.length, 0),
3955
+ views: views.map((v) => ({
3956
+ id: v.id,
3957
+ kind: v.kind,
3958
+ entities: v.entities.length,
3959
+ viewports: v.viewports.length
3960
+ })),
3961
+ layers: doc.layers.map((l) => l.name).slice(0, MAX_LAYER_ROWS),
3962
+ layerCount: doc.layers.length,
3963
+ approximated: tally("degraded"),
3964
+ notDrawn: tally("dropped"),
3965
+ notes: omissionNote(doc)
3966
+ };
3967
+ }
3968
+ /**
3969
+ * 取件号列表(FR-35.1 / AC-35.2)。
3970
+ *
3971
+ * 超过上限**整调用拒绝**,不截断:模型传了三份却只看到两份,
3972
+ * 它会把缺的那份归因到图纸而不是工具。
3973
+ */
3974
+ function requireFiles(args) {
3975
+ const raw = args["files"];
3976
+ if (!Array.isArray(raw) || raw.length === 0) throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} requires a non-empty "files" array.`);
3977
+ if (raw.length > MAX_DRAWINGS) throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} opens at most ${MAX_DRAWINGS} drawings at once; ${raw.length} were requested. Ask the user which two to look at.`);
3978
+ return raw.map((value, index) => {
3979
+ if (typeof value !== "string" || value.trim() === "") throw new WebSkillError("VALIDATION_FAILED", `${VIEW_DWG_TOOL} "files[${index}]" must be a non-empty string.`);
3980
+ return value.trim();
3981
+ });
3982
+ }
3983
+ /** 宽松判断:只要带协议头就按链接处理。宁可多脱敏一次,也不要漏掉一个带令牌的地址 */
3984
+ function looksLikeUrl(value) {
3985
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
3986
+ }
3987
+ /** 把 `load()` 的两种返回形态归一(FR-31.1)。判据是有没有 `document`,不是 `in` 关键字之外的猜测 */
3988
+ async function readDrawing(load, file, context) {
3989
+ const loaded = await load(file, context);
3990
+ if (!("document" in loaded)) return { document: loaded };
3991
+ const name = typeof loaded.name === "string" ? loaded.name.trim() : "";
3992
+ return {
3993
+ document: loaded.document,
3994
+ ...name === "" ? {} : { name }
3995
+ };
3996
+ }
3997
+ /**
3998
+ * DWG 查看工具(0.22.0 分册 19,分册 34 收敛为单工具)。
3999
+ *
4000
+ * 与 `capture_page_image` 同一套 `ExternalToolSource` 范式。
4001
+ * 解析与投放都由宿主注入:本包不认识 `.dwg` 字节,也不认识 `documentSurface`。
4002
+ * @experimental
4003
+ */
4004
+ function createDwgToolSource(options) {
4005
+ const { load, project } = options;
4006
+ const available = load !== void 0 && project !== void 0;
4007
+ return {
4008
+ kind: "dwg",
4009
+ listToolSpecs: () => Promise.resolve(available ? [{
4010
+ name: VIEW_DWG_TOOL,
4011
+ description: "Open a CAD drawing (DWG) in a viewer.",
4012
+ inputSchema: VIEW_SCHEMA
4013
+ }] : []),
4014
+ systemPrompt: () => Promise.resolve(available ? SYSTEM_PROMPT : void 0),
4015
+ canHandle: (name) => name === VIEW_DWG_TOOL,
4016
+ /**
4017
+ * 已审查(分册 30):文件引用**通常**是用户可见的名字。
4018
+ * 但 schema 允许传页面上的链接,而链接可能是带签名令牌的临时地址——
4019
+ * 那种东西进技能快照就是把凭证写进一份可分享的产物。
4020
+ * 所以不是把字段永久标成敏感,而是本次引用长得像 URL 时整调用不留。
4021
+ */
4022
+ argCaptureTrust: (name, args) => {
4023
+ if (name !== "view_dwg") return void 0;
4024
+ const files = args["files"];
4025
+ return Array.isArray(files) && files.some((f) => typeof f === "string" && looksLikeUrl(f)) ? { tier: "untrusted" } : { tier: "reviewed" };
4026
+ },
4027
+ captureArgs: (_name, args) => {
4028
+ const files = args["files"];
4029
+ return Array.isArray(files) ? { files: files.filter((f) => typeof f === "string") } : {};
4030
+ },
4031
+ call: async (name, args, context) => {
4032
+ try {
4033
+ if (!available || name !== "view_dwg") throw new WebSkillError("TOOL_UNSUPPORTED", `${name} is not available in this host.`);
4034
+ const files = requireFiles(args);
4035
+ const drawings = [];
4036
+ for (const file of files) {
4037
+ const drawing = await readDrawing(load, file, context);
4038
+ drawings.push({
4039
+ title: file,
4040
+ document: drawing.document,
4041
+ ...drawing.name === void 0 ? {} : { fileName: drawing.name }
4042
+ });
4043
+ }
4044
+ await project({ drawings }, context);
4045
+ return {
4046
+ ok: true,
4047
+ content: [{
4048
+ type: "json",
4049
+ data: { drawings: drawings.map((d) => ({
4050
+ file: d.title,
4051
+ ...d.fileName === void 0 ? {} : { fileName: d.fileName },
4052
+ ...summariseDocument(d.document)
4053
+ })) }
4054
+ }]
4055
+ };
4056
+ } catch (e) {
4057
+ return {
4058
+ ok: false,
4059
+ content: [],
4060
+ error: {
4061
+ code: e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED",
4062
+ message: messageOf(e)
4063
+ }
4064
+ };
4065
+ }
4066
+ }
4067
+ };
3063
4068
  }
3064
4069
 
3065
4070
  //#endregion
3066
- export { CLOSE_PAGE_TAB_TOOL, DEFAULT_MAX_DATA_SOURCE_BYTES, DELEGATE_TASK_TOOL, DELEGATION_SYSTEM_PROMPT, DOCUMENT_READ_BUDGET, DOWNLOAD_WAIT_DEFAULT_MS, DOWNLOAD_WAIT_MAX_MS, DOWNLOAD_WAIT_MIN_MS, DataSourceCandidatePolicy, DataSourcePolicy, DelegationOrchestrator, DownloadedFilePolicy, EXPORT_SPREADSHEET_TOOL, FOCUS_PAGE_TAB_TOOL, GENERATE_SKILL_TOOL, LIST_DOWNLOADED_FILES_TOOL, LIST_PAGE_TABS_TOOL, MANAGE_TODO_TOOL, PAGE_ACTION_KINDS, PAGE_ACTION_SYSTEM_PROMPT, PAGE_ACTION_TOOL, PAGE_DOCUMENT_REF, PDF_GARBLED_RATIO, PDF_LARGE_IMAGE_RATIO, PERCEIVE_PAGE_TOOL, PERCEPTION_PAGING_DEFAULTS, PERCEPTION_SYSTEM_PROMPT, PageActionPolicy, PagePerceptionPolicy, READ_DOCUMENT_TOOL, READ_DOWNLOADED_FILE_TOOL, SKILL_GENERATION_SYSTEM_PROMPT, SkillGenerator, TAB_WORKSET_DEFAULT_MAX, TAB_WORKSET_MAX_MAX, TAB_WORKSET_MIN_MAX, TAB_WORKSET_SYSTEM_PROMPT, TODO_SYSTEM_PROMPT, TabWorksetPolicy, TodoStore, asReadMode, clampDownloadWaitMs, clampWorksetMax, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createHttpDataSourceTransport, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTodoToolSource, defersImage, frameLabel, frameSteps, isPdfTextTrustworthy, measurePdfTextQuality, sniffDocumentFormat, takesImage, toActionFrameScopes, toFrameScopes, withDelegationOrigin };
4071
+ export { CAPTURE_PAGE_IMAGE_TOOL, CLOSE_PAGE_TAB_TOOL, DEFAULT_MAX_DATA_SOURCE_BYTES, DELEGATE_TASK_TOOL, DELEGATION_SYSTEM_PROMPT, DOCUMENT_READ_BUDGET, DOWNLOAD_WAIT_DEFAULT_MS, DOWNLOAD_WAIT_MAX_MS, DOWNLOAD_WAIT_MIN_MS, DWG_MODEL_VIEW_ID, DataSourceCandidatePolicy, DataSourcePolicy, DelegationOrchestrator, DownloadedFilePolicy, EMU_PER_PIXEL, EMU_PER_POINT, EMU_PER_TWIP, EXPORT_SPREADSHEET_TOOL, FETCH_REMOTE_IMAGE_TOOL, FILL_TEMPLATE_TOOL, FOCUS_PAGE_TAB_TOOL, GENERATE_SKILL_TOOL, INSPECT_TEMPLATE_TOOL, LIST_DOWNLOADED_FILES_TOOL, LIST_PAGE_TABS_TOOL, MANAGE_TODO_TOOL, MAX_DIGIT_WIDTH, PAGE_ACTION_KINDS, PAGE_ACTION_SYSTEM_PROMPT, PAGE_ACTION_TOOL, PAGE_DOCUMENT_REF, PDF_GARBLED_RATIO, PDF_LARGE_IMAGE_RATIO, PERCEIVE_PAGE_TOOL, PERCEPTION_PAGING_DEFAULTS, PERCEPTION_SYSTEM_PROMPT, PageActionPolicy, PagePerceptionPolicy, READ_DOCUMENT_TOOL, READ_DOWNLOADED_FILE_TOOL, SCROLL_DIRECTIONS, SKILL_GENERATION_SYSTEM_PROMPT, SkillGenerator, TAB_WORKSET_DEFAULT_MAX, TAB_WORKSET_MAX_MAX, TAB_WORKSET_MIN_MAX, TAB_WORKSET_SYSTEM_PROMPT, TEMPLATE_INSPECT_BUDGET, TODO_SYSTEM_PROMPT, TabWorksetPolicy, TodoStore, VIEW_DWG_TOOL, asReadMode, clampDownloadWaitMs, clampWorksetMax, columnWidthToPixels, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createDwgToolSource, createHttpDataSourceTransport, createPageActionToolSource, createPageImageToolSource, createPagePerceptionToolSource, createRemoteImageToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTemplateToolSource, createTodoToolSource, defersImage, dwgPickView, dwgViewsOf, fitWithin, formatCellAddress, frameLabel, frameSteps, isPdfTextTrustworthy, isRangeTopLeft, measurePdfTextQuality, parseCellAddress, parseCellRange, rangeContains, readImageSize, sniffDocumentFormat, takesImage, toActionFrameScopes, toFrameScopes, withDelegationOrigin };