@webskill/sdk 0.7.0 → 0.9.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 +1 -1
  2. package/dist/agent.js +1 -1
  3. package/dist/browser.d.ts +151 -4
  4. package/dist/browser.js +269 -30
  5. package/dist/{catalogComponents-Dr5dFMAb-DKH_7VPI.js → catalogComponents-DfxxfUvn-T7Ic8QFV.js} +4026 -997
  6. package/dist/{dist-DnYG2-eY.js → dist-BQe1uglQ.js} +498 -118
  7. package/dist/{dist-8oQRa8Xz.js → dist-Bev6i6Ip.js} +102 -7
  8. package/dist/{dist-D0qW6e40.js → dist-CFmkV45C.js} +234 -27
  9. package/dist/{eventTypes-DjIQpt8Y-Bj3vghj4.js → eventTypes-DbOpAECr-BjcjZVms.js} +15 -2
  10. package/dist/governance.d.ts +24 -3
  11. package/dist/governance.js +30 -11
  12. package/dist/{index-BwsK9lGk.d.ts → index-DXNTIa-6.d.ts} +163 -6
  13. package/dist/{index-Ba3xFtfz.d.ts → index-lLcCpHE-.d.ts} +2 -2
  14. package/dist/{index-DkbABR43.d.ts → index-znZjobkr.d.ts} +144 -76
  15. package/dist/index.d.ts +11 -3
  16. package/dist/index.js +13 -4
  17. package/dist/mcp.d.ts +13 -2
  18. package/dist/mcp.js +19 -5
  19. package/dist/{memoryArtifactStore-52Zn9npI-BMPYwvoy.js → memoryArtifactStore-52Zn9npI-LbCQaqyx.js} +1 -1
  20. package/dist/node.d.ts +3 -3
  21. package/dist/node.js +6 -6
  22. package/dist/{openUiLibrary-Bdrji9qK-D2LxmM-a.js → openUiLibrary-DURlAxjk-Do_yqg3u.js} +3 -3
  23. package/dist/{skillVersionStore-Bl-ElD45-CWPvGvoq.d.ts → skillVersionStore-Bl-ElD45-dMJ8Ybhb.d.ts} +1 -1
  24. package/dist/{testing-CYTFqkDm.js → testing-Csg5ljNm.js} +2 -2
  25. package/dist/testing.d.ts +1 -1
  26. package/dist/testing.js +2 -2
  27. package/dist/{types-CcxRLdJG-DCXyw1US.d.ts → types-B3n0cMZu-BDhheIhX.d.ts} +54 -6
  28. package/dist/ui-react.d.ts +19 -6
  29. package/dist/ui-react.js +156 -84
  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-BG7kL-Es.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 { K as validateSkills, M as messageOf, P as parseSkillMarkdown, R as renderAvailableSkillsXml, V as resolveInsideRoot, _ as assertSafePathSegment, h as WebSkillError, m as SkillReader, p as SkillDiscovery, v as atomicWriteText, y as buildCatalog } from "./dist-Bev6i6Ip.js";
2
+ import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-LbCQaqyx.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: #" ——
@@ -835,10 +897,26 @@ var AnthropicClient = class {
835
897
  }
836
898
  return res;
837
899
  }
838
- /** 轻量探测(GET /v1/models),集成测试据此决定 skip */
900
+ /**
901
+ * 真实调用路径探测(FR-27.1):`POST /v1/messages` 最小请求(max_tokens=1),200 即可达。
902
+ * Anthropic 兼容端点(如 LM Studio 的 /anthropic 层)不实现 `GET /v1/models`,
903
+ * 用 /models 探测会把「能对话」误判为「不可达」——checkAvailability 的语义从
904
+ * 「/models 可达」改为「能完成一次真实调用」。
905
+ */
839
906
  async checkAvailability() {
840
907
  try {
841
- return (await this.#fetch(`${this.#baseUrl()}/v1/models`, { headers: this.#headers() })).ok;
908
+ return (await this.#fetch(`${this.#baseUrl()}/v1/messages`, {
909
+ method: "POST",
910
+ headers: this.#headers(),
911
+ body: JSON.stringify({
912
+ model: this.#config.model,
913
+ max_tokens: 1,
914
+ messages: [{
915
+ role: "user",
916
+ content: "ping"
917
+ }]
918
+ })
919
+ })).ok;
842
920
  } catch {
843
921
  return false;
844
922
  }
@@ -1359,6 +1437,16 @@ const ASK_USER_TOOL = {
1359
1437
  source: "builtin"
1360
1438
  };
