@webskill/sdk 0.3.0 → 0.4.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.
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +2 -6
- package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js} +278 -122
- package/dist/{dist-rorEJsNi.js → dist-C-Sh0MDU.js} +498 -282
- package/dist/{dist-ZKaM8j06.js → dist-D9Lcn5Pp.js} +527 -837
- package/dist/governance.d.ts +45 -10
- package/dist/governance.js +151 -24
- package/dist/{index-wiV5X8Rz.d.ts → index-CHXxDccV.d.ts} +62 -144
- package/dist/{index-8d-oEDww.d.ts → index-DLfR2Y6I.d.ts} +215 -23
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +1 -1
- package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
- package/dist/node.d.ts +8 -4
- package/dist/node.js +1 -1
- package/dist/{openUiLibrary-B8-Cvou9-BbpNTXS3.js → openUiLibrary-YLS-cxyT-C96jWDQq.js} +6 -5
- package/dist/{skillVersionStore-DOEI9ptb-BxbYL70B.d.ts → skillVersionStore-uyefLPR1-DXOzbksv.d.ts} +41 -10
- package/dist/{testing-CsrG3XLz.js → testing-DDCJWvgA.js} +7 -5
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-AmKCKJn_-VGabeXK4.d.ts → types-7Wcg--Vh-1YlQ4jF9.d.ts} +85 -71
- package/dist/ui-react.d.ts +329 -18
- package/dist/ui-react.js +3714 -3463
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +4 -3
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-CNaUpasU-BslMcxRZ.js → webskillLitCatalog-CSTbhBe_-CYIs5BX8.js} +312 -122
- package/package.json +1 -1
- package/dist/jsonRenderRegistry-9GrWP_hE-U6Do3Kid.js +0 -2468
- package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
package/dist/governance.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as AuditLog, c as
|
|
1
|
+
import { $ as SkillDocument, N as FileSystemProvider, R as Page, Z as SkillCatalogEntry, c as LlmClient, d as LlmMessage, rt as SkillManagerPort, y as UiBridge, z as PageQuery } from "./types-7Wcg--Vh-1YlQ4jF9.js";
|
|
2
|
+
import { Ct as SkillOutcomeReporter, Lt as WebSkillRuntime, P as IntegrityVerdict, St as SkillIntegrityGuard, Tt as SkillStateGuard, ut as RuntimeRun } from "./index-DLfR2Y6I.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-uyefLPR1-DXOzbksv.js";
|
|
4
4
|
//#region ../governance/dist/index.d.ts
|
|
5
5
|
//#region src/candidate/candidateNormalizer.d.ts
|
|
6
6
|
/** 剥 markdown fence 与 <think> 块、截取首尾 {};非对象 → CANDIDATE_INVALID */
|
|
@@ -50,6 +50,8 @@ declare class LlmCandidateGenerator {
|
|
|
50
50
|
}
|
|
51
51
|
//#endregion
|
|
52
52
|
//#region src/audit/fsAuditLog.d.ts
|
|
53
|
+
/** 审计查询的缺省页长。缺省属于实现,不属于调用方 */
|
|
54
|
+
declare const AUDIT_PAGE_SIZE = 100;
|
|
53
55
|
interface AuditChainVerification {
|
|
54
56
|
ok: boolean;
|
|
55
57
|
/** 首个断链位置(行号,0 起;ok 时 undefined) */
|
|
@@ -74,11 +76,15 @@ declare class FsAuditLog implements AuditLog {
|
|
|
74
76
|
id?: string;
|
|
75
77
|
ts?: string;
|
|
76
78
|
}): Promise<AuditEvent>;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
/**
|
|
80
|
+
* 分页 + 筛选下推。整文件读仍在(JSONL 布局未改,备案 D25),
|
|
81
|
+
* 但**跨出端口的事件数**已由 `limit` 封顶,且筛选是端口参数而非上层 `.filter()`。
|
|
82
|
+
*
|
|
83
|
+
* 本页范围内的 hash 链必须自洽:逐条重算 hash 并核对与前一行的 `prevHash` 链接,
|
|
84
|
+
* 任一处对不上以 `GOVERNANCE_FAILED` 报错。范围之外的断链不在本方法职责内——
|
|
85
|
+
* 那是 `verifyChain()` 的事(全量 O(N) 校验不该压在每次翻页上)。
|
|
86
|
+
*/
|
|
87
|
+
query(filter?: AuditQueryFilter & PageQuery): Promise<Page<AuditEvent>>;
|
|
82
88
|
/** hash 链完整性校验:逐行重算 hash 并核对 prevHash 链接 */
|
|
83
89
|
verifyChain(): Promise<AuditChainVerification>;
|
|
84
90
|
}
|
|
@@ -144,7 +150,11 @@ declare class SkillStatePolicy {
|
|
|
144
150
|
actor?: string;
|
|
145
151
|
reason?: string;
|
|
146
152
|
}): Promise<void>;
|
|
147
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* 失败计数;达阈值(默认 3)→ quarantined + 审计。
|
|
155
|
+
*
|
|
156
|
+
* 计数是**累计**而非「连续」:成功不清零,只有 `setState(name, 'active')` 会清。
|
|
157
|
+
*/
|
|
148
158
|
recordFailure(skillName: string, input?: {
|
|
149
159
|
actor?: string;
|
|
150
160
|
}): Promise<SkillState>;
|
|
@@ -156,6 +166,31 @@ declare class SkillStatePolicy {
|
|
|
156
166
|
* quarantined/disabled 三入口全拦截。
|
|
157
167
|
*/
|
|
158
168
|
toSkillStateGuard(): SkillStateGuard;
|
|
169
|
+
/**
|
|
170
|
+
* runtime SkillIntegrityGuard 适配(D3):激活期校验失败 → 置 `quarantined` + 写审计。
|
|
171
|
+
*
|
|
172
|
+
* 需求 §3 任务 3 要的是「使技能进入不可用状态,而不是只抛错」——只抛错的话,
|
|
173
|
+
* 同一个坏技能下一次激活还会再试一遍。置了状态之后,后续激活先被
|
|
174
|
+
* `toSkillStateGuard().canActivate` 挡住,根本走不到校验。
|
|
175
|
+
*
|
|
176
|
+
* `verify` 由宿主传入:完整性校验的实现在 `@webskill/node` / `@webskill/browser`,
|
|
177
|
+
* governance 与 runtime 都不能依赖它们,装配点只能在宿主。
|
|
178
|
+
*/
|
|
179
|
+
toSkillIntegrityGuard(verify: (skillName: string) => Promise<IntegrityVerdict>): SkillIntegrityGuard;
|
|
180
|
+
/**
|
|
181
|
+
* runtime `SkillOutcomeReporter` 适配(F1):一次真实的技能脚本执行失败 → `recordFailure`,
|
|
182
|
+
* 达阈值即自动 `quarantined` + 写审计。
|
|
183
|
+
*
|
|
184
|
+
* 0.4.0 之前 `recordFailure` 没有任何生产调用方——`states.json` 的失败计数只在单测里增长,
|
|
185
|
+
* 于是「失败达阈值自动隔离」在真实部署里从不触发,Console 上那个计数恒为 0。
|
|
186
|
+
* 这个适配器就是把那一段接上:runtime 不能反向依赖治理,装配点只能在宿主。
|
|
187
|
+
*
|
|
188
|
+
* 只上报失败、不上报成功,与 `recordFailure` 的既有语义保持一致(累计计数,
|
|
189
|
+
* 成功不清零,只有 `setState(name, 'active')` 清零)。
|
|
190
|
+
*/
|
|
191
|
+
toSkillOutcomeReporter(input?: {
|
|
192
|
+
actor?: string;
|
|
193
|
+
}): SkillOutcomeReporter;
|
|
159
194
|
/** disabled → SKILL_DISABLED;quarantined → SKILL_QUARANTINED */
|
|
160
195
|
assertExecutable(skillName: string): Promise<void>;
|
|
161
196
|
/** runtime catalogFilter 实现:quarantined/deprecated/disabled 技能被路由过滤 */
|
|
@@ -328,4 +363,4 @@ declare function createMissHook(deps: {
|
|
|
328
363
|
*/
|
|
329
364
|
declare function completeText(llm: LlmClient, messages: LlmMessage[]): Promise<string>;
|
|
330
365
|
//#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 };
|
|
366
|
+
export { AUDIT_PAGE_SIZE, AlwaysHumanApprovalPolicy, type ApprovalDecision, type ApprovalPolicy, type AuditEvent, type AuditLog, type AuditQueryFilter, CANDIDATE_PAGE_SIZE, 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, SKILL_VERSION_PAGE_SIZE, type ScoreInput, SimilarityDetector, SkillScorer, type SkillState, SkillStatePolicy, type SkillVersion, SkillVersionStore, type SourceDocument, candidateToCatalogEntry, completeText, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
|
package/dist/governance.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -162,7 +193,7 @@ async function completeText(llm, messages) {
|
|
|
162
193
|
else if (event.type === "done") doneContent = event.content;
|
|
163
194
|
return doneContent ?? content;
|
|
164
195
|
}
|
|
165
|
-
return (await llm.complete({ messages })).content
|
|
196
|
+
return partsToText((await llm.complete({ messages })).content);
|
|
166
197
|
}
|
|
167
198
|
const GENERATION_PROMPT = (task, context) => [
|
|
168
199
|
"You generate a skill candidate as STRICT JSON only (no markdown, no explanation).",
|
|
@@ -193,7 +224,7 @@ var LlmCandidateGenerator = class {
|
|
|
193
224
|
const candidate = normalizeCandidate({
|
|
194
225
|
raw: parseJsonObject(await completeText(this.#llm, [{
|
|
195
226
|
role: "user",
|
|
196
|
-
content: GENERATION_PROMPT(input.prompt, input.context)
|
|
227
|
+
content: textParts(GENERATION_PROMPT(input.prompt, input.context))
|
|
197
228
|
}])),
|
|
198
229
|
source: input.source,
|
|
199
230
|
...this.#now ? { now: this.#now } : {},
|
|
@@ -237,6 +268,8 @@ var CompositeApprovalPolicy = class {
|
|
|
237
268
|
}
|
|
238
269
|
};
|
|
239
270
|
const fileOf$1 = (root) => `${root}/.webskill/audit.jsonl`;
|
|
271
|
+
/** 审计查询的缺省页长。缺省属于实现,不属于调用方 */
|
|
272
|
+
const AUDIT_PAGE_SIZE = 100;
|
|
240
273
|
/** 环境无关 sha256(WebCrypto;Node ≥17 与浏览器均有 globalThis.crypto.subtle) */
|
|
241
274
|
const sha256Hex$1 = async (text) => {
|
|
242
275
|
const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
@@ -304,28 +337,67 @@ var FsAuditLog = class {
|
|
|
304
337
|
this.#lastHash = full.hash;
|
|
305
338
|
return full;
|
|
306
339
|
}
|
|
307
|
-
|
|
340
|
+
/**
|
|
341
|
+
* 分页 + 筛选下推。整文件读仍在(JSONL 布局未改,备案 D25),
|
|
342
|
+
* 但**跨出端口的事件数**已由 `limit` 封顶,且筛选是端口参数而非上层 `.filter()`。
|
|
343
|
+
*
|
|
344
|
+
* 本页范围内的 hash 链必须自洽:逐条重算 hash 并核对与前一行的 `prevHash` 链接,
|
|
345
|
+
* 任一处对不上以 `GOVERNANCE_FAILED` 报错。范围之外的断链不在本方法职责内——
|
|
346
|
+
* 那是 `verifyChain()` 的事(全量 O(N) 校验不该压在每次翻页上)。
|
|
347
|
+
*/
|
|
348
|
+
async query(filter = {}) {
|
|
308
349
|
const path = fileOf$1(this.#root);
|
|
309
|
-
if (!await this.#fs.exists(path)) return [];
|
|
350
|
+
if (!await this.#fs.exists(path)) return { items: [] };
|
|
310
351
|
const raw = await this.#fs.readText(path);
|
|
311
|
-
|
|
352
|
+
/** 按文件行序保留(含解析失败的洞):链校验必须在文件行序上做,不能在筛选结果上做 */
|
|
353
|
+
const records = [];
|
|
312
354
|
let skippedLines = 0;
|
|
313
355
|
for (const line of raw.split("\n")) {
|
|
314
356
|
if (line.trim() === "") continue;
|
|
315
|
-
let event;
|
|
316
357
|
try {
|
|
317
|
-
|
|
358
|
+
records.push(JSON.parse(line));
|
|
318
359
|
} catch {
|
|
319
360
|
skippedLines += 1;
|
|
320
|
-
|
|
361
|
+
records.push(void 0);
|
|
321
362
|
}
|
|
363
|
+
}
|
|
364
|
+
if (skippedLines > 0) console.warn(`[webskill] Audit log at ${path} contains ${skippedLines} unparsable line(s) skipped by query; run verifyChain() to check integrity`);
|
|
365
|
+
const matched = [];
|
|
366
|
+
for (let i = 0; i < records.length; i++) {
|
|
367
|
+
const event = records[i];
|
|
368
|
+
if (event === void 0) continue;
|
|
322
369
|
if (filter.target !== void 0 && event.target !== filter.target) continue;
|
|
323
370
|
if (filter.type !== void 0 && event.type !== filter.type) continue;
|
|
371
|
+
if (filter.actor !== void 0 && event.actor !== filter.actor) continue;
|
|
324
372
|
if (filter.since !== void 0 && event.ts < filter.since) continue;
|
|
325
|
-
|
|
373
|
+
if (filter.until !== void 0 && event.ts > filter.until) continue;
|
|
374
|
+
matched.push(i);
|
|
326
375
|
}
|
|
327
|
-
|
|
328
|
-
|
|
376
|
+
const limit = filter.limit ?? 100;
|
|
377
|
+
if (!Number.isInteger(limit) || limit <= 0) throw new WebSkillError("VALIDATION_FAILED", `Page limit must be a positive integer, received ${String(limit)}`);
|
|
378
|
+
let end = matched.length;
|
|
379
|
+
if (filter.cursor !== void 0) {
|
|
380
|
+
end = Number(filter.cursor);
|
|
381
|
+
if (!Number.isInteger(end) || end < 0 || end > matched.length) throw new WebSkillError("VALIDATION_FAILED", `Invalid audit cursor: ${JSON.stringify(filter.cursor)}`);
|
|
382
|
+
}
|
|
383
|
+
const start = Math.max(0, end - limit);
|
|
384
|
+
const items = [];
|
|
385
|
+
for (const line of matched.slice(start, end)) {
|
|
386
|
+
const event = records[line];
|
|
387
|
+
await this.#assertChainAt(records, line, path);
|
|
388
|
+
items.push(event);
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
items,
|
|
392
|
+
...start > 0 ? { nextCursor: String(start) } : {}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
/** 单行的链自洽:本行 hash 重算相符,且 prevHash 指向前一行的 hash */
|
|
396
|
+
async #assertChainAt(records, index, path) {
|
|
397
|
+
const event = records[index];
|
|
398
|
+
const expectedPrev = index === 0 ? "GENESIS" : records[index - 1]?.hash;
|
|
399
|
+
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)`);
|
|
400
|
+
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
401
|
}
|
|
330
402
|
/** hash 链完整性校验:逐行重算 hash 并核对 prevHash 链接 */
|
|
331
403
|
async verifyChain() {
|
|
@@ -364,6 +436,8 @@ const dirOf = (root, skillName) => {
|
|
|
364
436
|
assertSafePathSegment(skillName, "skill name");
|
|
365
437
|
return `${root}/.webskill/versions/${skillName}`;
|
|
366
438
|
};
|
|
439
|
+
/** 版本列表的缺省页长。缺省属于实现,不属于调用方 */
|
|
440
|
+
const SKILL_VERSION_PAGE_SIZE = 20;
|
|
367
441
|
/** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断)。
|
|
368
442
|
* 保留策略:maxArchivesPerSkill(默认 5)超出时清理最旧版本(json + zip 归档一并删除)。 */
|
|
369
443
|
var SkillVersionStore = class {
|
|
@@ -382,7 +456,7 @@ var SkillVersionStore = class {
|
|
|
382
456
|
this.#maxArchives = deps.maxArchivesPerSkill ?? 5;
|
|
383
457
|
}
|
|
384
458
|
async add(skillName, input) {
|
|
385
|
-
const existing = await this
|
|
459
|
+
const existing = await this.#readAll(skillName);
|
|
386
460
|
const version = {
|
|
387
461
|
versionId: this.#createId?.() ?? `v-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
388
462
|
parentVersionId: input.parentVersionId ?? existing.at(-1)?.versionId,
|
|
@@ -402,7 +476,7 @@ var SkillVersionStore = class {
|
|
|
402
476
|
}
|
|
403
477
|
/** 保留策略:超出 maxArchivesPerSkill 时按 createdAt 清理最旧版本(json + zip) */
|
|
404
478
|
async #prune(skillName) {
|
|
405
|
-
const versions = await this
|
|
479
|
+
const versions = await this.#readAll(skillName);
|
|
406
480
|
const excess = versions.length - this.#maxArchives;
|
|
407
481
|
if (excess <= 0) return;
|
|
408
482
|
for (const old of versions.slice(0, excess)) {
|
|
@@ -416,7 +490,14 @@ var SkillVersionStore = class {
|
|
|
416
490
|
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
491
|
return this.#fs.readBinary(version.archivePath);
|
|
418
492
|
}
|
|
419
|
-
|
|
493
|
+
/**
|
|
494
|
+
* 版本列表分页(方向同 C1:无游标给**最新**一页)。
|
|
495
|
+
* 保留策略与谱系构建走 `#readAll`:它们必须看到全量,不能被分页截断。
|
|
496
|
+
*/
|
|
497
|
+
async list(skillName, options = {}) {
|
|
498
|
+
return takeTailPage(await this.#readAll(skillName), options, 20, "version");
|
|
499
|
+
}
|
|
500
|
+
async #readAll(skillName) {
|
|
420
501
|
const dir = dirOf(this.#root, skillName);
|
|
421
502
|
if (!await this.#fs.exists(dir)) return [];
|
|
422
503
|
const out = [];
|
|
@@ -472,15 +553,15 @@ var FailureAnalyzer = class {
|
|
|
472
553
|
const summary = failed.slice(0, 10).map((e) => `${e.type}: ${e.message ?? ""} ${JSON.stringify(e.data ?? {})}`).join("\n");
|
|
473
554
|
const response = await this.#llm.complete({ messages: [{
|
|
474
555
|
role: "user",
|
|
475
|
-
content: [
|
|
556
|
+
content: textParts([
|
|
476
557
|
"Diagnose this failed agent run as STRICT JSON only:",
|
|
477
558
|
"{\"cause\": string, \"suggestedFix\": string, \"errorCode\"?: string}",
|
|
478
559
|
`Run termination: ${run.terminationReason ?? "unknown"}`,
|
|
479
560
|
`Failures:\n${summary || "(none recorded)"}`
|
|
480
|
-
].join("\n")
|
|
561
|
+
].join("\n"))
|
|
481
562
|
}] });
|
|
482
563
|
try {
|
|
483
|
-
const parsed = parseJsonObject(response.content
|
|
564
|
+
const parsed = parseJsonObject(partsToText(response.content));
|
|
484
565
|
return {
|
|
485
566
|
cause: String(parsed["cause"] ?? "Unknown cause"),
|
|
486
567
|
suggestedFix: String(parsed["suggestedFix"] ?? "Manual inspection required"),
|
|
@@ -596,7 +677,11 @@ var SkillStatePolicy = class {
|
|
|
596
677
|
...input.reason !== void 0 ? { data: { reason: input.reason } } : {}
|
|
597
678
|
});
|
|
598
679
|
}
|
|
599
|
-
/**
|
|
680
|
+
/**
|
|
681
|
+
* 失败计数;达阈值(默认 3)→ quarantined + 审计。
|
|
682
|
+
*
|
|
683
|
+
* 计数是**累计**而非「连续」:成功不清零,只有 `setState(name, 'active')` 会清。
|
|
684
|
+
*/
|
|
600
685
|
async recordFailure(skillName, input = {}) {
|
|
601
686
|
assertSafePathSegment(skillName, "skill name");
|
|
602
687
|
const data = await this.#load();
|
|
@@ -640,6 +725,48 @@ var SkillStatePolicy = class {
|
|
|
640
725
|
canExecute: async (skillName) => this.canExecute(await this.getState(skillName))
|
|
641
726
|
};
|
|
642
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* runtime SkillIntegrityGuard 适配(D3):激活期校验失败 → 置 `quarantined` + 写审计。
|
|
730
|
+
*
|
|
731
|
+
* 需求 §3 任务 3 要的是「使技能进入不可用状态,而不是只抛错」——只抛错的话,
|
|
732
|
+
* 同一个坏技能下一次激活还会再试一遍。置了状态之后,后续激活先被
|
|
733
|
+
* `toSkillStateGuard().canActivate` 挡住,根本走不到校验。
|
|
734
|
+
*
|
|
735
|
+
* `verify` 由宿主传入:完整性校验的实现在 `@webskill/node` / `@webskill/browser`,
|
|
736
|
+
* governance 与 runtime 都不能依赖它们,装配点只能在宿主。
|
|
737
|
+
*/
|
|
738
|
+
toSkillIntegrityGuard(verify) {
|
|
739
|
+
return { verifyOnActivate: async (skillName) => {
|
|
740
|
+
assertSafePathSegment(skillName, "skill name");
|
|
741
|
+
let verdict;
|
|
742
|
+
try {
|
|
743
|
+
verdict = await verify(skillName);
|
|
744
|
+
} catch (e) {
|
|
745
|
+
verdict = {
|
|
746
|
+
ok: false,
|
|
747
|
+
reason: `integrity check failed: ${e instanceof Error ? e.message : String(e)}`
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
if (!verdict.ok) await this.setState(skillName, "quarantined", { reason: verdict.reason ?? "integrity verification failed on activation" });
|
|
751
|
+
return verdict;
|
|
752
|
+
} };
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* runtime `SkillOutcomeReporter` 适配(F1):一次真实的技能脚本执行失败 → `recordFailure`,
|
|
756
|
+
* 达阈值即自动 `quarantined` + 写审计。
|
|
757
|
+
*
|
|
758
|
+
* 0.4.0 之前 `recordFailure` 没有任何生产调用方——`states.json` 的失败计数只在单测里增长,
|
|
759
|
+
* 于是「失败达阈值自动隔离」在真实部署里从不触发,Console 上那个计数恒为 0。
|
|
760
|
+
* 这个适配器就是把那一段接上:runtime 不能反向依赖治理,装配点只能在宿主。
|
|
761
|
+
*
|
|
762
|
+
* 只上报失败、不上报成功,与 `recordFailure` 的既有语义保持一致(累计计数,
|
|
763
|
+
* 成功不清零,只有 `setState(name, 'active')` 清零)。
|
|
764
|
+
*/
|
|
765
|
+
toSkillOutcomeReporter(input = {}) {
|
|
766
|
+
return { onSkillFailed: async (report) => {
|
|
767
|
+
await this.recordFailure(report.skillName, input);
|
|
768
|
+
} };
|
|
769
|
+
}
|
|
643
770
|
/** disabled → SKILL_DISABLED;quarantined → SKILL_QUARANTINED */
|
|
644
771
|
async assertExecutable(skillName) {
|
|
645
772
|
const state = await this.getState(skillName);
|
|
@@ -863,7 +990,7 @@ var DocumentSkillExtractor = class {
|
|
|
863
990
|
const candidate = normalizeCandidate({
|
|
864
991
|
raw: parseJsonObject(await completeText(this.#llm, [{
|
|
865
992
|
role: "user",
|
|
866
|
-
content: EXTRACT_PROMPT(input.document, input.nameHint)
|
|
993
|
+
content: textParts(EXTRACT_PROMPT(input.document, input.nameHint))
|
|
867
994
|
}])),
|
|
868
995
|
source: "document",
|
|
869
996
|
...this.#now ? { now: this.#now } : {},
|
|
@@ -919,4 +1046,4 @@ function createMissHook(deps) {
|
|
|
919
1046
|
}
|
|
920
1047
|
|
|
921
1048
|
//#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 };
|
|
1049
|
+
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, createMissHook, jaccardSimilarity, normalizeCandidate, normalizeCandidateFiles, normalizeRisk, parseJsonObject, readDocument, sanitizeCandidateName, suggestFromFailedRun, validateCandidate };
|