@webskill/sdk 0.2.3 → 0.2.5

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.
@@ -1,4 +1,4 @@
1
- import { u as WebSkillError } from "./dist-D0saNPi_.js";
1
+ import { u as WebSkillError } from "./dist-BQzncxXg.js";
2
2
 
3
3
  //#region ../ui/dist/index.js
4
4
  /** 提交值按请求类型归形(WebFormBridge 与框架组件库共享单一来源) */
@@ -78,8 +78,16 @@ function interactionToFormModel(request) {
78
78
  }
79
79
  const isEmpty = (v) => v === void 0 || v === "";
80
80
  /**
81
+ * 控件名 → 属性选择器:优先 CSS.escape(标识符形式,免引号转义);
82
+ * 无 CSS.escape 的环境退化为引号包裹 + 转义反斜杠/双引号(防选择器注入崩溃)
83
+ */
84
+ function controlSelector(name) {
85
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") return `[data-webskill-control=${CSS.escape(name)}]`;
86
+ return `[data-webskill-control="${name.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"]`;
87
+ }
88
+ /**
81
89
  * 严格类型化控件值收集:
82
- * - number 空串 → undefined(非 0)
90
+ * - number 空串 → undefined(非 0);非数值输入(NaN)按缺失处理(required 时入 missingRequired
83
91
  * - select 用 option 值本身经 JSON 编码比对命中(对象值可正确命中)
84
92
  * - required 空值列入 missingRequired(由调用方阻止提交并标记)
85
93
  */
