@webskill/sdk 0.6.0 → 0.8.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 (35) hide show
  1. package/dist/agent.d.ts +2 -2
  2. package/dist/agent.js +218 -28
  3. package/dist/browser.d.ts +177 -4
  4. package/dist/browser.js +444 -35
  5. package/dist/{catalogComponents-Dr5dFMAb-Dacibl1e.js → catalogComponents-DfxxfUvn-D55Gbb2l.js} +4016 -1226
  6. package/dist/{dist-8oQRa8Xz.js → dist-59XlqDuv.js} +93 -6
  7. package/dist/{dist-DnYG2-eY.js → dist-CJqQsIm9.js} +498 -118
  8. package/dist/{dist-DusANsrn.js → dist-DmI5SBBF.js} +437 -55
  9. package/dist/eventTypes-g1BXL6x5-CibcOftR.js +37 -0
  10. package/dist/governance.d.ts +91 -11
  11. package/dist/governance.js +194 -52
  12. package/dist/{index-C-KFAZoF.d.ts → index-K-eewlGL.d.ts} +176 -75
  13. package/dist/{index-BMocOEi0.d.ts → index-P9J2LTfU.d.ts} +163 -6
  14. package/dist/{index-BuTpBMzr.d.ts → index-fLskQfAS.d.ts} +156 -5
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +4 -4
  17. package/dist/mcp.d.ts +129 -6
  18. package/dist/mcp.js +235 -28
  19. package/dist/{memoryArtifactStore-52Zn9npI-BMPYwvoy.js → memoryArtifactStore-52Zn9npI-upv5OWYf.js} +1 -1
  20. package/dist/node.d.ts +3 -3
  21. package/dist/node.js +4 -3
  22. package/dist/{openUiLibrary-Bdrji9qK-DzAxRlTY.js → openUiLibrary-DURlAxjk-CU6AzfSW.js} +3 -3
  23. package/dist/{skillVersionStore-BzLbzFOL-CxdAFWO2.d.ts → skillVersionStore-Bl-ElD45-gRfSaAby.d.ts} +8 -2
  24. package/dist/{testing-CYTFqkDm.js → testing-BCUO5gZR.js} +2 -2
  25. package/dist/testing.d.ts +1 -1
  26. package/dist/testing.js +2 -2
  27. package/dist/{types-4pg-qp_I-Gq63X8Oa.d.ts → types-B3n0cMZu-BdcqQ35O.d.ts} +74 -7
  28. package/dist/ui-react.d.ts +16 -7
  29. package/dist/ui-react.js +159 -108
  30. package/dist/ui-vue.d.ts +1 -1
  31. package/dist/ui-vue.js +2 -2
  32. package/dist/ui.d.ts +4 -4
  33. package/dist/ui.js +3 -3
  34. package/dist/{webskillLitCatalog-_mugzRHx-B_54vxum.js → webskillLitCatalog-DwTwSBFt-DiXXpNZA.js} +22 -3
  35. package/package.json +2 -2
@@ -1,7 +1,69 @@
1
- import { A as parseSkillMarkdown, L as resolveInsideRoot, O as messageOf, P as renderAvailableSkillsXml, V as validateSkills, f as SkillDiscovery, g as assertSafePathSegment, m as WebSkillError, p as SkillReader, v as buildCatalog } from "./dist-8oQRa8Xz.js";
2
- import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-BMPYwvoy.js";
1
+ import { A as messageOf, I as renderAvailableSkillsXml, M as parseSkillMarkdown, W as validateSkills, f as SkillDiscovery, g as assertSafePathSegment, m as WebSkillError, p as SkillReader, v as buildCatalog, z as resolveInsideRoot } from "./dist-59XlqDuv.js";
2
+ import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-upv5OWYf.js";
3
3
 
4
4
  //#region ../runtime/dist/index.js
5
+ /** 与正常工具结果同构:模型读到的是「这次调用被中断了」,而不是一个凭空消失的调用 */
6
+ function interruptedToolResult(call, options = {}) {
7
+ const code = options.code ?? "RUN_INTERRUPTED";
8
+ const because = options.reason === void 0 ? "" : ` (${options.reason})`;
9
+ return {
10
+ ok: false,
11
+ content: [],
12
+ error: {
13
+ code,
14
+ message: `Tool call "${call.name}" produced no result: the run ended before it finished${because}.`
15
+ }
16
+ };
17
+ }
18
+ /** 尚无 `role:'tool'` 应答的工具调用,按消息顺序 */
19
+ function findUnpairedToolCalls(messages) {
20
+ const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.toolCallId));
21
+ return messages.filter((m) => m.role === "assistant" && m.toolCalls?.length).flatMap((m) => m.toolCalls ?? []).filter((call) => !answered.has(call.id));
22
+ }
23
+ /**
24
+ * 为每个未应答的工具调用补一条结构化的中断说明。
25
+ *
26
+ * 补齐消息紧跟在该 assistant 已有的 tool 兄弟之后——供应商装配按相邻块分组,
27
+ * 插到序列末尾会让它归属到别的 assistant 消息上。
28
+ * 本函数**不改写入参**,也不写盘(AC-11.8)。
29
+ */
30
+ function sealToolCallPairs(messages, options = {}) {
31
+ const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.toolCallId));
32
+ const code = options.code ?? "RUN_INTERRUPTED";
33
+ const sealed = [];
34
+ const out = [];
35
+ for (let i = 0; i < messages.length; i += 1) {
36
+ const message = messages[i];
37
+ out.push(message);
38
+ if (message.role !== "assistant" || !message.toolCalls?.length) continue;
39
+ const missing = message.toolCalls.filter((call) => !answered.has(call.id));
40
+ if (missing.length === 0) continue;
41
+ while (i + 1 < messages.length && messages[i + 1].role === "tool") {
42
+ out.push(messages[i + 1]);
43
+ i += 1;
44
+ }
45
+ for (const call of missing) {
46
+ out.push({
47
+ role: "tool",
48
+ toolCallId: call.id,
49
+ content: textParts(JSON.stringify(interruptedToolResult(call, {
50
+ ...options,
51
+ code
52
+ })))
53
+ });
54
+ answered.add(call.id);
55
+ sealed.push({
56
+ callId: call.id,
57
+ toolName: call.name,
58
+ code
59
+ });
60
+ }
61
+ }
62
+ return {
63
+ messages: sealed.length === 0 ? [...messages] : out,
64
+ sealed
65
+ };
66
+ }
5
67
  /**
6
68
  * $defs 必须与 type / properties 同级,位于每个工具 inputSchema 的根。
7
69
  * 已验证:放进 properties.spec 内层会得到同样的 "Unsupported ref: #" ——
@@ -12,7 +74,7 @@ const DEFS_KEY = "$defs";
12
74
  const SELF_REF = "#";
13
75
  const NODE_NAME = "Node";
14
76
  const COMPONENT_PREFIX = "Component_";
15
- /** 值为「名称 → 子 schema」的映射。 */
77
+ /** 值为「名称 → 子 schema」的映射。包内导出:vendorSchema 要用同一套下钻规则(两套会漂移)。 */
16
78
  const SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
17
79
  "properties",
18
80
  "patternProperties",
