@webskill/sdk 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/abnfDiagram-O67JEVCF-DtYFjfqO.js +109 -0
  2. package/dist/agent.d.ts +3 -3
  3. package/dist/agent.js +1171 -73
  4. package/dist/{approval-Bbh_Apwg.js → approval-DWQlDPbY.js} +527 -29
  5. package/dist/arc-8U_6NQWm.js +130 -0
  6. package/dist/architecture-7GRP2DOG-RTw1wpAq.js +4 -0
  7. package/dist/architectureDiagram-NJMV4G6O-XmmXrJAK.js +7473 -0
  8. package/dist/array-DQfpQNY8.js +8 -0
  9. package/dist/blockDiagram-BEXU5L5S-D4yjnCD1.js +2582 -0
  10. package/dist/browser.d.ts +1129 -48
  11. package/dist/browser.js +3562 -278
  12. package/dist/c4Diagram-YGBWAQC7-BLmA_JSd.js +3668 -0
  13. package/dist/channel-CZxxLrG7.js +9 -0
  14. package/dist/chunk-2Q5K7J3B-Ck03gkFR.js +21 -0
  15. package/dist/chunk-3FUC2YCW-tr6jmbQf.js +530 -0
  16. package/dist/chunk-5DYCD2WN-Bt97YGTd.js +58 -0
  17. package/dist/chunk-5VM5RSS4-BBxa48HL.js +21 -0
  18. package/dist/chunk-6AEJRKK7-JXK89I-O.js +14 -0
  19. package/dist/chunk-742MDFTN-CPmgCGwC.js +466 -0
  20. package/dist/chunk-7INBJB4K-C9jq9o66.js +6552 -0
  21. package/dist/chunk-7M6MHVWA-BrKCWcR1.js +4894 -0
  22. package/dist/chunk-7PRAP22T-BHED6GVs.js +95 -0
  23. package/dist/chunk-DUW6YSOI-DoZj78PO.js +19 -0
  24. package/dist/chunk-FOHPRMQF-Dkbp819z.js +27189 -0
  25. package/dist/chunk-GNY47TPC-Bpvq1T49.js +87 -0
  26. package/dist/chunk-GTNCS2PH-CLmh4I2c.js +686 -0
  27. package/dist/chunk-GWA4HPMP-B6vN3fkL.js +95 -0
  28. package/dist/chunk-J5ZVWO5B-Bi_Pcobw.js +26 -0
  29. package/dist/chunk-JWPE2WC7-D1rC79GS.js +12 -0
  30. package/dist/chunk-LNGE3PJU-D5OIoe3Z.js +2582 -0
  31. package/dist/chunk-MBY4JIJT-C34L7a4f.js +2276 -0
  32. package/dist/chunk-NETBCI7D-Cg84k8N8.js +321 -0
  33. package/dist/chunk-O7XYJQB3-B6K0KOSL.js +8846 -0
  34. package/dist/chunk-UA2S7LBM-DxHM29_K.js +612 -0
  35. package/dist/chunk-WEXAMYUT-Ba6jFWhB.js +33 -0
  36. package/dist/chunk-XXDRQBXY-DPbSy4kg.js +12 -0
  37. package/dist/chunk-Y2CYZVJY-U3QOMqrW.js +15 -0
  38. package/dist/chunk-Z7XXMR3K-oLg1h8F3.js +1038 -0
  39. package/dist/chunk-ZIGJFQKS-DqEFBuHR.js +2215 -0
  40. package/dist/classDiagram-v2-NBCMYWYE-BqnOWIUt.js +3845 -0
  41. package/dist/cose-bilkent-JH36ORCC-C0dTAHVK.js +4270 -0
  42. package/dist/cynefin-OW5HDTMX-B2QvgEQj.js +4 -0
  43. package/dist/cynefinDiagram-VND7K2PF-yAWc6Z3W.js +479 -0
  44. package/dist/cytoscape.esm-C64mRh9B.js +28325 -0
  45. package/dist/dagre-6A5THRUB-D0w2W27v.js +4816 -0
  46. package/dist/defaultLocale-B_EoOkSF.js +249 -0
  47. package/dist/diagram-22UHCM2B-DvgzhUzP.js +6115 -0
  48. package/dist/diagram-3UASUU5V-DWgUB-ef.js +795 -0
  49. package/dist/diagram-ATOU4E4O-P1olqwIs.js +553 -0
  50. package/dist/diagram-CDSNMT55-BUlZuLUe.js +402 -0
  51. package/dist/diagram-MLGK6HIB-6w5qmkGt.js +186 -0
  52. package/dist/diagram-MPIPVDR6-DEFiSo8o.js +289 -0
  53. package/dist/dist-UajCDt3n.js +92 -0
  54. package/dist/ebnfDiagram-ZINNZB2B-CI8liCfR.js +123 -0
  55. package/dist/elk-276RUBZZ-3SvW6OAK.js +90034 -0
  56. package/dist/erDiagram-OPXOYQCR-CPTbKhwP.js +2301 -0
  57. package/dist/{eventTypes-CwciaqCU.js → eventTypes-DcTQXSV1.js} +7 -1
  58. package/dist/eventmodeling-NTZA5JFV-BlyuqGp0.js +4 -0
  59. package/dist/flowDiagram-KWPJA3E3-CcUJ6HbV.js +18 -0
  60. package/dist/ganttDiagram-FUAMR5RP-31UjY0KK.js +3939 -0
  61. package/dist/geometry-wx5ZTYzi.js +715 -0
  62. package/dist/gitGraph-4MIJSDKK-ChdVlrk7.js +4 -0
  63. package/dist/gitGraphDiagram-X574FWY7-DKuxJymB.js +1153 -0
  64. package/dist/governance.d.ts +9 -3
  65. package/dist/governance.js +1 -1
  66. package/dist/graphlib-tk9gQYuN.js +4194 -0
  67. package/dist/{index-ad11wflI.d.ts → index-BiO9_XRk.d.ts} +2 -2
  68. package/dist/{index-DQ3-6GFu.d.ts → index-CjwdZQOS.d.ts} +945 -8
  69. package/dist/{index-BDyXe-a5.d.ts → index-YPd8ZSEa.d.ts} +413 -7
  70. package/dist/index.d.ts +4 -4
  71. package/dist/index.js +47 -41
  72. package/dist/info-A6RAGUB7-CxRAt9S9.js +4 -0
  73. package/dist/infoDiagram-VRGFBTTK-pRspGfjZ.js +25 -0
  74. package/dist/init-CMOIWdg-.js +16 -0
  75. package/dist/ishikawaDiagram-OU5B5YK6-DKerAPAg.js +975 -0
  76. package/dist/journeyDiagram-ZHPQQLJL-C4MK-YO5.js +1227 -0
  77. package/dist/kanban-definition-PNTS6WVX-BqsZpMXx.js +1244 -0
  78. package/dist/katex-BcRRvyh_.js +26465 -0
  79. package/dist/kind-Dc8x0HWz.js +110 -0
  80. package/dist/line-LgV8iVMV.js +48 -0
  81. package/dist/linear-BWKR0Tqd.js +393 -0
  82. package/dist/linkedDocument-CIuh_Yp0.js +416 -0
  83. package/dist/mcp.d.ts +2 -2
  84. package/dist/{memoryArtifactStore-D6dLeY55.js → memoryArtifactStore-Bs7glPqm.js} +14 -0
  85. package/dist/mermaid-parser.core-pYR5siSx.js +754 -0
  86. package/dist/mermaid.core-DMtwGCHy.js +1756 -0
  87. package/dist/mindmap-definition-NLK3R4M7-CXbD-myG.js +1312 -0
  88. package/dist/node.d.ts +8 -3
  89. package/dist/node.js +79 -4
  90. package/dist/{openUiLibrary-b0i7F_oZ.js → openUiLibrary-B7j3rLrm.js} +1 -1
  91. package/dist/{openUiSpecLang-CLXJEeRU.js → openUiSpecLang-BSYYnjay.js} +1 -1
  92. package/dist/ordinal-CxA6GHQS.js +84 -0
  93. package/dist/packet-AYTQ26CC-BOm08cxl.js +4 -0
  94. package/dist/path-C27Z2Bjv.js +106 -0
  95. package/dist/pegDiagram-GJSIUBJH-BLGJOewQ.js +115 -0
  96. package/dist/pie-WAS4IAKB-Dvg7glpk.js +4 -0
  97. package/dist/pieDiagram-5QR66LMP-laoaV_PB.js +298 -0
  98. package/dist/processSandboxEntry.js +5 -0
  99. package/dist/quadrantDiagram-O4NWA36T-Db_wFBkT.js +2236 -0
  100. package/dist/radar-RG4KPBEZ-BZDaTu06.js +4 -0
  101. package/dist/railroad-74A4TZTK-C-UhjkOy.js +4 -0
  102. package/dist/railroad-abnf-HS5TGJTU-dUKVA4OS.js +4 -0
  103. package/dist/railroad-ebnf-LZEXJU2U-DzQumk18.js +4 -0
  104. package/dist/railroad-peg-WCYAUIDC-B3vvTWed.js +4 -0
  105. package/dist/railroadDiagram-XR7U4H2S-BY0sTM4X.js +89 -0
  106. package/dist/requirementDiagram-PLB6GJNP-3rkQfEhs.js +2474 -0
  107. package/dist/rough.cjs-AmDd_NQk.js +1395 -0
  108. package/dist/sandboxWorkerEntry.js +5 -0
  109. package/dist/sankeyDiagram-IPEJSGJF-CGAJhyG2.js +3578 -0
  110. package/dist/sequenceDiagram-PO4LG4MO-9FJjFX06.js +5522 -0
  111. package/dist/sizeCapture-INFHLROL-D8PWxdOn.js +56 -0
  112. package/dist/{skillVersionStore-B24Jjjry-v2CCU0Xo.d.ts → skillVersionStore-B24Jjjry-BY1H7Krp.d.ts} +1 -1
  113. package/dist/src-NkhI3UMl.js +2653 -0
  114. package/dist/stateDiagram-v2-GCMORJYK-B0O3wc1-.js +2715 -0
  115. package/dist/swimlanes-2SLR337P-D4EOnrpu.js +6637 -0
  116. package/dist/swimlanesDiagram-TC7HE7FX-BUx29EJ9.js +30 -0
  117. package/dist/testing.d.ts +2 -1
  118. package/dist/testing.js +1 -1
  119. package/dist/timeline-definition-EJHVYXUP-CrgiiB4C.js +1513 -0
  120. package/dist/treeView-Q6P3EWNA-C1by-Gzc.js +4 -0
  121. package/dist/treemap-WGGIJYW6-mC2NZ6ad.js +4 -0
  122. package/dist/{types-Btpdd1y--BcxQ10Fa.d.ts → types-CpDRZ0rA-BM5FI9oN.d.ts} +111 -9
  123. package/dist/ui-react.d.ts +3 -3
  124. package/dist/ui-react.js +3718 -77
  125. package/dist/ui-vue.d.ts +1 -1
  126. package/dist/ui.d.ts +3 -3
  127. package/dist/ui.js +2 -2
  128. package/dist/untrustedText-BIaRvPZK.js +16 -0
  129. package/dist/usecaseDiagram-POWQR4AR-BUt_TQOY.js +14397 -0
  130. package/dist/vennDiagram-UO4OBE2U-CwXr0wjA.js +2808 -0
  131. package/dist/wardley-WFR3VGLG-CVSYjq_S.js +4 -0
  132. package/dist/wardleyDiagram-VNRHLVJA-Cpfd47dQ.js +874 -0
  133. package/dist/{webSkillApi-CsvA69qk.js → webSkillApi-Dy-Zjv0y.js} +1 -1
  134. package/dist/{webskillLitCatalog-W06npwej.js → webskillLitCatalog-D5BCiMCU.js} +1 -1
  135. package/dist/xychartDiagram-PMCCYNJV-e1TeVoc6.js +2704 -0
  136. package/package.json +6 -5
  137. package/dist/kind-DxgS8LM-.js +0 -77
  138. package/dist/linkedDocument-xNXF-z8n.js +0 -94
  139. package/dist/types-DOJI5YC3.js +0 -90