@@ -87,13 +95,17 @@ function collectValues(controls, container) {
87
95
  const values = {};
88
96
  const missingRequired = [];
89
97
  for (const control of controls) {
90
- const el = container.querySelector(`[data-webskill-control="${control.name}"]`);
98
+ const el = container.querySelector(controlSelector(control.name));
91
99
  let value;
92
100
  if (el === null) value = void 0;
93
101
  else if (control.control === "boolean") value = el.checked;
94
102
  else if (control.control === "number") {
95
103
  const raw = el.value.trim();
96
- value = raw === "" ? void 0 : Number(raw);
104
+ if (raw === "") value = void 0;
105
+ else {
106
+ const n = Number(raw);
107
+ value = Number.isNaN(n) ? void 0 : n;
108
+ }
97
109
  } else if (control.control === "select") {
98
110
  const attr = el.value;
99
111
  const hit = control.options?.find((o) => JSON.stringify(o.value) === attr);
@@ -736,12 +748,15 @@ var VercelUiBridge = class {
736
748
  * - 每行一条语句 `identifier = Expression`,`root = Component(...)` 为入口
737
749
  * - 组件调用参数为位置参数;字符串双引号反斜杠转义
738
750
  * - 组件词汇(与应用 Library 约定):Form(title, children)、
751
+ * Text(content)(纯展示文本,authorize 的能力描述用)、
739
752
  * TextField(name, label, required?, defaultValue?)、NumberField、BooleanField(name, label, default?)、
740
753
  * SelectField(name, label, options)、SubmitButton(label, actionType, requestId)、
741
754
  * CancelButton(label, actionType, requestId)
742
755
  */
743
756
  const OPENUI_SUBMIT_ACTION = "webskill:submit";
744
757
  const OPENUI_CANCEL_ACTION = "webskill:cancel";
758
+ /** authorize 的 Allow 专用动作(提交即批准 → value:true;Deny 走 cancel → cancelled:true) */
759
+ const OPENUI_AUTHORIZE_ACTION = "webskill:authorize";
745
760
  const str = (s) => JSON.stringify(s);
746
761
  const val = (v) => JSON.stringify(v ?? null);
747
762
  function fieldStatements(fields) {
@@ -808,18 +823,19 @@ function toOpenUiLang(request) {
808
823
  break;
809
824
  case "authorize":
810
825
  title = "Authorization required";
811
- fields = [{
812
- name: "approved",
813
- label: request.message,
814
- control: "boolean",
815
- defaultValue: false
816
- }];
826
+ fields = [];
817
827
  break;
818
828
  }
819
829
  lines.push(...fieldStatements(fields));
820
830
  const children = fields.map((_, i) => `f${i}`);
821
- lines.push(`submit = SubmitButton("Submit", ${str(OPENUI_SUBMIT_ACTION)}, ${str(request.id)})`);
822
- lines.push(`cancel = CancelButton("Cancel", ${str(OPENUI_CANCEL_ACTION)}, ${str(request.id)})`);
831
+ const isAuthorize = request.type === "authorize";
832
+ if (isAuthorize) {
833
+ lines.push(`message = Text(${str(request.message)})`);
834
+ children.push("message");
835
+ }
836
+ const submitAction = isAuthorize ? OPENUI_AUTHORIZE_ACTION : OPENUI_SUBMIT_ACTION;
837
+ lines.push(`submit = SubmitButton(${str(isAuthorize ? "Allow" : "Submit")}, ${str(submitAction)}, ${str(request.id)})`);
838
+ lines.push(`cancel = CancelButton(${str(isAuthorize ? "Deny" : "Cancel")}, ${str(OPENUI_CANCEL_ACTION)}, ${str(request.id)})`);
823
839
  lines.push(`root = Form(${val(title)}, [${[
824
840
  ...children,
825
841
  "submit",
@@ -838,6 +854,10 @@ function fromOpenUiAction(action) {
838
854
  id,
839
855
  cancelled: true
840
856
  };
857
+ if (a.type === "webskill:authorize") return {
858
+ id,
859
+ value: true
860
+ };
841
861
  if (a.type === "webskill:submit") {
842
862
  const formState = a.formState;
843
863
  if (formState) return {
@@ -1002,20 +1022,70 @@ function toA2uiMessages(request) {
1002
1022
  return messages;
1003
1023
  }
1004
1024
  /**
1005
- * A2UI client→server action 事件 → InteractionResponse
1025
+ * InteractionRequest.type 解码提交值(绑定模型原样回传的是表单对象):
1026
+ * confirm 仅 confirmed===true 批准;select 还原原始 option 值(非字符串值经 JSON 编码比对);
1027
+ * form 的 number 字段转 number(NaN 保留原值);ask 取 answer;authorize 提交即批准。
1028
+ * 纯转换器:任何直接使用 fromA2uiAction 的消费者都应经此拿到正确类型。
1029
+ * @experimental
1030
+ */
1031
+ function decodeInteractionResponse(request, response) {
1032
+ if (response.cancelled) return response;
1033
+ const values = response.value;
1034
+ switch (request.type) {
1035
+ case "confirm": return {
1036
+ ...response,
1037
+ value: values?.confirmed === true
1038
+ };
1039
+ case "ask": return {
1040
+ ...response,
1041
+ value: values?.answer
1042
+ };
1043
+ case "select": {
1044
+ const raw = values?.selected;
1045
+ const option = request.options.find((o) => o.value === raw || JSON.stringify(o.value) === raw);
1046
+ return {
1047
+ ...response,
1048
+ value: option ? option.value : raw
1049
+ };
1050
+ }
1051
+ case "form": {
1052
+ if (typeof values !== "object" || values === null) return response;
1053
+ const out = { ...values };
1054
+ for (const field of request.fields) if (field.type === "number" && out[field.name] !== void 0) {
1055
+ const n = Number(out[field.name]);
1056
+ if (!Number.isNaN(n)) out[field.name] = n;
1057
+ }
1058
+ return {
1059
+ ...response,
1060
+ value: out
1061
+ };
1062
+ }
1063
+ case "authorize": return {
1064
+ ...response,
1065
+ value: true
1066
+ };
1067
+ default: return response;
1068
+ }
1069
+ }
1070
+ /**
1071
+ * A2UI client→server action 事件 → InteractionResponse。
1072
+ * 传入 request 时按类型解码提交值(见 decodeInteractionResponse)。
1006
1073
  * @experimental
1007
1074
  */
1008
- function fromA2uiAction(event) {
1075
+ function fromA2uiAction(event, request) {
1009
1076
  const e = event ?? {};
1010
1077
  const id = String(e.context?.["requestId"] ?? "");
1011
1078
  if (e.name === "webskill:cancel") return {
1012
1079
  id,
1013
1080
  cancelled: true
1014
1081
  };
1015
- if (e.name === "webskill:submit") return {
1016
- id,
1017
- value: e.context?.["values"] ?? e.context?.["form"] ?? e.context
1018
- };
1082
+ if (e.name === "webskill:submit") {
1083
+ const response = {
1084
+ id,
1085
+ value: e.context?.["values"] ?? e.context?.["form"] ?? e.context
1086
+ };
1087
+ return request ? decodeInteractionResponse(request, response) : response;
1088
+ }
1019
1089
  return {
1020
1090
  id,
1021
1091
  value: e.context
@@ -1041,6 +1111,8 @@ function loadA2uiRuntime() {
1041
1111
  var LitRendererBridge = class {
1042
1112
  #mount;
1043
1113
  #doc;
1114
+ /** 进行中的交互:id → resolve 与 surface 元素(cancel 时清理并以 cancelled resolve) */
1115
+ #pending = /* @__PURE__ */ new Map();
1044
1116
  constructor(deps) {
1045
1117
  this.#mount = deps.mount;
1046
1118
  this.#doc = deps.document ?? deps.mount.ownerDocument;
@@ -1050,75 +1122,42 @@ var LitRendererBridge = class {
1050
1122
  const messages = toA2uiMessages(input);
1051
1123
  const surfaceId = `webskill-${input.id}`;
1052
1124
  return new Promise((resolve) => {
1053
- const cleanup = (el) => el?.remove();
1054
- let surfaceEl;
1125
+ const entry = { resolve };
1126
+ this.#pending.set(input.id, entry);
1055
1127
  const processor = new MessageProcessor([basicCatalog], (action) => {
1056
1128
  const surfaces = processor.getClientDataModel("v0.9.1")?.surfaces;
1057
1129
  const formValues = typeof surfaces?.[surfaceId] === "object" && surfaces[surfaceId] !== null ? surfaces[surfaceId]["form"] ?? action.context : action.context;
1058
- cleanup(surfaceEl);
1059
- const response = fromA2uiAction({
1130
+ this.#pending.delete(input.id);
1131
+ entry.surfaceEl?.remove();
1132
+ resolve(fromA2uiAction({
1060
1133
  ...action,
1061
1134
  context: {
1062
1135
  ...action.context,
1063
1136
  values: formValues
1064
1137
  }
1065
- });
1066
- resolve(this.#decodeValue(input, response));
1138
+ }, input));
1067
1139
  }, { version: "v0.9.1" });
1068
1140
  processor.onSurfaceCreated((surface) => {
1069
1141
  const el = this.#doc.createElement("a2ui-surface");
1070
1142
  el.surface = surface;
1071
- surfaceEl = el;
1143
+ entry.surfaceEl = el;
1072
1144
  this.#mount.appendChild(el);
1073
1145
  });
1074
1146
  processor.processMessages(messages);
1075
1147
  });
1076
1148
  }
1077
- /**
1078
- * 按 InteractionRequest.type 解码提交值(绑定模型原样回传的是表单对象):
1079
- * confirm confirmed===true 批准;select 还原原始 option 值(非字符串值经 JSON 编码比对);
1080
- * form 的 number 字段转 number;ask 取 answer;authorize 提交即批准。
1081
- */
1082
- #decodeValue(input, response) {
1083
- if (response.cancelled) return response;
1084
- const values = response.value;
1085
- switch (input.type) {
1086
- case "confirm": return {
1087
- ...response,
1088
- value: values?.confirmed === true
1089
- };
1090
- case "ask": return {
1091
- ...response,
1092
- value: values?.answer
1093
- };
1094
- case "select": {
1095
- const raw = values?.selected;
1096
- const option = input.options.find((o) => o.value === raw || JSON.stringify(o.value) === raw);
1097
- return {
1098
- ...response,
1099
- value: option ? option.value : raw
1100
- };
1101
- }
1102
- case "form": {
1103
- if (typeof values !== "object" || values === null) return response;
1104
- const out = { ...values };
1105
- for (const field of input.fields) if (field.type === "number" && out[field.name] !== void 0) {
1106
- const n = Number(out[field.name]);
1107
- if (!Number.isNaN(n)) out[field.name] = n;
1108
- }
1109
- return {
1110
- ...response,
1111
- value: out
1112
- };
1113
- }
1114
- case "authorize": return {
1115
- ...response,
1116
- value: true
1117
- };
1118
- default: return response;
1119
- }
1149
+ /** runtime 交互超时/取消:移除 surface 并以 cancelled resolve pending Promise(防悬挂 + DOM 残留) */
1150
+ cancel(id) {
1151
+ const entry = this.#pending.get(id);
1152
+ if (!entry) return;
1153
+ this.#pending.delete(id);
1154
+ entry.surfaceEl?.remove();
1155
+ entry.resolve({
1156
+ id,
1157
+ cancelled: true
1158
+ });
1120
1159
  }
1121
1160
  };
1122
1161
 
1123
1162
  //#endregion
1124
- export { renderRenderResult as C, toVercelToolInvocation as D, toOpenUiLang as E, renderMiniMarkdown as S, toA2uiMessages as T, fromOpenUiAction as _, CHART_PALETTE as a, renderBlocks as b, OPENUI_SUBMIT_ACTION as c, WEBSKILL_STYLES_CSS as d, WebFormBridge as f, fromA2uiAction as g, ensureStyles as h, A2UI_VERSION as i, VERCEL_INTERACTION_TOOL_NAME as l, collectValues as m, A2UI_CANCEL_ACTION as n, LitRendererBridge as o, chartToTable as p, A2UI_SUBMIT_ACTION as r, OPENUI_CANCEL_ACTION as s, A2UI_BASIC_CATALOG_ID as t, VercelUiBridge as u, fromVercelToolResult as v, shapeInteractionValue as w, renderMiniChart as x, interactionToFormModel as y };
1163
+ export { renderMiniChart as C, toA2uiMessages as D, shapeInteractionValue as E, toOpenUiLang as O, renderBlocks as S, renderRenderResult as T, ensureStyles as _, CHART_PALETTE as a, fromVercelToolResult as b, OPENUI_CANCEL_ACTION as c, VercelUiBridge as d, WEBSKILL_STYLES_CSS as f, decodeInteractionResponse as g, collectValues as h, A2UI_VERSION as i, toVercelToolInvocation as k, OPENUI_SUBMIT_ACTION as l, chartToTable as m, A2UI_CANCEL_ACTION as n, LitRendererBridge as o, WebFormBridge as p, A2UI_SUBMIT_ACTION as r, OPENUI_AUTHORIZE_ACTION as s, A2UI_BASIC_CATALOG_ID as t, VERCEL_INTERACTION_TOOL_NAME as u, fromA2uiAction as v, renderMiniMarkdown as w, interactionToFormModel as x, fromOpenUiAction as y };
@@ -1,6 +1,6 @@
1
- import { F as SkillDocument, N as SkillCatalogEntry, c as LlmClient, u as LlmMessage, v as UiBridge, w as FileSystemProvider, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
2
- import { Q as ScriptExecutor, dt as WebSkillRuntime, ft as WebSkillRuntimeDeps, q as RuntimeRun, tt as SkillStateGuard } from "./index-gjFuBevI.js";
3
- import { d as SkillManager } from "./index-iqcz3_NS.js";
1
+ import { B as SkillManifest, I as SkillDocument, P as SkillCatalogEntry, c as LlmClient, u as LlmMessage, v as UiBridge, w as FileSystemProvider } from "./types-CKm5G_eQ-krKWW8WV.js";
2
+ import { Q as ScriptExecutor, dt as WebSkillRuntime, ft as WebSkillRuntimeDeps, q as RuntimeRun, tt as SkillStateGuard } from "./index-DfINBEOy.js";
3
+ import { d as SkillManager } from "./index-CsDJvYGV.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/types.d.ts
6
6
  type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
@@ -143,7 +143,8 @@ declare class CompositeApprovalPolicy implements ApprovalPolicy {
143
143
  }
144
144
  //#endregion
145
145
  //#region src/versioning/skillVersionStore.d.ts
146
- /** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断) */
146
+ /** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断)。
147
+ * 保留策略:maxArchivesPerSkill(默认 5)超出时清理最旧版本(json + zip 归档一并删除)。 */
147
148
  declare class SkillVersionStore {
148
149
  #private;
149
150
  constructor(deps: {
@@ -152,6 +153,8 @@ declare class SkillVersionStore {
152
153
  now?: () => string;
153
154
  createId?: () => string;
154
155
  audit?: AuditLog;
156
+ /** 每技能保留的版本/归档上限(默认 5,超出清理最旧) */
157
+ maxArchivesPerSkill?: number;
155
158
  });
156
159
  add(skillName: string, input: {
157
160
  reason: string;
@@ -209,7 +212,12 @@ interface AuditChainVerification {
209
212
  brokenAt?: number;
210
213
  reason?: string;
211
214
  }
212
- /** JSONL 追加的审计日志:<managedRoot>/.webskill/audit.jsonl;跨实例可恢复查询;prevHash 链可校验完整性 */
215
+ /**
216
+ * JSONL 追加式审计日志(<managedRoot>/.webskill/audit.jsonl):
217
+ * 真追加(fs.appendText,不整读改写)+ lastHash 内存缓存(同实例不重读)。
218
+ * 语义:**tamper-evident, not tamper-proof**——verifyChain 能检出篡改/删除/换序,
219
+ * 但不阻止有写权限者直接改写文件;更强保证需要签名信任体系(未排期)。
220
+ */
213
221
  declare class FsAuditLog implements AuditLog {
214
222
  #private;
215
223
  constructor(deps: {
@@ -1,6 +1,6 @@
1
- import { A as unzipWithLimits, b as isValidSkillName, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, u as WebSkillError } from "./dist-D0saNPi_.js";
2
- import { S as WebSkillRuntime } from "./dist-NM4Mylx4.js";
3
- import { i as NodeFS, s as ProcessSandboxExecutor, u as exportArchive } from "./dist-CvIMVwr3.js";
1
+ import { C as messageOf, M as unzipWithLimits, N as validateSkills, f as assertSafePathSegment, j as resolveInsideRoot, u as WebSkillError, x as isValidSkillName } from "./dist-BQzncxXg.js";
2
+ import { S as WebSkillRuntime } from "./dist-B77plHjw.js";
3
+ import { i as NodeFS, s as ProcessSandboxExecutor, u as exportArchive } from "./dist-Chk8iB-E.js";
4
4
  import path from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { mkdtemp } from "node:fs/promises";
@@ -242,7 +242,6 @@ var CompositeApprovalPolicy = class {
242
242
  return this.#fallback.evaluate(candidate);
243
243
  }
244
244
  };
245
- const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
246
245
  /** 审批工作流:review(UiBridge confirm 真实接线)/ publish(校验→安装→版本→审计) */
247
246
  var ApprovalWorkflow = class {
248
247
  #policy;
@@ -331,7 +330,7 @@ var ApprovalWorkflow = class {
331
330
  return manifest;
332
331
  } catch (e) {
333
332
  if (e instanceof WebSkillError) throw e;
334
- throw new WebSkillError("GOVERNANCE_FAILED", `Failed to publish candidate "${candidateId}": ${messageOf$1(e)}`, e);
333
+ throw new WebSkillError("GOVERNANCE_FAILED", `Failed to publish candidate "${candidateId}": ${messageOf(e)}`, e);
335
334
  } finally {
336
335
  try {
337
336
  await this.#fs.remove(stagingRoot, { recursive: true });
@@ -382,7 +381,7 @@ var ApprovalWorkflow = class {
382
381
  return manifest;
383
382
  } catch (e) {
384
383
  if (e instanceof WebSkillError) throw e;
385
- throw new WebSkillError("GOVERNANCE_FAILED", `Failed to roll back "${skillName}" to version "${versionId}": ${messageOf$1(e)}`, e);
384
+ throw new WebSkillError("GOVERNANCE_FAILED", `Failed to roll back "${skillName}" to version "${versionId}": ${messageOf(e)}`, e);
386
385
  } finally {
387
386
  try {
388
387
  await this.#fs.remove(stagingRoot, { recursive: true });
@@ -404,29 +403,42 @@ function canonical(event) {
404
403
  prevHash: event.prevHash
405
404
  });
406
405
  }
407
- /** JSONL 追加的审计日志:<managedRoot>/.webskill/audit.jsonl;跨实例可恢复查询;prevHash 链可校验完整性 */
406
+ /**
407
+ * JSONL 追加式审计日志(<managedRoot>/.webskill/audit.jsonl):
408
+ * 真追加(fs.appendText,不整读改写)+ lastHash 内存缓存(同实例不重读)。
409
+ * 语义:**tamper-evident, not tamper-proof**——verifyChain 能检出篡改/删除/换序,
410
+ * 但不阻止有写权限者直接改写文件;更强保证需要签名信任体系(未排期)。
411
+ */
408
412
  var FsAuditLog = class {
409
413
  #root;
410
414
  #fs;
411
415
  #now;
412
416
  #createId;
417
+ /** lastHash 内存缓存(同实例内追加不重读文件) */
418
+ #lastHash;
419
+ #lastHashSeeded = false;
413
420
  constructor(deps) {
414
421
  this.#root = deps.root.replace(/\/+$/, "");
415
422
  this.#fs = deps.fs;
416
423
  this.#now = deps.now;
417
424
  this.#createId = deps.createId;
418
425
  }
419
- async append(event) {
426
+ /** 首追加时播种 lastHash(读一次尾部;尾行损坏 → 抛错,不静默重开链) */
427
+ async #seedLastHash() {
428
+ this.#lastHashSeeded = true;
420
429
  const path = fileOf$1(this.#root);
421
- const existing = await this.#fs.exists(path) ? await this.#fs.readText(path) : "";
422
- const lines = existing.split("\n").filter((l) => l.trim() !== "");
423
- let prevHash = "GENESIS";
424
- if (lines.length > 0) try {
430
+ if (!await this.#fs.exists(path)) return "GENESIS";
431
+ const lines = (await this.#fs.readText(path)).split("\n").filter((l) => l.trim() !== "");
432
+ if (lines.length === 0) return "GENESIS";
433
+ try {
425
434
  const last = JSON.parse(lines.at(-1));
426
- prevHash = last.hash ?? sha256Hex(canonical(last));
427
- } catch {
428
- prevHash = "GENESIS";
435
+ return last.hash ?? sha256Hex(canonical(last));
436
+ } catch (e) {
437
+ throw new WebSkillError("GOVERNANCE_FAILED", `Audit log tail line at ${path} is corrupted; refusing to append (the chain must not silently restart)`, e);
429
438
  }
439
+ }
440
+ async append(event) {
441
+ const prevHash = this.#lastHashSeeded ? this.#lastHash : await this.#seedLastHash();
430
442
  const full = {
431
443
  id: event.id ?? this.#createId?.() ?? `audit-${Math.random().toString(36).slice(2, 10)}`,
432
444
  ts: event.ts ?? this.#now?.() ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -437,8 +449,8 @@ var FsAuditLog = class {
437
449
  prevHash
438
450
  };
439
451
  full.hash = sha256Hex(canonical(full));
440
- const prefix = existing === "" || existing.endsWith("\n") ? existing : `${existing}\n`;
441
- await this.#fs.writeText(path, `${prefix}${JSON.stringify(full)}\n`);
452
+ await this.#fs.appendText(fileOf$1(this.#root), `${JSON.stringify(full)}\n`);
453
+ this.#lastHash = full.hash;
442
454
  return full;
443
455
  }
444
456
  async query(filter) {
@@ -448,7 +460,12 @@ var FsAuditLog = class {
448
460
  const events = [];
449
461
  for (const line of raw.split("\n")) {
450
462
  if (line.trim() === "") continue;
451
- const event = JSON.parse(line);
463
+ let event;
464
+ try {
465
+ event = JSON.parse(line);
466
+ } catch {
467
+ continue;
468
+ }
452
469
  if (filter.target !== void 0 && event.target !== filter.target) continue;
453
470
  if (filter.type !== void 0 && event.type !== filter.type) continue;
454
471
  if (filter.since !== void 0 && event.ts < filter.since) continue;
@@ -493,19 +510,22 @@ const dirOf = (root, skillName) => {
493
510
  assertSafePathSegment(skillName, "skill name");
494
511
  return `${root}/.webskill/versions/${skillName}`;
495
512
  };
496
- /** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断) */
513
+ /** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断)。
514
+ * 保留策略:maxArchivesPerSkill(默认 5)超出时清理最旧版本(json + zip 归档一并删除)。 */
497
515
  var SkillVersionStore = class {
498
516
  #root;
499
517
  #fs;
500
518
  #now;
501
519
  #createId;
502
520
  #audit;
521
+ #maxArchives;
503
522
  constructor(deps) {
504
523
  this.#root = deps.root.replace(/\/+$/, "");
505
524
  this.#fs = deps.fs;
506
525
  this.#now = deps.now;
507
526
  this.#createId = deps.createId;
508
527
  this.#audit = deps.audit;
528
+ this.#maxArchives = deps.maxArchivesPerSkill ?? 5;
509
529
  }
510
530
  async add(skillName, input) {
511
531
  const existing = await this.list(skillName);
@@ -523,8 +543,19 @@ var SkillVersionStore = class {
523
543
  version.archivePath = archivePath;
524
544
  }
525
545
  await this.#fs.writeText(`${dirOf(this.#root, skillName)}/${version.versionId}.json`, JSON.stringify(version, null, 2));
546
+ await this.#prune(skillName);
526
547
  return version;
527
548
  }
549
+ /** 保留策略:超出 maxArchivesPerSkill 时按 createdAt 清理最旧版本(json + zip) */
550
+ async #prune(skillName) {
551
+ const versions = await this.list(skillName);
552
+ const excess = versions.length - this.#maxArchives;
553
+ if (excess <= 0) return;
554
+ for (const old of versions.slice(0, excess)) {
555
+ await this.#fs.remove(`${dirOf(this.#root, skillName)}/${old.versionId}.json`);
556
+ if (old.archivePath && await this.#fs.exists(old.archivePath)) await this.#fs.remove(old.archivePath);
557
+ }
558
+ }
528
559
  /** 读取版本归档字节(applyRollback 用;未捕获归档的旧版本 → GOVERNANCE_FAILED) */
529
560
  async readArchive(skillName, versionId) {
530
561
  const version = await this.get(skillName, versionId);
@@ -786,7 +817,6 @@ var SkillStatePolicy = class {
786
817
  });
787
818
  }
788
819
  };
789
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
790
820
  function matchExpected(expected, output, run) {
791
821
  if (expected === void 0) return run.status === "completed";
792
822
  if (typeof expected === "string") return output.includes(expected);
@@ -1,5 +1,5 @@
1
- import { C as FileStat, I as SkillInstallSource, K as VerifyResult, T as JsonSchema, W as SkillsLockfile, _ as RenderResultRequest, o as InteractionRequest, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
2
- import { N as NetworkPolicy, Q as ScriptExecutor, X as SchemaInferer, Z as ScriptExecutionContext, b as FsArtifactStore, d as BridgeCapabilities, it as ToolResult, nt as ToolDefinition, u as ApprovalScope, x as FsMemoryStore } from "./index-gjFuBevI.js";
1
+ import { B as SkillManifest, C as FileStat, G as SkillsLockfile, L as SkillInstallSource, T as JsonSchema, _ as RenderResultRequest, o as InteractionRequest, q as VerifyResult, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits } from "./types-CKm5G_eQ-krKWW8WV.js";
2
+ import { N as NetworkPolicy, Q as ScriptExecutor, X as SchemaInferer, Z as ScriptExecutionContext, b as FsArtifactStore, d as BridgeCapabilities, it as ToolResult, nt as ToolDefinition, u as ApprovalScope, x as FsMemoryStore } from "./index-DfINBEOy.js";
3
3
  import { Readable, Writable } from "node:stream";
4
4
  //#region ../node/dist/index.d.ts
5
5
  //#region src/fs/nodeFs.d.ts
@@ -14,8 +14,11 @@ declare class NodeFS implements FileSystemProvider {
14
14
  constructor(deps?: {
15
15
  root?: string;
16
16
  });
17
+ /** root 模式:全部方法(read/write/exists/stat/list/mkdir/remove/rename)目标 realpath 必须落在 root realpath 前缀内 */
18
+ withRoot(root: string): NodeFS;
17
19
  readText(p: string): Promise<string>;
18
20
  writeText(p: string, content: string): Promise<void>;
21
+ appendText(p: string, content: string): Promise<void>;
19
22
  readBinary(p: string): Promise<Uint8Array>;
20
23
  writeBinary(p: string, content: Uint8Array): Promise<void>;
21
24
  exists(p: string): Promise<boolean>;
@@ -103,6 +106,8 @@ interface ProcessSandboxOptions {
103
106
  uiBridge?: UiBridge;
104
107
  /** 授权粒度:默认 'once-per-run' */
105
108
  approvalScope?: ApprovalScope;
109
+ /** 池维护告警出口(recycle 重生失败等;默认 console.warn) */
110
+ onWarning?: (message: string) => void;
106
111
  }
107
112
  /**
108
113
  * child_process.fork + --permission 进程沙箱(真实进程隔离)。
@@ -117,7 +122,7 @@ declare class ProcessSandboxExecutor implements ScriptExecutor {
117
122
  #private;
118
123
  constructor(fs: FileSystemProvider, options?: ProcessSandboxOptions);
119
124
  get poolSize(): number;
120
- /** 池全部子进程销毁(测试收尾/进程退出前调用) */
125
+ /** 池全部子进程销毁(测试收尾/进程退出前调用);排队中的 acquire 一律 reject(不悬挂) */
121
126
  dispose(): Promise<void>;
122
127
  loadDefinition(skillRoot: string, scriptName: string): Promise<ToolDefinition>;
123
128
  execute(input: {
@@ -1,4 +1,4 @@
1
- import { F as SkillDocument, G as ValidationReport, I as SkillInstallSource, M as SkillCatalog, N as SkillCatalogEntry, P as SkillDiscovery, S as DiscoveryResult, T as JsonSchema, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, g as RenderBlock, h as MemoryStore, i as FormField, l as LlmCompleteInput, m as LlmToolSpec, n as ArtifactStore, o as InteractionRequest, r as ChartSpec, t as Artifact, u as LlmMessage, v as UiBridge, w as FileSystemProvider, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
1
+ import { B as SkillManifest, F as SkillDiscovery, I as SkillDocument, K as ValidationReport, L as SkillInstallSource, N as SkillCatalog, P as SkillCatalogEntry, S as DiscoveryResult, T as JsonSchema, Y as WebSkillErrorCode, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, g as RenderBlock, h as MemoryStore, i as FormField, l as LlmCompleteInput, m as LlmToolSpec, n as ArtifactStore, o as InteractionRequest, r as ChartSpec, t as Artifact, u as LlmMessage, v as UiBridge, w as FileSystemProvider } from "./types-CKm5G_eQ-krKWW8WV.js";
2
2
  //#region ../runtime/dist/index.d.ts
3
3
  //#region src/llm/openAiCompatibleClient.d.ts
4
4
  interface OpenAiCompatibleClientConfig {
@@ -33,6 +33,11 @@ interface AnthropicClientConfig {
33
33
  requestTimeoutMs?: number;
34
34
  /** max_tokens(Messages API 必填),默认 4096 */
35
35
  maxTokens?: number;
36
+ /**
37
+ * 浏览器直调 opt-in:true 时发送 anthropic-dangerous-direct-browser-access 头
38
+ * (默认 false 不发——仅在明确运行于浏览器且无服务端代理时开启)
39
+ */
40
+ dangerouslyAllowDirectBrowserAccess?: boolean;
36
41
  }
37
42
  /** Anthropic Messages API 客户端(零依赖 fetch;Node/浏览器通用) */
38
43
  declare class AnthropicClient implements LlmClient {
@@ -265,6 +270,8 @@ interface AgentLoopConfig {
265
270
  renderResult?: 'off';
266
271
  /** 工具结果回喂上限(字节,默认 100_000;超长头尾保留 + 完整内容落 artifact) */
267
272
  toolResultMaxBytes?: number;
273
+ /** session paramHistory 保留条数上限(默认 50,超出裁最旧) */
274
+ paramHistoryLimit?: number;
268
275
  }
269
276
  /** 技能状态拦截 port(治理装配;无注入默认全放行) */
270
277
  interface SkillStateGuard {
@@ -374,6 +381,7 @@ type LifecycleListener = (event: LifecycleEvent) => void;
374
381
  /** 生命周期事件总线:只读观测,支持按阶段或通配订阅 */
375
382
  declare class EventBus {
376
383
  #private;
384
+ constructor(onListenerError?: (error: unknown, event: LifecycleEvent) => void);
377
385
  /** 返回取消订阅函数 */
378
386
  on(phase: RuntimePhase | '*', listener: LifecycleListener): () => void;
379
387
  /** 当前订阅者数量(流式 delta 零订阅零开销判断用) */
@@ -431,6 +439,8 @@ declare class FsMemoryStore implements MemoryStore {
431
439
  declare class SerializingMemoryStore implements MemoryStore {
432
440
  #private;
433
441
  constructor(inner: MemoryStore);
442
+ /** 当前链上的 scope 数(监控/测试用;空闲时应回落到 0) */
443
+ get trackedScopeCount(): number;
434
444
  get(scope: string, key: string): Promise<unknown>;
435
445
  set(scope: string, key: string, value: unknown): Promise<void>;
436
446
  delete(scope: string, key: string): Promise<void>;
@@ -540,6 +550,18 @@ declare function isNetworkAllowed(policy: NetworkPolicy, url: string): boolean;
540
550
  /** 阻断 trace 用的脱敏 host(解析失败返回占位,不记录完整 URL) */
541
551
  declare function networkUrlHost(url: string): string;
542
552
  //#endregion
553
+ //#region src/sandbox/errorCodes.d.ts
554
+ /**
555
+ * 错误码白名单归一:沙箱/桥消息里出现的非白名单码(DOMException 数值码、
556
+ * Node 任意 ERR_* 码等)一律归为 TOOL_EXECUTION_FAILED。
557
+ */
558
+ declare function normalizeErrorCode(code: unknown): WebSkillErrorCode;
559
+ /** 归一化后的 (code, message):非白名单码保留在 message 尾部([original code: X]) */
560
+ declare function normalizeToolError(code: unknown, message: string): {
561
+ code: WebSkillErrorCode;
562
+ message: string;
563
+ };
564
+ //#endregion
543
565
  //#region src/sandbox/approval.d.ts
544
566
  /** 桥消息对应的三类能力 */
545
567
  type BridgeCapability = 'readReference' | 'writeArtifact' | 'confirm';
@@ -632,6 +654,8 @@ interface RunSnapshot {
632
654
  renderBlocks?: RenderBlock[];
633
655
  /** 交互 id 序号(resume 后续算,避免 id 冲突;旧快照缺省从 0 起) */
634
656
  interactionSeq?: number;
657
+ /** 已累计的交互等待 ms(resume 后续算,保持 totalTimeout 排除交互等待的语义;旧快照缺省为 0) */
658
+ pausedMs?: number;
635
659
  /** 进入 interrupted 时计算的过期时间 */
636
660
  interactionExpiresAt: string;
637
661
  config: {
@@ -790,9 +814,12 @@ declare class WebSkillRuntime {
790
814
  /**
791
815
  * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
792
816
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
817
+ * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
818
+ * maxHistoryMessages(默认 100)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
793
819
  */
794
820
  createSession(options?: {
795
821
  sessionId?: string;
822
+ maxHistoryMessages?: number;
796
823
  }): RuntimeSessionHandle;
797
824
  discover(): Promise<DiscoveryResult>;
798
825
  run(userPrompt: string, options?: {
@@ -810,4 +837,4 @@ declare class WebSkillRuntime {
810
837
  resumeRun(runId: string): Promise<RunResult>;
811
838
  }
812
839
  //#endregion
813
- export { SerializingMemoryStore as $, LifecycleHook as A, RUN_SNAPSHOT_SCHEMA_VERSION as B, FullDisclosureRouter as C, normalizeToolContent as Ct, HookRunnerOptions as D, toLlmToolSpec as Dt, HookRunner as E, schemaToForm as Et, OpenAiCompatibleClientConfig as F, RunTerminationReason as G, RunResult as H, ProgressiveRouter as I, RuntimeSession as J, RuntimePhase as K, READ_SKILL_FILE_INPUT_SCHEMA as L, LifecycleListener as M, NetworkPolicy as N, InstalledSkillManifest as O, toVercelToolSpecs as Ot, OpenAiCompatibleClient as P, ScriptExecutor as Q, READ_SKILL_FILE_TOOL as R, FsRunSnapshotStore as S, networkUrlHost as St, GoogleGenAiClientConfig as T, resolveToolName as Tt, RunSnapshot as U, RouteResult as V, RunSnapshotStore as W, SchemaInferer as X, RuntimeSessionHandle as Y, ScriptExecutionContext as Z, EventBus as _, extractChartSpec as _t, AgentLoopConfig as a, TraceClock as at, FsArtifactStore as b, isNetworkAllowed as bt, AnthropicClientConfig as c, TraceRecorder as ct, BridgeCapabilities as d, WebSkillRuntime as dt, SkillRouter as et, BridgeCapability as f, WebSkillRuntimeDeps as ft, CapabilityMode as g, createWebSkillApi as gt, CapabilityApproval as h, createScriptContext as ht, AgentLoop as i, ToolResult as it, LifecycleHookContext as j, LifecycleEvent as k, ApprovalDecision as l, VercelToolSpec as lt, BridgeResponse as m, buildRenderResult as mt, ASK_USER_TOOL as n, ToolDefinition as nt, AgentLoopDeps as o, TraceEvent as ot, BridgeRequest as p, bridgeError as pt, RuntimeRun as q, ASK_USER_TOOL_NAME as r, ToolResolution as rt, AnthropicClient as s, TraceEventType as st, ASK_USER_INPUT_SCHEMA as t, SkillStateGuard as tt, ApprovalScope as u, WebSkillApi as ut, ExternalSkillProvider as v, fromVercelResult as vt, GoogleGenAiClient as w, parseBridgeRequest as wt, FsMemoryStore as x, mergeCatalogEntries as xt, ExternalToolSource as y, fromVercelStreamPart as yt, READ_SKILL_FILE_TOOL_NAME as z };
840
+ export { SerializingMemoryStore as $, LifecycleHook as A, toVercelToolSpecs as At, RUN_SNAPSHOT_SCHEMA_VERSION as B, FullDisclosureRouter as C, normalizeErrorCode as Ct, HookRunnerOptions as D, resolveToolName as Dt, HookRunner as E, parseBridgeRequest as Et, OpenAiCompatibleClientConfig as F, RunTerminationReason as G, RunResult as H, ProgressiveRouter as I, RuntimeSession as J, RuntimePhase as K, READ_SKILL_FILE_INPUT_SCHEMA as L, LifecycleListener as M, NetworkPolicy as N, InstalledSkillManifest as O, schemaToForm as Ot, OpenAiCompatibleClient as P, ScriptExecutor as Q, READ_SKILL_FILE_TOOL as R, FsRunSnapshotStore as S, networkUrlHost as St, GoogleGenAiClientConfig as T, normalizeToolError as Tt, RunSnapshot as U, RouteResult as V, RunSnapshotStore as W, SchemaInferer as X, RuntimeSessionHandle as Y, ScriptExecutionContext as Z, EventBus as _, extractChartSpec as _t, AgentLoopConfig as a, TraceClock as at, FsArtifactStore as b, isNetworkAllowed as bt, AnthropicClientConfig as c, TraceRecorder as ct, BridgeCapabilities as d, WebSkillRuntime as dt, SkillRouter as et, BridgeCapability as f, WebSkillRuntimeDeps as ft, CapabilityMode as g, createWebSkillApi as gt, CapabilityApproval as h, createScriptContext as ht, AgentLoop as i, ToolResult as it, LifecycleHookContext as j, LifecycleEvent as k, toLlmToolSpec as kt, ApprovalDecision as l, VercelToolSpec as lt, BridgeResponse as m, buildRenderResult as mt, ASK_USER_TOOL as n, ToolDefinition as nt, AgentLoopDeps as o, TraceEvent as ot, BridgeRequest as p, bridgeError as pt, RuntimeRun as q, ASK_USER_TOOL_NAME as r, ToolResolution as rt, AnthropicClient as s, TraceEventType as st, ASK_USER_INPUT_SCHEMA as t, SkillStateGuard as tt, ApprovalScope as u, WebSkillApi as ut, ExternalSkillProvider as v, fromVercelResult as vt, GoogleGenAiClient as w, normalizeToolContent as wt, FsMemoryStore as x, mergeCatalogEntries as xt, ExternalToolSource as y, fromVercelStreamPart as yt, READ_SKILL_FILE_TOOL_NAME as z };