@webskill/sdk 0.11.0 → 0.12.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 (33) hide show
  1. package/dist/agent.d.ts +3 -3
  2. package/dist/agent.js +2 -2
  3. package/dist/browser.d.ts +3 -3
  4. package/dist/browser.js +7 -3
  5. package/dist/{catalogComponents-BFoqpT1v-CjUBZ3bc.js → catalogComponents-BgAJN0p8-C3K8klJd.js} +260 -914
  6. package/dist/{dist-qnlI2Iup.js → dist-DU9KDAuR.js} +194 -6
  7. package/dist/{dist-DTHZS2k1.js → dist-DqcL6jKO.js} +155 -39
  8. package/dist/{dist-B-cOu08W.js → dist-sdKFgERo.js} +431 -40
  9. package/dist/{echarts-DhNm2ene.js → echarts-De78wXqV.js} +599 -61
  10. package/dist/governance.d.ts +3 -3
  11. package/dist/{index-DACk2_XZ.d.ts → index-3fCHc1mQ.d.ts} +92 -8
  12. package/dist/{index-D3mONFHD.d.ts → index-C9pzXLKy.d.ts} +148 -4
  13. package/dist/{index-DWbs58LF.d.ts → index-DFhU1uks.d.ts} +64 -10
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.js +2 -2
  16. package/dist/mcp.d.ts +26 -3
  17. package/dist/mcp.js +37 -1
  18. package/dist/node.d.ts +3 -3
  19. package/dist/node.js +9 -3
  20. package/dist/{openUiLibrary-D5u8oIvx-BLOAQCho.js → openUiLibrary-BKXW7Iwx-DaymVubt.js} +3 -3
  21. package/dist/processSandboxEntry.js +2 -1
  22. package/dist/sandboxWorkerEntry.js +2 -1
  23. package/dist/{skillVersionStore-D-qHk9ZE-BcmFLykd.d.ts → skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts} +1 -1
  24. package/dist/testing.d.ts +1 -1
  25. package/dist/{types-C26b05fW-CdrRCRDb.d.ts → types-DLctJep_-B5G4uk2u.d.ts} +3 -2
  26. package/dist/ui-react.d.ts +13 -4
  27. package/dist/ui-react.js +69 -253
  28. package/dist/ui-vue.d.ts +1 -1
  29. package/dist/ui-vue.js +1 -1
  30. package/dist/ui.d.ts +4 -4
  31. package/dist/ui.js +3 -3
  32. package/dist/{webskillLitCatalog-DME6PBkV-CmYNLlIT.js → webskillLitCatalog-D_zCqeQF-C9lrvvMr.js} +2 -2
  33. package/package.json +1 -1
@@ -210,16 +210,89 @@ function uniqueName(base, taken) {
210
210
  while (taken.has(`${prefixed}_${index}`)) index += 1;
211
211
  return `${prefixed}_${index}`;
212
212
  }
213
+ function renameDefRefs(node, renames) {
214
+ if (Array.isArray(node)) return node.map((child) => renameDefRefs(child, renames));
215
+ if (!isRecord$7(node)) return node;
216
+ const out = {};
217
+ const prefix = `#/${DEFS_KEY}/`;
218
+ for (const [key, value] of Object.entries(node)) {
219
+ if (key === "$ref" && typeof value === "string" && value.startsWith(prefix)) {
220
+ const [head, ...tail] = value.slice(prefix.length).split("/");
221
+ const mapped = head === void 0 ? void 0 : renames.get(head);
222
+ out[key] = mapped === void 0 ? value : [`${prefix}${mapped}`, ...tail].join("/");
223
+ continue;
224
+ }
225
+ out[key] = renameDefRefs(value, renames);
226
+ }
227
+ return out;
228
+ }
229
+ /**
230
+ * 把非根位置的 `$defs` 搬到根。zod 每次 `toJSONSchema()` 各自产出 `$defs.__schemaN`,
231
+ * 嵌进大 schema 后其 `#/$defs/...` 从大根解析不到——提升即修复。重名时避让并改写引用。
232
+ */
233
+ function hoistNestedDefs(schema) {
234
+ const rootDefs = isRecord$7(schema[DEFS_KEY]) ? { ...schema[DEFS_KEY] } : {};
235
+ const taken = new Set(Object.keys(rootDefs));
236
+ let hoisted = false;
237
+ function visit(node, isRoot) {
238
+ if (Array.isArray(node)) return node.map((child) => visit(child, false));
239
+ if (!isRecord$7(node)) return node;
240
+ const nested = !isRoot && isRecord$7(node[DEFS_KEY]) ? node[DEFS_KEY] : void 0;
241
+ const body = {};
242
+ for (const [key, value] of Object.entries(node)) {
243
+ if (key === DEFS_KEY && nested !== void 0) continue;
244
+ if (key === DEFS_KEY) {
245
+ body[key] = value;
246
+ continue;
247
+ }
248
+ if (SCHEMA_MAP_KEYS.has(key) && isRecord$7(value)) {
249
+ const mapped = {};
250
+ for (const [name, child] of Object.entries(value)) mapped[name] = visit(child, false);
251
+ body[key] = mapped;
252
+ continue;
253
+ }
254
+ if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
255
+ body[key] = value.map((child) => visit(child, false));
256
+ continue;
257
+ }
258
+ if (SCHEMA_KEYS.has(key)) {
259
+ body[key] = visit(value, false);
260
+ continue;
261
+ }
262
+ body[key] = value;
263
+ }
264
+ if (nested === void 0) return body;
265
+ hoisted = true;
266
+ const renames = /* @__PURE__ */ new Map();
267
+ for (const name of Object.keys(nested)) {
268
+ const target = uniqueName(name, taken);
269
+ taken.add(target);
270
+ if (target !== name) renames.set(name, target);
271
+ }
272
+ for (const [name, def] of Object.entries(nested)) {
273
+ const moved = visit(def, false);
274
+ rootDefs[renames.get(name) ?? name] = renames.size > 0 ? renameDefRefs(moved, renames) : moved;
275
+ }
276
+ return renames.size > 0 ? renameDefRefs(body, renames) : body;
277
+ }
278
+ const out = visit(schema, true);
279
+ return hoisted ? {
280
+ ...out,
281
+ [DEFS_KEY]: rootDefs
282
+ } : schema;
283
+ }
213
284
  /**
214
285
  * 把工具 inputSchema 重写为严格解析器可接受的 JSON Schema 2020-12 形态:
215
- * 自引用的 schema resource 提到根 `$defs`,所有 `$ref: '#'` 改写为 `$ref: '#/$defs/<Node>'`。
286
+ * 非根 `$defs` 提到根,自引用的 schema resource 提到根 `$defs`,
287
+ * 所有 `$ref: '#'` 改写为 `$ref: '#/$defs/<Node>'`。
216
288
  *
217
- * 幂等:不含自引用的 schema 原样返回。
289
+ * 幂等:不含自引用也不含嵌套 `$defs` 的 schema 原样返回。
218
290
  *
219
291
  * 这是**传输层适配**,只应在出站请求体上调用;不要下沉到 AgentLoop,
220
292
  * 否则 trace / eventBus 观测到的 schema 会与技能作者写的不一致。
221
293
  */
