@webskill/sdk 0.3.0 → 0.5.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 -0
  2. package/dist/agent.js +867 -0
  3. package/dist/browser.d.ts +137 -4
  4. package/dist/browser.js +458 -22
  5. package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-DV7cPpUm-C77AEEx9.js} +477 -157
  6. package/dist/{dist-rorEJsNi.js → dist-6C03DShK.js} +654 -298
  7. package/dist/{dist-ZKaM8j06.js → dist-bewtXYlO.js} +1061 -807
  8. package/dist/governance.d.ts +87 -10
  9. package/dist/governance.js +194 -24
  10. package/dist/{index-wiV5X8Rz.d.ts → index-Bsqg4ftU.d.ts} +151 -143
  11. package/dist/index-D_7ZZjkl.d.ts +411 -0
  12. package/dist/{index-8d-oEDww.d.ts → index-vBz_FC9w.d.ts} +289 -24
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.js +3 -2
  15. package/dist/mcp.d.ts +2 -2
  16. package/dist/mcp.js +1 -1
  17. package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
  18. package/dist/node.d.ts +8 -4
  19. package/dist/node.js +1 -1
  20. package/dist/{openUiLibrary-B8-Cvou9-BbpNTXS3.js → openUiLibrary-W3Ce896k-ClFTRZFs.js} +6 -5
  21. package/dist/{skillVersionStore-DOEI9ptb-BxbYL70B.d.ts → skillVersionStore-BzLbzFOL-CxwIewHJ.d.ts} +43 -11
  22. package/dist/{testing-CsrG3XLz.js → testing-DDCJWvgA.js} +7 -5
  23. package/dist/testing.d.ts +1 -1
  24. package/dist/testing.js +2 -2
  25. package/dist/{types-AmKCKJn_-VGabeXK4.d.ts → types-D_hoCri8-BnNPiZCi.d.ts} +111 -72
  26. package/dist/ui-react.d.ts +352 -20
  27. package/dist/ui-react.js +3804 -3478
  28. package/dist/ui-vue.d.ts +1 -1
  29. package/dist/ui-vue.js +25 -6
  30. package/dist/ui.d.ts +4 -3
  31. package/dist/ui.js +3 -3
  32. package/dist/{webskillLitCatalog-CNaUpasU-BslMcxRZ.js → webskillLitCatalog-_mugzRHx-DiuJpCuf.js} +398 -122
  33. package/package.json +6 -1
  34. package/dist/jsonRenderRegistry-9GrWP_hE-U6Do3Kid.js +0 -2468
  35. package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
@@ -1,6 +1,6 @@
1
- import { P as FileSystemProvider, Q as SkillDocument, X as SkillCatalogEntry, c as LlmClient, nt as SkillManagerPort, u as LlmMessage, v as UiBridge } from "./types-AmKCKJn_-VGabeXK4.js";
2
- import { mt as SkillStateGuard, nt as RuntimeRun, wt as WebSkillRuntime } from "./index-8d-oEDww.js";
3
- import { a as AuditLog, c as CandidateSkill, d as CandidateStore, f as CompositeApprovalPolicy, g as candidateToCatalogEntry, h as SkillVersionStore, i as AuditEvent, l as CandidateSource, m as SkillVersion, n as ApprovalDecision, o as CandidateFile, p as SkillState, r as ApprovalPolicy, s as CandidateRisk, t as AlwaysHumanApprovalPolicy, u as CandidateStatus } from "./skillVersionStore-DOEI9ptb-BxbYL70B.js";
1
+ import { B as PageQuery, P as FileSystemProvider, Q as SkillCatalogEntry, b as UiBridge, et as SkillDocument, f as LlmMessage, it as SkillManagerPort, l as LlmClient, z as Page } from "./types-D_hoCri8-BnNPiZCi.js";
2
+ import { Et as SkillOutcomeReporter, L as IntegrityVerdict, Ot as SkillStateGuard, Tt as SkillIntegrityGuard, Vt as WebSkillRuntime, pt as RuntimeRun } from "./index-vBz_FC9w.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-BzLbzFOL-CxwIewHJ.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/candidate/candidateNormalizer.d.ts
6
6
  /** 剥 markdown fence 与 <think> 块、截取首尾 {};非对象 → CANDIDATE_INVALID */
