@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.
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/browser.d.ts +151 -4
- package/dist/browser.js +269 -30
- package/dist/{catalogComponents-Dr5dFMAb-DKH_7VPI.js → catalogComponents-DfxxfUvn-T7Ic8QFV.js} +4026 -997
- package/dist/{dist-DnYG2-eY.js → dist-BQe1uglQ.js} +498 -118
- package/dist/{dist-8oQRa8Xz.js → dist-Bev6i6Ip.js} +102 -7
- package/dist/{dist-D0qW6e40.js → dist-CFmkV45C.js} +234 -27
- package/dist/{eventTypes-DjIQpt8Y-Bj3vghj4.js → eventTypes-DbOpAECr-BjcjZVms.js} +15 -2
- package/dist/governance.d.ts +24 -3
- package/dist/governance.js +30 -11
- package/dist/{index-BwsK9lGk.d.ts → index-DXNTIa-6.d.ts} +163 -6
- package/dist/{index-Ba3xFtfz.d.ts → index-lLcCpHE-.d.ts} +2 -2
- package/dist/{index-DkbABR43.d.ts → index-znZjobkr.d.ts} +144 -76
- package/dist/index.d.ts +11 -3
- package/dist/index.js +13 -4
- package/dist/mcp.d.ts +13 -2
- package/dist/mcp.js +19 -5
- package/dist/{memoryArtifactStore-52Zn9npI-BMPYwvoy.js → memoryArtifactStore-52Zn9npI-LbCQaqyx.js} +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +6 -6
- package/dist/{openUiLibrary-Bdrji9qK-D2LxmM-a.js → openUiLibrary-DURlAxjk-Do_yqg3u.js} +3 -3
- package/dist/{skillVersionStore-Bl-ElD45-CWPvGvoq.d.ts → skillVersionStore-Bl-ElD45-dMJ8Ybhb.d.ts} +1 -1
- package/dist/{testing-CYTFqkDm.js → testing-Csg5ljNm.js} +2 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-CcxRLdJG-DCXyw1US.d.ts → types-B3n0cMZu-BDhheIhX.d.ts} +54 -6
- package/dist/ui-react.d.ts +19 -6
- package/dist/ui-react.js +156 -84
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +4 -4
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-_mugzRHx-B_54vxum.js → webskillLitCatalog-DwTwSBFt-BG7kL-Es.js} +22 -3
- package/package.json +2 -2
|
@@ -1,7 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as textParts, i as rejectUnsupportedPart, n as partsToText, o as validateLlmMessages, r as promptText, t as MemoryArtifactStore } from "./memoryArtifactStore-52Zn9npI-
|
|
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
|
-
/**
|
|
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/
|
|
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":
|
|
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 =
|
|
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
|
|
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 !==
|
|
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 ===
|
|
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:
|
|
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
|
-
|
|
3262
|
+
for (const event of state.surfaceEvents) try {
|
|
3263
|
+
await bridge.renderSurface(structuredClone(event));
|
|
3100
3264
|
} catch (e) {
|
|
3101
|
-
state.trace.record("
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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-
|
|
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
|
-
|
|
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);
|
package/dist/governance.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { H as
|
|
2
|
-
import { H as IntegrityVerdict,
|
|
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-
|
|
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
|
/** 稳定展示序:筛选下拉与文档表格都从这里取,不各自维护一份顺序 */
|