@@ -3,11 +3,11 @@ import { b as parseSkillMarkdown, i as buildCatalog, n as SkillDiscovery, r as S
3
3
  import { a as atomicWriteText, r as resolveInsideRoot, t as assertSafePathSegment } from "./pathSecurity-B1owvJAF.js";
4
4
  import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
5
5
  import { c as findUnpairedToolCalls, d as partsToText, f as promptText, l as interruptedToolResult, p as textParts, u as sealToolCallPairs } from "./llm-eIQNO9tr.js";
6
- import { a as fromBase64, c as DEFAULT_MAX_DOCUMENT_BYTES, d as TEXT_BUDGETED_CONTENT_TYPES, i as UNSUPPORTED_DOCUMENT_MESSAGE, l as DEFAULT_MAX_DOCUMENT_TEXT_BYTES, n as READ_LINKED_DOCUMENT_TOOL_NAME, o as toBase64, r as SUPPORTED_DOCUMENT_MIME, t as READ_LINKED_DOCUMENT_TOOL } from "./linkedDocument-xNXF-z8n.js";
6
+ import { a as UNSUPPORTED_DOCUMENT_MESSAGE, c as toBase64, f as SPREADSHEET_MIME_TYPE, g as DEFAULT_MAX_DOCUMENT_TEXT_BYTES, h as DEFAULT_MAX_DOCUMENT_BYTES, i as SUPPORTED_DOCUMENT_MIME, l as encodeSpreadsheet, n as READ_LINKED_DOCUMENT_TOOL, o as fromBase64, r as READ_LINKED_DOCUMENT_TOOL_NAME, s as isBytesOnlyDocument, v as TEXT_BUDGETED_CONTENT_TYPES } from "./linkedDocument-CIuh_Yp0.js";
7
7
  import { t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
8
8
  import { i as isArtifactCardHidden, n as extractChartSpec, r as admitArtifactMetadata, t as buildRenderResult } from "./renderResult-D9Q-Vu2x.js";
9
9
  import { t as extractUiSpecEvents } from "./surface-DVGiCmwq.js";
10
- import { t as MemoryArtifactStore } from "./memoryArtifactStore-D6dLeY55.js";
10
+ import { t as MemoryArtifactStore } from "./memoryArtifactStore-Bs7glPqm.js";
11
11
 
12
12
  //#region ../core/src/render/xmlRenderer.ts
13
13
  /** 转义 XML 五个特殊字符:& < > " ' */
@@ -298,12 +298,68 @@ function resolveSkillDir(skillRoot, dir, relativePath) {
298
298
  return resolveInsideRoot(skillRoot, `${dir}/${relativePath}`);
299
299
  }
300
300
  /**
301
+ * 脚本上下文里 `context.writeSpreadsheet` 的图片引用解析(FR-20.8)。
302
+ *
303
+ * 前缀族与文档那一侧同源(分册 11):`artifact:` 读本 run 已写出的产物,
304
+ * `upload:` 读发起本 run 的那条消息里的附件。两条都**不经模型**、也不经沙箱桥——
305
+ * 脚本交上来的始终只是一个引用,字节在这里才第一次出现。
306
+ *
307
+ * `remote:` 不收:表格必须把字节嵌进去,留外链等于交一份在别人机器上打不开的表。
308
+ * 要用远端图,先落成产物再引用。
309
+ */
310
+ function createScriptImageResolver(deps) {
311
+ return async (ref) => {
312
+ const separator = ref.indexOf(":");
313
+ const kind = separator === -1 ? "" : ref.slice(0, separator);
314
+ const rest = ref.slice(separator + 1);
315
+ if (kind === "artifact") {
316
+ if (deps.artifactStore.readArtifact === void 0) return void 0;
317
+ const found = (await deps.artifactStore.listArtifacts(deps.runId)).find((a) => a.path === rest);
318
+ if (found === void 0) return void 0;
319
+ return {
320
+ bytes: await deps.artifactStore.readArtifact(deps.runId, rest),
321
+ contentType: found.mimeType ?? "application/octet-stream"
322
+ };
323
+ }
324
+ if (kind === "upload") {
325
+ if (deps.uploadFiles === void 0) return void 0;
326
+ const files = await deps.uploadFiles.list();
327
+ const info = files.find((file) => file.id === rest) ?? files.find((file) => file.name === rest);
328
+ if (info === void 0) return void 0;
329
+ return {
330
+ bytes: await deps.uploadFiles.read(info.id),
331
+ contentType: info.contentType
332
+ };
333
+ }
334
+ if (kind === "remote") throw new WebSkillError("VALIDATION_FAILED", `image reference "${ref}" cannot be embedded: spreadsheets need the bytes, not a link. Fetch it into an artifact first and reference that.`);
335
+ throw new WebSkillError("VALIDATION_FAILED", `image reference "${ref}" has no known prefix; expected artifact: or upload:`);
336
+ };
337
+ }
338
+ /**
301
339
  * 脚本执行上下文:只暴露 readReference / readAsset / readAssetBinary / writeArtifact
302
340
  * 四个显式能力,不暴露 fs 本体(沙箱语义,对齐 deferred-items D1)。
303
341
  */
304
342
  function createScriptContext(deps) {
305
343
  const { fs, artifactStore, skillName, skillRoot, runId, confirm, fetchData, uploadFiles, onWarning, onArtifactCreated } = deps;
306
344
  const readFs = fs.withRoot?.(skillRoot) ?? fs;
345
+ const writeArtifact = async (path, content, options) => {
346
+ const metadata = options?.metadata === void 0 ? void 0 : admitArtifactMetadata(options.metadata);
347
+ const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
348
+ runId,
349
+ path,
350
+ content,
351
+ mimeType: options?.mimeType,
352
+ ...metadata === void 0 ? {} : { metadata }
353
+ }) : await artifactStore.createBinaryArtifact({
354
+ runId,
355
+ path,
356
+ content,
357
+ mimeType: options?.mimeType,
358
+ ...metadata === void 0 ? {} : { metadata }
359
+ });
360
+ onArtifactCreated?.(artifact);
361
+ return artifact;
362
+ };
307
363
  return {
308
364
  skillName,
309
365
  runId,
@@ -316,24 +372,18 @@ function createScriptContext(deps) {
316
372
  async readAssetBinary(relativePath) {
317
373
  return readFs.readBinary(resolveSkillDir(skillRoot, "assets", relativePath));
318
374
  },
319
- async writeArtifact(path, content, options) {
320
- const metadata = options?.metadata === void 0 ? void 0 : admitArtifactMetadata(options.metadata);
321
- const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
375
+ writeArtifact,
376
+ ...deps.writeSpreadsheet ? { writeSpreadsheet: async (path, spec, options) => {
377
+ const resolveImage = createScriptImageResolver({
378
+ artifactStore,
322
379
  runId,
323
- path,
324
- content,
325
- mimeType: options?.mimeType,
326
- ...metadata === void 0 ? {} : { metadata }
327
- }) : await artifactStore.createBinaryArtifact({
328
- runId,
329
- path,
330
- content,
331
- mimeType: options?.mimeType,
332
- ...metadata === void 0 ? {} : { metadata }
380
+ ...uploadFiles ? { uploadFiles } : {}
333
381
  });
334
- onArtifactCreated?.(artifact);
335
- return artifact;
336
- },
382
+ return writeArtifact(path, await encodeSpreadsheet(spec, resolveImage), {
383
+ mimeType: SPREADSHEET_MIME_TYPE,
384
+ ...options?.metadata === void 0 ? {} : { metadata: options.metadata }
385
+ });
386
+ } } : {},
337
387
  ...confirm ? { confirm } : {},
338
388
  ...fetchData ? { fetchData } : {},
339
389
  ...deps.documentSurface ? { documentSurface: true } : {},
@@ -687,6 +737,90 @@ function renderCurrentTimePrompt(nowIso) {
687
737
  */
688
738
  const ON_DEMAND_TOOLS_HINT = "Some tools are disclosed on demand and are not listed above. If a capability you need seems missing, look for a related skill in the catalog and read its SKILL.md first — activating it may reveal additional tools. Do not conclude the capability does not exist.";
689
739
 
740
+ //#endregion
741
+ //#region ../runtime/src/engine/visionBatch.ts
742
+ /**
743
+ * 视觉委派的分批并发调度(0.21.0 分册 26 · FR-26.5 ~ FR-26.7)。
744
+ *
745
+ * 从 `agentLoop` 里拆出来,是因为它有三条彼此独立的分支——并发、慢降级、失败隔离——
746
+ * 塞进那个已经三千行的文件里就只能靠端到端测,而这三条恰恰是最需要单独逼出来的。
747
+ * @experimental
748
+ */
749
+ /** 并发度上限。再高只会让每一张都变慢,同时把端点的速率限制撞穿(FR-26.5) @experimental */
750
+ const VISION_MAX_CONCURRENCY = 3;
751
+ /** @experimental */
752
+ const VISION_DEFAULT_CONCURRENCY = 2;
753
+ /**
754
+ * 超过它就退回串行(FR-26.6)。云端视觉端点单张 3–15 秒,本地 Qwen-VL 一类 20–40 秒;
755
+ * 越过 25 秒说明端点已经在排队,继续并发只会让第一张有用的描述来得更晚。
756
+ * @experimental
757
+ */
758
+ const VISION_SLOW_THRESHOLD_MS = 25e3;
759
+ /** 配错了应该退化成能用,不是让 run 失败(FR-26.5) @experimental */
760
+ function clampVisionConcurrency(value) {
761
+ if (typeof value !== "number" || !Number.isFinite(value)) return 2;
762
+ return Math.min(3, Math.max(1, Math.round(value)));
763
+ }
764
+ /**
765
+ * 逐张委派、按并发度并行。
766
+ *
767
+ * 第一波结算后测一次墙钟决定要不要降级:测第一波而不是第一张,
768
+ * 因为并发下第一张返回得早纯属运气,一波的完成时间才反映端点的实际吞吐。
769
+ * 降级是**单向**的,本次委派内不再回升。
770
+ * @experimental
771
+ */
772
+ async function runVisionBatch(options) {
773
+ const total = options.images.length;
774
+ const now = options.now ?? (() => Date.now());
775
+ const slowThresholdMs = options.slowThresholdMs ?? 25e3;
776
+ const outcome = {
777
+ described: new Array(total).fill(void 0),
778
+ failures: [],
779
+ declined: false
780
+ };
781
+ let cursor = 0;
782
+ let done = 0;
783
+ let concurrency = clampVisionConcurrency(options.concurrency);
784
+ const settle = (index, result) => {
785
+ if (result.outcome === "described") {
786
+ outcome.described[index] = result.text;
787
+ outcome.endpoint ??= result.endpoint;
788
+ } else if (result.outcome === "declined") outcome.declined = true;
789
+ else outcome.failures.push({
790
+ index,
791
+ reason: result.reason
792
+ });
793
+ done += 1;
794
+ options.onProgress?.(done, total);
795
+ };
796
+ const stopped = () => outcome.declined || options.signal?.aborted === true;
797
+ const one = async (index) => {
798
+ try {
799
+ settle(index, await options.describe(options.images[index], index));
800
+ } catch (e) {
801
+ settle(index, {
802
+ outcome: "failed",
803
+ reason: e instanceof Error ? e.message : String(e)
804
+ });
805
+ }
806
+ };
807
+ const firstWave = [];
808
+ const startedAt = now();
809
+ for (; cursor < total && firstWave.length < concurrency; cursor += 1) firstWave.push(one(cursor));
810
+ await Promise.all(firstWave);
811
+ if (now() - startedAt > slowThresholdMs) concurrency = 1;
812
+ const worker = async () => {
813
+ while (!stopped()) {
814
+ const index = cursor;
815
+ if (index >= total) return;
816
+ cursor += 1;
817
+ await one(index);
818
+ }
819
+ };
820
+ await Promise.all(Array.from({ length: Math.min(concurrency, total - cursor) }, () => worker()));
821
+ return outcome;
822
+ }
823
+
690
824
  //#endregion
691
825
  //#region ../runtime/src/trace/traceRecorder.ts
692
826
  /** 结构化 trace 记录器;时钟与 id 工厂可注入(golden trace 用固定值保证确定性) */
@@ -1047,6 +1181,11 @@ const LLM_TURN_CODES = /* @__PURE__ */ new Set([
1047
1181
  "TOOL_SCHEMA_UNAVAILABLE"
1048
1182
  ]);
1049
1183
  /**
1184
+ * 端点 200 但整轮 run 没给出任何答复时的说明。
1185
+ * 上下文撑爆只是最常见的成因,不是判据——判据是「一句回答都没有」这个事实本身。
1186
+ */
1187
+ const EMPTY_RESPONSE_MESSAGE = "The model produced no answer and requested no tools, so this run has nothing to show. The most common cause is a conversation that has outgrown the model context window: start a new chat, remove large attachments, or switch to a model with a larger context window.";
1188
+ /**
1050
1189
  * 模型侧图片通道收得下的位图格式(三家 provider 的交集)。
1051
1190
  * SVG 是文本、`application/octet-stream` 是未知字节——两者编成 data URL 发出去,
1052
1191
  * 端点当场 400(LM Studio:`'url' field must be a base64 encoded image.`),
@@ -1198,7 +1337,8 @@ var AgentLoop = class {
1198
1337
  maxUnknownToolRetries: config.maxUnknownToolRetries ?? 2,
1199
1338
  askUserMaxFields: config.askUserMaxFields ?? 20,
1200
1339
  maxDocumentBytes: config.maxDocumentBytes ?? 256e5,
1201
- maxDocumentTextBytes: config.maxDocumentTextBytes ?? 512e3,
1340
+ maxDocumentTextBytes: config.maxDocumentTextBytes ?? 512e4,
1341
+ visionConcurrency: clampVisionConcurrency(config.visionConcurrency),
1202
1342
  temperature: config.temperature,
1203
1343
  renderResult: config.renderResult,
1204
1344
  remoteUrl: config.remoteUrl
@@ -1256,6 +1396,7 @@ var AgentLoop = class {
1256
1396
  interactionSeq: 0,
1257
1397
  surfaceActionSeq: 0,
1258
1398
  documentSeq: 0,
1399
+ binaries: /* @__PURE__ */ new Map(),
1259
1400
  messages: [],
1260
1401
  turn: 0,
1261
1402
  renderBlocks: [],
@@ -1421,6 +1562,7 @@ var AgentLoop = class {
1421
1562
  const { llm } = this.#deps;
1422
1563
  const { trace, messages } = state;
1423
1564
  this.#armDeadline(state);
1565
+ let producedOutput = false;
1424
1566
  try {
1425
1567
  for (let turn = startTurn;; turn++) {
1426
1568
  state.turn = turn;
@@ -1502,12 +1644,14 @@ var AgentLoop = class {
1502
1644
  outputTokens: response.usage.outputTokens
1503
1645
  } });
1504
1646
  if (!response.toolCalls?.length) {
1647
+ if (responseText === "" && !producedOutput) return finish("failed", "llm-error", EMPTY_RESPONSE_MESSAGE, "LLM_REQUEST_FAILED");
1505
1648
  messages.push({
1506
1649
  role: "assistant",
1507
1650
  content: response.content ?? []
1508
1651
  });
1509
1652
  return finish("completed", "final-answer", responseText);
1510
1653
  }
1654
+ producedOutput = true;
1511
1655
  await this.#lifecycle({
1512
1656
  phase: "execute",
1513
1657
  data: {
@@ -1698,6 +1842,7 @@ var AgentLoop = class {
1698
1842
  trace,
1699
1843
  reader: new SkillReader(this.#deps.fs, this.#deps.skillIndex),
1700
1844
  documentSeq: 0,
1845
+ binaries: /* @__PURE__ */ new Map(),
1701
1846
  activated: new Set(snapshot.activeSkillNames),
1702
1847
  activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
1703
1848
  skillAllowedTools: new Map(Object.entries(snapshot.skillAllowedTools ?? {})),
@@ -2126,7 +2271,11 @@ var AgentLoop = class {
2126
2271
  const source = resolution.kind === "script" ? void 0 : (this.#deps.externalTools ?? []).find((s) => s.canHandle(call.name));
2127
2272
  if (source === void 0 && !this.#checkToolAccess(state, call.name)) result = this.#deniedToolError(state, call.name);
2128
2273
  else if (resolution.kind === "script") result = await this.#handleScriptTool(call, resolution.skillName, resolution.scriptName, state);
2129
- else if (source) result = await source.call(call.name, call.arguments, { runId: state.runId });
2274
+ else if (source) result = await source.call(call.name, call.arguments, {
2275
+ runId: state.runId,
2276
+ signal: state.controller.signal,
2277
+ binaries: { get: (id) => state.binaries.get(id) }
2278
+ });
2130
2279
  else {
2131
2280
  if (resolution.kind === "not-found") state.unknownToolCalls += 1;
2132
2281
  result = toolError(resolution.code, resolution.message, resolution.kind === "not-found" ? resolution.data : void 0);
@@ -2520,20 +2669,55 @@ var AgentLoop = class {
2520
2669
  };
2521
2670
  }
2522
2671
  let text;
2523
- if (mime === SUPPORTED_DOCUMENT_MIME.docx || mime === SUPPORTED_DOCUMENT_MIME.xlsx) {
2524
- const docx = mime === SUPPORTED_DOCUMENT_MIME.docx;
2525
- const kind = docx ? "docx" : "xlsx";
2526
- const extractor = docx ? this.#deps.docxExtractor : this.#deps.xlsxExtractor;
2527
- if (extractor === void 0) {
2672
+ if (isBytesOnlyDocument(mime, target.href)) {
2673
+ state.binaries.set(id, {
2674
+ name,
2675
+ mimeType: mime,
2676
+ bytes: fetched.bytes
2677
+ });
2678
+ await audit(record);
2679
+ return {
2680
+ ok: true,
2681
+ content: [{
2682
+ type: "json",
2683
+ data: {
2684
+ id,
2685
+ name,
2686
+ mimeType: mime,
2687
+ bytes: fetched.bytes.length,
2688
+ note: "Stored for tools. Its contents are not readable as text — pass this id to a tool that reads this format."
2689
+ }
2690
+ }]
2691
+ };
2692
+ }
2693
+ const route = {
2694
+ [SUPPORTED_DOCUMENT_MIME.docx]: {
2695
+ kind: "docx",
2696
+ label: "Word documents",
2697
+ ...this.#deps.docxExtractor ? { extractor: this.#deps.docxExtractor } : {}
2698
+ },
2699
+ [SUPPORTED_DOCUMENT_MIME.xlsx]: {
2700
+ kind: "xlsx",
2701
+ label: "Excel workbooks",
2702
+ ...this.#deps.xlsxExtractor ? { extractor: this.#deps.xlsxExtractor } : {}
2703
+ },
2704
+ [SUPPORTED_DOCUMENT_MIME.pptx]: {
2705
+ kind: "pptx",
2706
+ label: "PowerPoint presentations",
2707
+ ...this.#deps.pptxExtractor ? { extractor: this.#deps.pptxExtractor } : {}
2708
+ }
2709
+ }[mime];
2710
+ if (route !== void 0) {
2711
+ if (route.extractor === void 0) {
2528
2712
  await audit({
2529
2713
  ...record,
2530
2714
  ok: false,
2531
- reason: `no ${kind} extractor`
2715
+ reason: `no ${route.kind} extractor`
2532
2716
  });
2533
- return toolError("TOOL_UNSUPPORTED", `${docx ? "Word documents" : "Excel workbooks"} cannot be read in this environment: no ${kind} text extractor is configured.`);
2717
+ return toolError("TOOL_UNSUPPORTED", `${route.label} cannot be read in this environment: no ${route.kind} text extractor is configured.`);
2534
2718
  }
2535
2719
  try {
2536
- text = await extractor(fetched.bytes);
2720
+ text = await route.extractor(fetched.bytes);
2537
2721
  } catch (e) {
2538
2722
  const message = messageOf(e);
2539
2723
  await audit({
@@ -2811,6 +2995,131 @@ var AgentLoop = class {
2811
2995
  });
2812
2996
  return dropped > 0;
2813
2997
  }
2998
+ /**
2999
+ * 工具产出的图 → 第二个端点认出来的文本(0.21.0 分册 17)。
3000
+ *
3001
+ * 返回 `undefined` 表示「本次不委派」,调用方原样投图——注入位缺席、
3002
+ * 选中模型本来就看得了图、或者这批结果里根本没有图,都走这条。
3003
+ * 返回值里的 `kept` 是去掉图之后仍要投出的分片(PDF 等文件分片不受影响)。
3004
+ *
3005
+ * 四种失败一律**返回可读说明**而不是抛错:静默丢图会让主模型以为自己看过了
3006
+ * (FR-17.5a,与 `#dropRejectedImages` 同口径)。
3007
+ */
3008
+ async #delegateVision(state, call, carried) {
3009
+ const delegate = this.#deps.visionDelegate;
3010
+ if (delegate === void 0) return void 0;
3011
+ const images = carried.filter(isImagePart);
3012
+ if (images.length === 0) return void 0;
3013
+ try {
3014
+ if (await delegate.applies() !== true) return void 0;
3015
+ } catch (e) {
3016
+ state.trace.record("run.warning", { message: `Vision delegation availability check failed: ${messageOf(e)}` });
3017
+ return;
3018
+ }
3019
+ const kept = carried.filter((part) => !isImagePart(part));
3020
+ const subject = `the ${images.length === 1 ? "image" : `${images.length} images`} from "${call.name}"`;
3021
+ const dropped = `The ${images.length === 1 ? "image was" : "images were"} not attached; work from the surrounding text, and say so if the answer depends on what ${images.length === 1 ? "it shows" : "they show"}.`;
3022
+ const finish = (note) => {
3023
+ state.trace.record("run.warning", {
3024
+ message: note,
3025
+ data: {
3026
+ images: images.length,
3027
+ tool: call.name
3028
+ }
3029
+ });
3030
+ return {
3031
+ kept,
3032
+ note
3033
+ };
3034
+ };
3035
+ const remainingTurns = state.maxTurns - state.turn;
3036
+ const remainingMs = state.totalTimeoutMs - this.#elapsed(state);
3037
+ const timeoutMs = Math.min(state.toolTimeoutMs * images.length, remainingMs);
3038
+ if (remainingTurns < 1 || timeoutMs <= 0) return finish(`The parent run has no budget left to describe ${subject} (${remainingTurns} turns and ${Math.max(0, Math.round(remainingMs))} ms remaining). ${dropped}`);
3039
+ const payload = images.map((part) => ({
3040
+ mimeType: part.mimeType,
3041
+ data: part.data
3042
+ }));
3043
+ const cancelled = state.controller.signal;
3044
+ const describeOne = async (image) => {
3045
+ const left = state.totalTimeoutMs - this.#elapsed(state);
3046
+ const budget = Math.min(state.toolTimeoutMs, left);
3047
+ if (budget <= 0) return {
3048
+ outcome: "failed",
3049
+ reason: "the parent run has no time budget left"
3050
+ };
3051
+ const controller = new AbortController();
3052
+ const cancelParent = () => controller.abort(cancelled.reason);
3053
+ cancelled.addEventListener("abort", cancelParent, { once: true });
3054
+ let timedOut = false;
3055
+ const timer = setTimeout(() => {
3056
+ timedOut = true;
3057
+ controller.abort(/* @__PURE__ */ new Error("vision delegation timed out"));
3058
+ }, budget);
3059
+ try {
3060
+ const one = await delegate.describe({
3061
+ images: [image],
3062
+ task: state.run.userPrompt,
3063
+ source: call.name,
3064
+ batch: {
3065
+ id: call.id,
3066
+ total: payload.length
3067
+ },
3068
+ timeoutMs: budget,
3069
+ signal: controller.signal
3070
+ });
3071
+ return timedOut ? {
3072
+ outcome: "failed",
3073
+ reason: `exceeded its ${budget} ms budget`
3074
+ } : one;
3075
+ } catch (e) {
3076
+ return {
3077
+ outcome: "failed",
3078
+ reason: timedOut ? `exceeded its ${budget} ms budget` : messageOf(e)
3079
+ };
3080
+ } finally {
3081
+ clearTimeout(timer);
3082
+ cancelled.removeEventListener("abort", cancelParent);
3083
+ }
3084
+ };
3085
+ const batch = await runVisionBatch({
3086
+ images: payload,
3087
+ concurrency: clampVisionConcurrency(this.#config.visionConcurrency),
3088
+ describe: describeOne,
3089
+ signal: cancelled,
3090
+ onProgress: (progressed, total) => {
3091
+ this.#lifecycle({
3092
+ phase: "execute",
3093
+ data: {
3094
+ kind: "vision-progress",
3095
+ source: call.name,
3096
+ done: progressed,
3097
+ total
3098
+ }
3099
+ }, state);
3100
+ }
3101
+ });
3102
+ if (batch.declined) return finish(`The user declined to send ${subject} to another provider for description. ${dropped}`);
3103
+ const describedCount = batch.described.filter((text) => text !== void 0).length;
3104
+ if (describedCount === 0) return finish(`Due to image recognition errors, ${subject} could not be read: ${batch.failures[0]?.reason ?? "no description was produced"}. ${dropped}`);
3105
+ const lines = batch.described.map((text, index) => {
3106
+ const failure = batch.failures.find((entry) => entry.index === index);
3107
+ const body = text ?? `not read due to image recognition errors: ${failure?.reason ?? "the endpoint gave no reason"}`;
3108
+ return images.length === 1 ? body : `[${index + 1}/${images.length}] ${body}`;
3109
+ });
3110
+ if (describedCount < images.length) state.trace.record("run.warning", {
3111
+ message: `Due to image recognition errors, ${images.length - describedCount} of ${images.length} images from "${call.name}" were not read.`,
3112
+ data: {
3113
+ images: images.length,
3114
+ described: describedCount,
3115
+ tool: call.name
3116
+ }
3117
+ });
3118
+ return {
3119
+ kept,
3120
+ note: `--- ${images.length === 1 ? "Image" : `${images.length} images`} from "${call.name}", described by ${batch.endpoint ?? "another model"} because the assistant model cannot read images ---\n${lines.join("\n")}`
3121
+ };
3122
+ }
2814
3123
  /** 单个 PDF 分片 → 文本;包装形态与 chatEngine 的附件标签同口径(FR-22.3 / FR-22.4) */
2815
3124
  async #pdfAsText(state, part, extract) {
2816
3125
  const name = part.name ?? "document.pdf";
@@ -2906,6 +3215,12 @@ var AgentLoop = class {
2906
3215
  state.trace.record("run.warning", { message: note });
2907
3216
  notes.push(note);
2908
3217
  }
3218
+ const delegated = await this.#delegateVision(state, call, carried);
3219
+ if (delegated !== void 0) {
3220
+ carried.length = 0;
3221
+ carried.push(...delegated.kept);
3222
+ notes.push(delegated.note);
3223
+ }
2909
3224
  return {
2910
3225
  tool: [
2911
3226
  {
@@ -3193,6 +3508,7 @@ var AgentLoop = class {
3193
3508
  confirm: (message) => this.#confirm(message, state),
3194
3509
  ...this.#deps.fetchData ? { fetchData: this.#deps.fetchData } : {},
3195
3510
  ...this.#deps.documentSurface ? { documentSurface: true } : {},
3511
+ ...this.#deps.writeSpreadsheet ? { writeSpreadsheet: true } : {},
3196
3512
  ...this.#deps.uploadFiles ? { uploadFiles: this.#uploadFilePort(this.#deps.uploadFiles, skillName, state) } : {},
3197
3513
  onWarning: (message) => state.trace.record("run.warning", { message }),
3198
3514
  onArtifactCreated: (artifact) => createdIds.add(artifact.id)
@@ -3535,6 +3851,148 @@ var AgentLoop = class {
3535
3851
  }
3536
3852
  };
3537
3853
 
3854
+ //#endregion
3855
+ //#region ../runtime/src/engine/compaction.ts
3856
+ /** 摘要消息的固定前缀:压缩留痕靠它识别,UI 与后续压缩都读这个标记 */
3857
+ const COMPACTION_PREFIX = "[Earlier conversation summary]";
3858
+ /** 触发比例默认值:留出四分之一余量,够装下这一轮的提问与工具结果 */
3859
+ const DEFAULT_COMPACTION_THRESHOLD = .75;
3860
+ /** 压缩后至少保留的原文消息数 */
3861
+ const DEFAULT_KEEP_RECENT_MESSAGES = 12;
3862
+ /** 保留原文的下界:压到比这更少就等于换了个会话(FR-20.6 第 3 条) */
3863
+ const MIN_KEEP_RECENT_MESSAGES = 6;
3864
+ /**
3865
+ * 要不要压缩。三个输入缺一不压(FR-20.6 第 4 条的「不猜」):
3866
+ * 没有窗口就没有分母,没有真实用量就没有分子——两者都不许估算。
3867
+ */
3868
+ function shouldCompact(lastInputTokens, contextWindow, threshold) {
3869
+ if (lastInputTokens === void 0 || contextWindow === void 0) return false;
3870
+ if (!Number.isFinite(lastInputTokens) || !Number.isFinite(contextWindow) || contextWindow <= 0) return false;
3871
+ return lastInputTokens / contextWindow >= threshold;
3872
+ }
3873
+ /**
3874
+ * 把想要的切点挪到不破坏 tool 契约的位置:
3875
+ * 切点之后不得以孤儿 `tool` 消息开头(它的 assistant 调用会被压掉),
3876
+ * 切点之前不得以未应答的 assistant `toolCalls` 结尾。
3877
+ *
3878
+ * 与 `trimSessionHistory` 是同一套边界规则,区别只在这里要的是**一个**切点。
3879
+ * 返回 0 表示没有安全切点,调用方应当放弃本次压缩。
3880
+ */
3881
+ function alignCut(messages, desired) {
3882
+ let cut = Math.max(0, Math.min(desired, messages.length));
3883
+ while (cut < messages.length && messages[cut].role === "tool") cut += 1;
3884
+ if (cut >= messages.length) return 0;
3885
+ while (cut > 0) {
3886
+ const last = messages[cut - 1];
3887
+ if (last.role === "assistant" && last.toolCalls?.length) cut -= 1;
3888
+ else break;
3889
+ }
3890
+ return cut;
3891
+ }
3892
+ /** 把一段消息压成给模型看的纯文本转录;图片与文件只留占位,不塞 base64 */
3893
+ function transcribe(messages) {
3894
+ return messages.map((message) => {
3895
+ const body = message.content.map((part) => {
3896
+ if (part.type === "text") return part.text;
3897
+ if (part.type === "image") return "[image]";
3898
+ return `[file${part.name !== void 0 ? ` ${part.name}` : ""}]`;
3899
+ }).join("\n").trim();
3900
+ const calls = message.toolCalls?.map((call) => `[called ${call.name}]`).join(" ") ?? "";
3901
+ return `${message.role.toUpperCase()}: ${[body, calls].filter((s) => s !== "").join(" ")}`.trim();
3902
+ }).join("\n");
3903
+ }
3904
+ const SUMMARY_INSTRUCTION = "Summarize the conversation transcript below so that another assistant can continue the conversation without seeing the original messages. Keep every fact, decision, user preference, identifier, number and unfinished task. Drop greetings, acknowledgements and repetition. Write plain prose, no headings, no bullet lists, under 400 words. Do not add information that is not in the transcript.";
3905
+ /**
3906
+ * 压缩会话历史:最早的一段换成一条摘要消息,最近若干条保留原文。
3907
+ *
3908
+ * 返回 `undefined` 表示**没压**——历史还不够长、找不到安全切点、或摘要调用失败。
3909
+ * 压缩是优化,任何一步不顺都退回原历史,不能把一次正常提问变成一次失败(FR-20.6 第 5 条)。
3910
+ *
3911
+ * 摘要用 `user` 角色而不是 `system`:会话历史里本就不含 system(每轮 run 由路由重起),
3912
+ * 且 Anthropic 不接受非首条的 system 消息。
3913
+ */
3914
+ async function compactHistory(history, options) {
3915
+ const keep = Math.max(6, options.keepRecentMessages ?? 12);
3916
+ const cut = alignCut(history, history.length - keep);
3917
+ if (cut <= 0) return void 0;
3918
+ const head = history.slice(0, cut);
3919
+ const tail = history.slice(cut);
3920
+ let summary;
3921
+ try {
3922
+ summary = ((await options.llm.complete({ messages: [{
3923
+ role: "system",
3924
+ content: [{
3925
+ type: "text",
3926
+ text: SUMMARY_INSTRUCTION
3927
+ }]
3928
+ }, {
3929
+ role: "user",
3930
+ content: [{
3931
+ type: "text",
3932
+ text: transcribe(head)
3933
+ }]
3934
+ }] })).content ?? []).map((part) => part.type === "text" ? part.text : "").join("").trim();
3935
+ } catch (e) {
3936
+ options.onWarn?.(`compaction summary request failed: ${e instanceof Error ? e.message : String(e)}`);
3937
+ return;
3938
+ }
3939
+ if (summary === "") {
3940
+ options.onWarn?.("compaction summary was empty; history left untouched");
3941
+ return;
3942
+ }
3943
+ return {
3944
+ messages: [{
3945
+ role: "user",
3946
+ content: [{
3947
+ type: "text",
3948
+ text: `${COMPACTION_PREFIX}\n${summary}`
3949
+ }]
3950
+ }, ...tail],
3951
+ removed: head.length,
3952
+ kept: tail.length
3953
+ };
3954
+ }
3955
+
3956
+ //#endregion
3957
+ //#region ../runtime/src/engine/usage.ts
3958
+ /**
3959
+ * 从 run 的 trace 聚合大模型用量(请求次数 + 输入/输出 token)。
3960
+ * 与 `summarizeToolCalls` 同型:消费方不再各自遍历 trace。
3961
+ * @stable
3962
+ */
3963
+ function summarizeRunUsage(trace) {
3964
+ let llmCalls = 0;
3965
+ let inputTokens = 0;
3966
+ let outputTokens = 0;
3967
+ let hasUsage = false;
3968
+ let lastInputTokens;
3969
+ for (const event of trace) {
3970
+ if (event.type === "llm.request") {
3971
+ llmCalls += 1;
3972
+ continue;
3973
+ }
3974
+ if (event.type !== "llm.usage") continue;
3975
+ const input = event.data?.["inputTokens"];
3976
+ const output = event.data?.["outputTokens"];
3977
+ const hasInput = typeof input === "number" && Number.isFinite(input);
3978
+ const hasOutput = typeof output === "number" && Number.isFinite(output);
3979
+ if (hasInput) {
3980
+ inputTokens += input;
3981
+ lastInputTokens = input;
3982
+ }
3983
+ if (hasOutput) outputTokens += output;
3984
+ if (hasInput || hasOutput) hasUsage = true;
3985
+ }
3986
+ return {
3987
+ llmCalls,
3988
+ ...hasUsage ? {
3989
+ inputTokens,
3990
+ outputTokens
3991
+ } : {},
3992
+ ...lastInputTokens !== void 0 ? { lastInputTokens } : {}
3993
+ };
3994
+ }
3995
+
3538
3996
  //#endregion
3539
3997
  //#region ../runtime/src/engine/runtime.ts
3540
3998
  /**
@@ -3604,24 +4062,46 @@ var WebSkillRuntime = class {
3604
4062
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
3605
4063
  * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
3606
4064
  * maxHistoryMessages(默认取 `DEFAULT_LOOP_LIMITS`)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
4065
+ *
4066
+ * 配了 `contextWindow` 时另加一道**主动压缩**(分册 20 FR-20.6):按上一轮的真实
4067
+ * `prompt_tokens` 与窗口的比值判定,超阈值就在下一轮发起前把最早的一段摘要掉。
4068
+ * 三个输入(窗口、用量、足够长的历史)缺一不压——不估算、不猜。
3607
4069
  */
3608
4070
  createSession(options = {}) {
3609
4071
  const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
3610
4072
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
3611
4073
  const maxHistory = options.maxHistoryMessages ?? DEFAULT_LOOP_LIMITS.maxHistoryMessages;
4074
+ const threshold = options.compactionThreshold ?? .75;
3612
4075
  let history = [];
3613
4076
  let queue = Promise.resolve();
4077
+ /** 上一轮结束时的上下文水位线;没拿到 usage 就一直是 undefined,届时不压缩 */
4078
+ let lastInputTokens;
3614
4079
  return {
3615
4080
  id: sessionId,
3616
4081
  createdAt,
3617
4082
  run: (prompt, runOptions = {}) => {
3618
4083
  const result = queue.then(async () => {
4084
+ if (shouldCompact(lastInputTokens, options.contextWindow, threshold)) {
4085
+ const compacted = await compactHistory(history, {
4086
+ llm: this.#deps.llm,
4087
+ ...options.keepRecentMessages !== void 0 ? { keepRecentMessages: options.keepRecentMessages } : {}
4088
+ });
4089
+ if (compacted) {
4090
+ history = compacted.messages;
4091
+ lastInputTokens = void 0;
4092
+ options.onCompacted?.({
4093
+ removed: compacted.removed,
4094
+ kept: compacted.kept
4095
+ });
4096
+ }
4097
+ }
3619
4098
  const r = await this.run(prompt, {
3620
4099
  sessionId,
3621
4100
  history,
3622
4101
  ...runOptions
3623
4102
  });
3624
4103
  history = trimSessionHistory(r.messages.slice(1), maxHistory);
4104
+ lastInputTokens = summarizeRunUsage(r.run.trace).lastInputTokens;
3625
4105
  return r;
3626
4106
  });
3627
4107
  queue = result.then(() => void 0, () => void 0);
@@ -3686,8 +4166,10 @@ var WebSkillRuntime = class {
3686
4166
  docxExtractor: this.#deps.docxExtractor,
3687
4167
  xlsxExtractor: this.#deps.xlsxExtractor,
3688
4168
  pdfExtractor: this.#deps.pdfExtractor,
4169
+ visionDelegate: this.#deps.visionDelegate,
3689
4170
  documentAudit: this.#deps.documentAudit,
3690
4171
  documentSurface: this.#deps.documentSurface,
4172
+ writeSpreadsheet: this.#deps.writeSpreadsheet,
3691
4173
  uploadFileAudit: this.#deps.uploadFileAudit,
3692
4174
  maxUploadFileBytes: this.#deps.maxUploadFileBytes,
3693
4175
  uploadFiles: options.uploadFiles
@@ -3819,8 +4301,10 @@ var WebSkillRuntime = class {
3819
4301
  docxExtractor: this.#deps.docxExtractor,
3820
4302
  xlsxExtractor: this.#deps.xlsxExtractor,
3821
4303
  pdfExtractor: this.#deps.pdfExtractor,
4304
+ visionDelegate: this.#deps.visionDelegate,
3822
4305
  documentAudit: this.#deps.documentAudit,
3823
4306
  documentSurface: this.#deps.documentSurface,
4307
+ writeSpreadsheet: this.#deps.writeSpreadsheet,
3824
4308
  uploadFileAudit: this.#deps.uploadFileAudit,
3825
4309
  maxUploadFileBytes: this.#deps.maxUploadFileBytes
3826
4310
  }, this.#deps.config);
@@ -3979,6 +4463,9 @@ var FsArtifactStore = class {
3979
4463
  return [];
3980
4464
  }
3981
4465
  }
4466
+ async readArtifact(runId, path) {
4467
+ return this.#fs.readBinary(this.#artifactPath(runId, path));
4468
+ }
3982
4469
  #artifactPath(runId, artifactPath) {
3983
4470
  assertSafePathSegment(runId, "runId");
3984
4471
  return resolveInsideRoot(`${this.#root}/${runId}`, artifactPath);
@@ -4030,6 +4517,17 @@ function parseBridgeRequest(data) {
4030
4517
  ..."metadata" in data ? { metadata: data["metadata"] } : {}
4031
4518
  };
4032
4519
  }
4520
+ case "writeSpreadsheet": {
4521
+ const { path, spec } = data;
4522
+ if (!isNonEmptyString(path) || !isRecord(spec)) return void 0;
4523
+ return {
4524
+ kind,
4525
+ id,
4526
+ path,
4527
+ spec,
4528
+ ..."metadata" in data ? { metadata: data["metadata"] } : {}
4529
+ };
4530
+ }
4033
4531
  case "confirm": return isNonEmptyString(data["message"]) ? {
4034
4532
  kind,
4035
4533
  id,
@@ -4255,4 +4753,4 @@ var CapabilityApproval = class CapabilityApproval {
4255
4753
  };
4256
4754
 
4257
4755
  //#endregion
4258
- export { scriptToolName as $, readUserProfile as A, createScriptContext as B, TraceRecorder as C, mergeProfileEntries as D, appendBehaviorRecords as E, DEFAULT_USER_PROFILE_LIMITS as F, ASK_USER_TOOL_NAME as G, ASK_USER_INPUT_SCHEMA as H, EMPTY_USER_PROFILE as I, READ_SKILL_FILE_TOOL_NAME as J, READ_SKILL_FILE_INPUT_SCHEMA as K, SerializingMemoryStore as L, USER_PROFILE_NO_INVENTION_RULE as M, USER_PROFILE_PROMPT_HEADER as N, readBehaviorRecords as O, renderUserProfileContext as P, schemaSourceLabel as Q, EventBus as R, extractTodoTraceEvents as S, USER_PROFILE_KEY as T, ASK_USER_MAX_FIELDS as U, ASK_USER_FIELD_TYPES as V, ASK_USER_TOOL as W, formatSkillScriptManifest as X, ALLOWED_TOOLS_EXCLUSION_REASON as Y, listSkillScripts as Z, RUN_SNAPSHOT_SCHEMA_VERSION as _, networkPolicyLibSource as a, xmlRenderer as at, MAX_TOOL_STEP_ARG_BYTES as b, bridgeError as c, FsMemoryStore as d, resolveToolName as et, WebSkillRuntime as f, FsRunSnapshotStore as g, redactToolStepArgs as h, isNetworkAllowed as i, renderAvailableSkillsXml as it, sampleBehaviorRecords as j, readProfileEntries as k, parseBridgeRequest as l, SENSITIVE_ANNOTATION as m, normalizeErrorCode as n, ProgressiveRouter as nt, networkUrlHost as o, AgentLoop as p, READ_SKILL_FILE_TOOL as q, normalizeToolError as r, escapeXml as rt, UPLOAD_FILES_UNAVAILABLE as s, CapabilityApproval as t, toLlmToolSpec as tt, FsArtifactStore as u, isUnsupportedRunSnapshot as v, BEHAVIOR_RECORDS_KEY as w, extractSkillCandidate as x, DEFAULT_LOOP_LIMITS as y, schemaToForm as z };
4756
+ export { READ_SKILL_FILE_TOOL as $, BEHAVIOR_RECORDS_KEY as A, renderUserProfileContext as B, extractTodoTraceEvents as C, VISION_SLOW_THRESHOLD_MS as D, VISION_MAX_CONCURRENCY as E, readProfileEntries as F, schemaToForm as G, EMPTY_USER_PROFILE as H, readUserProfile as I, ASK_USER_INPUT_SCHEMA as J, createScriptContext as K, sampleBehaviorRecords as L, appendBehaviorRecords as M, mergeProfileEntries as N, clampVisionConcurrency as O, readBehaviorRecords as P, READ_SKILL_FILE_INPUT_SCHEMA as Q, USER_PROFILE_NO_INVENTION_RULE as R, extractSkillCandidate as S, VISION_DEFAULT_CONCURRENCY as T, SerializingMemoryStore as U, DEFAULT_USER_PROFILE_LIMITS as V, EventBus as W, ASK_USER_TOOL as X, ASK_USER_MAX_FIELDS as Y, ASK_USER_TOOL_NAME as Z, FsRunSnapshotStore as _, networkPolicyLibSource as a, scriptToolName as at, DEFAULT_LOOP_LIMITS as b, bridgeError as c, ProgressiveRouter as ct, FsMemoryStore as d, xmlRenderer as dt, READ_SKILL_FILE_TOOL_NAME as et, WebSkillRuntime as f, redactToolStepArgs as g, SENSITIVE_ANNOTATION as h, isNetworkAllowed as i, schemaSourceLabel as it, USER_PROFILE_KEY as j, runVisionBatch as k, parseBridgeRequest as l, escapeXml as lt, AgentLoop as m, normalizeErrorCode as n, formatSkillScriptManifest as nt, networkUrlHost as o, resolveToolName as ot, summarizeRunUsage as p, ASK_USER_FIELD_TYPES as q, normalizeToolError as r, listSkillScripts as rt, UPLOAD_FILES_UNAVAILABLE as s, toLlmToolSpec as st, CapabilityApproval as t, ALLOWED_TOOLS_EXCLUSION_REASON as tt, FsArtifactStore as u, renderAvailableSkillsXml as ut, RUN_SNAPSHOT_SCHEMA_VERSION as v, TraceRecorder as w, MAX_TOOL_STEP_ARG_BYTES as x, isUnsupportedRunSnapshot as y, USER_PROFILE_PROMPT_HEADER as z };