@webskill/sdk 0.1.4 → 0.2.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 +9 -4
- package/dist/browser.js +25 -22
- package/dist/{dist-MTc2KB03.js → dist-BJobG0i-.js} +30 -7
- package/dist/{dist-DAn07zHu.js → dist-BS5OpedX.js} +279 -63
- package/dist/{dist-DvoBsChX.js → dist-D0saNPi_.js} +154 -10
- package/dist/{dist-BVXZF7H_.js → dist-Dj6QjHBn.js} +148 -200
- package/dist/env--jJB-TSX-04klhTYi.js +143 -0
- package/dist/env-BPUBZCwJ-4jat_SVG.d.ts +38 -0
- package/dist/governance.d.ts +38 -6
- package/dist/governance.js +171 -11
- package/dist/{index-BSS3EWY-.d.ts → index-CySxIvRz.d.ts} +68 -3
- package/dist/{index-npFMt2Zw.d.ts → index-Vn63HJRO.d.ts} +15 -38
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +35 -9
- package/dist/node.d.ts +5 -4
- package/dist/node.js +5 -4
- package/dist/sandboxWorkerEntry.js +20 -3
- package/dist/{testing-S8SN9Lc6.js → testing-B4pq6JYa.js} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{types-CgNC-oQu-DYrxhD3z.d.ts → types-CKm5G_eQ-8W8FnP4u.d.ts} +41 -4
- package/dist/ui-react.d.ts +4 -2
- package/dist/ui-react.js +25 -4
- package/dist/ui-vue.d.ts +3 -2
- package/dist/ui-vue.js +44 -15
- package/dist/ui.d.ts +5 -3
- package/dist/ui.js +2 -2
- package/package.json +21 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as parseSkillMarkdown, E as renderAvailableSkillsXml, c as SkillDiscovery, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, l as SkillReader, p as buildCatalog, u as WebSkillError } from "./dist-D0saNPi_.js";
|
|
2
2
|
import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
|
|
3
3
|
|
|
4
4
|
//#region ../runtime/dist/index.js
|
|
@@ -986,6 +986,41 @@ var EventBus = class {
|
|
|
986
986
|
for (const listener of this.#listeners.get("*") ?? []) listener(frozen);
|
|
987
987
|
}
|
|
988
988
|
};
|
|
989
|
+
/**
|
|
990
|
+
* per-scope 串行化 MemoryStore 装饰器:同一 scope 的 get/set/delete/list
|
|
991
|
+
* 按 promise 链串行(并发 run 的 read-modify-write 不丢计数)。
|
|
992
|
+
*/
|
|
993
|
+
var SerializingMemoryStore = class {
|
|
994
|
+
#inner;
|
|
995
|
+
#chains = /* @__PURE__ */ new Map();
|
|
996
|
+
constructor(inner) {
|
|
997
|
+
this.#inner = inner;
|
|
998
|
+
}
|
|
999
|
+
#serialize(scope, fn) {
|
|
1000
|
+
const next = (this.#chains.get(scope) ?? Promise.resolve()).then(fn, fn);
|
|
1001
|
+
this.#chains.set(scope, next.catch(() => void 0));
|
|
1002
|
+
return next;
|
|
1003
|
+
}
|
|
1004
|
+
get(scope, key) {
|
|
1005
|
+
return this.#serialize(scope, () => this.#inner.get(scope, key));
|
|
1006
|
+
}
|
|
1007
|
+
set(scope, key, value) {
|
|
1008
|
+
return this.#serialize(scope, () => this.#inner.set(scope, key, value));
|
|
1009
|
+
}
|
|
1010
|
+
delete(scope, key) {
|
|
1011
|
+
return this.#serialize(scope, () => this.#inner.delete(scope, key));
|
|
1012
|
+
}
|
|
1013
|
+
list(scope) {
|
|
1014
|
+
return this.#serialize(scope, () => this.#inner.list(scope));
|
|
1015
|
+
}
|
|
1016
|
+
clear(scope) {
|
|
1017
|
+
return this.#serialize(scope ?? "", () => this.#inner.clear(scope));
|
|
1018
|
+
}
|
|
1019
|
+
/** scope 级原子段:read-modify-write 全段在 promise 链内串行执行(fn 收到底层 store) */
|
|
1020
|
+
transaction(scope, fn) {
|
|
1021
|
+
return this.#serialize(scope, () => fn(this.#inner));
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
989
1024
|
/** 结构化 trace 记录器;时钟与 id 工厂可注入(golden trace 用固定值保证确定性) */
|
|
990
1025
|
var TraceRecorder = class {
|
|
991
1026
|
#runId;
|
|
@@ -1050,6 +1085,7 @@ var AgentLoop = class {
|
|
|
1050
1085
|
maxTurns: config.maxTurns ?? 10,
|
|
1051
1086
|
totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
|
|
1052
1087
|
toolTimeoutMs: config.toolTimeoutMs ?? 3e4,
|
|
1088
|
+
toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
|
|
1053
1089
|
temperature: config.temperature,
|
|
1054
1090
|
renderResult: config.renderResult
|
|
1055
1091
|
};
|
|
@@ -1060,8 +1096,6 @@ var AgentLoop = class {
|
|
|
1060
1096
|
};
|
|
1061
1097
|
}
|
|
1062
1098
|
async run(input) {
|
|
1063
|
-
this.#deps.llm;
|
|
1064
|
-
this.#deps.artifactStore;
|
|
1065
1099
|
const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1066
1100
|
const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
|
|
1067
1101
|
const trace = new TraceRecorder(runId, this.#deps.clock);
|
|
@@ -1089,7 +1123,12 @@ var AgentLoop = class {
|
|
|
1089
1123
|
interactionSeq: 0,
|
|
1090
1124
|
messages: [],
|
|
1091
1125
|
turn: 0,
|
|
1092
|
-
renderBlocks: []
|
|
1126
|
+
renderBlocks: [],
|
|
1127
|
+
startMs,
|
|
1128
|
+
pausedMs: 0,
|
|
1129
|
+
maxTurns: this.#config.maxTurns,
|
|
1130
|
+
totalTimeoutMs: this.#config.totalTimeoutMs,
|
|
1131
|
+
controller: new AbortController()
|
|
1093
1132
|
};
|
|
1094
1133
|
if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
|
|
1095
1134
|
const route = this.#deps.catalogFilter ? {
|
|
@@ -1109,29 +1148,54 @@ var AgentLoop = class {
|
|
|
1109
1148
|
return [];
|
|
1110
1149
|
}
|
|
1111
1150
|
}))).flat();
|
|
1112
|
-
state.messages = [
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1151
|
+
state.messages = [
|
|
1152
|
+
{
|
|
1153
|
+
role: "system",
|
|
1154
|
+
content: route.systemPrompt
|
|
1155
|
+
},
|
|
1156
|
+
...(input.history ?? []).map((m) => ({ ...m })),
|
|
1157
|
+
{
|
|
1158
|
+
role: "user",
|
|
1159
|
+
content: input.userPrompt
|
|
1160
|
+
}
|
|
1161
|
+
];
|
|
1119
1162
|
try {
|
|
1120
|
-
return await this.#turnLoop(state,
|
|
1163
|
+
return await this.#turnLoop(state, 1, externalSpecs);
|
|
1121
1164
|
} catch (e) {
|
|
1122
1165
|
if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
|
|
1123
1166
|
throw e;
|
|
1124
1167
|
}
|
|
1125
1168
|
}
|
|
1169
|
+
/** 总时长消耗(wall - 交互等待);交互期间不计入 totalTimeout */
|
|
1170
|
+
#elapsed(state) {
|
|
1171
|
+
return Date.parse(state.now()) - state.startMs - state.pausedMs;
|
|
1172
|
+
}
|
|
1173
|
+
/** 重设硬期限定时器(剩余 = totalTimeoutMs - 已消耗;耗尽立即 abort) */
|
|
1174
|
+
#armDeadline(state) {
|
|
1175
|
+
if (state.timer) clearTimeout(state.timer);
|
|
1176
|
+
const remaining = state.totalTimeoutMs - this.#elapsed(state);
|
|
1177
|
+
if (remaining <= 0) {
|
|
1178
|
+
state.controller.abort();
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
state.timer = setTimeout(() => state.controller.abort(), remaining);
|
|
1182
|
+
}
|
|
1183
|
+
#disarmDeadline(state) {
|
|
1184
|
+
if (state.timer) {
|
|
1185
|
+
clearTimeout(state.timer);
|
|
1186
|
+
state.timer = void 0;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1126
1189
|
/** 主循环(run 从第 1 轮、resume 从快照轮次续跑;totalTimeout 以 startedAt 续算) */
|
|
1127
|
-
async #turnLoop(state,
|
|
1190
|
+
async #turnLoop(state, startTurn, externalSpecs) {
|
|
1128
1191
|
const finish = (status, reason, output, errorCode) => this.#finish(state, status, reason, output, errorCode);
|
|
1129
1192
|
const { llm } = this.#deps;
|
|
1130
1193
|
const { trace, messages } = state;
|
|
1194
|
+
this.#armDeadline(state);
|
|
1131
1195
|
for (let turn = startTurn;; turn++) {
|
|
1132
1196
|
state.turn = turn;
|
|
1133
|
-
if (turn >
|
|
1134
|
-
if (
|
|
1197
|
+
if (turn > state.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${state.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
|
|
1198
|
+
if (this.#elapsed(state) > state.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
|
|
1135
1199
|
const toolSpecs = [
|
|
1136
1200
|
toLlmToolSpec(READ_SKILL_FILE_TOOL),
|
|
1137
1201
|
...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
|
|
@@ -1149,14 +1213,17 @@ var AgentLoop = class {
|
|
|
1149
1213
|
model: this.#deps.model,
|
|
1150
1214
|
messages,
|
|
1151
1215
|
tools: toolSpecs,
|
|
1152
|
-
temperature: this.#config.temperature
|
|
1216
|
+
temperature: this.#config.temperature,
|
|
1217
|
+
signal: state.controller.signal
|
|
1153
1218
|
}) : await llm.complete({
|
|
1154
1219
|
model: this.#deps.model,
|
|
1155
1220
|
messages,
|
|
1156
1221
|
tools: toolSpecs,
|
|
1157
|
-
temperature: this.#config.temperature
|
|
1222
|
+
temperature: this.#config.temperature,
|
|
1223
|
+
signal: state.controller.signal
|
|
1158
1224
|
});
|
|
1159
1225
|
} catch (e) {
|
|
1226
|
+
if (state.controller.signal.aborted) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
|
|
1160
1227
|
const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
|
|
1161
1228
|
return finish("failed", "llm-error", messageOf$1(e), code);
|
|
1162
1229
|
}
|
|
@@ -1165,7 +1232,13 @@ var AgentLoop = class {
|
|
|
1165
1232
|
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
1166
1233
|
contentLength: response.content?.length ?? 0
|
|
1167
1234
|
} });
|
|
1168
|
-
if (!response.toolCalls?.length)
|
|
1235
|
+
if (!response.toolCalls?.length) {
|
|
1236
|
+
messages.push({
|
|
1237
|
+
role: "assistant",
|
|
1238
|
+
content: response.content ?? ""
|
|
1239
|
+
});
|
|
1240
|
+
return finish("completed", "final-answer", response.content ?? "");
|
|
1241
|
+
}
|
|
1169
1242
|
await this.#lifecycle("execute", state, { turn });
|
|
1170
1243
|
messages.push({
|
|
1171
1244
|
role: "assistant",
|
|
@@ -1183,13 +1256,14 @@ var AgentLoop = class {
|
|
|
1183
1256
|
messages.push({
|
|
1184
1257
|
role: "tool",
|
|
1185
1258
|
toolCallId: call.id,
|
|
1186
|
-
content:
|
|
1259
|
+
content: await this.#serializeToolResult(call, result, state)
|
|
1187
1260
|
});
|
|
1188
1261
|
}
|
|
1189
1262
|
}
|
|
1190
1263
|
}
|
|
1191
1264
|
/** 统一终态处理:completed/failed/cancelled;终态即删快照(快照是续命机制,审计归治理) */
|
|
1192
1265
|
async #finish(state, status, reason, output, errorCode) {
|
|
1266
|
+
this.#disarmDeadline(state);
|
|
1193
1267
|
const { run, trace } = state;
|
|
1194
1268
|
run.status = status;
|
|
1195
1269
|
run.terminationReason = reason;
|
|
@@ -1229,7 +1303,8 @@ var AgentLoop = class {
|
|
|
1229
1303
|
run.trace = trace.list();
|
|
1230
1304
|
return {
|
|
1231
1305
|
output,
|
|
1232
|
-
run
|
|
1306
|
+
run,
|
|
1307
|
+
messages: state.messages.map((m) => ({ ...m }))
|
|
1233
1308
|
};
|
|
1234
1309
|
}
|
|
1235
1310
|
/** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
|
|
@@ -1248,10 +1323,12 @@ var AgentLoop = class {
|
|
|
1248
1323
|
activatedTools: [...state.activatedTools.values()],
|
|
1249
1324
|
pendingInteraction: request,
|
|
1250
1325
|
interactionExpiresAt: state.run.interruptExpiresAt,
|
|
1326
|
+
renderBlocks: [...state.renderBlocks],
|
|
1327
|
+
interactionSeq: state.interactionSeq,
|
|
1251
1328
|
config: {
|
|
1252
|
-
maxTurns:
|
|
1253
|
-
totalTimeoutMs:
|
|
1254
|
-
toolTimeoutMs:
|
|
1329
|
+
maxTurns: state.maxTurns,
|
|
1330
|
+
totalTimeoutMs: state.totalTimeoutMs,
|
|
1331
|
+
toolTimeoutMs: state.toolTimeoutMs,
|
|
1255
1332
|
...this.#config.temperature !== void 0 ? { temperature: this.#config.temperature } : {}
|
|
1256
1333
|
},
|
|
1257
1334
|
snapshotAt: state.now()
|
|
@@ -1290,10 +1367,15 @@ var AgentLoop = class {
|
|
|
1290
1367
|
activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
|
|
1291
1368
|
toolTimeoutMs: snapshot.config.toolTimeoutMs,
|
|
1292
1369
|
now,
|
|
1293
|
-
interactionSeq: 0,
|
|
1370
|
+
interactionSeq: snapshot.interactionSeq ?? 0,
|
|
1294
1371
|
messages: snapshot.messages.map((m) => ({ ...m })),
|
|
1295
1372
|
turn: snapshot.turn,
|
|
1296
|
-
renderBlocks: []
|
|
1373
|
+
renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
|
|
1374
|
+
startMs,
|
|
1375
|
+
pausedMs: 0,
|
|
1376
|
+
maxTurns: snapshot.config.maxTurns,
|
|
1377
|
+
totalTimeoutMs: snapshot.config.totalTimeoutMs,
|
|
1378
|
+
controller: new AbortController()
|
|
1297
1379
|
};
|
|
1298
1380
|
if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
|
|
1299
1381
|
trace.record("run.resumed", { data: {
|
|
@@ -1364,7 +1446,7 @@ var AgentLoop = class {
|
|
|
1364
1446
|
}
|
|
1365
1447
|
}))).flat();
|
|
1366
1448
|
try {
|
|
1367
|
-
return await this.#turnLoop(state,
|
|
1449
|
+
return await this.#turnLoop(state, snapshot.turn + 1, externalSpecs);
|
|
1368
1450
|
} catch (e) {
|
|
1369
1451
|
if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
|
|
1370
1452
|
throw e;
|
|
@@ -1431,8 +1513,18 @@ var AgentLoop = class {
|
|
|
1431
1513
|
* 取消 → RunTerminated(cancelled);超时 → RunTerminated(interaction-timeout)。
|
|
1432
1514
|
*/
|
|
1433
1515
|
async #interact(state, request, traceData) {
|
|
1516
|
+
if (!this.#deps.uiBridge) throw new Error("interact() requires a UiBridge");
|
|
1517
|
+
this.#disarmDeadline(state);
|
|
1518
|
+
const waitStartMs = Date.parse(state.now());
|
|
1519
|
+
try {
|
|
1520
|
+
return await this.#interactInner(state, request, traceData);
|
|
1521
|
+
} finally {
|
|
1522
|
+
state.pausedMs += Date.parse(state.now()) - waitStartMs;
|
|
1523
|
+
this.#armDeadline(state);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async #interactInner(state, request, traceData) {
|
|
1434
1527
|
const bridge = this.#deps.uiBridge;
|
|
1435
|
-
if (!bridge) throw new Error("interact() requires a UiBridge");
|
|
1436
1528
|
const { run } = state;
|
|
1437
1529
|
run.status = "interrupted";
|
|
1438
1530
|
run.interruptExpiresAt = new Date(Date.parse(state.now()) + this.#policy.interactionTimeoutMs).toISOString();
|
|
@@ -1448,7 +1540,7 @@ var AgentLoop = class {
|
|
|
1448
1540
|
} });
|
|
1449
1541
|
let response;
|
|
1450
1542
|
try {
|
|
1451
|
-
response = await this.#withInteractionTimeout(bridge.request(request), this.#policy.interactionTimeoutMs);
|
|
1543
|
+
response = await this.#withInteractionTimeout(bridge.request(request), this.#policy.interactionTimeoutMs, () => bridge.cancel?.(request.id));
|
|
1452
1544
|
} catch (e) {
|
|
1453
1545
|
run.status = "running";
|
|
1454
1546
|
run.interruptExpiresAt = void 0;
|
|
@@ -1477,9 +1569,14 @@ var AgentLoop = class {
|
|
|
1477
1569
|
await this.#appendParamHistory(state, request, response.value);
|
|
1478
1570
|
return response.value;
|
|
1479
1571
|
}
|
|
1480
|
-
#withInteractionTimeout(promise, timeoutMs) {
|
|
1572
|
+
#withInteractionTimeout(promise, timeoutMs, onTimeout) {
|
|
1481
1573
|
return new Promise((resolve, reject) => {
|
|
1482
|
-
const timer = setTimeout(() =>
|
|
1574
|
+
const timer = setTimeout(() => {
|
|
1575
|
+
try {
|
|
1576
|
+
onTimeout?.();
|
|
1577
|
+
} catch {}
|
|
1578
|
+
reject(new WebSkillError("RUN_INTERACTION_TIMEOUT", `Interaction timed out after ${timeoutMs}ms`));
|
|
1579
|
+
}, timeoutMs);
|
|
1483
1580
|
promise.then((v) => {
|
|
1484
1581
|
clearTimeout(timer);
|
|
1485
1582
|
resolve(v);
|
|
@@ -1560,6 +1657,7 @@ var AgentLoop = class {
|
|
|
1560
1657
|
if (typeof pathArg !== "string") return toolError("TOOL_EXECUTION_FAILED", "read_skill_file \"path\" must be a string");
|
|
1561
1658
|
if (skillName.startsWith("mcp://")) return this.#handleReadExternalSkill(skillName, pathArg, state);
|
|
1562
1659
|
try {
|
|
1660
|
+
if (await this.#guardDenied("canRead", skillName)) return toolError("SKILL_DISABLED", `Skill "${skillName}" is blocked by the skill state guard (read)`);
|
|
1563
1661
|
const text = await state.reader.readSkillFile(skillName, pathArg);
|
|
1564
1662
|
let note = "";
|
|
1565
1663
|
if (pathArg === "SKILL.md" && !state.activated.has(skillName)) note = await this.#activateSkill(skillName, state, void 0, text);
|
|
@@ -1618,8 +1716,35 @@ var AgentLoop = class {
|
|
|
1618
1716
|
const detail = failures.length > 0 ? ` (provider errors: ${failures.join("; ")})` : "";
|
|
1619
1717
|
return toolError("SKILL_NOT_FOUND", `Skill not found via external providers: ${skillKey}${detail}`);
|
|
1620
1718
|
}
|
|
1719
|
+
/**
|
|
1720
|
+
* 工具结果回喂序列化:超过 toolResultMaxBytes(默认 100KB)时头尾保留截断,
|
|
1721
|
+
* 完整内容经 artifactStore 落 artifact,回喂摘要含 artifact id。
|
|
1722
|
+
*/
|
|
1723
|
+
async #serializeToolResult(call, result, state) {
|
|
1724
|
+
const text = JSON.stringify(result);
|
|
1725
|
+
const max = this.#config.toolResultMaxBytes;
|
|
1726
|
+
if (text.length <= max) return text;
|
|
1727
|
+
const artifact = await this.#deps.artifactStore.createTextArtifact({
|
|
1728
|
+
runId: state.runId,
|
|
1729
|
+
path: `tool-results/${call.id}.json`,
|
|
1730
|
+
content: text
|
|
1731
|
+
});
|
|
1732
|
+
const head = text.slice(0, Math.floor(max * .6));
|
|
1733
|
+
const tail = text.slice(-Math.floor(max * .3));
|
|
1734
|
+
return `${head}\n...[truncated ${text.length - head.length - tail.length} chars; full tool result saved as artifact "${artifact.id}"]...\n${tail}`;
|
|
1735
|
+
}
|
|
1736
|
+
/** SkillStateGuard 判定(无注入默认全放行;仅显式 false 拦截) */
|
|
1737
|
+
async #guardDenied(kind, skillName) {
|
|
1738
|
+
const check = this.#deps.skillStateGuard?.[kind];
|
|
1739
|
+
if (!check) return false;
|
|
1740
|
+
return await check(skillName) === false;
|
|
1741
|
+
}
|
|
1621
1742
|
/** 首次读到 SKILL.md 时激活技能:加载其 scripts 工具定义,供后续轮次使用;dependencies 级联激活(via 记录来源) */
|
|
1622
1743
|
async #activateSkill(skillName, state, via, skillMdText) {
|
|
1744
|
+
if (await this.#guardDenied("canActivate", skillName)) {
|
|
1745
|
+
state.trace.record("run.warning", { message: `Skill "${skillName}" is blocked by the skill state guard (activate)` });
|
|
1746
|
+
return "";
|
|
1747
|
+
}
|
|
1623
1748
|
state.activated.add(skillName);
|
|
1624
1749
|
state.trace.record("skill.activated", { data: {
|
|
1625
1750
|
skillName,
|
|
@@ -1644,7 +1769,7 @@ var AgentLoop = class {
|
|
|
1644
1769
|
const executor = this.#deps.executor;
|
|
1645
1770
|
const loaded = [];
|
|
1646
1771
|
if (executor) {
|
|
1647
|
-
let scriptFiles
|
|
1772
|
+
let scriptFiles;
|
|
1648
1773
|
try {
|
|
1649
1774
|
scriptFiles = (await this.#deps.fs.list(`${root}/scripts`)).filter((s) => s.type === "file").map((s) => baseName(s.path));
|
|
1650
1775
|
} catch {
|
|
@@ -1692,8 +1817,12 @@ var AgentLoop = class {
|
|
|
1692
1817
|
for (const ext of ["ts", "js"]) {
|
|
1693
1818
|
const scriptPath = `${skillRoot}/scripts/${scriptName}.${ext}`;
|
|
1694
1819
|
if (!await this.#deps.fs.exists(scriptPath)) continue;
|
|
1695
|
-
|
|
1696
|
-
|
|
1820
|
+
try {
|
|
1821
|
+
const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
|
|
1822
|
+
if (inferred) def.inputSchema = inferred;
|
|
1823
|
+
} catch (e) {
|
|
1824
|
+
state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf$1(e)}` });
|
|
1825
|
+
}
|
|
1697
1826
|
return;
|
|
1698
1827
|
}
|
|
1699
1828
|
}
|
|
@@ -1735,12 +1864,14 @@ var AgentLoop = class {
|
|
|
1735
1864
|
onArtifactCreated: (artifact) => createdIds.add(artifact.id)
|
|
1736
1865
|
});
|
|
1737
1866
|
try {
|
|
1867
|
+
if (await this.#guardDenied("canExecute", skillName)) return toolError("SKILL_DISABLED", `Skill "${skillName}" is blocked by the skill state guard (execute)`);
|
|
1868
|
+
const remainingMs = state.totalTimeoutMs - this.#elapsed(state);
|
|
1738
1869
|
const result = await this.#deps.executor.execute({
|
|
1739
1870
|
skillRoot: root,
|
|
1740
1871
|
scriptName,
|
|
1741
1872
|
args,
|
|
1742
1873
|
context,
|
|
1743
|
-
timeoutMs: state.toolTimeoutMs
|
|
1874
|
+
timeoutMs: Math.max(1, Math.min(state.toolTimeoutMs, remainingMs))
|
|
1744
1875
|
});
|
|
1745
1876
|
if (createdIds.size > 0) {
|
|
1746
1877
|
const fresh = (await this.#deps.artifactStore.listArtifacts(state.runId)).filter((a) => createdIds.has(a.id));
|
|
@@ -1793,6 +1924,22 @@ var AgentLoop = class {
|
|
|
1793
1924
|
state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
|
|
1794
1925
|
}
|
|
1795
1926
|
}
|
|
1927
|
+
/** read-modify-write:有 transaction 实现在 scope 原子段内执行(并发不丢计数),否则顺序执行 */
|
|
1928
|
+
async #memoryMutate(scope, key, state, mutate) {
|
|
1929
|
+
const memory = this.#deps.memory;
|
|
1930
|
+
if (!memory) return;
|
|
1931
|
+
if (memory.transaction) {
|
|
1932
|
+
try {
|
|
1933
|
+
await memory.transaction(scope, async (inner) => {
|
|
1934
|
+
await inner.set(scope, key, mutate(await inner.get(scope, key)));
|
|
1935
|
+
});
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
|
|
1938
|
+
}
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
await this.#memorySet(scope, key, mutate(await this.#memoryGet(scope, key)), state);
|
|
1942
|
+
}
|
|
1796
1943
|
async #writeActivationMemory(skillName, state) {
|
|
1797
1944
|
if (!this.#deps.memory) return;
|
|
1798
1945
|
const sessionScope = `session:${state.run.sessionId}`;
|
|
@@ -1800,33 +1947,38 @@ var AgentLoop = class {
|
|
|
1800
1947
|
await this.#bumpSkillStat(skillName, "activations", state);
|
|
1801
1948
|
if (this.#deps.longTerm?.enabled) {
|
|
1802
1949
|
const userScope = `user:${this.#deps.longTerm.userId}`;
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1950
|
+
await this.#memoryMutate(userScope, "skillUsage", state, (current) => {
|
|
1951
|
+
const usage = current ?? {};
|
|
1952
|
+
usage[skillName] = (usage[skillName] ?? 0) + 1;
|
|
1953
|
+
return usage;
|
|
1954
|
+
});
|
|
1806
1955
|
}
|
|
1807
1956
|
}
|
|
1808
1957
|
async #bumpSkillStat(skillName, field, state) {
|
|
1809
1958
|
if (!this.#deps.memory) return;
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1959
|
+
await this.#memoryMutate(`skill:${skillName}`, "stats", state, (current) => {
|
|
1960
|
+
const stats = current ?? {
|
|
1961
|
+
activations: 0,
|
|
1962
|
+
successes: 0,
|
|
1963
|
+
failures: 0
|
|
1964
|
+
};
|
|
1965
|
+
stats[field] = (stats[field] ?? 0) + 1;
|
|
1966
|
+
return stats;
|
|
1967
|
+
});
|
|
1818
1968
|
}
|
|
1819
1969
|
async #appendParamHistory(state, request, value) {
|
|
1820
1970
|
if (!this.#deps.memory) return;
|
|
1821
1971
|
const scope = `session:${state.run.sessionId}`;
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1972
|
+
await this.#memoryMutate(scope, "paramHistory", state, (current) => {
|
|
1973
|
+
const history = current ?? [];
|
|
1974
|
+
history.push({
|
|
1975
|
+
ts: state.now(),
|
|
1976
|
+
interactionId: request.id,
|
|
1977
|
+
type: request.type,
|
|
1978
|
+
value
|
|
1979
|
+
});
|
|
1980
|
+
return history;
|
|
1828
1981
|
});
|
|
1829
|
-
await this.#memorySet(scope, "paramHistory", history, state);
|
|
1830
1982
|
}
|
|
1831
1983
|
};
|
|
1832
1984
|
/**
|
|
@@ -1843,6 +1995,10 @@ const SNAPSHOT_SUFFIX = ".snapshot.json";
|
|
|
1843
1995
|
/**
|
|
1844
1996
|
* FileSystemProvider 后端的快照存储:<root>/<runId>.snapshot.json。
|
|
1845
1997
|
* 坏 JSON → RUN_SNAPSHOT_INCOMPATIBLE 并自动清理坏文件;runId 过路径安全校验。
|
|
1998
|
+
* save/list 时顺带清理已过 interactionExpiresAt 的过期快照。
|
|
1999
|
+
*
|
|
2000
|
+
* 数据敏感性说明:快照含完整对话历史(用户输入、工具结果、可能的凭据片段),
|
|
2001
|
+
* 以明文 JSON 落盘于宿主提供的 fs;宿主应将其视为会话数据同等保护。
|
|
1846
2002
|
* @experimental
|
|
1847
2003
|
*/
|
|
1848
2004
|
var FsRunSnapshotStore = class {
|
|
@@ -1857,6 +2013,7 @@ var FsRunSnapshotStore = class {
|
|
|
1857
2013
|
}
|
|
1858
2014
|
async save(snapshot) {
|
|
1859
2015
|
await this.#fs.writeText(this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
|
|
2016
|
+
await this.#pruneExpired();
|
|
1860
2017
|
}
|
|
1861
2018
|
async load(runId) {
|
|
1862
2019
|
const path = this.#path(runId);
|
|
@@ -1880,6 +2037,7 @@ var FsRunSnapshotStore = class {
|
|
|
1880
2037
|
if (await this.#fs.exists(path)) await this.#fs.remove(path);
|
|
1881
2038
|
}
|
|
1882
2039
|
async list() {
|
|
2040
|
+
await this.#pruneExpired();
|
|
1883
2041
|
if (!await this.#fs.exists(this.#root)) return [];
|
|
1884
2042
|
const out = [];
|
|
1885
2043
|
for (const entry of await this.#fs.list(this.#root)) {
|
|
@@ -1890,6 +2048,20 @@ var FsRunSnapshotStore = class {
|
|
|
1890
2048
|
}
|
|
1891
2049
|
return out.sort((a, b) => a.snapshotAt.localeCompare(b.snapshotAt));
|
|
1892
2050
|
}
|
|
2051
|
+
/** 过期快照清理(save/list 时顺带;失败静默不阻断主流程) */
|
|
2052
|
+
async #pruneExpired() {
|
|
2053
|
+
try {
|
|
2054
|
+
if (!await this.#fs.exists(this.#root)) return;
|
|
2055
|
+
const now = Date.now();
|
|
2056
|
+
for (const entry of await this.#fs.list(this.#root)) {
|
|
2057
|
+
if (entry.type !== "file" || !entry.path.endsWith(SNAPSHOT_SUFFIX)) continue;
|
|
2058
|
+
try {
|
|
2059
|
+
const parsed = JSON.parse(await this.#fs.readText(entry.path));
|
|
2060
|
+
if (parsed.interactionExpiresAt !== void 0 && Date.parse(parsed.interactionExpiresAt) < now) await this.#fs.remove(entry.path);
|
|
2061
|
+
} catch {}
|
|
2062
|
+
}
|
|
2063
|
+
} catch {}
|
|
2064
|
+
}
|
|
1893
2065
|
};
|
|
1894
2066
|
/**
|
|
1895
2067
|
* runtime 门面:组合 discovery / router / agent loop / lifecycle
|
|
@@ -1903,7 +2075,10 @@ var WebSkillRuntime = class {
|
|
|
1903
2075
|
#events;
|
|
1904
2076
|
#catalogCache;
|
|
1905
2077
|
constructor(deps) {
|
|
1906
|
-
this.#deps =
|
|
2078
|
+
this.#deps = {
|
|
2079
|
+
...deps,
|
|
2080
|
+
...deps.memory ? { memory: new SerializingMemoryStore(deps.memory) } : {}
|
|
2081
|
+
};
|
|
1907
2082
|
this.#discovery = new SkillDiscovery(deps.fs, deps.roots);
|
|
1908
2083
|
this.#router = deps.router ?? new ProgressiveRouter();
|
|
1909
2084
|
this.#events = deps.eventBus ?? new EventBus();
|
|
@@ -1919,6 +2094,34 @@ var WebSkillRuntime = class {
|
|
|
1919
2094
|
get events() {
|
|
1920
2095
|
return this.#events;
|
|
1921
2096
|
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Catalog 缓存失效:下次 run/discover 重新扫描技能目录。
|
|
2099
|
+
* 与 SkillManager onChanged 接线:`new SkillManager({ ..., onChanged: () => runtime.invalidate() })`。
|
|
2100
|
+
*/
|
|
2101
|
+
invalidate() {
|
|
2102
|
+
this.#catalogCache = void 0;
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
|
|
2106
|
+
* 既有 runtime.run(prompt) 保持无状态单次语义不变。
|
|
2107
|
+
*/
|
|
2108
|
+
createSession(options = {}) {
|
|
2109
|
+
const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
|
|
2110
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2111
|
+
let history = [];
|
|
2112
|
+
return {
|
|
2113
|
+
id: sessionId,
|
|
2114
|
+
createdAt,
|
|
2115
|
+
run: async (prompt) => {
|
|
2116
|
+
const result = await this.run(prompt, {
|
|
2117
|
+
sessionId,
|
|
2118
|
+
history
|
|
2119
|
+
});
|
|
2120
|
+
history = result.messages.slice(1);
|
|
2121
|
+
return result;
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
1922
2125
|
async discover() {
|
|
1923
2126
|
const result = await this.#discovery.discover();
|
|
1924
2127
|
this.#catalogCache = {
|
|
@@ -1927,7 +2130,7 @@ var WebSkillRuntime = class {
|
|
|
1927
2130
|
};
|
|
1928
2131
|
return result;
|
|
1929
2132
|
}
|
|
1930
|
-
async run(userPrompt) {
|
|
2133
|
+
async run(userPrompt, options = {}) {
|
|
1931
2134
|
if (!this.#catalogCache) await this.discover();
|
|
1932
2135
|
const cache = this.#catalogCache;
|
|
1933
2136
|
if (!cache) throw new Error("discover() did not populate the catalog cache");
|
|
@@ -1960,11 +2163,13 @@ var WebSkillRuntime = class {
|
|
|
1960
2163
|
externalTools: this.#deps.externalTools,
|
|
1961
2164
|
skillProviders: this.#deps.skillProviders,
|
|
1962
2165
|
catalogFilter: this.#deps.catalogFilter,
|
|
1963
|
-
snapshotStore: this.#deps.snapshotStore
|
|
2166
|
+
snapshotStore: this.#deps.snapshotStore,
|
|
2167
|
+
skillStateGuard: this.#deps.skillStateGuard
|
|
1964
2168
|
}, this.#deps.config).run({
|
|
1965
|
-
sessionId: this.#session.id,
|
|
2169
|
+
sessionId: options.sessionId ?? this.#session.id,
|
|
1966
2170
|
userPrompt,
|
|
1967
|
-
route
|
|
2171
|
+
route,
|
|
2172
|
+
...options.history ? { history: options.history } : {}
|
|
1968
2173
|
});
|
|
1969
2174
|
for (const failure of providerFailures) result.run.trace.push({
|
|
1970
2175
|
id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2033,7 +2238,8 @@ var WebSkillRuntime = class {
|
|
|
2033
2238
|
code: "RUN_INTERACTION_TIMEOUT"
|
|
2034
2239
|
}
|
|
2035
2240
|
}]
|
|
2036
|
-
}
|
|
2241
|
+
},
|
|
2242
|
+
messages: []
|
|
2037
2243
|
};
|
|
2038
2244
|
}
|
|
2039
2245
|
if (!this.#catalogCache) await this.discover();
|
|
@@ -2056,7 +2262,8 @@ var WebSkillRuntime = class {
|
|
|
2056
2262
|
externalTools: this.#deps.externalTools,
|
|
2057
2263
|
skillProviders: this.#deps.skillProviders,
|
|
2058
2264
|
catalogFilter: this.#deps.catalogFilter,
|
|
2059
|
-
snapshotStore: store
|
|
2265
|
+
snapshotStore: store,
|
|
2266
|
+
skillStateGuard: this.#deps.skillStateGuard
|
|
2060
2267
|
}, this.#deps.config).resume(snapshot);
|
|
2061
2268
|
}
|
|
2062
2269
|
};
|
|
@@ -2134,7 +2341,7 @@ function createWebSkillApi(deps) {
|
|
|
2134
2341
|
}
|
|
2135
2342
|
};
|
|
2136
2343
|
}
|
|
2137
|
-
/** install URL 分派:http(s)
|
|
2344
|
+
/** install URL 分派:http(s) .zip → http;.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
|
|
2138
2345
|
* 其余 http(s) URL → INSTALL_FAILED(不静默当 local);非 URL → local */
|
|
2139
2346
|
function sourceFromUrl(url) {
|
|
2140
2347
|
const bare = url.split("?")[0] ?? url;
|
|
@@ -2422,16 +2629,21 @@ function networkUrlHost(url) {
|
|
|
2422
2629
|
* once-per-run(默认)在 run 内记忆已批准能力,every-call 每次调用都问。
|
|
2423
2630
|
* @stable
|
|
2424
2631
|
*/
|
|
2425
|
-
var CapabilityApproval = class {
|
|
2632
|
+
var CapabilityApproval = class CapabilityApproval {
|
|
2426
2633
|
#uiBridge;
|
|
2427
2634
|
#scope;
|
|
2428
|
-
/** runId → 已批准能力集合(once-per-run
|
|
2635
|
+
/** runId → 已批准能力集合(once-per-run 记忆;LRU 上限防长跑进程膨胀) */
|
|
2429
2636
|
#approved = /* @__PURE__ */ new Map();
|
|
2430
2637
|
#seq = 0;
|
|
2638
|
+
static #MAX_RUNS = 128;
|
|
2431
2639
|
constructor(deps = {}) {
|
|
2432
2640
|
this.#uiBridge = deps.uiBridge;
|
|
2433
2641
|
this.#scope = deps.scope ?? "once-per-run";
|
|
2434
2642
|
}
|
|
2643
|
+
/** run 终态清理授权记录(宿主在 run 结束后调用;同时有 LRU 上限兜底) */
|
|
2644
|
+
clearRun(runId) {
|
|
2645
|
+
this.#approved.delete(runId);
|
|
2646
|
+
}
|
|
2435
2647
|
async authorize(input) {
|
|
2436
2648
|
const { runId, capability, mode } = input;
|
|
2437
2649
|
if (mode === false) return "disabled";
|
|
@@ -2452,10 +2664,14 @@ var CapabilityApproval = class {
|
|
|
2452
2664
|
this.#approved.set(runId, set);
|
|
2453
2665
|
}
|
|
2454
2666
|
set.add(capability);
|
|
2667
|
+
if (this.#approved.size > CapabilityApproval.#MAX_RUNS) {
|
|
2668
|
+
const oldest = this.#approved.keys().next().value;
|
|
2669
|
+
if (oldest !== void 0) this.#approved.delete(oldest);
|
|
2670
|
+
}
|
|
2455
2671
|
}
|
|
2456
2672
|
return "allowed";
|
|
2457
2673
|
}
|
|
2458
2674
|
};
|
|
2459
2675
|
|
|
2460
2676
|
//#endregion
|
|
2461
|
-
export {
|
|
2677
|
+
export { isNetworkAllowed as A, bridgeError as C, extractChartSpec as D, createWebSkillApi as E, resolveToolName as F, schemaToForm as I, toLlmToolSpec as L, networkUrlHost as M, normalizeToolContent as N, fromVercelResult as O, parseBridgeRequest as P, toVercelToolSpecs as R, WebSkillRuntime as S, createScriptContext as T, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, SerializingMemoryStore as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, mergeCatalogEntries as j, fromVercelStreamPart as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, buildRenderResult as w, TraceRecorder as x, RUN_SNAPSHOT_SCHEMA_VERSION as y };
|