222
- function toPortableToolSchema(schema) {
294
+ function toPortableToolSchema(input) {
295
+ const schema = hoistNestedDefs(input);
223
296
  const roots = /* @__PURE__ */ new Map();
224
297
  collectSelfRefRoots(schema, [], [], roots);
225
298
  if (roots.size === 0) return schema;
@@ -382,6 +455,10 @@ const ANTHROPIC_SCHEMA_PROFILE = {
382
455
  /**
383
456
  * Google Gen AI 的 `FunctionDeclaration.parameters` 收到 `additionalProperties`
384
457
  * 直接回 400 `Unknown name "additionalProperties"`,且不解析 `$ref` / `$defs`。
458
+ *
459
+ * 本 profile 描述的是 **`parameters` 这一字段**的能力,不是 Google 的能力上限:
460
+ * 完整 JSON Schema(含 `$ref` / `$defs`)走另一个字段 `parametersJsonSchema`。
461
+ * 把 `supportsRefs: false` 读成「Google 不支持引用」是错的,分流见 `googleGenAiClient.toGenAiTools`。
385
462
  */
386
463
  const GOOGLE_SCHEMA_PROFILE = {
387
464
  id: "google",
@@ -445,6 +522,46 @@ function sanitizeForVendor(schema, profile, toolName) {
445
522
  if (!profile.supportsRefs && containsRef(schema)) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool "${toolName}" requires $ref support that provider "${profile.id}" does not accept`);
446
523
  return sanitizeNode(schema, profile);
447
524
  }
525
+ /**
526
+ * schema 能否走 profile 描述的受限通道。与 `sanitizeForVendor` 同判据,但不抛错——
527
+ * 调用方需要的是「选哪条通道」而不是「失败」。
528
+ */
529
+ function isExpressibleForVendor(schema, profile) {
530
+ return profile.supportsRefs || !containsRef(schema);
531
+ }
532
+ /**
533
+ * 放宽不可终止的递归环:Gemini 只接受「可选 / 可为 null / 可零长数组」的引用环。
534
+ * 对 `items` 直接是 `$ref` 的数组节点删去 `minItems`。
535
+ *
536
+ * **只作用于出站副本**:catalog 本体的校验判据不变。
537
+ */
538
+ function relaxRefLoops(schema) {
539
+ return relaxNode(schema);
540
+ }
541
+ function relaxNode(node) {
542
+ if (Array.isArray(node)) return node.map(relaxNode);
543
+ if (!isRecord$5(node)) return node;
544
+ const out = {};
545
+ for (const [key, value] of Object.entries(node)) {
546
+ if (key === "minItems" && isRecord$5(node["items"]) && typeof node["items"]["$ref"] === "string") continue;
547
+ if (SCHEMA_MAP_KEYS.has(key) && isRecord$5(value)) {
548
+ const mapped = {};
549
+ for (const [name, child] of Object.entries(value)) mapped[name] = relaxNode(child);
550
+ out[key] = mapped;
551
+ continue;
552
+ }
553
+ if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
554
+ out[key] = value.map(relaxNode);
555
+ continue;
556
+ }
557
+ if (SCHEMA_KEYS.has(key)) {
558
+ out[key] = relaxNode(value);
559
+ continue;
560
+ }
561
+ out[key] = value;
562
+ }
563
+ return out;
564
+ }
448
565
  function createSseFrameReader() {
449
566
  let buffer = "";
450
567
  let data = [];
@@ -1151,11 +1268,14 @@ function toGenAiContents(messages) {
1151
1268
  contents: out
1152
1269
  };
1153
1270
  }
1154
- const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
1155
- name: tool.name,
1156
- ...tool.description ? { description: tool.description } : {},
1157
- parameters: sanitizeForVendor(toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema)), GOOGLE_SCHEMA_PROFILE, tool.name)
1158
- })) }];
1271
+ const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => {
1272
+ const portable = toPortableToolSchema(stripModelUnfillableParams(tool.inputSchema));
1273
+ return {
1274
+ name: tool.name,
1275
+ ...tool.description ? { description: tool.description } : {},
1276
+ ...isExpressibleForVendor(portable, GOOGLE_SCHEMA_PROFILE) ? { parameters: sanitizeForVendor(portable, GOOGLE_SCHEMA_PROFILE, tool.name) } : { parametersJsonSchema: relaxRefLoops(portable) }
1277
+ };
1278
+ }) }];
1159
1279
  /** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
1160
1280
  var GoogleGenAiClient = class {
1161
1281
  #config;
@@ -1682,6 +1802,7 @@ const ASK_USER_INPUT_SCHEMA = {
1682
1802
  type: "string",
1683
1803
  description: "Help text shown under the input."
1684
1804
  },
1805
+ defaultValue: { description: "A value the user already stated in this conversation; it is filled into the input for them. Only pass it when the user actually said it — do not guess." },
1685
1806
  options: {
1686
1807
  type: "array",
1687
1808
  items: {
@@ -1725,6 +1846,78 @@ const ASK_USER_TOOL = {
1725
1846
  source: "builtin"
1726
1847
  };
1727
1848
  /**
1849
+ * 产物 metadata 的准入与判读(分册 18)。
1850
+ *
1851
+ * metadata 是能力桥上第一个**开放结构**的入参,且来自沙箱内的技能脚本。
1852
+ * 校验落在 `createScriptContext.writeArtifact`——它是 in-process 与三个沙箱执行器
1853
+ * 唯一的汇聚点,在此抛错才能带上正确的桥请求 id 回给脚本(DV-10)。
1854
+ */
1855
+ /** 序列化后的字节上限;`index.json` 是整覆盖原子写,无上限等于廉价的放大面 */
1856
+ const ARTIFACT_METADATA_MAX_BYTES = 4096;
1857
+ /** 原型污染键;JSON 解析产生的同名键是**自有键**,可被 getOwnPropertyNames 查出 */
1858
+ const FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
1859
+ "__proto__",
1860
+ "constructor",
1861
+ "prototype"
1862
+ ]);
1863
+ function isPlainObject$1(value) {
1864
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1865
+ const proto = Object.getPrototypeOf(value);
1866
+ return proto === Object.prototype || proto === null;
1867
+ }
1868
+ /** 函数声明而非箭头函数赋值:只有前者能让 TS 的控制流分析认到 never 终止 */
1869
+ function reject$1(message) {
1870
+ throw new WebSkillError("VALIDATION_FAILED", message);
1871
+ }
1872
+ /**
1873
+ * 递归拒绝原型污染键与不可序列化值。
1874
+ * `JSON.stringify` 会**静默丢弃**函数与 symbol,而静默正是本需求明令禁止的形态,
1875
+ * 所以必须在序列化之前显式查一遍。
1876
+ */
1877
+ function assertSerializableShape(value, path, seen) {
1878
+ const kind = typeof value;
1879
+ if (kind === "function" || kind === "symbol" || kind === "bigint") reject$1(`Artifact metadata value at "${path}" is not JSON-serializable (${kind})`);
1880
+ if (kind !== "object" || value === null) return;
1881
+ const container = value;
1882
+ if (seen.has(container)) reject$1(`Artifact metadata contains a circular reference at "${path}"`);
1883
+ seen.add(container);
1884
+ if (Array.isArray(value)) value.forEach((item, index) => assertSerializableShape(item, `${path}[${index}]`, seen));
1885
+ else {
1886
+ if (!isPlainObject$1(value)) reject$1(`Artifact metadata value at "${path}" must be a plain object or array`);
1887
+ for (const key of Object.getOwnPropertyNames(value)) {
1888
+ if (FORBIDDEN_KEYS.has(key)) reject$1(`Artifact metadata must not contain the reserved key "${key}" (at "${path}")`);
1889
+ assertSerializableShape(value[key], `${path}.${key}`, seen);
1890
+ }
1891
+ }
1892
+ seen.delete(container);
1893
+ }
1894
+ /**
1895
+ * 准入校验。通过后返回 `JSON.parse(JSON.stringify(...))` 的副本:
1896
+ * in-process 路径由此获得与 JSON stdio 沙箱路径完全相同的语义,
1897
+ * 而不是依赖某条传输链路碰巧做了序列化。
1898
+ */
1899
+ function admitArtifactMetadata(value) {
1900
+ if (!isPlainObject$1(value)) reject$1("Artifact metadata must be a plain object");
1901
+ assertSerializableShape(value, "metadata", /* @__PURE__ */ new Set());
1902
+ let json;
1903
+ try {
1904
+ json = JSON.stringify(value);
1905
+ } catch (error) {
1906
+ reject$1(`Artifact metadata is not JSON-serializable: ${error.message}`);
1907
+ }
1908
+ const bytes = new TextEncoder().encode(json).length;
1909
+ if (bytes > 4096) reject$1(`Artifact metadata is too large: ${bytes} bytes exceeds the ${ARTIFACT_METADATA_MAX_BYTES} byte limit`);
1910
+ return JSON.parse(json);
1911
+ }
1912
+ /**
1913
+ * 「这份产物不出自动下载卡」的判读。**严格 `=== false`**:
1914
+ * 隐式真值转换会让写错的键值静默生效,而隐藏是不可见的失败。
1915
+ * `buildRenderResult` 与 agentLoop 的去重集合共用本函数,两处判据不得分叉。
1916
+ */
1917
+ function isArtifactCardHidden(artifact) {
1918
+ return artifact.metadata?.["resultCard"] === false;
1919
+ }
1920
+ /**
1728
1921
  * 越权 / 绝对路径 / 父级遍历的技能目录读取:策略拒绝(FS_PERMISSION_DENIED,
1729
1922
  * 计入 POLICY_DENIAL_CODES → 不计入隔离失败计数),
1730
1923
  * 与「目录内路径确实不存在 → FS_NOT_FOUND」区分(0.9.0 分册 14,UX-04)。
@@ -1762,16 +1955,19 @@ function createScriptContext(deps) {
1762
1955
  return readFs.readBinary(resolveSkillDir(skillRoot, "assets", relativePath));
1763
1956
  },
1764
1957
  async writeArtifact(path, content, options) {
1958
+ const metadata = options?.metadata === void 0 ? void 0 : admitArtifactMetadata(options.metadata);
1765
1959
  const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
1766
1960
  runId,
1767
1961
  path,
1768
1962
  content,
1769
- mimeType: options?.mimeType
1963
+ mimeType: options?.mimeType,
1964
+ ...metadata === void 0 ? {} : { metadata }
1770
1965
  }) : await artifactStore.createBinaryArtifact({
1771
1966
  runId,
1772
1967
  path,
1773
1968
  content,
1774
- mimeType: options?.mimeType
1969
+ mimeType: options?.mimeType,
1970
+ ...metadata === void 0 ? {} : { metadata }
1775
1971
  });
1776
1972
  onArtifactCreated?.(artifact);
1777
1973
  return artifact;
@@ -1826,12 +2022,15 @@ function buildRenderResult(run, output, renderBlocks = []) {
1826
2022
  text: output
1827
2023
  });
1828
2024
  }
1829
- for (const artifact of run.artifacts) blocks.push({
1830
- type: "file",
1831
- path: artifact.path,
1832
- ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
1833
- size: artifact.size
1834
- });
2025
+ for (const artifact of run.artifacts) {
2026
+ if (isArtifactCardHidden(artifact)) continue;
2027
+ blocks.push({
2028
+ type: "file",
2029
+ path: artifact.path,
2030
+ ...artifact.mimeType ? { mimeType: artifact.mimeType } : {},
2031
+ size: artifact.size
2032
+ });
2033
+ }
1835
2034
  return {
1836
2035
  runId: run.id,
1837
2036
  summary: run.terminationReason,
@@ -2391,10 +2590,18 @@ function extractSkillCandidate(data) {
2391
2590
  * @stable
2392
2591
  */
2393
2592
  const DEFAULT_LOOP_LIMITS = {
2394
- maxTurns: 10,
2593
+ maxTurns: 1e3,
2395
2594
  totalTimeoutMs: 36e5,
2396
- toolTimeoutMs: 6e5
2595
+ toolTimeoutMs: 6e5,
2596
+ maxHistoryMessages: 1e3
2397
2597
  };
2598
+ /**
2599
+ * 工具参数留存的单次体积上限(分册 30 / FR-30.2)。**全仓唯一来源**(S8)。
2600
+ *
2601
+ * 超限时不留半截参数,整条标记为 `truncated`。
2602
+ * @stable
2603
+ */
2604
+ const MAX_TOOL_STEP_ARG_BYTES = 32768;
2398
2605
  const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
2399
2606
  /** @experimental */
2400
2607
  function isUnsupportedRunSnapshot(entry) {
@@ -2537,6 +2744,69 @@ function evaluateToolAccess(state, llmToolName) {
2537
2744
  reason: denialReason(state, canonical)
2538
2745
  };
2539
2746
  }
2747
+ /**
2748
+ * 参数敏感标注关键字(分册 30 / FR-30.3)。
2749
+ *
2750
+ * 不用 `format: 'password'`:`format` 是 JSON Schema 的规范关键字,provider 侧
2751
+ * 可能据其做转换或校验,而且它只对 `type: 'string'` 有意义,对象型凭据标不了。
2752
+ * `x-` 前缀走 `JsonSchema` 的索引签名,不与任何规范语义抢占。
2753
+ */
2754
+ const SENSITIVE_ANNOTATION = "x-webskill-sensitive";
2755
+ const isSensitive = (schema) => schema?.[SENSITIVE_ANNOTATION] === true;
2756
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2757
+ const join = (prefix, key) => prefix === "" ? key : `${prefix}.${key}`;
2758
+ /**
2759
+ * 按 schema 标注逐字段脱敏。
2760
+ *
2761
+ * 脱敏值取 `null` 而不是删键,也不是 `'***'`:删键会让生成器以为这个步骤
2762
+ * 本来就没有这个参数,从而写出漏参的技能;掩码则仍然泄露长度与存在性。
2763
+ */
2764
+ function redactAnnotated(value, schema, path, redacted) {
2765
+ if (isSensitive(schema)) {
2766
+ redacted.push(path);
2767
+ return null;
2768
+ }
2769
+ if (Array.isArray(value)) return value.map((item, i) => redactAnnotated(item, schema?.items, `${path}[${i}]`, redacted));
2770
+ if (isPlainObject(value)) {
2771
+ const out = {};
2772
+ for (const [key, entry] of Object.entries(value)) out[key] = redactAnnotated(entry, schema?.properties?.[key], join(path, key), redacted);
2773
+ return out;
2774
+ }
2775
+ return value;
2776
+ }
2777
+ /** 不可信来源:所有叶子脱敏,键与结构保留 */
2778
+ function redactAll(value, path, redacted) {
2779
+ if (Array.isArray(value)) return value.map((item, i) => redactAll(item, `${path}[${i}]`, redacted));
2780
+ if (isPlainObject(value)) {
2781
+ const out = {};
2782
+ for (const [key, entry] of Object.entries(value)) out[key] = redactAll(entry, join(path, key), redacted);
2783
+ return out;
2784
+ }
2785
+ redacted.push(path);
2786
+ return null;
2787
+ }
2788
+ /**
2789
+ * 分层脱敏(FR-30.3)——**全仓唯一实现**。
2790
+ *
2791
+ * 1. `untrusted` → 所有字段脱敏;
2792
+ * 2. `reviewed` / `host-trusted` → 逐字段走 schema,标注为敏感的脱敏。
2793
+ *
2794
+ * 标注为敏感的字段在**任何**层级都脱敏:信任声明说的是「未标注的可以留」,
2795
+ * 不是「标注了也能留」。
2796
+ */
2797
+ function redactToolStepArgs(args, schema, trust) {
2798
+ const redacted = [];
2799
+ const redactedArgs = trust.tier === "untrusted" ? redactAll(args, "", redacted) : redactAnnotated(args, schema, "", redacted);
2800
+ if (JSON.stringify(redactedArgs).length > 32768) return {
2801
+ args: {},
2802
+ redacted: [],
2803
+ truncated: true
2804
+ };
2805
+ return {
2806
+ args: redactedArgs,
2807
+ redacted
2808
+ };
2809
+ }
2540
2810
  const MAX_SURFACE_PATCHES_PER_SECOND = 240;
2541
2811
  /**
2542
2812
  * 一轮 LLM 调用可能抛出、且**值得原样上报**的结构化码。
@@ -2572,6 +2842,11 @@ const summarizeArgs = (args) => {
2572
2842
  const json = JSON.stringify(args);
2573
2843
  return json.length > 100 ? `${json.slice(0, 100)}…` : json;
2574
2844
  };
2845
+ /** 外部工具的 inputSchema 索引(分册 30);run 开始与 resume 各重建一次 */
2846
+ function indexExternalToolSchemas(state, specs) {
2847
+ state.externalToolSchemas.clear();
2848
+ for (const spec of specs) state.externalToolSchemas.set(spec.name, spec.inputSchema);
2849
+ }
2575
2850
  /**
2576
2851
  * file-pick 的回填值带整个文件的 base64,落进 `paramHistory` 会把用户选的文件
2577
2852
  * 原样长期留在记忆里(体积与隐私都不可接受)。只留可辨识的描述,正文不留。
@@ -2717,6 +2992,7 @@ var AgentLoop = class {
2717
2992
  pausedMs: 0,
2718
2993
  maxTurns: this.#config.maxTurns,
2719
2994
  totalTimeoutMs: this.#config.totalTimeoutMs,
2995
+ externalToolSchemas: /* @__PURE__ */ new Map(),
2720
2996
  controller: new AbortController()
2721
2997
  };
2722
2998
  this.#controllers.set(runId, state.controller);
@@ -2736,14 +3012,8 @@ var AgentLoop = class {
2736
3012
  candidates: route.catalog.entries.map((e) => e.name)
2737
3013
  }
2738
3014
  }, state);
2739
- const externalSpecs = this.#config.toolCallingDisabled ? [] : (await Promise.all((this.#deps.externalTools ?? []).map(async (source) => {
2740
- try {
2741
- return await source.listToolSpecs();
2742
- } catch (e) {
2743
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
2744
- return [];
2745
- }
2746
- }))).flat();
3015
+ const externalSpecs = this.#config.toolCallingDisabled ? [] : await this.#collectExternalSpecs(state);
3016
+ indexExternalToolSchemas(state, externalSpecs);
2747
3017
  const externalSystemPrompts = [];
2748
3018
  if (!this.#config.toolCallingDisabled) for (const source of this.#deps.externalTools ?? []) {
2749
3019
  if (source.systemPrompt === void 0) continue;
@@ -3113,6 +3383,7 @@ var AgentLoop = class {
3113
3383
  pausedMs: snapshot.pausedMs ?? 0,
3114
3384
  maxTurns: snapshot.config.maxTurns,
3115
3385
  totalTimeoutMs: snapshot.config.totalTimeoutMs,
3386
+ externalToolSchemas: /* @__PURE__ */ new Map(),
3116
3387
  controller: new AbortController()
3117
3388
  };
3118
3389
  this.#controllers.set(runId, state.controller);
@@ -3126,6 +3397,8 @@ var AgentLoop = class {
3126
3397
  } });
3127
3398
  const pending = pendingInteraction;
3128
3399
  const pendingCall = this.#findPendingToolCall(state.messages);
3400
+ const externalSpecs = await this.#collectExternalSpecs(state);
3401
+ indexExternalToolSchemas(state, externalSpecs);
3129
3402
  const carried = [];
3130
3403
  try {
3131
3404
  await this.#replaySurfaceEvents(state);
@@ -3183,14 +3456,6 @@ var AgentLoop = class {
3183
3456
  if (e instanceof RunTerminated) return this.#finish(state, e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
3184
3457
  throw e;
3185
3458
  }
3186
- const externalSpecs = (await Promise.all((this.#deps.externalTools ?? []).map(async (source) => {
3187
- try {
3188
- return await source.listToolSpecs();
3189
- } catch (e) {
3190
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
3191
- return [];
3192
- }
3193
- }))).flat();
3194
3459
  try {
3195
3460
  return await this.#turnLoop(state, snapshot.turn + 1, externalSpecs);
3196
3461
  } catch (e) {
@@ -3419,6 +3684,81 @@ var AgentLoop = class {
3419
3684
  });
3420
3685
  });
3421
3686
  }
3687
+ /** 外部工具 specs:单个来源失败跳过并记 warning(run 开始与 resume 共用) */
3688
+ async #collectExternalSpecs(state) {
3689
+ return (await Promise.all((this.#deps.externalTools ?? []).map(async (source) => {
3690
+ try {
3691
+ return await source.listToolSpecs();
3692
+ } catch (e) {
3693
+ state.trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
3694
+ return [];
3695
+ }
3696
+ }))).flat();
3697
+ }
3698
+ /**
3699
+ * 一次工具调用的信任层级与 inputSchema(分册 30)。
3700
+ *
3701
+ * 技能脚本工具按 `untrusted`:它们的 schema 来自各技能的 frontmatter,
3702
+ * 是第三方内容,不是本仓审查过的。
3703
+ */
3704
+ #toolCaptureContext(call, state) {
3705
+ if (call.name === "read_skill_file") return {
3706
+ trust: { tier: "reviewed" },
3707
+ schema: READ_SKILL_FILE_TOOL.inputSchema,
3708
+ args: call.arguments
3709
+ };
3710
+ if (call.name === "ask_user") return {
3711
+ trust: { tier: "reviewed" },
3712
+ schema: ASK_USER_TOOL.inputSchema,
3713
+ args: call.arguments
3714
+ };
3715
+ if (call.name === "read_linked_document") return {
3716
+ trust: { tier: "reviewed" },
3717
+ schema: READ_LINKED_DOCUMENT_TOOL.inputSchema,
3718
+ args: call.arguments
3719
+ };
3720
+ const source = (this.#deps.externalTools ?? []).find((s) => s.canHandle(call.name));
3721
+ if (source === void 0) return {
3722
+ trust: { tier: "untrusted" },
3723
+ args: call.arguments
3724
+ };
3725
+ const schema = state.externalToolSchemas.get(call.name);
3726
+ const trust = source.argCaptureTrust?.(call.name, call.arguments) ?? { tier: "untrusted" };
3727
+ const args = source.captureArgs?.(call.name, call.arguments) ?? call.arguments;
3728
+ return schema === void 0 ? {
3729
+ trust,
3730
+ args
3731
+ } : {
3732
+ trust,
3733
+ schema,
3734
+ args
3735
+ };
3736
+ }
3737
+ /**
3738
+ * 成功调用的完整参数留存(FR-30.2)。失败的调用不留存:它什么也没做成,
3739
+ * 让模型引用它只会生成一个跑不通的步骤。
3740
+ */
3741
+ async #captureToolStep(call, state) {
3742
+ const store = this.#deps.toolSteps;
3743
+ if (store === void 0) return;
3744
+ const { trust, schema, args: captured } = this.#toolCaptureContext(call, state);
3745
+ const { args, redacted, truncated } = redactToolStepArgs(captured, schema, trust);
3746
+ try {
3747
+ await store.append({
3748
+ runId: state.runId,
3749
+ sessionId: state.run.sessionId,
3750
+ callId: call.id,
3751
+ tool: call.name,
3752
+ at: state.now(),
3753
+ args,
3754
+ redacted,
3755
+ ...truncated !== void 0 ? { truncated } : {},
3756
+ trust
3757
+ });
3758
+ } catch (e) {
3759
+ state.trace.record("run.warning", { message: `Failed to capture tool step arguments: ${messageOf(e)}` });
3760
+ }
3761
+ }
3422
3762
  async #executeCall(call, state) {
3423
3763
  const argsSummary = summarizeArgs(call.arguments);
3424
3764
  const owner = this.#skillOf(call, state);
@@ -3458,6 +3798,7 @@ var AgentLoop = class {
3458
3798
  durationMs,
3459
3799
  ...attribution
3460
3800
  } });
3801
+ await this.#captureToolStep(call, state);
3461
3802
  this.#emitTool(state, "completed", call);
3462
3803
  for (const item of result.content) {
3463
3804
  if (item.type !== "json") continue;
@@ -3497,7 +3838,7 @@ var AgentLoop = class {
3497
3838
  this.#emitTool(state, "failed", call, result.error?.code);
3498
3839
  }
3499
3840
  for (const artifact of result.artifacts ?? []) {
3500
- state.artifactPaths.add(artifact.path);
3841
+ if (!isArtifactCardHidden(artifact)) state.artifactPaths.add(artifact.path);
3501
3842
  state.trace.record("artifact.created", { data: {
3502
3843
  artifactId: artifact.id,
3503
3844
  path: artifact.path
@@ -3575,7 +3916,7 @@ var AgentLoop = class {
3575
3916
  async #stripDuplicateFileLinks(state, event) {
3576
3917
  if (state.artifactPaths.size === 0) {
3577
3918
  const listed = await this.#deps.artifactStore.listArtifacts(state.runId).catch(() => []);
3578
- for (const artifact of listed) state.artifactPaths.add(artifact.path);
3919
+ for (const artifact of listed) if (!isArtifactCardHidden(artifact)) state.artifactPaths.add(artifact.path);
3579
3920
  }
3580
3921
  if (state.artifactPaths.size === 0) return event;
3581
3922
  const paths = state.artifactPaths;
@@ -3956,6 +4297,7 @@ var AgentLoop = class {
3956
4297
  type,
3957
4298
  ...field["required"] === true ? { required: true } : {},
3958
4299
  ...typeof field["description"] === "string" ? { description: field["description"] } : {},
4300
+ ...field["defaultValue"] !== void 0 ? { defaultValue: field["defaultValue"] } : {},
3959
4301
  ...Array.isArray(options) ? { options: options.filter((o) => typeof o === "object" && o !== null).map((o) => ({
3960
4302
  label: String(o["label"] ?? o["value"]),
3961
4303
  value: o["value"]
@@ -4719,12 +5061,12 @@ var WebSkillRuntime = class {
4719
5061
  * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
4720
5062
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
4721
5063
  * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
4722
- * maxHistoryMessages(默认 100)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
5064
+ * maxHistoryMessages(默认取 `DEFAULT_LOOP_LIMITS`)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
4723
5065
  */
4724
5066
  createSession(options = {}) {
4725
5067
  const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
4726
5068
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
4727
- const maxHistory = options.maxHistoryMessages ?? 100;
5069
+ const maxHistory = options.maxHistoryMessages ?? DEFAULT_LOOP_LIMITS.maxHistoryMessages;
4728
5070
  let history = [];
4729
5071
  let queue = Promise.resolve();
4730
5072
  return {
@@ -4791,6 +5133,7 @@ var WebSkillRuntime = class {
4791
5133
  skillProviders: this.#deps.skillProviders,
4792
5134
  catalogFilter: this.#deps.catalogFilter,
4793
5135
  snapshotStore: this.#deps.snapshotStore,
5136
+ toolSteps: this.#deps.toolSteps,
4794
5137
  skillStateGuard: this.#deps.skillStateGuard,
4795
5138
  skillIntegrityGuard: this.#deps.skillIntegrityGuard,
4796
5139
  skillOutcomeReporter: this.#deps.skillOutcomeReporter,
@@ -4916,6 +5259,7 @@ var WebSkillRuntime = class {
4916
5259
  skillProviders: this.#deps.skillProviders,
4917
5260
  catalogFilter: this.#deps.catalogFilter,
4918
5261
  snapshotStore: store,
5262
+ toolSteps: this.#deps.toolSteps,
4919
5263
  skillStateGuard: this.#deps.skillStateGuard,
4920
5264
  skillIntegrityGuard: this.#deps.skillIntegrityGuard,
4921
5265
  skillOutcomeReporter: this.#deps.skillOutcomeReporter,
@@ -5449,7 +5793,8 @@ function parseBridgeRequest(data) {
5449
5793
  id,
5450
5794
  path,
5451
5795
  content,
5452
- ...isNonEmptyString(mimeType) ? { mimeType } : {}
5796
+ ...isNonEmptyString(mimeType) ? { mimeType } : {},
5797
+ ..."metadata" in data ? { metadata: data["metadata"] } : {}
5453
5798
  };
5454
5799
  }
5455
5800
  case "confirm": return isNonEmptyString(data["message"]) ? {
@@ -5892,6 +6237,7 @@ function sortByStartedAtDesc(summaries) {
5892
6237
  function applyFilter(summaries, filter) {
5893
6238
  let runs = summaries;
5894
6239
  if (filter.status !== void 0 && filter.status !== "") runs = runs.filter((r) => r.status === filter.status);
6240
+ if (filter.sessionId !== void 0 && filter.sessionId !== "") runs = runs.filter((r) => r.sessionId === filter.sessionId);
5895
6241
  const q = filter.search?.trim().toLowerCase();
5896
6242
  if (q !== void 0 && q !== "") runs = runs.filter((r) => r.runId.toLowerCase().includes(q) || r.activeSkills.some((s) => s.toLowerCase().includes(q)));
5897
6243
  return runs;
@@ -6151,6 +6497,51 @@ var FsSessionStore = class {
6151
6497
  });
6152
6498
  }
6153
6499
  };
6500
+ /**
6501
+ * 按会话分文件的 append-only JSONL 留存。
6502
+ *
6503
+ * 分文件维度取会话而非 run:读取侧唯一的查询就是「这个会话的全部步骤」,
6504
+ * 按 run 分会让一次生成要开 N 个文件。
6505
+ */
6506
+ var FsToolStepStore = class {
6507
+ #root;
6508
+ #fs;
6509
+ #onError;
6510
+ constructor(deps) {
6511
+ this.#root = deps.root.replace(/\/+$/, "");
6512
+ this.#fs = deps.fs;
6513
+ this.#onError = deps.onError ?? ((error, record) => {
6514
+ console.warn(`Failed to persist tool step for call "${record.callId}": ${messageOf(error)}`);
6515
+ });
6516
+ }
6517
+ #path(sessionId) {
6518
+ assertSafePathSegment(sessionId, "session id");
6519
+ return resolveInsideRoot(this.#root, `${sessionId}.jsonl`);
6520
+ }
6521
+ async append(record) {
6522
+ const path = this.#path(record.sessionId);
6523
+ try {
6524
+ await this.#fs.appendText(path, `${JSON.stringify(record)}\n`);
6525
+ } catch (e) {
6526
+ this.#onError(e, record);
6527
+ }
6528
+ }
6529
+ async listBySession(sessionId) {
6530
+ const path = this.#path(sessionId);
6531
+ if (!await this.#fs.exists(path)) return [];
6532
+ const raw = await this.#fs.readText(path);
6533
+ const records = [];
6534
+ for (const line of raw.split("\n")) {
6535
+ if (line.trim() === "") continue;
6536
+ try {
6537
+ records.push(JSON.parse(line));
6538
+ } catch {
6539
+ continue;
6540
+ }
6541
+ }
6542
+ return records;
6543
+ }
6544
+ };
6154
6545
 
6155
6546
  //#endregion
6156
- export { createWebSkillApi as $, READ_LINKED_DOCUMENT_TOOL_NAME as A, schemaSourceLabel as At, TraceRecorder as B, validateUiSpecNode as Bt, FsSessionStore as C, readBehaviorRecords as Ct, OpenAiCompatibleClient as D, renderUserProfileContext as Dt, HookRunner as E, refineUserProfile as Et, RUN_TRACE_SCHEMA_VERSION as F, summarizeToolCalls as Ft, USER_PROFILE_PROMPT_HEADER as G, USER_PROFILE_EXPORT_VERSION as H, SESSION_SCHEMA_VERSION as I, toLlmToolSpec as It, appendBehaviorRecords as J, USER_PROFILE_REFINE_PROMPT as K, SUPPORTED_DOCUMENT_MIME as L, toRecordDigests as Lt, READ_SKILL_FILE_TOOL as M, scriptToolName as Mt, READ_SKILL_FILE_TOOL_NAME as N, sealToolCallPairs as Nt, ProgressiveRouter as O, resolveToolName as Ot, RUN_SNAPSHOT_SCHEMA_VERSION as P, summarizeRunUsage as Pt, createScriptContext as Q, SerializingMemoryStore as R, toVercelToolSpecs as Rt, FsRunTraceStore as S, parseUserProfileExport as St, GoogleGenAiClient as T, readUserProfile as Tt, USER_PROFILE_KEY as U, UNSUPPORTED_DOCUMENT_MESSAGE as V, USER_PROFILE_NO_INVENTION_RULE as W, bridgeError as X, applyUserProfileImport as Y, buildRenderResult as Z, EventBus as _, networkUrlHost as _t, ASK_USER_TOOL as a, extractUiSpecEvents as at, FsMemoryStore as b, normalizeToolError as bt, AnthropicClient as c, fromVercelResult as ct, DEFAULT_LOOP_LIMITS as d, isNetworkAllowed as dt, diffUserProfile as et, DEFAULT_MAX_DATA_SOURCE_BYTES as f, isUnsupportedRunSnapshot as ft, EMPTY_USER_PROFILE as g, networkPolicyLibSource as gt, DEFAULT_USER_PROFILE_LIMITS as h, mergeProfileEntries as ht, ASK_USER_MAX_FIELDS as i, extractTodoTraceEvents as it, READ_SKILL_FILE_INPUT_SCHEMA as j, schemaToForm as jt, READ_LINKED_DOCUMENT_TOOL as k, sampleBehaviorRecords as kt, BEHAVIOR_RECORDS_KEY as l, fromVercelStreamPart as lt, DEFAULT_MAX_DOCUMENT_TEXT_BYTES as m, mergeCatalogEntries as mt, ASK_USER_FIELD_TYPES as n, extractChartSpec as nt, ASK_USER_TOOL_NAME as o, findUnpairedToolCalls as ot, DEFAULT_MAX_DOCUMENT_BYTES as p, listSkillScripts as pt, WebSkillRuntime as q, ASK_USER_INPUT_SCHEMA as r, extractSkillCandidate as rt, AgentLoop as s, formatSkillScriptManifest as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, exportUserProfile as tt, CapabilityApproval as u, interruptedToolResult as ut, FS_SESSION_PAGE_SIZE as v, normalizeErrorCode as vt, FullDisclosureRouter as w, readProfileEntries as wt, FsRunSnapshotStore as x, parseBridgeRequest as xt, FsArtifactStore as y, normalizeToolContent as yt, TEXT_BUDGETED_CONTENT_TYPES as z, validateUiSpecEvent as zt };
6547
+ export { bridgeError as $, ProgressiveRouter as A, refineUserProfile as At, SUPPORTED_DOCUMENT_MIME as B, toLlmToolSpec as Bt, FsSessionStore as C, normalizeToolError as Ct, HookRunner as D, readProfileEntries as Dt, GoogleGenAiClient as E, readBehaviorRecords as Et, READ_SKILL_FILE_TOOL_NAME as F, schemaToForm as Ft, USER_PROFILE_EXPORT_VERSION as G, TEXT_BUDGETED_CONTENT_TYPES as H, toVercelToolSpecs as Ht, RUN_SNAPSHOT_SCHEMA_VERSION as I, scriptToolName as It, USER_PROFILE_PROMPT_HEADER as J, USER_PROFILE_KEY as K, RUN_TRACE_SCHEMA_VERSION as L, sealToolCallPairs as Lt, READ_LINKED_DOCUMENT_TOOL_NAME as M, resolveToolName as Mt, READ_SKILL_FILE_INPUT_SCHEMA as N, sampleBehaviorRecords as Nt, MAX_TOOL_STEP_ARG_BYTES as O, readUserProfile as Ot, READ_SKILL_FILE_TOOL as P, schemaSourceLabel as Pt, applyUserProfileImport as Q, SENSITIVE_ANNOTATION as R, summarizeRunUsage as Rt, FsRunTraceStore as S, normalizeToolContent as St, FullDisclosureRouter as T, parseUserProfileExport as Tt, TraceRecorder as U, validateUiSpecEvent as Ut, SerializingMemoryStore as V, toRecordDigests as Vt, UNSUPPORTED_DOCUMENT_MESSAGE as W, validateUiSpecNode as Wt, WebSkillRuntime as X, USER_PROFILE_REFINE_PROMPT as Y, appendBehaviorRecords as Z, EventBus as _, mergeCatalogEntries as _t, ASK_USER_TOOL as a, extractChartSpec as at, FsMemoryStore as b, networkUrlHost as bt, AnthropicClient as c, extractUiSpecEvents as ct, DEFAULT_LOOP_LIMITS as d, fromVercelResult as dt, buildRenderResult as et, DEFAULT_MAX_DATA_SOURCE_BYTES as f, fromVercelStreamPart as ft, EMPTY_USER_PROFILE as g, listSkillScripts as gt, DEFAULT_USER_PROFILE_LIMITS as h, isUnsupportedRunSnapshot as ht, ASK_USER_MAX_FIELDS as i, exportUserProfile as it, READ_LINKED_DOCUMENT_TOOL as j, renderUserProfileContext as jt, OpenAiCompatibleClient as k, redactToolStepArgs as kt, BEHAVIOR_RECORDS_KEY as l, findUnpairedToolCalls as lt, DEFAULT_MAX_DOCUMENT_TEXT_BYTES as m, isNetworkAllowed as mt, ASK_USER_FIELD_TYPES as n, createWebSkillApi as nt, ASK_USER_TOOL_NAME as o, extractSkillCandidate as ot, DEFAULT_MAX_DOCUMENT_BYTES as p, interruptedToolResult as pt, USER_PROFILE_NO_INVENTION_RULE as q, ASK_USER_INPUT_SCHEMA as r, diffUserProfile as rt, AgentLoop as s, extractTodoTraceEvents as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, createScriptContext as tt, CapabilityApproval as u, formatSkillScriptManifest as ut, FS_SESSION_PAGE_SIZE as v, mergeProfileEntries as vt, FsToolStepStore as w, parseBridgeRequest as wt, FsRunSnapshotStore as x, normalizeErrorCode as xt, FsArtifactStore as y, networkPolicyLibSource as yt, SESSION_SCHEMA_VERSION as z, summarizeToolCalls as zt };