1361
1439
  /**
1440
+ * 越权 / 绝对路径 / 父级遍历的引用读取:策略拒绝(FS_PERMISSION_DENIED,
1441
+ * 计入 POLICY_DENIAL_CODES → 不计入隔离失败计数),
1442
+ * 与「references/ 内路径确实不存在 → FS_NOT_FOUND」区分(0.9.0 分册 14,UX-04)。
1443
+ */
1444
+ function assertReferencePath(relativePath) {
1445
+ const trimmed = relativePath.trim();
1446
+ if (trimmed === "" || trimmed.startsWith("/") || trimmed.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(trimmed)) throw new WebSkillError("FS_PERMISSION_DENIED", `Reference access denied: absolute or empty path (path: ${JSON.stringify(relativePath)})`);
1447
+ if (trimmed.replace(/\\/g, "/").split("/").includes("..")) throw new WebSkillError("FS_PERMISSION_DENIED", `Reference access denied: parent traversal (path: ${JSON.stringify(relativePath)})`);
1448
+ }
1449
+ /**
1362
1450
  * 脚本执行上下文:只暴露 readReference / writeArtifact 两个显式能力,
1363
1451
  * 不暴露 fs 本体(沙箱语义,对齐 deferred-items D1)。
1364
1452
  */
@@ -1369,6 +1457,7 @@ function createScriptContext(deps) {
1369
1457
  skillName,
1370
1458
  runId,
1371
1459
  async readReference(relativePath) {
1460
+ assertReferencePath(relativePath);
1372
1461
  return readFs.readText(resolveInsideRoot(skillRoot, `references/${relativePath}`));
1373
1462
  },
1374
1463
  async writeArtifact(path, content, options) {
@@ -1617,7 +1706,9 @@ function schemaToForm(schema, providedArgs) {
1617
1706
  function mapFieldType(prop) {
1618
1707
  if (Array.isArray(prop.enum)) return "select";
1619
1708
  switch (prop.type) {
1620
- case "string": return prop["format"] === "binary" || prop["contentEncoding"] === "base64" ? "file" : "text";
1709
+ case "string":
1710
+ if (prop["format"] === "binary" || prop["contentEncoding"] === "base64") return "file";
1711
+ return prop["format"] === "password" || prop["writeOnly"] === true ? "password" : "text";
1621
1712
  case "number":
1622
1713
  case "integer": return "number";
1623
1714
  case "boolean": return "boolean";
@@ -1965,7 +2056,7 @@ const DEFAULT_LOOP_LIMITS = {
1965
2056
  totalTimeoutMs: 12e4,
1966
2057
  toolTimeoutMs: 3e4
1967
2058
  };
1968
- const RUN_SNAPSHOT_SCHEMA_VERSION = 2;
2059
+ const RUN_SNAPSHOT_SCHEMA_VERSION = 3;
1969
2060
  /** @experimental */
1970
2061
  function isUnsupportedRunSnapshot(entry) {
1971
2062
  return entry.unsupported === true;
@@ -1991,7 +2082,7 @@ var FsRunSnapshotStore = class {
1991
2082
  return resolveInsideRoot(this.#root, `${runId}${SNAPSHOT_SUFFIX}`);
1992
2083
  }
1993
2084
  async save(snapshot) {
1994
- await this.#fs.writeText(this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
2085
+ await atomicWriteText(this.#fs, this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
1995
2086
  await this.#pruneExpired();
1996
2087
  }
1997
2088
  async load(runId) {
@@ -2010,7 +2101,7 @@ var FsRunSnapshotStore = class {
2010
2101
  throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
2011
2102
  }
2012
2103
  const schemaVersion = typeof snapshot.schemaVersion === "number" ? snapshot.schemaVersion : 0;
2013
- 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.`);
2104
+ 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.`);
2014
2105
  if (snapshot.runId !== runId) {
2015
2106
  await this.#fs.remove(path).catch(() => void 0);
2016
2107
  throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
@@ -2030,7 +2121,7 @@ var FsRunSnapshotStore = class {
2030
2121
  try {
2031
2122
  const parsed = JSON.parse(await this.#fs.readText(entry.path));
2032
2123
  const schemaVersion = typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : 0;
2033
- if (schemaVersion === 2) {
2124
+ if (schemaVersion === 3) {
2034
2125
  out.push(parsed);
2035
2126
  continue;
2036
2127
  }
@@ -2143,6 +2234,21 @@ const redactFileValue = (value) => {
2143
2234
  return rest;
2144
2235
  };
2145
2236
  /**
2237
+ * 密码字段名集合(FR-23.7)。
2238
+ *
2239
+ * 既有的三处脱敏各有各的判别口径(看 request.type、看值是否标量、看值里有没有 data/mimeType),
2240
+ * 没有一处按**字段类型**判。行为记录那处尤其危险:它的安全假设是「标量是安全的」,
2241
+ * 而密码恰好是字符串标量——假设失效了。
2242
+ */
2243
+ const passwordFieldNames = (request) => new Set(request.type === "form" ? request.fields.filter((f) => f.type === "password").map((f) => f.name) : []);
2244
+ /** 去掉密码字段的值;不是掩码而是**不存**,掩码存下来仍然泄露长度与存在性 */
2245
+ const withoutPasswords = (value, passwords) => {
2246
+ if (passwords.size === 0 || typeof value !== "object" || value === null || Array.isArray(value)) return value;
2247
+ const out = {};
2248
+ for (const [key, entry] of Object.entries(value)) if (!passwords.has(key)) out[key] = entry;
2249
+ return out;
2250
+ };
2251
+ /**
2146
2252
  * 触顶失败的结构化细节(FR-21.2)。由终止原因推出上限字段,而不是在每个终止点各写一遍——
2147
2253
  * 终止点有四个,写四遍就迟早有一处对不上。
2148
2254
  */
@@ -2156,6 +2262,16 @@ const limitDetails = (state, reason) => {
2156
2262
  value: state.maxTurns
2157
2263
  };
2158
2264
  };
2265
+ /** 补齐消息里带的错误码:能说清「为什么没结果」的,就不要退化成 RUN_INTERRUPTED(设计 11 §2.5) */
2266
+ const sealCodeFor = (reason, errorCode) => {
2267
+ switch (reason) {
2268
+ case "interaction-timeout": return "RUN_INTERACTION_TIMEOUT";
2269
+ case "user-cancelled": return "RUN_CANCELLED";
2270
+ case "timeout": return "RUN_TIMEOUT";
2271
+ case "tool-resolution-exhausted": return "TOOL_RESOLUTION_EXHAUSTED";
2272
+ default: return errorCode ?? "RUN_INTERRUPTED";
2273
+ }
2274
+ };
2159
2275
  /**
2160
2276
  * 多轮 Agent 循环。
2161
2277
  * 工具失败一律转为 ToolResult{ok:false} 回喂 LLM 继续循环;
@@ -2487,6 +2603,7 @@ var AgentLoop = class {
2487
2603
  } catch (e) {
2488
2604
  trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
2489
2605
  }
2606
+ await this.#sealToolCalls(state, reason, errorCode);
2490
2607
  run.trace = trace.list();
2491
2608
  return {
2492
2609
  output,
@@ -2494,12 +2611,45 @@ var AgentLoop = class {
2494
2611
  messages: state.messages.map((m) => ({ ...m }))
2495
2612
  };
2496
2613
  }
2614
+ /**
2615
+ * S1 出口守卫(FR-11.1):中断留下的未应答工具调用在这里补齐。
2616
+ *
2617
+ * 放在 `#finish` 而不是四个早退点:它是唯一的汇流出口,写四遍就迟早漏一处。
2618
+ * 补齐产物只进 `LlmMessage[]`(下一轮请求装配用),不进 chatbot 的消息流(AC-11.2)。
2619
+ */
2620
+ async #sealToolCalls(state, reason, errorCode) {
2621
+ const pending = findUnpairedToolCalls(state.messages);
2622
+ if (pending.length === 0) return;
2623
+ const code = sealCodeFor(reason, errorCode);
2624
+ for (const call of pending) {
2625
+ const result = interruptedToolResult(call, {
2626
+ code,
2627
+ reason
2628
+ });
2629
+ state.messages.push({
2630
+ role: "tool",
2631
+ toolCallId: call.id,
2632
+ content: await this.#toolResultParts(call, result, state)
2633
+ });
2634
+ state.trace.record("run.warning", {
2635
+ message: `Sealed unanswered tool call "${call.name}": the run ended before it produced a result.`,
2636
+ data: {
2637
+ kind: "tool-call-sealed",
2638
+ callId: call.id,
2639
+ toolName: call.name,
2640
+ side: "write",
2641
+ reason,
2642
+ code
2643
+ }
2644
+ });
2645
+ }
2646
+ }
2497
2647
  /** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
2498
2648
  async #saveSnapshot(state, pending) {
2499
2649
  const store = this.#deps.snapshotStore;
2500
2650
  if (!store) return;
2501
2651
  const snapshot = {
2502
- schemaVersion: 2,
2652
+ schemaVersion: 3,
2503
2653
  runId: state.runId,
2504
2654
  sessionId: state.run.sessionId,
2505
2655
  userPrompt: state.run.userPrompt,
@@ -2674,6 +2824,15 @@ var AgentLoop = class {
2674
2824
  throw e;
2675
2825
  }
2676
2826
  }
2827
+ /**
2828
+ * 工具的技能归属。内置工具与外部工具没有归属,返回 `undefined`——
2829
+ * 拿 `activated[0]` 顶替会把内置工具的失败栽给一个无关技能,比不归因更糟(设计 26 §2.3)。
2830
+ */
2831
+ #skillOf(call, state) {
2832
+ if (call.name === "read_skill_file" || call.name === "ask_user") return void 0;
2833
+ const resolution = resolveToolName(call.name, state.activated, state.activatedTools.keys());
2834
+ return resolution.kind === "script" ? resolution.skillName : void 0;
2835
+ }
2677
2836
  /** 最后一条 assistant 消息里第一个尚无 tool 响应的工具调用(即中断时正在处理的那个) */
2678
2837
  #findPendingToolCall(messages) {
2679
2838
  const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" && m.toolCalls?.length);
@@ -2864,11 +3023,14 @@ var AgentLoop = class {
2864
3023
  }
2865
3024
  async #executeCall(call, state) {
2866
3025
  const argsSummary = summarizeArgs(call.arguments);
3026
+ const owner = this.#skillOf(call, state);
3027
+ const attribution = owner === void 0 ? {} : { skillName: owner };
2867
3028
  const callStartMs = Date.parse(state.now());
2868
3029
  state.trace.record("tool.started", { data: {
2869
3030
  name: call.name,
2870
3031
  callId: call.id,
2871
- args: argsSummary
3032
+ args: argsSummary,
3033
+ ...attribution
2872
3034
  } });
2873
3035
  this.#emitTool(state, "started", call);
2874
3036
  let result;
@@ -2894,7 +3056,8 @@ var AgentLoop = class {
2894
3056
  name: call.name,
2895
3057
  callId: call.id,
2896
3058
  args: argsSummary,
2897
- durationMs
3059
+ durationMs,
3060
+ ...attribution
2898
3061
  } });
2899
3062
  this.#emitTool(state, "completed", call);
2900
3063
  for (const item of result.content) {
@@ -2928,7 +3091,8 @@ var AgentLoop = class {
2928
3091
  callId: call.id,
2929
3092
  code: result.error?.code,
2930
3093
  args: argsSummary,
2931
- durationMs
3094
+ durationMs,
3095
+ ...attribution
2932
3096
  }
2933
3097
  });
2934
3098
  this.#emitTool(state, "failed", call, result.error?.code);
@@ -3095,10 +3259,17 @@ var AgentLoop = class {
3095
3259
  async #replaySurfaceEvents(state) {
3096
3260
  const bridge = this.#deps.uiBridge;
3097
3261
  if (!bridge?.renderSurface || state.surfaceEvents.length === 0) return;
3098
- try {
3099
- for (const event of state.surfaceEvents) await bridge.renderSurface(structuredClone(event));
3262
+ for (const event of state.surfaceEvents) try {
3263
+ await bridge.renderSurface(structuredClone(event));
3100
3264
  } catch (e) {
3101
- state.trace.record("run.warning", { message: `Failed to replay UI surfaces: ${messageOf(e)}` });
3265
+ state.trace.record("ui.degraded", {
3266
+ message: `Failed to replay UI surface "${event.id}": ${messageOf(e)}`,
3267
+ data: {
3268
+ kind: "replay-failed",
3269
+ surfaceId: event.id,
3270
+ eventType: event.type
3271
+ }
3272
+ });
3102
3273
  }
3103
3274
  }
3104
3275
  /**
@@ -3662,7 +3833,8 @@ var AgentLoop = class {
3662
3833
  if (!this.#deps.memory) return;
3663
3834
  const scope = `session:${state.run.sessionId}`;
3664
3835
  const limit = this.#config.paramHistoryLimit;
3665
- const recorded = request.type === "file-pick" ? redactFileValue(value) : value;
3836
+ const passwords = passwordFieldNames(request);
3837
+ const recorded = request.type === "file-pick" ? redactFileValue(value) : withoutPasswords(value, passwords);
3666
3838
  await this.#memoryMutate(scope, "paramHistory", state, (current) => {
3667
3839
  const history = current ?? [];
3668
3840
  history.push({
@@ -3729,6 +3901,7 @@ var AgentLoop = class {
3729
3901
  else if (request.type === "form" && typeof value === "object" && value !== null) {
3730
3902
  const submitted = value;
3731
3903
  for (const field of request.fields) {
3904
+ if (field.type === "password") continue;
3732
3905
  const next = submitted[field.name];
3733
3906
  if (typeof next !== "string" && typeof next !== "number" && typeof next !== "boolean") continue;
3734
3907
  if (next === "") continue;
@@ -3882,6 +4055,7 @@ var WebSkillRuntime = class {
3882
4055
  if (!this.#catalogCache) await this.discover();
3883
4056
  const cache = this.#catalogCache;
3884
4057
  if (!cache) throw new Error("discover() did not populate the catalog cache");
4058
+ const sealedHistory = options.history ? sealToolCallPairs(options.history) : void 0;
3885
4059
  const providerFailures = [];
3886
4060
  const providerEntries = (await Promise.all((this.#deps.skillProviders ?? []).map(async (p) => {
3887
4061
  try {
@@ -3928,11 +4102,25 @@ var WebSkillRuntime = class {
3928
4102
  userPrompt,
3929
4103
  route,
3930
4104
  runId,
3931
- ...options.history ? { history: options.history } : {}
4105
+ ...sealedHistory ? { history: sealedHistory.messages } : {}
3932
4106
  });
3933
4107
  } finally {
3934
4108
  this.#loops.delete(runId);
3935
4109
  }
4110
+ for (const record of sealedHistory?.sealed ?? []) result.run.trace.push({
4111
+ id: `evt-seal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4112
+ runId: result.run.id,
4113
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4114
+ type: "run.warning",
4115
+ message: `Sealed unanswered tool call "${record.toolName}" carried over from an earlier interrupted run.`,
4116
+ data: {
4117
+ kind: "tool-call-sealed",
4118
+ callId: record.callId,
4119
+ toolName: record.toolName,
4120
+ side: "read",
4121
+ code: record.code
4122
+ }
4123
+ });
3936
4124
  for (const failure of providerFailures) result.run.trace.push({
3937
4125
  id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
3938
4126
  runId: result.run.id,
@@ -4237,7 +4425,7 @@ var FsMemoryStore = class {
4237
4425
  }
4238
4426
  }
4239
4427
  async set(scope, key, value) {
4240
- await this.#fs.writeText(this.#keyPath(scope, key), JSON.stringify(value, null, 2));
4428
+ await atomicWriteText(this.#fs, this.#keyPath(scope, key), JSON.stringify(value, null, 2));
4241
4429
  }
4242
4430
  async delete(scope, key) {
4243
4431
  const path = this.#keyPath(scope, key);
@@ -4428,7 +4616,6 @@ function parseUserProfileExport(raw) {
4428
4616
  entries
4429
4617
  };
4430
4618
  }
4431
- /** 导入前给用户看的差异(FR-19.7):新增哪些、覆盖哪些 @experimental */
4432
4619
  function diffUserProfile(current, incoming) {
4433
4620
  const byId = new Map(current.entries.map((entry) => [entry.id, entry]));
4434
4621
  const added = [];
@@ -4474,7 +4661,7 @@ var FsArtifactStore = class {
4474
4661
  }
4475
4662
  async createTextArtifact(input) {
4476
4663
  const size = new TextEncoder().encode(input.content).length;
4477
- await this.#fs.writeText(this.#artifactPath(input.runId, input.path), input.content);
4664
+ await atomicWriteText(this.#fs, this.#artifactPath(input.runId, input.path), input.content);
4478
4665
  return this.#persist({
4479
4666
  ...input,
4480
4667
  type: "text",
@@ -4524,7 +4711,7 @@ var FsArtifactStore = class {
4524
4711
  metadata: input.metadata
4525
4712
  };
4526
4713
  const next = [...(await this.listArtifacts(input.runId)).filter((a) => a.id !== artifact.id), artifact];
4527
- await this.#fs.writeText(`${this.#root}/${input.runId}/${INDEX_FILE$1}`, JSON.stringify({ artifacts: next }, null, 2));
4714
+ await atomicWriteText(this.#fs, `${this.#root}/${input.runId}/${INDEX_FILE$1}`, JSON.stringify({ artifacts: next }, null, 2));
4528
4715
  return artifact;
4529
4716
  }
4530
4717
  };
@@ -4834,7 +5021,7 @@ var FsRunTraceStore = class {
4834
5021
  events: run.trace
4835
5022
  };
4836
5023
  try {
4837
- await this.#fs.writeText(this.#path(run.id), JSON.stringify(trace, null, 2));
5024
+ await atomicWriteText(this.#fs, this.#path(run.id), JSON.stringify(trace, null, 2));
4838
5025
  } catch (e) {
4839
5026
  this.#onError(e, run);
4840
5027
  return;
@@ -4990,6 +5177,7 @@ const toMeta = (record) => ({
4990
5177
  ...record.title !== void 0 ? { title: record.title } : {},
4991
5178
  ...record.titleLocked === true ? { titleLocked: true } : {},
4992
5179
  ...record.archived === true ? { archived: true } : {},
5180
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
4993
5181
  messageCount: record.messages.length
4994
5182
  });
4995
5183
  const newSessionId = () => `session-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -5036,6 +5224,7 @@ function parseSessionFile(raw, path) {
5036
5224
  ...typeof file.title === "string" ? { title: file.title } : {},
5037
5225
  ...file.titleLocked === true ? { titleLocked: true } : {},
5038
5226
  ...file.archived === true ? { archived: true } : {},
5227
+ ...typeof file.modelId === "string" ? { modelId: file.modelId } : {},
5039
5228
  messages: Array.isArray(file.messages) ? file.messages : [],
5040
5229
  messageCount: Array.isArray(file.messages) ? file.messages.length : 0
5041
5230
  };
@@ -5051,6 +5240,8 @@ var FsSessionStore = class {
5051
5240
  #fs;
5052
5241
  /** id → 该 id 上最后一次变更的完成时点,用于串行化读改写 */
5053
5242
  #writes = /* @__PURE__ */ new Map();
5243
+ /** FR-11.4:损坏文件诊断只报一次,避免 list 轮询刷屏 */
5244
+ #warnedDataLoss = false;
5054
5245
  constructor(deps) {
5055
5246
  this.#root = deps.root.replace(/\/+$/, "");
5056
5247
  this.#fs = deps.fs;
@@ -5077,9 +5268,10 @@ var FsSessionStore = class {
5077
5268
  ...record.title !== void 0 ? { title: record.title } : {},
5078
5269
  ...record.titleLocked === true ? { titleLocked: true } : {},
5079
5270
  ...record.archived === true ? { archived: true } : {},
5271
+ ...record.modelId !== void 0 ? { modelId: record.modelId } : {},
5080
5272
  messages: record.messages
5081
5273
  };
5082
- await this.#fs.writeText(this.#path(record.id), JSON.stringify(file, null, 2));
5274
+ await atomicWriteText(this.#fs, this.#path(record.id), JSON.stringify(file, null, 2));
5083
5275
  }
5084
5276
  async #require(id) {
5085
5277
  const record = await this.get(id);
@@ -5099,14 +5291,17 @@ var FsSessionStore = class {
5099
5291
  let record;
5100
5292
  try {
5101
5293
  record = parseSessionFile(await this.#fs.readText(entry.path), entry.path);
5102
- } catch (e) {
5103
- console.warn(`Skipping unreadable session file "${entry.path}": ${e instanceof Error ? e.message : String(e)}`);
5294
+ } catch {
5104
5295
  skipped.push(baseName(entry.path));
5105
5296
  continue;
5106
5297
  }
5107
5298
  if (record.archived === true && options.includeArchived !== true) continue;
5108
5299
  metas.push(toMeta(record));
5109
5300
  }
5301
+ if (skipped.length > 0 && !this.#warnedDataLoss) {
5302
+ this.#warnedDataLoss = true;
5303
+ console.warn(`[webskill] ${skipped.length} session file(s) under ${this.#root} are unreadable (possible data loss from an interrupted write): ${skipped.join(", ")}`);
5304
+ }
5110
5305
  metas.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
5111
5306
  return {
5112
5307
  ...takeTailPage(metas, options, "session"),
@@ -5170,6 +5365,18 @@ var FsSessionStore = class {
5170
5365
  await this.#write(record);
5171
5366
  });
5172
5367
  }
5368
+ async setModel(id, modelId) {
5369
+ await this.#serialize(id, async () => {
5370
+ const record = await this.#require(id);
5371
+ if (modelId === void 0) delete record.modelId;
5372
+ else record.modelId = modelId;
5373
+ await this.#write(record);
5374
+ });
5375
+ }
5376
+ async getMeta(id) {
5377
+ const record = await this.get(id);
5378
+ return record === void 0 ? void 0 : toMeta(record);
5379
+ }
5173
5380
  async delete(id) {
5174
5381
  await this.#serialize(id, async () => {
5175
5382
  const path = this.#path(id);
@@ -5179,4 +5386,4 @@ var FsSessionStore = class {
5179
5386
  };
5180
5387
 
5181
5388
  //#endregion
5182
- export { isNetworkAllowed as $, SerializingMemoryStore as A, bridgeError as B, ProgressiveRouter as C, toRecordDigests as Ct, RUN_SNAPSHOT_SCHEMA_VERSION as D, READ_SKILL_FILE_TOOL_NAME as E, validateUiSpecNode 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, USER_PROFILE_NO_INVENTION_RULE as P, fromVercelStreamPart as Q, appendBehaviorRecords as R, OpenAiCompatibleClient as S, toLlmToolSpec as St, READ_SKILL_FILE_TOOL as T, validateUiSpecEvent as Tt, createWebSkillApi as U, buildRenderResult as V, diffUserProfile as W, formatSkillScriptManifest as X, extractUiSpecEvents as Y, fromVercelResult as Z, FsRunTraceStore as _, sampleBehaviorRecords as _t, AgentLoop as a, networkUrlHost as at, GoogleGenAiClient as b, scriptToolName as bt, CapabilityApproval as c, normalizeToolError as ct, EMPTY_USER_PROFILE as d, readBehaviorRecords as dt, isUnsupportedRunSnapshot as et, EventBus as f, readProfileEntries as ft, FsRunSnapshotStore as g, resolveToolName as gt, FsMemoryStore as h, renderUserProfileContext as ht, ASK_USER_TOOL_NAME as i, networkPolicyLibSource as it, TraceRecorder as j, SESSION_SCHEMA_VERSION as k, DEFAULT_LOOP_LIMITS as l, parseBridgeRequest as lt, FsArtifactStore as m, refineUserProfile as mt, ASK_USER_INPUT_SCHEMA as n, mergeCatalogEntries as nt, AnthropicClient as o, normalizeErrorCode as ot, FS_SESSION_PAGE_SIZE as p, readUserProfile as pt, extractSkillCandidate as q, ASK_USER_TOOL as r, mergeProfileEntries as rt, BEHAVIOR_RECORDS_KEY as s, normalizeToolContent as st, ALLOWED_TOOLS_EXCLUSION_REASON as t, listSkillScripts as tt, DEFAULT_USER_PROFILE_LIMITS as u, parseUserProfileExport as ut, FsSessionStore as v, schemaSourceLabel as vt, READ_SKILL_FILE_INPUT_SCHEMA as w, toVercelToolSpecs as wt, HookRunner as x, summarizeToolCalls as xt, FullDisclosureRouter as y, schemaToForm as yt, applyUserProfileImport as z };
5389
+ 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 };
@@ -1,4 +1,4 @@
1
- //#region ../governance/dist/eventTypes-DjIQpt8Y.js
1
+ //#region ../governance/dist/eventTypes-DbOpAECr.js
2
2
  /**
3
3
  * 审计事件类型常量(FR-13.4)。
4
4
  *
@@ -17,13 +17,26 @@ const AUDIT_EVENT_TYPES = {
17
17
  skillPolicyDenied: "skill.policy_denied",
18
18
  skillUninstalled: "skill.uninstalled",
19
19
  skillInstalled: "skill.installed",
20
+ skillExported: "skill.exported",
21
+ skillRenamed: "skill.renamed",
22
+ skillVersioned: "skill.versioned",
20
23
  trustKeyAdded: "trust.key_added",
21
24
  trustKeyRemoved: "trust.key_removed",
22
25
  policyNetworkChanged: "policy.network_changed",
23
26
  policyPrivacyChanged: "policy.privacy_changed",
27
+ policySecurityChanged: "policy.security_changed",
28
+ policySandboxChanged: "policy.sandbox_changed",
29
+ policyAgentCapabilitiesChanged: "policy.agent_capabilities_changed",
30
+ profileChanged: "profile.changed",
24
31
  mcpEndpointChanged: "mcp.endpoint_changed",
25
32
  mcpPolicyRelaxed: "mcp.policy_relaxed",
26
- providerChanged: "provider.changed"
33
+ webmcpEnabled: "webmcp.enabled",
34
+ providerChanged: "provider.changed",
35
+ candidateCreated: "candidate.created",
36
+ candidateGenerated: "candidate.generated",
37
+ candidateSubmitted: "candidate.submitted",
38
+ candidateApproved: "candidate.approved",
39
+ candidateRejected: "candidate.rejected"
27
40
  };
28
41
  /** 稳定展示序:筛选下拉与文档表格都从这里取,不各自维护一份顺序 */
29
42
  const AUDIT_EVENT_TYPE_LIST = Object.values(AUDIT_EVENT_TYPES);
@@ -1,6 +1,6 @@
1
- import { H as PageQuery, I as FileSystemProvider, V as Page, et as SkillCatalogEntry, f as LlmMessage, l as LlmClient, nt as SkillDocument, ot as SkillManagerPort, x as UiBridge } from "./types-CcxRLdJG-DCXyw1US.js";
2
- import { H as IntegrityVerdict, Nt as SkillIntegrityGuard, Pt as SkillOutcomeReporter, Rt as SkillStateGuard, bt as RuntimeRun, ln as WebSkillRuntime } from "./index-DkbABR43.js";
3
- import { _ as SkillVersion, a as AuditLog, c as CandidateFile, d as CandidateSource, f as CandidateStatus, g as SkillState, h as SKILL_VERSION_PAGE_SIZE, i as AuditEvent, l as CandidateRisk, m as CompositeApprovalPolicy, n as ApprovalDecision, o as AuditQueryFilter, p as CandidateStore, r as ApprovalPolicy, s as CANDIDATE_PAGE_SIZE, t as AlwaysHumanApprovalPolicy, u as CandidateSkill, v as SkillVersionStore, y as candidateToCatalogEntry } from "./skillVersionStore-Bl-ElD45-CWPvGvoq.js";
1
+ import { H as Page, L as FileSystemProvider, U as PageQuery, at as SkillDocument, f as LlmMessage, l as LlmClient, lt as SkillManagerPort, rt as SkillCatalogEntry, x as UiBridge } from "./types-B3n0cMZu-BDhheIhX.js";
2
+ import { H as IntegrityVerdict, It as SkillIntegrityGuard, Lt as SkillOutcomeReporter, Vt as SkillStateGuard, bt as RuntimeRun, pn as WebSkillRuntime } from "./index-znZjobkr.js";
3
+ import { _ as SkillVersion, a as AuditLog, c as CandidateFile, d as CandidateSource, f as CandidateStatus, g as SkillState, h as SKILL_VERSION_PAGE_SIZE, i as AuditEvent, l as CandidateRisk, m as CompositeApprovalPolicy, n as ApprovalDecision, o as AuditQueryFilter, p as CandidateStore, r as ApprovalPolicy, s as CANDIDATE_PAGE_SIZE, t as AlwaysHumanApprovalPolicy, u as CandidateSkill, v as SkillVersionStore, y as candidateToCatalogEntry } from "./skillVersionStore-Bl-ElD45-dMJ8Ybhb.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/candidate/candidateNormalizer.d.ts
6
6
  /** 剥 markdown fence 与 <think> 块、截取首尾 {};非对象 → CANDIDATE_INVALID */
@@ -110,6 +110,14 @@ interface AuditChainVerification {
110
110
  interface AuditPage extends Page<AuditEvent> {
111
111
  chainBrokenAt?: number;
112
112
  chainReason?: string;
113
+ /**
114
+ * 解析失败的行。长度 > 0 即「读不动」,但已解析的记录仍在 `items` 里——
115
+ * 不给这个字段的话,坏行被吞掉后的返回值与「真的没有事件」完全一样(FR-18.3)。
116
+ */
117
+ malformed?: {
118
+ count: number;
119
+ firstLine: number;
120
+ };
113
121
  }
114
122
  /**
115
123
  * JSONL 追加式审计日志(<managedRoot>/.webskill/audit.jsonl):
@@ -167,13 +175,26 @@ declare const AUDIT_EVENT_TYPES: {
167
175
  readonly skillPolicyDenied: "skill.policy_denied";
168
176
  readonly skillUninstalled: "skill.uninstalled";
169
177
  readonly skillInstalled: "skill.installed";
178
+ readonly skillExported: "skill.exported";
179
+ readonly skillRenamed: "skill.renamed";
180
+ readonly skillVersioned: "skill.versioned";
170
181
  readonly trustKeyAdded: "trust.key_added";
171
182
  readonly trustKeyRemoved: "trust.key_removed";
172
183
  readonly policyNetworkChanged: "policy.network_changed";
173
184
  readonly policyPrivacyChanged: "policy.privacy_changed";
185
+ readonly policySecurityChanged: "policy.security_changed";
186
+ readonly policySandboxChanged: "policy.sandbox_changed";
187
+ readonly policyAgentCapabilitiesChanged: "policy.agent_capabilities_changed";
188
+ readonly profileChanged: "profile.changed";
174
189
  readonly mcpEndpointChanged: "mcp.endpoint_changed";
175
190
  readonly mcpPolicyRelaxed: "mcp.policy_relaxed";
191
+ readonly webmcpEnabled: "webmcp.enabled";
176
192
  readonly providerChanged: "provider.changed";
193
+ readonly candidateCreated: "candidate.created";
194
+ readonly candidateGenerated: "candidate.generated";
195
+ readonly candidateSubmitted: "candidate.submitted";
196
+ readonly candidateApproved: "candidate.approved";
197
+ readonly candidateRejected: "candidate.rejected";
177
198
  };
178
199
  type AuditEventType = (typeof AUDIT_EVENT_TYPES)[keyof typeof AUDIT_EVENT_TYPES];
179
200
  /** 稳定展示序:筛选下拉与文档表格都从这里取,不各自维护一份顺序 */