@@ -41,11 +103,11 @@ const SCHEMA_KEYS = /* @__PURE__ */ new Set([
41
103
  "then",
42
104
  "else"
43
105
  ]);
44
- function isRecord$4(value) {
106
+ function isRecord$6(value) {
45
107
  return typeof value === "object" && value !== null && !Array.isArray(value);
46
108
  }
47
109
  function isSelfRef(value) {
48
- return isRecord$4(value) && value["$ref"] === SELF_REF;
110
+ return isRecord$6(value) && value["$ref"] === SELF_REF;
49
111
  }
50
112
  /**
51
113
  * 按 JSON Schema 关键字下降到子 schema。
@@ -55,7 +117,7 @@ function isSelfRef(value) {
55
117
  function forEachSubSchema(node, visit) {
56
118
  for (const [key, value] of Object.entries(node)) {
57
119
  if (SCHEMA_MAP_KEYS.has(key)) {
58
- if (isRecord$4(value)) for (const name of Object.keys(value)) visit(value[name], [key, name]);
120
+ if (isRecord$6(value)) for (const name of Object.keys(value)) visit(value[name], [key, name]);
59
121
  continue;
60
122
  }
61
123
  if (SCHEMA_LIST_KEYS.has(key)) {
@@ -74,7 +136,7 @@ function pathKey(path) {
74
136
  * `#` 解析到最近的带 `$id` 的祖先;没有则是文档根。
75
137
  */
76
138
  function collectSelfRefRoots(node, path, resourceRoot, out) {
77
- if (!isRecord$4(node)) return;
139
+ if (!isRecord$6(node)) return;
78
140
  if (isSelfRef(node)) {
79
141
  out.set(pathKey(resourceRoot), resourceRoot);
80
142
  return;
@@ -87,7 +149,7 @@ function collectSelfRefRoots(node, path, resourceRoot, out) {
87
149
  function getAt(schema, path) {
88
150
  let current = schema;
89
151
  for (const segment of path) if (Array.isArray(current) && typeof segment === "number") current = current[segment];
90
- else if (isRecord$4(current) && typeof segment === "string") current = current[segment];
152
+ else if (isRecord$6(current) && typeof segment === "string") current = current[segment];
91
153
  else return void 0;
92
154
  return current;
93
155
  }
@@ -113,7 +175,7 @@ function setAt(schema, path, replacement) {
113
175
  * 正是严格解析器无法解析 `#` 的直接原因。
114
176
  */
115
177
  function rewriteSelfRefs(node, target, depth) {
116
- if (!isRecord$4(node)) return node;
178
+ if (!isRecord$6(node)) return node;
117
179
  if (depth > 8) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool schema nesting exceeds 8 levels; cannot produce a portable form`);
118
180
  if (isSelfRef(node)) {
119
181
  const rest = {};
@@ -126,7 +188,7 @@ function rewriteSelfRefs(node, target, depth) {
126
188
  const result = {};
127
189
  for (const [key, value] of Object.entries(node)) {
128
190
  if (key === "$id" || key === "$schema") continue;
129
- if (SCHEMA_MAP_KEYS.has(key) && isRecord$4(value)) {
191
+ if (SCHEMA_MAP_KEYS.has(key) && isRecord$6(value)) {
130
192
  const mapped = {};
131
193
  for (const [name, child] of Object.entries(value)) mapped[name] = rewriteSelfRefs(child, target, depth + 1);
132
194
  result[key] = mapped;
@@ -164,8 +226,8 @@ function toPortableToolSchema(schema) {
164
226
  if (roots.size > 1) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", "Tool schema contains self references in more than one schema resource; cannot produce a portable form");
165
227
  const rootPath = [...roots.values()][0] ?? [];
166
228
  const resource = getAt(schema, rootPath);
167
- if (!isRecord$4(resource)) return schema;
168
- const existingDefs = isRecord$4(schema[DEFS_KEY]) ? schema[DEFS_KEY] : void 0;
229
+ if (!isRecord$6(resource)) return schema;
230
+ const existingDefs = isRecord$6(schema[DEFS_KEY]) ? schema[DEFS_KEY] : void 0;
169
231
  const taken = new Set(Object.keys(existingDefs ?? {}));
170
232
  const nodeName = uniqueName(NODE_NAME, taken);
171
233
  taken.add(nodeName);
@@ -196,6 +258,136 @@ function toPortableToolSchema(schema) {
196
258
  }
197
259
  };
198
260
  }
261
+ /** 各家都认的基础关键字。Google `FunctionDeclaration.parameters` 是其中最窄的一家。 */
262
+ const BASE_KEYWORDS = /* @__PURE__ */ new Set([
263
+ "type",
264
+ "description",
265
+ "title",
266
+ "default",
267
+ "enum",
268
+ "const",
269
+ "properties",
270
+ "required",
271
+ "items",
272
+ "anyOf",
273
+ "oneOf",
274
+ "minimum",
275
+ "maximum",
276
+ "minItems",
277
+ "maxItems",
278
+ "nullable",
279
+ "format"
280
+ ]);
281
+ /** 宽松侧:保留 0.6.0 的透传行为,本版不主动收紧(没有实证之前收紧只会制造新的破坏) */
282
+ const PERMISSIVE_KEYWORDS = /* @__PURE__ */ new Set([
283
+ ...BASE_KEYWORDS,
284
+ "additionalProperties",
285
+ "patternProperties",
286
+ "dependentSchemas",
287
+ "definitions",
288
+ "$defs",
289
+ "$ref",
290
+ "$schema",
291
+ "$id",
292
+ "allOf",
293
+ "not",
294
+ "if",
295
+ "then",
296
+ "else",
297
+ "prefixItems",
298
+ "additionalItems",
299
+ "unevaluatedItems",
300
+ "unevaluatedProperties",
301
+ "contains",
302
+ "propertyNames",
303
+ "exclusiveMinimum",
304
+ "exclusiveMaximum",
305
+ "multipleOf",
306
+ "minLength",
307
+ "maxLength",
308
+ "pattern",
309
+ "uniqueItems",
310
+ "examples",
311
+ "readOnly",
312
+ "writeOnly",
313
+ "deprecated"
314
+ ]);
315
+ const OPENAI_SCHEMA_PROFILE = {
316
+ id: "openai-compatible",
317
+ allowedKeywords: PERMISSIVE_KEYWORDS,
318
+ supportsRefs: true
319
+ };
320
+ const ANTHROPIC_SCHEMA_PROFILE = {
321
+ id: "anthropic",
322
+ allowedKeywords: PERMISSIVE_KEYWORDS,
323
+ supportsRefs: true
324
+ };
325
+ /**
326
+ * Google Gen AI 的 `FunctionDeclaration.parameters` 收到 `additionalProperties`
327
+ * 直接回 400 `Unknown name "additionalProperties"`,且不解析 `$ref` / `$defs`。
328
+ */
329
+ const GOOGLE_SCHEMA_PROFILE = {
330
+ id: "google",
331
+ allowedKeywords: BASE_KEYWORDS,
332
+ supportsRefs: false
333
+ };
334
+ function isRecord$5(value) {
335
+ return typeof value === "object" && value !== null && !Array.isArray(value);
336
+ }
337
+ /** 只在 schema 位置上找 `$ref`:enum / const / default 里的同名键是字面数据,不是引用 */
338
+ function containsRef(node) {
339
+ if (Array.isArray(node)) return node.some(containsRef);
340
+ if (!isRecord$5(node)) return false;
341
+ if (typeof node["$ref"] === "string") return true;
342
+ for (const [key, value] of Object.entries(node)) {
343
+ if (SCHEMA_MAP_KEYS.has(key)) {
344
+ if (isRecord$5(value) && Object.values(value).some(containsRef)) return true;
345
+ continue;
346
+ }
347
+ if (SCHEMA_LIST_KEYS.has(key)) {
348
+ if (Array.isArray(value) && value.some(containsRef)) return true;
349
+ continue;
350
+ }
351
+ if (SCHEMA_KEYS.has(key) && containsRef(value)) return true;
352
+ }
353
+ return false;
354
+ }
355
+ function sanitizeNode(node, profile) {
356
+ if (Array.isArray(node)) return node.map((item) => sanitizeNode(item, profile));
357
+ if (!isRecord$5(node)) return node;
358
+ const out = {};
359
+ for (const [key, value] of Object.entries(node)) {
360
+ if (!profile.allowedKeywords.has(key)) continue;
361
+ if (SCHEMA_MAP_KEYS.has(key) && isRecord$5(value)) {
362
+ const mapped = {};
363
+ for (const [name, child] of Object.entries(value)) mapped[name] = sanitizeNode(child, profile);
364
+ out[key] = mapped;
365
+ continue;
366
+ }
367
+ if (SCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
368
+ out[key] = value.map((child) => sanitizeNode(child, profile));
369
+ continue;
370
+ }
371
+ if (SCHEMA_KEYS.has(key)) {
372
+ out[key] = sanitizeNode(value, profile);
373
+ continue;
374
+ }
375
+ out[key] = value;
376
+ }
377
+ return out;
378
+ }
379
+ /**
380
+ * 按供应商 profile 清洗出站 schema。产出新对象,不修改入参(工具源可能复用同一个实例)。
381
+ *
382
+ * 这是**传输层适配**,只应在出站请求体上调用;不要下沉到 AgentLoop,
383
+ * 否则 trace / eventBus 观测到的 schema 会与技能作者写的不一致。
384
+ *
385
+ * @throws WebSkillError `TOOL_SCHEMA_UNAVAILABLE` — schema 依赖该供应商不支持的引用
386
+ */
387
+ function sanitizeForVendor(schema, profile, toolName) {
388
+ if (!profile.supportsRefs && containsRef(schema)) throw new WebSkillError("TOOL_SCHEMA_UNAVAILABLE", `Tool "${toolName}" requires $ref support that provider "${profile.id}" does not accept`);
389
+ return sanitizeNode(schema, profile);
390
+ }
199
391
  function createSseFrameReader() {
200
392
  let buffer = "";
201
393
  let data = [];
@@ -307,7 +499,7 @@ const toOpenAiTools = (tools) => tools.map((tool) => ({
307
499
  function: {
308
500
  name: tool.name,
309
501
  ...tool.description ? { description: tool.description } : {},
310
- parameters: toPortableToolSchema(tool.inputSchema)
502
+ parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), OPENAI_SCHEMA_PROFILE, tool.name)
311
503
  }
312
504
  }));
313
505
  const errorMessage$2 = (e) => e instanceof Error ? e.message : String(e);
@@ -564,7 +756,7 @@ function toAnthropicMessages(messages) {
564
756
  const toAnthropicTools = (tools) => tools.map((tool) => ({
565
757
  name: tool.name,
566
758
  ...tool.description ? { description: tool.description } : {},
567
- input_schema: toPortableToolSchema(tool.inputSchema)
759
+ input_schema: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), ANTHROPIC_SCHEMA_PROFILE, tool.name)
568
760
  }));
569
761
  /** Anthropic Messages API 客户端(零依赖 fetch;Node/浏览器通用) */
570
762
  var AnthropicClient = class {
@@ -807,7 +999,7 @@ function toGenAiContents(messages) {
807
999
  const toGenAiTools = (tools) => [{ functionDeclarations: tools.map((tool) => ({
808
1000
  name: tool.name,
809
1001
  ...tool.description ? { description: tool.description } : {},
810
- parameters: toPortableToolSchema(tool.inputSchema)
1002
+ parameters: sanitizeForVendor(toPortableToolSchema(tool.inputSchema), GOOGLE_SCHEMA_PROFILE, tool.name)
811
1003
  })) }];
812
1004
  /** Google GenAI(generateContent / streamGenerateContent)客户端(零依赖 fetch) */
813
1005
  var GoogleGenAiClient = class {
@@ -1260,22 +1452,22 @@ function createScriptContext(deps) {
1260
1452
  ...onWarning ? { onWarning } : {}
1261
1453
  };
1262
1454
  }
1263
- const isRecord$3 = (v) => typeof v === "object" && v !== null;
1455
+ const isRecord$4 = (v) => typeof v === "object" && v !== null;
1264
1456
  /**
1265
1457
  * $chart 约定的形状校验:JSON content 的 data 含 $chart 键且形状合法 → ChartSpec;
1266
1458
  * 任何畸形(kind 非法 / labels 非字符串数组 / series 项缺数值 data)→ undefined(忽略不炸)。
1267
1459
  */
1268
1460
  function extractChartSpec(data) {
1269
- if (!isRecord$3(data)) return void 0;
1461
+ if (!isRecord$4(data)) return void 0;
1270
1462
  const raw = data["$chart"];
1271
- if (!isRecord$3(raw)) return void 0;
1463
+ if (!isRecord$4(raw)) return void 0;
1272
1464
  const { kind, labels, series } = raw;
1273
1465
  if (kind !== "bar" && kind !== "line" && kind !== "pie") return void 0;
1274
1466
  if (!Array.isArray(labels) || !labels.every((l) => typeof l === "string")) return void 0;
1275
1467
  if (!Array.isArray(series)) return void 0;
1276
1468
  const validSeries = [];
1277
1469
  for (const item of series) {
1278
- if (!isRecord$3(item) || !Array.isArray(item["data"]) || !item["data"].every((n) => typeof n === "number")) return;
1470
+ if (!isRecord$4(item) || !Array.isArray(item["data"]) || !item["data"].every((n) => typeof n === "number")) return;
1279
1471
  const name = item["name"];
1280
1472
  validSeries.push({
1281
1473
  ...typeof name === "string" ? { name } : {},
@@ -1293,13 +1485,18 @@ function extractChartSpec(data) {
1293
1485
  /**
1294
1486
  * 默认的结果渲染构造:run 内收集的 renderBlocks(chart 等)在前,
1295
1487
  * LLM 最终输出 → markdown block,run.artifacts → file blocks 在后;summary 取 terminationReason。
1488
+ * 注入 output block 时一并记下它的下标(S7),供消费方按来源去重。
1296
1489
  */
1297
1490
  function buildRenderResult(run, output, renderBlocks = []) {
1298
1491
  const blocks = [...renderBlocks];
1299
- if (output.trim() !== "") blocks.push({
1300
- type: "markdown",
1301
- text: output
1302
- });
1492
+ let outputBlockIndex;
1493
+ if (output.trim() !== "") {
1494
+ outputBlockIndex = blocks.length;
1495
+ blocks.push({
1496
+ type: "markdown",
1497
+ text: output
1498
+ });
1499
+ }
1303
1500
  for (const artifact of run.artifacts) blocks.push({
1304
1501
  type: "file",
1305
1502
  path: artifact.path,
@@ -1310,7 +1507,8 @@ function buildRenderResult(run, output, renderBlocks = []) {
1310
1507
  runId: run.id,
1311
1508
  summary: run.terminationReason,
1312
1509
  blocks,
1313
- artifacts: run.artifacts
1510
+ artifacts: run.artifacts,
1511
+ ...outputBlockIndex === void 0 ? {} : { outputBlockIndex }
1314
1512
  };
1315
1513
  }
1316
1514
  const MAX_SURFACE_BYTES = 256 * 1024;
@@ -1326,7 +1524,7 @@ const actionIntents = /* @__PURE__ */ new Set([
1326
1524
  "download",
1327
1525
  "refresh"
1328
1526
  ]);
1329
- function isRecord$2(value) {
1527
+ function isRecord$3(value) {
1330
1528
  return typeof value === "object" && value !== null && !Array.isArray(value);
1331
1529
  }
1332
1530
  function reject(message) {
@@ -1343,7 +1541,7 @@ function isJsonValue(value, depth = 0) {
1343
1541
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1344
1542
  if (typeof value === "number") return Number.isFinite(value);
1345
1543
  if (Array.isArray(value)) return value.every((item) => isJsonValue(item, depth + 1));
1346
- if (!isRecord$2(value)) return false;
1544
+ if (!isRecord$3(value)) return false;
1347
1545
  return Object.keys(value).every((key) => key !== "__proto__" && key !== "constructor" && isJsonValue(value[key], depth + 1));
1348
1546
  }
1349
1547
  /**
@@ -1353,10 +1551,10 @@ function isJsonValue(value, depth = 0) {
1353
1551
  function assertNode(value, path, depth, counter) {
1354
1552
  if (depth > MAX_NODE_DEPTH) reject(`A UI spec tree must not nest deeper than ${MAX_NODE_DEPTH} levels`);
1355
1553
  if (++counter.nodes > MAX_NODES) reject(`A UI spec tree must contain at most ${MAX_NODES} nodes`);
1356
- if (!isRecord$2(value)) reject(`${path} must be an object`);
1554
+ if (!isRecord$3(value)) reject(`${path} must be an object`);
1357
1555
  requireString(value["component"], `${path}.component`);
1358
1556
  if (value["id"] !== void 0) requireString(value["id"], `${path}.id`);
1359
- if (value["props"] !== void 0 && (!isRecord$2(value["props"]) || !isJsonValue(value["props"]))) reject(`${path}.props must be a JSON object`);
1557
+ if (value["props"] !== void 0 && (!isRecord$3(value["props"]) || !isJsonValue(value["props"]))) reject(`${path}.props must be a JSON object`);
1360
1558
  const children = value["children"];
1361
1559
  if (children === void 0) return;
1362
1560
  if (!Array.isArray(children)) reject(`${path}.children must be an array`);
@@ -1366,7 +1564,7 @@ function assertActions(value) {
1366
1564
  if (value === void 0) return;
1367
1565
  if (!Array.isArray(value) || value.length > MAX_ACTIONS) reject(`Surface actions must contain at most ${MAX_ACTIONS} items`);
1368
1566
  for (const action of value) {
1369
- if (!isRecord$2(action)) reject("A surface action must be an object");
1567
+ if (!isRecord$3(action)) reject("A surface action must be an object");
1370
1568
  requireString(action["id"], "Surface action ID");
1371
1569
  const intent = action["intent"];
1372
1570
  if (typeof intent !== "string" || !actionIntents.has(intent)) reject("Surface action intent is invalid");
@@ -1382,7 +1580,7 @@ function validateUiSpecNode(value) {
1382
1580
  return structuredClone(value);
1383
1581
  }
1384
1582
  function assertPatch(value) {
1385
- if (!isRecord$2(value)) reject("A surface patch operation must be an object");
1583
+ if (!isRecord$3(value)) reject("A surface patch operation must be an object");
1386
1584
  if (value["op"] !== "replace" && value["op"] !== "merge" && value["op"] !== "append") reject("Surface patch operation is invalid");
1387
1585
  requireString(value["path"], "Surface patch path");
1388
1586
  if (!value["path"].startsWith("/")) reject("Surface patch path must be a JSON pointer");
@@ -1390,7 +1588,7 @@ function assertPatch(value) {
1390
1588
  }
1391
1589
  /** Validates an individual event in the framework-neutral surface stream. @experimental */
1392
1590
  function validateUiSpecEvent(value) {
1393
- if (!isRecord$2(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
1591
+ if (!isRecord$3(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
1394
1592
  if (value["runId"] !== void 0) requireString(value["runId"], "Surface event run ID");
1395
1593
  switch (value["type"]) {
1396
1594
  case "open":
@@ -1448,7 +1646,7 @@ function validateUiSpecEvent(value) {
1448
1646
  }
1449
1647
  /** Extracts validated surface stream events from structured tool output. @experimental */
1450
1648
  function extractUiSpecEvents(data) {
1451
- if (!isRecord$2(data) || data["$surface"] === void 0) return [];
1649
+ if (!isRecord$3(data) || data["$surface"] === void 0) return [];
1452
1650
  const raw = data["$surface"];
1453
1651
  return (Array.isArray(raw) ? raw : [raw]).map((event) => validateUiSpecEvent(event));
1454
1652
  }
@@ -1481,7 +1679,9 @@ function schemaToForm(schema, providedArgs) {
1481
1679
  function mapFieldType(prop) {
1482
1680
  if (Array.isArray(prop.enum)) return "select";
1483
1681
  switch (prop.type) {
1484
- case "string": return prop["format"] === "binary" || prop["contentEncoding"] === "base64" ? "file" : "text";
1682
+ case "string":
1683
+ if (prop["format"] === "binary" || prop["contentEncoding"] === "base64") return "file";
1684
+ return prop["format"] === "password" || prop["writeOnly"] === true ? "password" : "text";
1485
1685
  case "number":
1486
1686
  case "integer": return "number";
1487
1687
  case "boolean": return "boolean";
@@ -1767,7 +1967,7 @@ var TraceRecorder = class {
1767
1967
  return [...this.#events];
1768
1968
  }
1769
1969
  };
1770
- const isRecord$1 = (v) => typeof v === "object" && v !== null;
1970
+ const isRecord$2 = (v) => typeof v === "object" && v !== null;
1771
1971
  /** 工具结果可经 `$todo` 标记记入 trace 的事件类型;其余类型不接受,防止工具源伪造 run.* */
1772
1972
  const TODO_TRACE_TYPES = /* @__PURE__ */ new Set([
1773
1973
  "todo.created",
@@ -1782,12 +1982,12 @@ const TODO_TRACE_TYPES = /* @__PURE__ */ new Set([
1782
1982
  * @experimental
1783
1983
  */
1784
1984
  function extractTodoTraceEvents(data) {
1785
- if (!isRecord$1(data) || data["$todo"] === void 0) return [];
1985
+ if (!isRecord$2(data) || data["$todo"] === void 0) return [];
1786
1986
  const raw = data["$todo"];
1787
1987
  const entries = Array.isArray(raw) ? raw : [raw];
1788
1988
  const events = [];
1789
1989
  for (const entry of entries) {
1790
- if (!isRecord$1(entry)) continue;
1990
+ if (!isRecord$2(entry)) continue;
1791
1991
  const { type, ...rest } = entry;
1792
1992
  if (typeof type !== "string" || !TODO_TRACE_TYPES.has(type)) continue;
1793
1993
  events.push({
@@ -1797,7 +1997,39 @@ function extractTodoTraceEvents(data) {
1797
1997
  }
1798
1998
  return events;
1799
1999
  }
1800
- const RUN_SNAPSHOT_SCHEMA_VERSION = 2;
2000
+ const isRecord$1 = (v) => typeof v === "object" && v !== null;
2001
+ /**
2002
+ * `$skillCandidate` 约定的形状校验:JSON content 的 data 含合法 `$skillCandidate` 键 → 候选载荷。
2003
+ *
2004
+ * 与 `$chart` / `$todo` / `$surface` 同一条既有通道,是第四个。
2005
+ * 候选的生成与存储全部在 `@webskill/agent`,runtime 只认这个形状,畸形忽略不炸。
2006
+ * @experimental
2007
+ */
2008
+ function extractSkillCandidate(data) {
2009
+ if (!isRecord$1(data)) return void 0;
2010
+ const raw = data["$skillCandidate"];
2011
+ if (!isRecord$1(raw)) return void 0;
2012
+ const { id, name } = raw;
2013
+ if (typeof id !== "string" || id === "" || typeof name !== "string" || name === "") return void 0;
2014
+ return {
2015
+ id,
2016
+ name
2017
+ };
2018
+ }
2019
+ /**
2020
+ * Agent loop 的运行上限默认值。**全仓唯一来源**(S8)。
2021
+ *
2022
+ * 在 0.7.0 之前这三个数字在 `AgentLoop` 的构造与 ui-kit 的 `defaultRuntimeConfig()`
2023
+ * 里各写了一遍。两份值今天恰好相等,所以不出事;一旦其中一处被改,
2024
+ * 「恢复默认值」会恢复到 agent loop 根本不用的值——验收绿、行为错。
2025
+ * @stable
2026
+ */
2027
+ const DEFAULT_LOOP_LIMITS = {
2028
+ maxTurns: 10,
2029
+ totalTimeoutMs: 12e4,
2030
+ toolTimeoutMs: 3e4
2031
+ };
2032
+ const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
1801
2033
  /** @experimental */
1802
2034
  function isUnsupportedRunSnapshot(entry) {
1803
2035
  return entry.unsupported === true;
@@ -1842,7 +2074,7 @@ var FsRunSnapshotStore = class {
1842
2074
  throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
1843
2075
  }
1844
2076
  const schemaVersion = typeof snapshot.schemaVersion === "number" ? snapshot.schemaVersion : 0;
1845
- if (schemaVersion !== 2) throw new WebSkillError("RUN_SNAPSHOT_SCHEMA_UNSUPPORTED", `Snapshot for run "${runId}" uses schema version ${schemaVersion}; this runtime reads version 2. The file was kept for read-only inspection.`);
2077
+ if (schemaVersion !== 3) throw new WebSkillError("RUN_SNAPSHOT_SCHEMA_UNSUPPORTED", `Snapshot for run "${runId}" uses schema version ${schemaVersion}; this runtime reads version 3. The file was kept for read-only inspection.`);
1846
2078
  if (snapshot.runId !== runId) {
1847
2079
  await this.#fs.remove(path).catch(() => void 0);
1848
2080
  throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
@@ -1862,7 +2094,7 @@ var FsRunSnapshotStore = class {
1862
2094
  try {
1863
2095
  const parsed = JSON.parse(await this.#fs.readText(entry.path));
1864
2096
  const schemaVersion = typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : 0;
1865
- if (schemaVersion === 2) {
2097
+ if (schemaVersion === 3) {
1866
2098
  out.push(parsed);
1867
2099
  continue;
1868
2100
  }
@@ -1975,6 +2207,45 @@ const redactFileValue = (value) => {
1975
2207
  return rest;
1976
2208
  };
1977
2209
  /**
2210
+ * 密码字段名集合(FR-23.7)。
2211
+ *
2212
+ * 既有的三处脱敏各有各的判别口径(看 request.type、看值是否标量、看值里有没有 data/mimeType),
2213
+ * 没有一处按**字段类型**判。行为记录那处尤其危险:它的安全假设是「标量是安全的」,
2214
+ * 而密码恰好是字符串标量——假设失效了。
2215
+ */
2216
+ const passwordFieldNames = (request) => new Set(request.type === "form" ? request.fields.filter((f) => f.type === "password").map((f) => f.name) : []);
2217
+ /** 去掉密码字段的值;不是掩码而是**不存**,掩码存下来仍然泄露长度与存在性 */
2218
+ const withoutPasswords = (value, passwords) => {
2219
+ if (passwords.size === 0 || typeof value !== "object" || value === null || Array.isArray(value)) return value;
2220
+ const out = {};
2221
+ for (const [key, entry] of Object.entries(value)) if (!passwords.has(key)) out[key] = entry;
2222
+ return out;
2223
+ };
2224
+ /**
2225
+ * 触顶失败的结构化细节(FR-21.2)。由终止原因推出上限字段,而不是在每个终止点各写一遍——
2226
+ * 终止点有四个,写四遍就迟早有一处对不上。
2227
+ */
2228
+ const limitDetails = (state, reason) => {
2229
+ if (reason === "timeout") return {
2230
+ limit: "totalTimeoutMs",
2231
+ value: state.totalTimeoutMs
2232
+ };
2233
+ if (reason === "max-turns") return {
2234
+ limit: "maxTurns",
2235
+ value: state.maxTurns
2236
+ };
2237
+ };
2238
+ /** 补齐消息里带的错误码:能说清「为什么没结果」的,就不要退化成 RUN_INTERRUPTED(设计 11 §2.5) */
2239
+ const sealCodeFor = (reason, errorCode) => {
2240
+ switch (reason) {
2241
+ case "interaction-timeout": return "RUN_INTERACTION_TIMEOUT";
2242
+ case "user-cancelled": return "RUN_CANCELLED";
2243
+ case "timeout": return "RUN_TIMEOUT";
2244
+ case "tool-resolution-exhausted": return "TOOL_RESOLUTION_EXHAUSTED";
2245
+ default: return errorCode ?? "RUN_INTERRUPTED";
2246
+ }
2247
+ };
2248
+ /**
1978
2249
  * 多轮 Agent 循环。
1979
2250
  * 工具失败一律转为 ToolResult{ok:false} 回喂 LLM 继续循环;
1980
2251
  * 缺参/确认/提问经 UiBridge 行内 await 暂停恢复;取消/超时/LLM 异常/护栏超限才终止 run。
@@ -1993,9 +2264,9 @@ var AgentLoop = class {
1993
2264
  artifactStore: deps.artifactStore ?? new MemoryArtifactStore()
1994
2265
  };
1995
2266
  this.#config = {
1996
- maxTurns: config.maxTurns ?? 10,
1997
- totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
1998
- toolTimeoutMs: config.toolTimeoutMs ?? 3e4,
2267
+ maxTurns: config.maxTurns ?? DEFAULT_LOOP_LIMITS.maxTurns,
2268
+ totalTimeoutMs: config.totalTimeoutMs ?? DEFAULT_LOOP_LIMITS.totalTimeoutMs,
2269
+ toolTimeoutMs: config.toolTimeoutMs ?? DEFAULT_LOOP_LIMITS.toolTimeoutMs,
1999
2270
  toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
2000
2271
  paramHistoryLimit: config.paramHistoryLimit ?? 50,
2001
2272
  toolCallingDisabled: config.toolCallingDisabled ?? false,
@@ -2210,6 +2481,7 @@ var AgentLoop = class {
2210
2481
  return finish("failed", "llm-error", messageOf(e), code);
2211
2482
  }
2212
2483
  const responseText = partsToText(response.content);
2484
+ if (!llm.stream && responseText !== "") await this.#deps.uiBridge?.onTextDelta?.(state.runId, responseText);
2213
2485
  trace.record("llm.response", { data: {
2214
2486
  turn,
2215
2487
  hasToolCalls: Boolean(response.toolCalls?.length),
@@ -2279,7 +2551,8 @@ var AgentLoop = class {
2279
2551
  message: output,
2280
2552
  data: {
2281
2553
  reason,
2282
- ...errorCode ? { code: errorCode } : {}
2554
+ ...errorCode ? { code: errorCode } : {},
2555
+ ...limitDetails(state, reason) ?? {}
2283
2556
  }
2284
2557
  });
2285
2558
  const bridge = this.#deps.uiBridge;
@@ -2303,6 +2576,7 @@ var AgentLoop = class {
2303
2576
  } catch (e) {
2304
2577
  trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
2305
2578
  }
2579
+ await this.#sealToolCalls(state, reason, errorCode);
2306
2580
  run.trace = trace.list();
2307
2581
  return {
2308
2582
  output,
@@ -2310,12 +2584,45 @@ var AgentLoop = class {
2310
2584
  messages: state.messages.map((m) => ({ ...m }))
2311
2585
  };
2312
2586
  }
2587
+ /**
2588
+ * S1 出口守卫(FR-11.1):中断留下的未应答工具调用在这里补齐。
2589
+ *
2590
+ * 放在 `#finish` 而不是四个早退点:它是唯一的汇流出口,写四遍就迟早漏一处。
2591
+ * 补齐产物只进 `LlmMessage[]`(下一轮请求装配用),不进 chatbot 的消息流(AC-11.2)。
2592
+ */
2593
+ async #sealToolCalls(state, reason, errorCode) {
2594
+ const pending = findUnpairedToolCalls(state.messages);
2595
+ if (pending.length === 0) return;
2596
+ const code = sealCodeFor(reason, errorCode);
2597
+ for (const call of pending) {
2598
+ const result = interruptedToolResult(call, {
2599
+ code,
2600
+ reason
2601
+ });
2602
+ state.messages.push({
2603
+ role: "tool",
2604
+ toolCallId: call.id,
2605
+ content: await this.#toolResultParts(call, result, state)
2606
+ });
2607
+ state.trace.record("run.warning", {
2608
+ message: `Sealed unanswered tool call "${call.name}": the run ended before it produced a result.`,
2609
+ data: {
2610
+ kind: "tool-call-sealed",
2611
+ callId: call.id,
2612
+ toolName: call.name,
2613
+ side: "write",
2614
+ reason,
2615
+ code
2616
+ }
2617
+ });
2618
+ }
2619
+ }
2313
2620
  /** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
2314
2621
  async #saveSnapshot(state, pending) {
2315
2622
  const store = this.#deps.snapshotStore;
2316
2623
  if (!store) return;
2317
2624
  const snapshot = {
2318
- schemaVersion: 2,
2625
+ schemaVersion: 3,
2319
2626
  runId: state.runId,
2320
2627
  sessionId: state.run.sessionId,
2321
2628
  userPrompt: state.run.userPrompt,
@@ -2490,6 +2797,15 @@ var AgentLoop = class {
2490
2797
  throw e;
2491
2798
  }
2492
2799
  }
2800
+ /**
2801
+ * 工具的技能归属。内置工具与外部工具没有归属,返回 `undefined`——
2802
+ * 拿 `activated[0]` 顶替会把内置工具的失败栽给一个无关技能,比不归因更糟(设计 26 §2.3)。
2803
+ */
2804
+ #skillOf(call, state) {
2805
+ if (call.name === "read_skill_file" || call.name === "ask_user") return void 0;
2806
+ const resolution = resolveToolName(call.name, state.activated, state.activatedTools.keys());
2807
+ return resolution.kind === "script" ? resolution.skillName : void 0;
2808
+ }
2493
2809
  /** 最后一条 assistant 消息里第一个尚无 tool 响应的工具调用(即中断时正在处理的那个) */
2494
2810
  #findPendingToolCall(messages) {
2495
2811
  const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" && m.toolCalls?.length);
@@ -2680,11 +2996,14 @@ var AgentLoop = class {
2680
2996
  }
2681
2997
  async #executeCall(call, state) {
2682
2998
  const argsSummary = summarizeArgs(call.arguments);
2999
+ const owner = this.#skillOf(call, state);
3000
+ const attribution = owner === void 0 ? {} : { skillName: owner };
2683
3001
  const callStartMs = Date.parse(state.now());
2684
3002
  state.trace.record("tool.started", { data: {
2685
3003
  name: call.name,
2686
3004
  callId: call.id,
2687
- args: argsSummary
3005
+ args: argsSummary,
3006
+ ...attribution
2688
3007
  } });
2689
3008
  this.#emitTool(state, "started", call);
2690
3009
  let result;
@@ -2710,7 +3029,8 @@ var AgentLoop = class {
2710
3029
  name: call.name,
2711
3030
  callId: call.id,
2712
3031
  args: argsSummary,
2713
- durationMs
3032
+ durationMs,
3033
+ ...attribution
2714
3034
  } });
2715
3035
  this.#emitTool(state, "completed", call);
2716
3036
  for (const item of result.content) {
@@ -2721,6 +3041,12 @@ var AgentLoop = class {
2721
3041
  chart
2722
3042
  });
2723
3043
  for (const todo of extractTodoTraceEvents(item.data)) state.trace.record(todo.type, { data: todo.data });
3044
+ const candidate = extractSkillCandidate(item.data);
3045
+ if (candidate) try {
3046
+ await this.#deps.uiBridge?.onSkillCandidate?.(state.runId, candidate);
3047
+ } catch (e) {
3048
+ state.trace.record("run.warning", { message: `Skill candidate was not handed off to the UI: ${messageOf(e)}` });
3049
+ }
2724
3050
  try {
2725
3051
  for (const event of extractUiSpecEvents(item.data)) await this.#renderSurface(state, event);
2726
3052
  } catch (e) {
@@ -2738,7 +3064,8 @@ var AgentLoop = class {
2738
3064
  callId: call.id,
2739
3065
  code: result.error?.code,
2740
3066
  args: argsSummary,
2741
- durationMs
3067
+ durationMs,
3068
+ ...attribution
2742
3069
  }
2743
3070
  });
2744
3071
  this.#emitTool(state, "failed", call, result.error?.code);
@@ -2905,10 +3232,17 @@ var AgentLoop = class {
2905
3232
  async #replaySurfaceEvents(state) {
2906
3233
  const bridge = this.#deps.uiBridge;
2907
3234
  if (!bridge?.renderSurface || state.surfaceEvents.length === 0) return;
2908
- try {
2909
- for (const event of state.surfaceEvents) await bridge.renderSurface(structuredClone(event));
3235
+ for (const event of state.surfaceEvents) try {
3236
+ await bridge.renderSurface(structuredClone(event));
2910
3237
  } catch (e) {
2911
- state.trace.record("run.warning", { message: `Failed to replay UI surfaces: ${messageOf(e)}` });
3238
+ state.trace.record("ui.degraded", {
3239
+ message: `Failed to replay UI surface "${event.id}": ${messageOf(e)}`,
3240
+ data: {
3241
+ kind: "replay-failed",
3242
+ surfaceId: event.id,
3243
+ eventType: event.type
3244
+ }
3245
+ });
2912
3246
  }
2913
3247
  }
2914
3248
  /**
@@ -3332,7 +3666,8 @@ var AgentLoop = class {
3332
3666
  if (fresh.length > 0) result.artifacts = [...result.artifacts ?? [], ...fresh];
3333
3667
  }
3334
3668
  await this.#bumpSkillStat(skillName, result.ok ? "successes" : "failures", state);
3335
- if (!result.ok) await this.#reportSkillFailure(skillName, state, {
3669
+ if (result.ok) await this.#reportSkillSuccess(skillName, state);
3670
+ else await this.#reportSkillFailure(skillName, state, {
3336
3671
  code: result.error?.code ?? "TOOL_EXECUTION_FAILED",
3337
3672
  message: result.error?.message ?? `Tool "${call.name}" returned a failure result`
3338
3673
  });
@@ -3369,6 +3704,22 @@ var AgentLoop = class {
3369
3704
  state.trace.record("run.warning", { message: `Skill failure report for "${skillName}" was not recorded: ${messageOf(e)}` });
3370
3705
  }
3371
3706
  }
3707
+ /**
3708
+ * D-5 技能成功上报:把失败计数从「累计」变成「连续」的那半边信号。
3709
+ * 与失败上报同样是可选 port,未注入即整段跳过。
3710
+ */
3711
+ async #reportSkillSuccess(skillName, state) {
3712
+ const report = this.#deps.skillOutcomeReporter?.onSkillSucceeded;
3713
+ if (report === void 0) return;
3714
+ try {
3715
+ await report.call(this.#deps.skillOutcomeReporter, {
3716
+ skillName,
3717
+ runId: state.runId
3718
+ });
3719
+ } catch (e) {
3720
+ state.trace.record("run.warning", { message: `Skill success report for "${skillName}" was not recorded: ${messageOf(e)}` });
3721
+ }
3722
+ }
3372
3723
  /** context.confirm 触发点:默认真实询问;auto-approve 直通;无 bridge 降级直通 + warning */