@@ -32,6 +32,48 @@ declare function normalizeCandidate(input: {
32
32
  /** 候选校验:缺 SKILL.md / 非法扩展名 → CANDIDATE_INVALID */
33
33
  declare function validateCandidate(candidate: CandidateSkill): void;
34
34
  //#endregion
35
+ //#region src/candidate/candidateSink.d.ts
36
+ /**
37
+ * agent 侧 `SkillCandidateSink.submit()` 的入参形状。
38
+ * 这里按结构声明而不是 import `@webskill/agent`:治理层是可选层,
39
+ * 不该因为 agent 策略而多一条运行时依赖(两侧的兼容性由测试钉住)。
40
+ */
41
+ interface GeneratedSkillSubmission {
42
+ draft: {
43
+ name: string;
44
+ description: string;
45
+ /** SKILL.md 全文,含 frontmatter */
46
+ content: string;
47
+ files?: readonly {
48
+ path: string;
49
+ content: string;
50
+ }[];
51
+ };
52
+ /** 触发生成的会话,写进候选 metadata 供 console 溯源 */
53
+ sessionId?: string;
54
+ /** 用户确认的时刻(FR-9.7 的「用户确认事实」) */
55
+ confirmedAt: string;
56
+ }
57
+ interface CandidateSinkOptions {
58
+ store: CandidateStore;
59
+ /** 缺省不写审计——但 FR-9.7 要求宿主接上它 */
60
+ audit?: AuditLog;
61
+ now?: () => string;
62
+ createId?: () => string;
63
+ }
64
+ interface CandidateSink {
65
+ submit(input: GeneratedSkillSubmission): Promise<{
66
+ id: string;
67
+ }>;
68
+ }
69
+ /**
70
+ * 把 agent 生成的技能草稿接进既有的候选管线(设计 02 §5)。
71
+ *
72
+ * 走的是与文档抽取、runtime-miss 完全相同的 `normalizeCandidate` → `CandidateStore.save`,
73
+ * 因此候选恒为 `draft`,审批/发布链路一行都不用改;差别只有 `source: 'generated'`。
74
+ */
75
+ declare function createCandidateSink(options: CandidateSinkOptions): CandidateSink;
76
+ //#endregion
35
77
  //#region src/candidate/llmCandidateGenerator.d.ts
36
78
  /** LLM 候选生成器:prompt 强制约束 + 不信任归一化管线 + candidate.created 审计 */
37
79
  declare class LlmCandidateGenerator {
@@ -50,6 +92,8 @@ declare class LlmCandidateGenerator {
50
92
  }
51
93
  //#endregion
52
94
  //#region src/audit/fsAuditLog.d.ts
95
+ /** 审计查询的缺省页长。缺省属于实现,不属于调用方 */
96
+ declare const AUDIT_PAGE_SIZE = 100;
53
97
  interface AuditChainVerification {
54
98
  ok: boolean;
55
99
  /** 首个断链位置(行号,0 起;ok 时 undefined) */
@@ -74,11 +118,15 @@ declare class FsAuditLog implements AuditLog {
74
118
  id?: string;
75
119
  ts?: string;
76
120
  }): Promise<AuditEvent>;
77
- query(filter: {
78
- target?: string;
79
- type?: string;
80
- since?: string;
81
- }): Promise<AuditEvent[]>;
121
+ /**
122
+ * 分页 + 筛选下推。整文件读仍在(JSONL 布局未改,备案 D25),
123
+ * 但**跨出端口的事件数**已由 `limit` 封顶,且筛选是端口参数而非上层 `.filter()`。
124
+ *
125
+ * 本页范围内的 hash 链必须自洽:逐条重算 hash 并核对与前一行的 `prevHash` 链接,
126
+ * 任一处对不上以 `GOVERNANCE_FAILED` 报错。范围之外的断链不在本方法职责内——
127
+ * 那是 `verifyChain()` 的事(全量 O(N) 校验不该压在每次翻页上)。
128
+ */
129
+ query(filter?: AuditQueryFilter & PageQuery): Promise<Page<AuditEvent>>;
82
130
  /** hash 链完整性校验:逐行重算 hash 并核对 prevHash 链接 */
83
131
  verifyChain(): Promise<AuditChainVerification>;
84
132
  }
@@ -144,7 +192,11 @@ declare class SkillStatePolicy {
144
192
  actor?: string;
145
193
  reason?: string;
146
194
  }): Promise<void>;
147
- /** 失败计数;达阈值(默认 3)→ quarantined + 审计 */
195
+ /**
196
+ * 失败计数;达阈值(默认 3)→ quarantined + 审计。
197
+ *
198
+ * 计数是**累计**而非「连续」:成功不清零,只有 `setState(name, 'active')` 会清。
199
+ */
148
200
  recordFailure(skillName: string, input?: {
149
201
  actor?: string;
150
202
  }): Promise<SkillState>;
@@ -156,6 +208,31 @@ declare class SkillStatePolicy {
156
208
  * quarantined/disabled 三入口全拦截。
157
209
  */
158
210
  toSkillStateGuard(): SkillStateGuard;
211
+ /**
212
+ * runtime SkillIntegrityGuard 适配(D3):激活期校验失败 → 置 `quarantined` + 写审计。
213
+ *
214
+ * 需求 §3 任务 3 要的是「使技能进入不可用状态,而不是只抛错」——只抛错的话,
215
+ * 同一个坏技能下一次激活还会再试一遍。置了状态之后,后续激活先被
216
+ * `toSkillStateGuard().canActivate` 挡住,根本走不到校验。
217
+ *
218
+ * `verify` 由宿主传入:完整性校验的实现在 `@webskill/node` / `@webskill/browser`,
219
+ * governance 与 runtime 都不能依赖它们,装配点只能在宿主。
220
+ */
221
+ toSkillIntegrityGuard(verify: (skillName: string) => Promise<IntegrityVerdict>): SkillIntegrityGuard;
222
+ /**
223
+ * runtime `SkillOutcomeReporter` 适配(F1):一次真实的技能脚本执行失败 → `recordFailure`,
224
+ * 达阈值即自动 `quarantined` + 写审计。
225
+ *
226
+ * 0.4.0 之前 `recordFailure` 没有任何生产调用方——`states.json` 的失败计数只在单测里增长,
227
+ * 于是「失败达阈值自动隔离」在真实部署里从不触发,Console 上那个计数恒为 0。
228
+ * 这个适配器就是把那一段接上:runtime 不能反向依赖治理,装配点只能在宿主。
229
+ *
230
+ * 只上报失败、不上报成功,与 `recordFailure` 的既有语义保持一致(累计计数,
231
+ * 成功不清零,只有 `setState(name, 'active')` 清零)。
232
+ */
233
+ toSkillOutcomeReporter(input?: {
234
+ actor?: string;
235
+ }): SkillOutcomeReporter;
159
236
  /** disabled → SKILL_DISABLED;quarantined → SKILL_QUARANTINED */
160
237
  assertExecutable(skillName: string): Promise<void>;
161
238
  /** runtime catalogFilter 实现:quarantined/deprecated/disabled 技能被路由过滤 */
@@ -328,4 +405,4 @@ declare function createMissHook(deps: {
328
405
  */
329
406
  declare function completeText(llm: LlmClient, messages: LlmMessage[]): Promise<string>;
330
407
  //#endregion
331
- export { AlwaysHumanApprovalPolicy, type ApprovalDecision, type ApprovalPolicy, type AuditEvent, type AuditLog, type CandidateFile, type CandidateRisk, type CandidateSkill, type CandidateSource, type CandidateStatus, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, type EvaluationReport, type EvaluationResult, EvaluationRunner, type EvaluationTask, FailureAnalyzer, type FailureDiagnosis, FsAuditLog, LlmCandidateGenerator, type RepairOption, RepairPlanner, SCORING_WEIGHTS, type ScoreInput, SimilarityDetector, SkillScorer, type SkillState, SkillStatePolicy, type SkillVersion, SkillVersionStore, type SourceDocument, candidateToCatalogEntry, completeText, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
408
+ export { AUDIT_PAGE_SIZE, AlwaysHumanApprovalPolicy, type ApprovalDecision, type ApprovalPolicy, type AuditEvent, type AuditLog, type AuditQueryFilter, CANDIDATE_PAGE_SIZE, type CandidateFile, type CandidateRisk, type CandidateSink, type CandidateSinkOptions, type CandidateSkill, type CandidateSource, type CandidateStatus, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, type EvaluationReport, type EvaluationResult, EvaluationRunner, type EvaluationTask, FailureAnalyzer, type FailureDiagnosis, FsAuditLog, type GeneratedSkillSubmission, LlmCandidateGenerator, type RepairOption, RepairPlanner, SCORING_WEIGHTS, SKILL_VERSION_PAGE_SIZE, type ScoreInput, SimilarityDetector, SkillScorer, type SkillState, SkillStatePolicy, type SkillVersion, SkillVersionStore, type SourceDocument, candidateToCatalogEntry, completeText, createCandidateSink, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
@@ -1,4 +1,5 @@
1
1
  import { O as messageOf, T as isValidSkillName, g as assertSafePathSegment, m as WebSkillError } from "./dist-8oQRa8Xz.js";
2
+ import { i as textParts, n as partsToText } from "./memoryArtifactStore-BtOeB_hm-tj3fC5ip.js";
2
3
 
3
4
  //#region ../governance/dist/index.js
4
5
  const invalid = (message, details) => {
@@ -99,7 +100,29 @@ function validateCandidate(candidate) {
99
100
  if (!candidate.files.some((f) => f.kind === "skill-md")) throw new WebSkillError("CANDIDATE_INVALID", `Candidate "${candidate.name}" is missing SKILL.md`);
100
101
  for (const file of candidate.files) if (file.kind === "script" && !/\.(ts|js)$/.test(file.path)) throw new WebSkillError("CANDIDATE_INVALID", `Candidate "${candidate.name}" has an unsupported script file: ${file.path}`);
101
102
  }
103
+ /**
104
+ * 治理侧各列表共用的游标切片,形状与会话消息(C1)完全一致:
105
+ * 无游标给**最新**一页,`nextCursor` 指向更早的历史,页内按时间升序。
106
+ *
107
+ * 缺省页长由各 store 传入——缺省属于实现,不属于调用方。
108
+ */
109
+ function takeTailPage(all, options, defaultLimit, what) {
110
+ const limit = options.limit ?? defaultLimit;
111
+ if (!Number.isInteger(limit) || limit <= 0) throw new WebSkillError("VALIDATION_FAILED", `Page limit must be a positive integer, received ${String(limit)}`);
112
+ let end = all.length;
113
+ if (options.cursor !== void 0) {
114
+ end = Number(options.cursor);
115
+ if (!Number.isInteger(end) || end < 0 || end > all.length) throw new WebSkillError("VALIDATION_FAILED", `Invalid ${what} cursor: ${JSON.stringify(options.cursor)}`);
116
+ }
117
+ const start = Math.max(0, end - limit);
118
+ return {
119
+ items: all.slice(start, end),
120
+ ...start > 0 ? { nextCursor: String(start) } : {}
121
+ };
122
+ }
102
123
  const dirOf$1 = (root) => `${root}/.webskill/candidates`;
124
+ /** 候选列表的缺省页长。缺省属于实现,不属于调用方 */
125
+ const CANDIDATE_PAGE_SIZE = 50;
103
126
  /** 逐文件持久化的候选存储:<managedRoot>/.webskill/candidates/<id>.json */
104
127
  var CandidateStore = class {
105
128
  #root;
@@ -119,15 +142,23 @@ var CandidateStore = class {
119
142
  if (!await this.#fs.exists(path)) throw new WebSkillError("GOVERNANCE_FAILED", `Candidate not found: ${id}`);
120
143
  return JSON.parse(await this.#fs.readText(path));
121
144
  }
122
- async list() {
145
+ /**
146
+ * 候选(= 审批队列)分页。`status` 是端口参数而不是上层 `.filter()`:
147
+ * 审批页面只看 `pending-review`,没理由把已发布的候选全部拉过端口。
148
+ * 目录扫描仍是逐文件读(备案 D25),但返回量已由 `limit` 封顶。
149
+ */
150
+ async list(options = {}) {
123
151
  const dir = dirOf$1(this.#root);
124
- if (!await this.#fs.exists(dir)) return [];
152
+ if (!await this.#fs.exists(dir)) return { items: [] };
125
153
  const out = [];
126
154
  for (const entry of await this.#fs.list(dir)) {
127
155
  if (entry.type !== "file" || !entry.path.endsWith(".json")) continue;
128
- out.push(JSON.parse(await this.#fs.readText(entry.path)));
156
+ const candidate = JSON.parse(await this.#fs.readText(entry.path));
157
+ if (options.status !== void 0 && candidate.status !== options.status) continue;
158
+ out.push(candidate);
129
159
  }
130
- return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
160
+ out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
161
+ return takeTailPage(out, options, 50, "candidate");
131
162
  }
132
163
  async updateStatus(id, status, now) {
133
164
  const candidate = await this.get(id);
@@ -151,6 +182,49 @@ function candidateToCatalogEntry(candidate) {
151
182
  };
152
183
  }
153
184
  /**
185
+ * 把 agent 生成的技能草稿接进既有的候选管线(设计 02 §5)。
186
+ *
187
+ * 走的是与文档抽取、runtime-miss 完全相同的 `normalizeCandidate` → `CandidateStore.save`,
188
+ * 因此候选恒为 `draft`,审批/发布链路一行都不用改;差别只有 `source: 'generated'`。
189
+ */
190
+ function createCandidateSink(options) {
191
+ return { async submit(input) {
192
+ const { draft } = input;
193
+ const candidate = normalizeCandidate({
194
+ raw: {
195
+ name: draft.name,
196
+ description: draft.description,
197
+ files: [{
198
+ path: "SKILL.md",
199
+ content: draft.content
200
+ }, ...draft.files ?? []]
201
+ },
202
+ source: "generated",
203
+ ...options.now ? { now: options.now } : {},
204
+ ...options.createId ? { createId: options.createId } : {}
205
+ });
206
+ candidate.metadata = {
207
+ ...candidate.metadata,
208
+ confirmedAt: input.confirmedAt,
209
+ ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {}
210
+ };
211
+ await options.store.save(candidate);
212
+ await options.audit?.append({
213
+ type: "candidate.created",
214
+ target: candidate.id,
215
+ data: {
216
+ name: candidate.name,
217
+ source: candidate.source,
218
+ risk: candidate.risk,
219
+ userConfirmed: true,
220
+ confirmedAt: input.confirmedAt,
221
+ ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {}
222
+ }
223
+ });
224
+ return { id: candidate.id };
225
+ } };
226
+ }
227
+ /**
154
228
  * 文本补全:优先走流式(慢速本地模型下非流式整响应缓冲会触发 fetch 头超时,
155
229
  * SSE 首包秒到不受此限);无 stream 的客户端回退 complete。
156
230
  */
@@ -162,7 +236,7 @@ async function completeText(llm, messages) {
162
236
  else if (event.type === "done") doneContent = event.content;
163
237
  return doneContent ?? content;
164
238
  }
165
- return (await llm.complete({ messages })).content ?? "";
239
+ return partsToText((await llm.complete({ messages })).content);
166
240
  }
167
241
  const GENERATION_PROMPT = (task, context) => [
168
242
  "You generate a skill candidate as STRICT JSON only (no markdown, no explanation).",
@@ -193,7 +267,7 @@ var LlmCandidateGenerator = class {
193
267
  const candidate = normalizeCandidate({
194
268
  raw: parseJsonObject(await completeText(this.#llm, [{
195
269
  role: "user",
196
- content: GENERATION_PROMPT(input.prompt, input.context)
270
+ content: textParts(GENERATION_PROMPT(input.prompt, input.context))
197
271
  }])),
198
272
  source: input.source,
199
273
  ...this.#now ? { now: this.#now } : {},
@@ -237,6 +311,8 @@ var CompositeApprovalPolicy = class {
237
311
  }
238
312
  };
239
313
  const fileOf$1 = (root) => `${root}/.webskill/audit.jsonl`;
314
+ /** 审计查询的缺省页长。缺省属于实现,不属于调用方 */
315
+ const AUDIT_PAGE_SIZE = 100;
240
316
  /** 环境无关 sha256(WebCrypto;Node ≥17 与浏览器均有 globalThis.crypto.subtle) */
241
317
  const sha256Hex$1 = async (text) => {
242
318
  const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
@@ -304,28 +380,67 @@ var FsAuditLog = class {
304
380
  this.#lastHash = full.hash;
305
381
  return full;
306
382
  }
307
- async query(filter) {
383
+ /**
384
+ * 分页 + 筛选下推。整文件读仍在(JSONL 布局未改,备案 D25),
385
+ * 但**跨出端口的事件数**已由 `limit` 封顶,且筛选是端口参数而非上层 `.filter()`。
386
+ *
387
+ * 本页范围内的 hash 链必须自洽:逐条重算 hash 并核对与前一行的 `prevHash` 链接,
388
+ * 任一处对不上以 `GOVERNANCE_FAILED` 报错。范围之外的断链不在本方法职责内——
389
+ * 那是 `verifyChain()` 的事(全量 O(N) 校验不该压在每次翻页上)。
390
+ */
391
+ async query(filter = {}) {
308
392
  const path = fileOf$1(this.#root);
309
- if (!await this.#fs.exists(path)) return [];
393
+ if (!await this.#fs.exists(path)) return { items: [] };
310
394
  const raw = await this.#fs.readText(path);
311
- const events = [];
395
+ /** 按文件行序保留(含解析失败的洞):链校验必须在文件行序上做,不能在筛选结果上做 */
396
+ const records = [];
312
397
  let skippedLines = 0;
313
398
  for (const line of raw.split("\n")) {
314
399
  if (line.trim() === "") continue;
315
- let event;
316
400
  try {
317
- event = JSON.parse(line);
401
+ records.push(JSON.parse(line));
318
402
  } catch {
319
403
  skippedLines += 1;
320
- continue;
404
+ records.push(void 0);
321
405
  }
406
+ }
407
+ if (skippedLines > 0) console.warn(`[webskill] Audit log at ${path} contains ${skippedLines} unparsable line(s) skipped by query; run verifyChain() to check integrity`);
408
+ const matched = [];
409
+ for (let i = 0; i < records.length; i++) {
410
+ const event = records[i];
411
+ if (event === void 0) continue;
322
412
  if (filter.target !== void 0 && event.target !== filter.target) continue;
323
413
  if (filter.type !== void 0 && event.type !== filter.type) continue;
414
+ if (filter.actor !== void 0 && event.actor !== filter.actor) continue;
324
415
  if (filter.since !== void 0 && event.ts < filter.since) continue;
325
- events.push(event);
416
+ if (filter.until !== void 0 && event.ts > filter.until) continue;
417
+ matched.push(i);
326
418
  }
327
- if (skippedLines > 0) console.warn(`[webskill] Audit log at ${path} contains ${skippedLines} unparsable line(s) skipped by query; run verifyChain() to check integrity`);
328
- return events;
419
+ const limit = filter.limit ?? 100;
420
+ if (!Number.isInteger(limit) || limit <= 0) throw new WebSkillError("VALIDATION_FAILED", `Page limit must be a positive integer, received ${String(limit)}`);
421
+ let end = matched.length;
422
+ if (filter.cursor !== void 0) {
423
+ end = Number(filter.cursor);
424
+ if (!Number.isInteger(end) || end < 0 || end > matched.length) throw new WebSkillError("VALIDATION_FAILED", `Invalid audit cursor: ${JSON.stringify(filter.cursor)}`);
425
+ }
426
+ const start = Math.max(0, end - limit);
427
+ const items = [];
428
+ for (const line of matched.slice(start, end)) {
429
+ const event = records[line];
430
+ await this.#assertChainAt(records, line, path);
431
+ items.push(event);
432
+ }
433
+ return {
434
+ items,
435
+ ...start > 0 ? { nextCursor: String(start) } : {}
436
+ };
437
+ }
438
+ /** 单行的链自洽:本行 hash 重算相符,且 prevHash 指向前一行的 hash */
439
+ async #assertChainAt(records, index, path) {
440
+ const event = records[index];
441
+ const expectedPrev = index === 0 ? "GENESIS" : records[index - 1]?.hash;
442
+ if (expectedPrev === void 0 || event.prevHash !== expectedPrev) throw new WebSkillError("GOVERNANCE_FAILED", `Audit chain is broken at line ${index} of ${path}: prevHash link mismatch (events may have been removed, reordered or corrupted)`);
443
+ if (event.hash !== await sha256Hex$1(canonical(event))) throw new WebSkillError("GOVERNANCE_FAILED", `Audit chain is broken at line ${index} of ${path}: event hash mismatch (event content was tampered)`);
329
444
  }
330
445
  /** hash 链完整性校验:逐行重算 hash 并核对 prevHash 链接 */
331
446
  async verifyChain() {
@@ -364,6 +479,8 @@ const dirOf = (root, skillName) => {
364
479
  assertSafePathSegment(skillName, "skill name");
365
480
  return `${root}/.webskill/versions/${skillName}`;
366
481
  };
482
+ /** 版本列表的缺省页长。缺省属于实现,不属于调用方 */
483
+ const SKILL_VERSION_PAGE_SIZE = 20;
367
484
  /** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断)。
368
485
  * 保留策略:maxArchivesPerSkill(默认 5)超出时清理最旧版本(json + zip 归档一并删除)。 */
369
486
  var SkillVersionStore = class {
@@ -382,7 +499,7 @@ var SkillVersionStore = class {
382
499
  this.#maxArchives = deps.maxArchivesPerSkill ?? 5;
383
500
  }
384
501
  async add(skillName, input) {
385
- const existing = await this.list(skillName);
502
+ const existing = await this.#readAll(skillName);
386
503
  const version = {
387
504
  versionId: this.#createId?.() ?? `v-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
388
505
  parentVersionId: input.parentVersionId ?? existing.at(-1)?.versionId,
@@ -402,7 +519,7 @@ var SkillVersionStore = class {
402
519
  }
403
520
  /** 保留策略:超出 maxArchivesPerSkill 时按 createdAt 清理最旧版本(json + zip) */
404
521
  async #prune(skillName) {
405
- const versions = await this.list(skillName);
522
+ const versions = await this.#readAll(skillName);
406
523
  const excess = versions.length - this.#maxArchives;
407
524
  if (excess <= 0) return;
408
525
  for (const old of versions.slice(0, excess)) {
@@ -416,7 +533,14 @@ var SkillVersionStore = class {
416
533
  if (!version.archivePath || !await this.#fs.exists(version.archivePath)) throw new WebSkillError("GOVERNANCE_FAILED", `Version "${versionId}" of skill "${skillName}" has no captured archive (recorded before archive capture existed)`);
417
534
  return this.#fs.readBinary(version.archivePath);
418
535
  }
419
- async list(skillName) {
536
+ /**
537
+ * 版本列表分页(方向同 C1:无游标给**最新**一页)。
538
+ * 保留策略与谱系构建走 `#readAll`:它们必须看到全量,不能被分页截断。
539
+ */
540
+ async list(skillName, options = {}) {
541
+ return takeTailPage(await this.#readAll(skillName), options, 20, "version");
542
+ }
543
+ async #readAll(skillName) {
420
544
  const dir = dirOf(this.#root, skillName);
421
545
  if (!await this.#fs.exists(dir)) return [];
422
546
  const out = [];
@@ -472,15 +596,15 @@ var FailureAnalyzer = class {
472
596
  const summary = failed.slice(0, 10).map((e) => `${e.type}: ${e.message ?? ""} ${JSON.stringify(e.data ?? {})}`).join("\n");
473
597
  const response = await this.#llm.complete({ messages: [{
474
598
  role: "user",
475
- content: [
599
+ content: textParts([
476
600
  "Diagnose this failed agent run as STRICT JSON only:",
477
601
  "{\"cause\": string, \"suggestedFix\": string, \"errorCode\"?: string}",
478
602
  `Run termination: ${run.terminationReason ?? "unknown"}`,
479
603
  `Failures:\n${summary || "(none recorded)"}`
480
- ].join("\n")
604
+ ].join("\n"))
481
605
  }] });
482
606
  try {
483
- const parsed = parseJsonObject(response.content ?? "");
607
+ const parsed = parseJsonObject(partsToText(response.content));
484
608
  return {
485
609
  cause: String(parsed["cause"] ?? "Unknown cause"),
486
610
  suggestedFix: String(parsed["suggestedFix"] ?? "Manual inspection required"),
@@ -596,7 +720,11 @@ var SkillStatePolicy = class {
596
720
  ...input.reason !== void 0 ? { data: { reason: input.reason } } : {}
597
721
  });
598
722
  }
599
- /** 失败计数;达阈值(默认 3)→ quarantined + 审计 */
723
+ /**
724
+ * 失败计数;达阈值(默认 3)→ quarantined + 审计。
725
+ *
726
+ * 计数是**累计**而非「连续」:成功不清零,只有 `setState(name, 'active')` 会清。
727
+ */
600
728
  async recordFailure(skillName, input = {}) {
601
729
  assertSafePathSegment(skillName, "skill name");
602
730
  const data = await this.#load();
@@ -640,6 +768,48 @@ var SkillStatePolicy = class {
640
768
  canExecute: async (skillName) => this.canExecute(await this.getState(skillName))
641
769
  };
642
770
  }
771
+ /**
772
+ * runtime SkillIntegrityGuard 适配(D3):激活期校验失败 → 置 `quarantined` + 写审计。
773
+ *
774
+ * 需求 §3 任务 3 要的是「使技能进入不可用状态,而不是只抛错」——只抛错的话,
775
+ * 同一个坏技能下一次激活还会再试一遍。置了状态之后,后续激活先被
776
+ * `toSkillStateGuard().canActivate` 挡住,根本走不到校验。
777
+ *
778
+ * `verify` 由宿主传入:完整性校验的实现在 `@webskill/node` / `@webskill/browser`,
779
+ * governance 与 runtime 都不能依赖它们,装配点只能在宿主。
780
+ */
781
+ toSkillIntegrityGuard(verify) {
782
+ return { verifyOnActivate: async (skillName) => {
783
+ assertSafePathSegment(skillName, "skill name");
784
+ let verdict;
785
+ try {
786
+ verdict = await verify(skillName);
787
+ } catch (e) {
788
+ verdict = {
789
+ ok: false,
790
+ reason: `integrity check failed: ${e instanceof Error ? e.message : String(e)}`
791
+ };
792
+ }
793
+ if (!verdict.ok) await this.setState(skillName, "quarantined", { reason: verdict.reason ?? "integrity verification failed on activation" });
794
+ return verdict;
795
+ } };
796
+ }
797
+ /**
798
+ * runtime `SkillOutcomeReporter` 适配(F1):一次真实的技能脚本执行失败 → `recordFailure`,
799
+ * 达阈值即自动 `quarantined` + 写审计。
800
+ *
801
+ * 0.4.0 之前 `recordFailure` 没有任何生产调用方——`states.json` 的失败计数只在单测里增长,
802
+ * 于是「失败达阈值自动隔离」在真实部署里从不触发,Console 上那个计数恒为 0。
803
+ * 这个适配器就是把那一段接上:runtime 不能反向依赖治理,装配点只能在宿主。
804
+ *
805
+ * 只上报失败、不上报成功,与 `recordFailure` 的既有语义保持一致(累计计数,
806
+ * 成功不清零,只有 `setState(name, 'active')` 清零)。
807
+ */
808
+ toSkillOutcomeReporter(input = {}) {
809
+ return { onSkillFailed: async (report) => {
810
+ await this.recordFailure(report.skillName, input);
811
+ } };
812
+ }
643
813
  /** disabled → SKILL_DISABLED;quarantined → SKILL_QUARANTINED */
644
814
  async assertExecutable(skillName) {
645
815
  const state = await this.getState(skillName);
@@ -863,7 +1033,7 @@ var DocumentSkillExtractor = class {
863
1033
  const candidate = normalizeCandidate({
864
1034
  raw: parseJsonObject(await completeText(this.#llm, [{
865
1035
  role: "user",
866
- content: EXTRACT_PROMPT(input.document, input.nameHint)
1036
+ content: textParts(EXTRACT_PROMPT(input.document, input.nameHint))
867
1037
  }])),
868
1038
  source: "document",
869
1039
  ...this.#now ? { now: this.#now } : {},
@@ -919,4 +1089,4 @@ function createMissHook(deps) {
919
1089
  }
920
1090
 
921
1091
  //#endregion
922
- export { AlwaysHumanApprovalPolicy, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, EvaluationRunner, FailureAnalyzer, FsAuditLog, LlmCandidateGenerator, RepairPlanner, SCORING_WEIGHTS, SimilarityDetector, SkillScorer, SkillStatePolicy, SkillVersionStore, candidateToCatalogEntry, completeText, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
1092
+ export { AUDIT_PAGE_SIZE, AlwaysHumanApprovalPolicy, CANDIDATE_PAGE_SIZE, CandidateStore, CompositeApprovalPolicy, DependencyGraph, DocumentSkillExtractor, EvaluationRunner, FailureAnalyzer, FsAuditLog, LlmCandidateGenerator, RepairPlanner, SCORING_WEIGHTS, SKILL_VERSION_PAGE_SIZE, SimilarityDetector, SkillScorer, SkillStatePolicy, SkillVersionStore, candidateToCatalogEntry, completeText, createCandidateSink, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };