@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.
- package/dist/abnfDiagram-O67JEVCF-DtYFjfqO.js +109 -0
- package/dist/agent.d.ts +3 -3
- package/dist/agent.js +1174 -169
- package/dist/{approval-DwN2o2QG.js → approval-7CuZS3Z4.js} +104 -18
- package/dist/arc-8U_6NQWm.js +130 -0
- package/dist/architecture-7GRP2DOG-RTw1wpAq.js +4 -0
- package/dist/architectureDiagram-NJMV4G6O-XmmXrJAK.js +7473 -0
- package/dist/array-DQfpQNY8.js +8 -0
- package/dist/blockDiagram-BEXU5L5S-D4yjnCD1.js +2582 -0
- package/dist/browser.d.ts +455 -49
- package/dist/browser.js +2134 -138
- package/dist/c4Diagram-YGBWAQC7-BLmA_JSd.js +3668 -0
- package/dist/channel-CZxxLrG7.js +9 -0
- package/dist/chunk-2Q5K7J3B-Ck03gkFR.js +21 -0
- package/dist/chunk-3FUC2YCW-tr6jmbQf.js +530 -0
- package/dist/chunk-5DYCD2WN-Bt97YGTd.js +58 -0
- package/dist/chunk-5VM5RSS4-BBxa48HL.js +21 -0
- package/dist/chunk-6AEJRKK7-JXK89I-O.js +14 -0
- package/dist/chunk-742MDFTN-CPmgCGwC.js +466 -0
- package/dist/chunk-7INBJB4K-C9jq9o66.js +6552 -0
- package/dist/chunk-7M6MHVWA-BrKCWcR1.js +4894 -0
- package/dist/chunk-7PRAP22T-BHED6GVs.js +95 -0
- package/dist/chunk-DUW6YSOI-DoZj78PO.js +19 -0
- package/dist/chunk-FOHPRMQF-Dkbp819z.js +27189 -0
- package/dist/chunk-GNY47TPC-Bpvq1T49.js +87 -0
- package/dist/chunk-GTNCS2PH-CLmh4I2c.js +686 -0
- package/dist/chunk-GWA4HPMP-B6vN3fkL.js +95 -0
- package/dist/chunk-J5ZVWO5B-Bi_Pcobw.js +26 -0
- package/dist/chunk-JWPE2WC7-D1rC79GS.js +12 -0
- package/dist/chunk-LNGE3PJU-D5OIoe3Z.js +2582 -0
- package/dist/chunk-MBY4JIJT-C34L7a4f.js +2276 -0
- package/dist/chunk-NETBCI7D-Cg84k8N8.js +321 -0
- package/dist/chunk-O7XYJQB3-B6K0KOSL.js +8846 -0
- package/dist/chunk-UA2S7LBM-DxHM29_K.js +612 -0
- package/dist/chunk-WEXAMYUT-Ba6jFWhB.js +33 -0
- package/dist/chunk-XXDRQBXY-DPbSy4kg.js +12 -0
- package/dist/chunk-Y2CYZVJY-U3QOMqrW.js +15 -0
- package/dist/chunk-Z7XXMR3K-oLg1h8F3.js +1038 -0
- package/dist/chunk-ZIGJFQKS-DqEFBuHR.js +2215 -0
- package/dist/classDiagram-v2-NBCMYWYE-BqnOWIUt.js +3845 -0
- package/dist/cose-bilkent-JH36ORCC-C0dTAHVK.js +4270 -0
- package/dist/cynefin-OW5HDTMX-B2QvgEQj.js +4 -0
- package/dist/cynefinDiagram-VND7K2PF-yAWc6Z3W.js +479 -0
- package/dist/cytoscape.esm-C64mRh9B.js +28325 -0
- package/dist/dagre-6A5THRUB-D0w2W27v.js +4816 -0
- package/dist/defaultLocale-B_EoOkSF.js +249 -0
- package/dist/diagram-22UHCM2B-DvgzhUzP.js +6115 -0
- package/dist/diagram-3UASUU5V-DWgUB-ef.js +795 -0
- package/dist/diagram-ATOU4E4O-P1olqwIs.js +553 -0
- package/dist/diagram-CDSNMT55-BUlZuLUe.js +402 -0
- package/dist/diagram-MLGK6HIB-6w5qmkGt.js +186 -0
- package/dist/diagram-MPIPVDR6-DEFiSo8o.js +289 -0
- package/dist/dist-UajCDt3n.js +92 -0
- package/dist/ebnfDiagram-ZINNZB2B-CI8liCfR.js +123 -0
- package/dist/elk-276RUBZZ-3SvW6OAK.js +90034 -0
- package/dist/erDiagram-OPXOYQCR-CPTbKhwP.js +2301 -0
- package/dist/eventmodeling-NTZA5JFV-BlyuqGp0.js +4 -0
- package/dist/flowDiagram-KWPJA3E3-CcUJ6HbV.js +18 -0
- package/dist/ganttDiagram-FUAMR5RP-31UjY0KK.js +3939 -0
- package/dist/{toolSource-C9oNuKmr.js → geometry-CRhuySKV.js} +169 -12
- package/dist/gitGraph-4MIJSDKK-ChdVlrk7.js +4 -0
- package/dist/gitGraphDiagram-X574FWY7-DKuxJymB.js +1153 -0
- package/dist/governance.d.ts +22 -4
- package/dist/governance.js +100 -10
- package/dist/graphlib-tk9gQYuN.js +4194 -0
- package/dist/{index-DHopiH_m.d.ts → index-7No55dRb.d.ts} +161 -11
- package/dist/{index-B8Tgmpwh.d.ts → index-ShvCB5I9.d.ts} +2 -2
- package/dist/{index-CYLt5u4L.d.ts → index-V76_OFj2.d.ts} +789 -21
- package/dist/index.d.ts +4 -4
- package/dist/index.js +11 -9
- package/dist/info-A6RAGUB7-CxRAt9S9.js +4 -0
- package/dist/infoDiagram-VRGFBTTK-pRspGfjZ.js +25 -0
- package/dist/init-CMOIWdg-.js +16 -0
- package/dist/ishikawaDiagram-OU5B5YK6-DKerAPAg.js +975 -0
- package/dist/journeyDiagram-ZHPQQLJL-C4MK-YO5.js +1227 -0
- package/dist/kanban-definition-PNTS6WVX-BqsZpMXx.js +1244 -0
- package/dist/katex-BcRRvyh_.js +26465 -0
- package/dist/{kind-DaKqLX2F.js → kind-Dc8x0HWz.js} +54 -7
- package/dist/line-LgV8iVMV.js +48 -0
- package/dist/linear-BWKR0Tqd.js +393 -0
- package/dist/linkedDocument-CZZOl9cO.js +456 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/{memoryArtifactStore-D6dLeY55.js → memoryArtifactStore-Bs7glPqm.js} +14 -0
- package/dist/mermaid-parser.core-pYR5siSx.js +754 -0
- package/dist/mermaid.core-DMtwGCHy.js +1756 -0
- package/dist/mindmap-definition-NLK3R4M7-CXbD-myG.js +1312 -0
- package/dist/node.d.ts +3 -3
- package/dist/node.js +12 -5
- package/dist/{openUiLibrary-b0i7F_oZ.js → openUiLibrary-B7j3rLrm.js} +1 -1
- package/dist/{openUiSpecLang-CLXJEeRU.js → openUiSpecLang-BSYYnjay.js} +1 -1
- package/dist/ordinal-CxA6GHQS.js +84 -0
- package/dist/packet-AYTQ26CC-BOm08cxl.js +4 -0
- package/dist/path-C27Z2Bjv.js +106 -0
- package/dist/{pathSecurity-B1owvJAF.js → pathSecurity-CirkQwWP.js} +12 -1
- package/dist/pegDiagram-GJSIUBJH-BLGJOewQ.js +115 -0
- package/dist/pie-WAS4IAKB-Dvg7glpk.js +4 -0
- package/dist/pieDiagram-5QR66LMP-laoaV_PB.js +298 -0
- package/dist/quadrantDiagram-O4NWA36T-Db_wFBkT.js +2236 -0
- package/dist/radar-RG4KPBEZ-BZDaTu06.js +4 -0
- package/dist/railroad-74A4TZTK-C-UhjkOy.js +4 -0
- package/dist/railroad-abnf-HS5TGJTU-dUKVA4OS.js +4 -0
- package/dist/railroad-ebnf-LZEXJU2U-DzQumk18.js +4 -0
- package/dist/railroad-peg-WCYAUIDC-B3vvTWed.js +4 -0
- package/dist/railroadDiagram-XR7U4H2S-BY0sTM4X.js +89 -0
- package/dist/requirementDiagram-PLB6GJNP-3rkQfEhs.js +2474 -0
- package/dist/rough.cjs-AmDd_NQk.js +1395 -0
- package/dist/sankeyDiagram-IPEJSGJF-CGAJhyG2.js +3578 -0
- package/dist/sequenceDiagram-PO4LG4MO-9FJjFX06.js +5522 -0
- package/dist/sizeCapture-INFHLROL-D8PWxdOn.js +56 -0
- package/dist/{skill-CAJMsLod.js → skill-WSUDp6A1.js} +1 -1
- package/dist/{skillVersionStore-B24Jjjry-BkOk9wI_.d.ts → skillVersionStore-S4_HJ_Fl-Cvy9LqVu.d.ts} +35 -4
- package/dist/src-NkhI3UMl.js +2653 -0
- package/dist/stateDiagram-v2-GCMORJYK-B0O3wc1-.js +2715 -0
- package/dist/swimlanes-2SLR337P-D4EOnrpu.js +6637 -0
- package/dist/swimlanesDiagram-TC7HE7FX-BUx29EJ9.js +30 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1 -1
- package/dist/timeline-definition-EJHVYXUP-CrgiiB4C.js +1513 -0
- package/dist/treeView-Q6P3EWNA-C1by-Gzc.js +4 -0
- package/dist/treemap-WGGIJYW6-mC2NZ6ad.js +4 -0
- package/dist/{types-BvTV_05--BW8zDuEk.d.ts → types-CpDRZ0rA-BiZBXGm4.d.ts} +66 -8
- package/dist/ui-react.d.ts +3 -3
- package/dist/ui-react.js +3746 -96
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +2 -2
- package/dist/usecaseDiagram-POWQR4AR-BUt_TQOY.js +14397 -0
- package/dist/vennDiagram-UO4OBE2U-CwXr0wjA.js +2808 -0
- package/dist/wardley-WFR3VGLG-CVSYjq_S.js +4 -0
- package/dist/wardleyDiagram-VNRHLVJA-Cpfd47dQ.js +874 -0
- package/dist/{webSkillApi-Cib6G-94.js → webSkillApi-D5VMUnrv.js} +2 -2
- package/dist/{webskillLitCatalog-W06npwej.js → webskillLitCatalog-D5BCiMCU.js} +1 -1
- package/dist/xychartDiagram-PMCCYNJV-e1TeVoc6.js +2704 -0
- package/package.json +1 -1
- 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-
|
|
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 {
|
|
6
|
-
import { c as
|
|
7
|
-
import {
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
243
|
-
if (status !== void 0 && typeof status !== "string") return Promise.resolve(toolError$
|
|
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$
|
|
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$
|
|
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$
|
|
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 ?? [])
|
|
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$
|
|
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: {
|
|
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"
|
|
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$
|
|
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"
|
|
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:
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
*
|
|
762
|
+
* 委派编排器(设计 03 §13–§16,并发改造见 0.23.0 分册 19)。
|
|
676
763
|
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
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
|
-
|
|
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
|
-
/**
|
|
788
|
+
/** 正在执行的委派任务之一;全部空闲则 undefined。仅供诊断 */
|
|
692
789
|
get running() {
|
|
693
|
-
|
|
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 ??
|
|
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)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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.#
|
|
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
|
-
*
|
|
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
|
-
"-
|
|
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.
|
|
773
|
-
const INPUT_SCHEMA$
|
|
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$
|
|
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$
|
|
829
|
-
if (typeof task !== "string" || task.trim() === "") return toolError$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
1405
|
-
inputSchema: INPUT_SCHEMA$
|
|
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$
|
|
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$
|
|
1423
|
-
if (ref !== void 0 && ref !== pending.subtreeRef) return toolError$
|
|
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$
|
|
1486
|
-
return toolError$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
|
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$
|
|
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$
|
|
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\",
|
|
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$
|
|
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$
|
|
2158
|
-
inputSchema: INPUT_SCHEMA$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
2219
|
-
if (action === "scroll" && value
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
/**
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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 =
|
|
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
|
|
2724
|
-
return `${
|
|
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 ((
|
|
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$
|
|
2829
|
-
function toolError$
|
|
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$
|
|
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$
|
|
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
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
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
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
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.
|
|
3145
|
+
maxItems: SPREADSHEET_LIMITS.rowsPerSheet,
|
|
2953
3146
|
items: {
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
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
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
};
|
|
2976
|
-
|
|
2977
|
-
function
|
|
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:
|
|
3222
|
+
description: DESCRIPTION$2,
|
|
3223
|
+
inputSchema: schema
|
|
3027
3224
|
}] : []),
|
|
3028
|
-
systemPrompt: () => Promise.resolve(writeArtifact ?
|
|
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 —  — 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 };
|