3373
3724
  async #confirm(message, state) {
3374
3725
  if (this.#policy.confirmations === "auto-approve") return true;
@@ -3455,7 +3806,8 @@ var AgentLoop = class {
3455
3806
  if (!this.#deps.memory) return;
3456
3807
  const scope = `session:${state.run.sessionId}`;
3457
3808
  const limit = this.#config.paramHistoryLimit;
3458
- const recorded = request.type === "file-pick" ? redactFileValue(value) : value;
3809
+ const passwords = passwordFieldNames(request);
3810
+ const recorded = request.type === "file-pick" ? redactFileValue(value) : withoutPasswords(value, passwords);
3459
3811
  await this.#memoryMutate(scope, "paramHistory", state, (current) => {
3460
3812
  const history = current ?? [];
3461
3813
  history.push({
@@ -3522,6 +3874,7 @@ var AgentLoop = class {
3522
3874
  else if (request.type === "form" && typeof value === "object" && value !== null) {
3523
3875
  const submitted = value;
3524
3876
  for (const field of request.fields) {
3877
+ if (field.type === "password") continue;
3525
3878
  const next = submitted[field.name];
3526
3879
  if (typeof next !== "string" && typeof next !== "number" && typeof next !== "boolean") continue;
3527
3880
  if (next === "") continue;
@@ -3675,6 +4028,7 @@ var WebSkillRuntime = class {
3675
4028
  if (!this.#catalogCache) await this.discover();
3676
4029
  const cache = this.#catalogCache;
3677
4030
  if (!cache) throw new Error("discover() did not populate the catalog cache");
4031
+ const sealedHistory = options.history ? sealToolCallPairs(options.history) : void 0;
3678
4032
  const providerFailures = [];
3679
4033
  const providerEntries = (await Promise.all((this.#deps.skillProviders ?? []).map(async (p) => {
3680
4034
  try {
@@ -3721,11 +4075,25 @@ var WebSkillRuntime = class {
3721
4075
  userPrompt,
3722
4076
  route,
3723
4077
  runId,
3724
- ...options.history ? { history: options.history } : {}
4078
+ ...sealedHistory ? { history: sealedHistory.messages } : {}
3725
4079
  });
3726
4080
  } finally {
3727
4081
  this.#loops.delete(runId);
3728
4082
  }
4083
+ for (const record of sealedHistory?.sealed ?? []) result.run.trace.push({
4084
+ id: `evt-seal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4085
+ runId: result.run.id,
4086
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4087
+ type: "run.warning",
4088
+ message: `Sealed unanswered tool call "${record.toolName}" carried over from an earlier interrupted run.`,
4089
+ data: {
4090
+ kind: "tool-call-sealed",
4091
+ callId: record.callId,
4092
+ toolName: record.toolName,
4093
+ side: "read",
4094
+ code: record.code
4095
+ }
4096
+ });
3729
4097
  for (const failure of providerFailures) result.run.trace.push({
3730
4098
  id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3731
4099
  runId: result.run.id,
@@ -4221,7 +4589,6 @@ function parseUserProfileExport(raw) {
4221
4589
  entries
4222
4590
  };
4223
4591
  }
4224
- /** 导入前给用户看的差异(FR-19.7):新增哪些、覆盖哪些 @experimental */
4225
4592
  function diffUserProfile(current, incoming) {
4226
4593
  const byId = new Map(current.entries.map((entry) => [entry.id, entry]));
4227
4594
  const added = [];
@@ -4783,6 +5150,7 @@ const toMeta = (record) => ({
4783
5150
  ...record.title !== void 0 ? { title: record.title } : {},
4784
5151
  ...record.titleLocked === true ? { titleLocked: true } : {},
4785
5152
  ...record.archived === true ? { archived: true } : {},
5153
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
4786
5154
  messageCount: record.messages.length
4787
5155
  });
4788
5156
  const newSessionId = () => `session-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -4829,6 +5197,7 @@ function parseSessionFile(raw, path) {
4829
5197
  ...typeof file.title === "string" ? { title: file.title } : {},
4830
5198
  ...file.titleLocked === true ? { titleLocked: true } : {},
4831
5199
  ...file.archived === true ? { archived: true } : {},
5200
+ ...typeof file.modelId === "string" ? { modelId: file.modelId } : {},
4832
5201
  messages: Array.isArray(file.messages) ? file.messages : [],
4833
5202
  messageCount: Array.isArray(file.messages) ? file.messages.length : 0
4834
5203
  };
@@ -4870,6 +5239,7 @@ var FsSessionStore = class {
4870
5239
  ...record.title !== void 0 ? { title: record.title } : {},
4871
5240
  ...record.titleLocked === true ? { titleLocked: true } : {},
4872
5241
  ...record.archived === true ? { archived: true } : {},
5242
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
4873
5243
  messages: record.messages
4874
5244
  };
4875
5245
  await this.#fs.writeText(this.#path(record.id), JSON.stringify(file, null, 2));
@@ -4963,6 +5333,18 @@ var FsSessionStore = class {
4963
5333
  await this.#write(record);
4964
5334
  });
4965
5335
  }
5336
+ async setModel(id, modelId) {
5337
+ await this.#serialize(id, async () => {
5338
+ const record = await this.#require(id);
5339
+ if (modelId === void 0) delete record.modelId;
5340
+ else record.modelId = modelId;
5341
+ await this.#write(record);
5342
+ });
5343
+ }
5344
+ async getMeta(id) {
5345
+ const record = await this.get(id);
5346
+ return record === void 0 ? void 0 : toMeta(record);
5347
+ }
4966
5348
  async delete(id) {
4967
5349
  await this.#serialize(id, async () => {
4968
5350
  const path = this.#path(id);
@@ -4972,4 +5354,4 @@ var FsSessionStore = class {
4972
5354
  };
4973
5355
 
4974
5356
  //#endregion
4975
- export { listSkillScripts as $, TraceRecorder as A, buildRenderResult as B, READ_SKILL_FILE_INPUT_SCHEMA as C, validateUiSpecEvent as Ct, RUN_TRACE_SCHEMA_VERSION as D, RUN_SNAPSHOT_SCHEMA_VERSION as E, USER_PROFILE_REFINE_PROMPT as F, extractChartSpec as G, createWebSkillApi as H, WebSkillRuntime as I, formatSkillScriptManifest as J, extractTodoTraceEvents as K, appendBehaviorRecords as L, USER_PROFILE_KEY as M, USER_PROFILE_NO_INVENTION_RULE as N, SESSION_SCHEMA_VERSION as O, USER_PROFILE_PROMPT_HEADER as P, isUnsupportedRunSnapshot as Q, applyUserProfileImport as R, ProgressiveRouter as S, toVercelToolSpecs as St, READ_SKILL_FILE_TOOL_NAME as T, diffUserProfile as U, createScriptContext as V, exportUserProfile as W, fromVercelStreamPart as X, fromVercelResult as Y, isNetworkAllowed as Z, FsSessionStore as _, schemaToForm as _t, AgentLoop as a, normalizeToolContent as at, HookRunner as b, toLlmToolSpec as bt, CapabilityApproval as c, parseUserProfileExport as ct, EventBus as d, readUserProfile as dt, mergeCatalogEntries as et, FS_SESSION_PAGE_SIZE as f, refineUserProfile as ft, FsRunTraceStore as g, schemaSourceLabel as gt, FsRunSnapshotStore as h, sampleBehaviorRecords as ht, ASK_USER_TOOL_NAME as i, normalizeErrorCode as it, USER_PROFILE_EXPORT_VERSION as j, SerializingMemoryStore as k, DEFAULT_USER_PROFILE_LIMITS as l, readBehaviorRecords as lt, FsMemoryStore as m, resolveToolName as mt, ASK_USER_INPUT_SCHEMA as n, networkPolicyLibSource as nt, AnthropicClient as o, normalizeToolError as ot, FsArtifactStore as p, renderUserProfileContext as pt, extractUiSpecEvents as q, ASK_USER_TOOL as r, networkUrlHost as rt, BEHAVIOR_RECORDS_KEY as s, parseBridgeRequest as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, mergeProfileEntries as tt, EMPTY_USER_PROFILE as u, readProfileEntries as ut, FullDisclosureRouter as v, scriptToolName as vt, READ_SKILL_FILE_TOOL as w, validateUiSpecNode as wt, OpenAiCompatibleClient as x, toRecordDigests as xt, GoogleGenAiClient as y, summarizeToolCalls as yt, bridgeError as z };
5357
+ export { fromVercelStreamPart as $, SerializingMemoryStore as A, bridgeError as B, ProgressiveRouter as C, sealToolCallPairs as Ct, RUN_SNAPSHOT_SCHEMA_VERSION as D, toVercelToolSpecs as Dt, READ_SKILL_FILE_TOOL_NAME as E, toRecordDigests as Et, USER_PROFILE_PROMPT_HEADER as F, exportUserProfile as G, createScriptContext as H, USER_PROFILE_REFINE_PROMPT as I, extractTodoTraceEvents as J, extractChartSpec as K, WebSkillRuntime as L, USER_PROFILE_EXPORT_VERSION as M, USER_PROFILE_KEY as N, RUN_TRACE_SCHEMA_VERSION as O, validateUiSpecEvent as Ot, USER_PROFILE_NO_INVENTION_RULE as P, fromVercelResult as Q, appendBehaviorRecords as R, OpenAiCompatibleClient as S, scriptToolName as St, READ_SKILL_FILE_TOOL as T, toLlmToolSpec as Tt, createWebSkillApi as U, buildRenderResult as V, diffUserProfile as W, findUnpairedToolCalls as X, extractUiSpecEvents as Y, formatSkillScriptManifest as Z, FsRunTraceStore as _, renderUserProfileContext as _t, AgentLoop as a, mergeProfileEntries as at, GoogleGenAiClient as b, schemaSourceLabel as bt, CapabilityApproval as c, normalizeErrorCode as ct, EMPTY_USER_PROFILE as d, parseBridgeRequest as dt, interruptedToolResult as et, EventBus as f, parseUserProfileExport as ft, FsRunSnapshotStore as g, refineUserProfile as gt, FsMemoryStore as h, readUserProfile as ht, ASK_USER_TOOL_NAME as i, mergeCatalogEntries as it, TraceRecorder as j, SESSION_SCHEMA_VERSION as k, validateUiSpecNode as kt, DEFAULT_LOOP_LIMITS as l, normalizeToolContent as lt, FsArtifactStore as m, readProfileEntries as mt, ASK_USER_INPUT_SCHEMA as n, isUnsupportedRunSnapshot as nt, AnthropicClient as o, networkPolicyLibSource as ot, FS_SESSION_PAGE_SIZE as p, readBehaviorRecords as pt, extractSkillCandidate as q, ASK_USER_TOOL as r, listSkillScripts as rt, BEHAVIOR_RECORDS_KEY as s, networkUrlHost as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, isNetworkAllowed as tt, DEFAULT_USER_PROFILE_LIMITS as u, normalizeToolError as ut, FsSessionStore as v, resolveToolName as vt, READ_SKILL_FILE_INPUT_SCHEMA as w, summarizeToolCalls as wt, HookRunner as x, schemaToForm as xt, FullDisclosureRouter as y, sampleBehaviorRecords as yt, applyUserProfileImport as z };