@webskill/sdk 0.1.5 → 0.2.1
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/README.md +16 -10
- package/dist/browser.d.ts +37 -3
- package/dist/browser.js +106 -8
- package/dist/{dist-CfBOjJ4p.js → dist-BJobG0i-.js} +30 -7
- package/dist/{dist-LIFS3ZjI.js → dist-BRGQcLqR.js} +405 -179
- package/dist/{dist-fZFZaf43.js → dist-D0saNPi_.js} +22 -4
- package/dist/{dist-DuGN5x0v.js → dist-DrySSQ5R.js} +185 -50
- package/dist/env--jJB-TSX-04klhTYi.js +143 -0
- package/dist/env-BPUBZCwJ-4jat_SVG.d.ts +38 -0
- package/dist/governance.d.ts +43 -7
- package/dist/governance.js +168 -11
- package/dist/{index-Bun1aEf4.d.ts → index-BmzysJX5.d.ts} +48 -38
- package/dist/{index-COuOu6gw.d.ts → index-gjFuBevI.d.ts} +53 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +28 -8
- package/dist/node.d.ts +5 -4
- package/dist/node.js +5 -4
- package/dist/processSandboxEntry.d.ts +1 -0
- package/dist/processSandboxEntry.js +202 -0
- package/dist/{testing-BmR48Pn1.js → testing-B4pq6JYa.js} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{types-CgNC-oQu-DEcIBJKG.d.ts → types-CKm5G_eQ-8W8FnP4u.d.ts} +13 -2
- 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
|
@@ -174,7 +174,26 @@ var MemoryFS = class {
|
|
|
174
174
|
for (const k of descendants) this.#entries.delete(k);
|
|
175
175
|
this.#entries.delete(key);
|
|
176
176
|
}
|
|
177
|
+
async rename(from, to) {
|
|
178
|
+
const src = this.#normalize(from);
|
|
179
|
+
const dst = this.#normalize(to);
|
|
180
|
+
const entry = this.#entries.get(src);
|
|
181
|
+
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
|
|
182
|
+
const prefix = src + "/";
|
|
183
|
+
const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
|
|
184
|
+
this.#ensureParents(dst);
|
|
185
|
+
this.#entries.set(dst, entry);
|
|
186
|
+
for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
|
|
187
|
+
for (const [k] of descendants) this.#entries.delete(k);
|
|
188
|
+
this.#entries.delete(src);
|
|
189
|
+
}
|
|
177
190
|
};
|
|
191
|
+
/** 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景) */
|
|
192
|
+
async function atomicWriteText(fs, path, content) {
|
|
193
|
+
const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
194
|
+
await fs.writeText(tmp, content);
|
|
195
|
+
await fs.rename(tmp, path);
|
|
196
|
+
}
|
|
178
197
|
/** 统一分隔符为 `/`,去除 `.` 段与重复分隔符(不解析 `..`) */
|
|
179
198
|
function normalizePath(path) {
|
|
180
199
|
return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
@@ -570,8 +589,7 @@ var SkillDiscovery = class {
|
|
|
570
589
|
});
|
|
571
590
|
continue;
|
|
572
591
|
}
|
|
573
|
-
|
|
574
|
-
if (child.type !== "directory") continue;
|
|
592
|
+
await Promise.all((await this.#fs.list(root)).filter((child) => child.type === "directory").map(async (child) => {
|
|
575
593
|
const dirName = baseName(child.path);
|
|
576
594
|
const skillRoot = `${root}/${dirName}`;
|
|
577
595
|
const skillMdPath = `${skillRoot}/SKILL.md`;
|
|
@@ -596,7 +614,7 @@ var SkillDiscovery = class {
|
|
|
596
614
|
const dependencies = metadata["dependencies"];
|
|
597
615
|
adjacency.set(metadata.name, Array.isArray(dependencies) ? dependencies.filter((d) => typeof d === "string") : []);
|
|
598
616
|
}
|
|
599
|
-
}
|
|
617
|
+
}));
|
|
600
618
|
}
|
|
601
619
|
const cycles = checkDependencyCycles(adjacency);
|
|
602
620
|
const claimedNames = /* @__PURE__ */ new Set();
|
|
@@ -725,4 +743,4 @@ const xmlRenderer = {
|
|
|
725
743
|
};
|
|
726
744
|
|
|
727
745
|
//#endregion
|
|
728
|
-
export {
|
|
746
|
+
export { unzipWithLimits as A, parseSkillMarkdown as C, renderCatalogJson as D, renderAvailableSkillsXml as E, verifyManifest as M, xmlRenderer as N, resolveArchiveLimits as O, normalizePath as S, readResponseWithLimit as T, computeDigest as _, SKILL_NAME_MAX_LENGTH as a, isValidSkillName as b, SkillDiscovery as c, assertSafePathSegment as d, atomicWriteText as f, checkSkillRules as g, checkDependencyCycles as h, SKILL_MANIFEST_FILE as i, validateSkills as j, resolveInsideRoot as k, SkillReader as l, buildManifest as m, MemoryFS as n, SKILL_NAME_PATTERN as o, buildCatalog as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, escapeXml as v, parseSkillPackManifest as w, jsonRenderer as x, exportSkills as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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;
|
|
@@ -1061,8 +1096,6 @@ var AgentLoop = class {
|
|
|
1061
1096
|
};
|
|
1062
1097
|
}
|
|
1063
1098
|
async run(input) {
|
|
1064
|
-
this.#deps.llm;
|
|
1065
|
-
this.#deps.artifactStore;
|
|
1066
1099
|
const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1067
1100
|
const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
|
|
1068
1101
|
const trace = new TraceRecorder(runId, this.#deps.clock);
|
|
@@ -1115,13 +1148,17 @@ var AgentLoop = class {
|
|
|
1115
1148
|
return [];
|
|
1116
1149
|
}
|
|
1117
1150
|
}))).flat();
|
|
1118
|
-
state.messages = [
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
+
];
|
|
1125
1162
|
try {
|
|
1126
1163
|
return await this.#turnLoop(state, 1, externalSpecs);
|
|
1127
1164
|
} catch (e) {
|
|
@@ -1195,7 +1232,13 @@ var AgentLoop = class {
|
|
|
1195
1232
|
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
1196
1233
|
contentLength: response.content?.length ?? 0
|
|
1197
1234
|
} });
|
|
1198
|
-
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
|
+
}
|
|
1199
1242
|
await this.#lifecycle("execute", state, { turn });
|
|
1200
1243
|
messages.push({
|
|
1201
1244
|
role: "assistant",
|
|
@@ -1260,7 +1303,8 @@ var AgentLoop = class {
|
|
|
1260
1303
|
run.trace = trace.list();
|
|
1261
1304
|
return {
|
|
1262
1305
|
output,
|
|
1263
|
-
run
|
|
1306
|
+
run,
|
|
1307
|
+
messages: state.messages.map((m) => ({ ...m }))
|
|
1264
1308
|
};
|
|
1265
1309
|
}
|
|
1266
1310
|
/** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
|
|
@@ -1279,10 +1323,12 @@ var AgentLoop = class {
|
|
|
1279
1323
|
activatedTools: [...state.activatedTools.values()],
|
|
1280
1324
|
pendingInteraction: request,
|
|
1281
1325
|
interactionExpiresAt: state.run.interruptExpiresAt,
|
|
1326
|
+
renderBlocks: [...state.renderBlocks],
|
|
1327
|
+
interactionSeq: state.interactionSeq,
|
|
1282
1328
|
config: {
|
|
1283
|
-
maxTurns:
|
|
1284
|
-
totalTimeoutMs:
|
|
1285
|
-
toolTimeoutMs:
|
|
1329
|
+
maxTurns: state.maxTurns,
|
|
1330
|
+
totalTimeoutMs: state.totalTimeoutMs,
|
|
1331
|
+
toolTimeoutMs: state.toolTimeoutMs,
|
|
1286
1332
|
...this.#config.temperature !== void 0 ? { temperature: this.#config.temperature } : {}
|
|
1287
1333
|
},
|
|
1288
1334
|
snapshotAt: state.now()
|
|
@@ -1321,10 +1367,10 @@ var AgentLoop = class {
|
|
|
1321
1367
|
activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
|
|
1322
1368
|
toolTimeoutMs: snapshot.config.toolTimeoutMs,
|
|
1323
1369
|
now,
|
|
1324
|
-
interactionSeq: 0,
|
|
1370
|
+
interactionSeq: snapshot.interactionSeq ?? 0,
|
|
1325
1371
|
messages: snapshot.messages.map((m) => ({ ...m })),
|
|
1326
1372
|
turn: snapshot.turn,
|
|
1327
|
-
renderBlocks: [],
|
|
1373
|
+
renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
|
|
1328
1374
|
startMs,
|
|
1329
1375
|
pausedMs: 0,
|
|
1330
1376
|
maxTurns: snapshot.config.maxTurns,
|
|
@@ -1494,7 +1540,7 @@ var AgentLoop = class {
|
|
|
1494
1540
|
} });
|
|
1495
1541
|
let response;
|
|
1496
1542
|
try {
|
|
1497
|
-
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));
|
|
1498
1544
|
} catch (e) {
|
|
1499
1545
|
run.status = "running";
|
|
1500
1546
|
run.interruptExpiresAt = void 0;
|
|
@@ -1523,9 +1569,14 @@ var AgentLoop = class {
|
|
|
1523
1569
|
await this.#appendParamHistory(state, request, response.value);
|
|
1524
1570
|
return response.value;
|
|
1525
1571
|
}
|
|
1526
|
-
#withInteractionTimeout(promise, timeoutMs) {
|
|
1572
|
+
#withInteractionTimeout(promise, timeoutMs, onTimeout) {
|
|
1527
1573
|
return new Promise((resolve, reject) => {
|
|
1528
|
-
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);
|
|
1529
1580
|
promise.then((v) => {
|
|
1530
1581
|
clearTimeout(timer);
|
|
1531
1582
|
resolve(v);
|
|
@@ -1718,7 +1769,7 @@ var AgentLoop = class {
|
|
|
1718
1769
|
const executor = this.#deps.executor;
|
|
1719
1770
|
const loaded = [];
|
|
1720
1771
|
if (executor) {
|
|
1721
|
-
let scriptFiles
|
|
1772
|
+
let scriptFiles;
|
|
1722
1773
|
try {
|
|
1723
1774
|
scriptFiles = (await this.#deps.fs.list(`${root}/scripts`)).filter((s) => s.type === "file").map((s) => baseName(s.path));
|
|
1724
1775
|
} catch {
|
|
@@ -1766,8 +1817,12 @@ var AgentLoop = class {
|
|
|
1766
1817
|
for (const ext of ["ts", "js"]) {
|
|
1767
1818
|
const scriptPath = `${skillRoot}/scripts/${scriptName}.${ext}`;
|
|
1768
1819
|
if (!await this.#deps.fs.exists(scriptPath)) continue;
|
|
1769
|
-
|
|
1770
|
-
|
|
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
|
+
}
|
|
1771
1826
|
return;
|
|
1772
1827
|
}
|
|
1773
1828
|
}
|
|
@@ -1869,6 +1924,22 @@ var AgentLoop = class {
|
|
|
1869
1924
|
state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
|
|
1870
1925
|
}
|
|
1871
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
|
+
}
|
|
1872
1943
|
async #writeActivationMemory(skillName, state) {
|
|
1873
1944
|
if (!this.#deps.memory) return;
|
|
1874
1945
|
const sessionScope = `session:${state.run.sessionId}`;
|
|
@@ -1876,33 +1947,38 @@ var AgentLoop = class {
|
|
|
1876
1947
|
await this.#bumpSkillStat(skillName, "activations", state);
|
|
1877
1948
|
if (this.#deps.longTerm?.enabled) {
|
|
1878
1949
|
const userScope = `user:${this.#deps.longTerm.userId}`;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1950
|
+
await this.#memoryMutate(userScope, "skillUsage", state, (current) => {
|
|
1951
|
+
const usage = current ?? {};
|
|
1952
|
+
usage[skillName] = (usage[skillName] ?? 0) + 1;
|
|
1953
|
+
return usage;
|
|
1954
|
+
});
|
|
1882
1955
|
}
|
|
1883
1956
|
}
|
|
1884
1957
|
async #bumpSkillStat(skillName, field, state) {
|
|
1885
1958
|
if (!this.#deps.memory) return;
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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
|
+
});
|
|
1894
1968
|
}
|
|
1895
1969
|
async #appendParamHistory(state, request, value) {
|
|
1896
1970
|
if (!this.#deps.memory) return;
|
|
1897
1971
|
const scope = `session:${state.run.sessionId}`;
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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;
|
|
1904
1981
|
});
|
|
1905
|
-
await this.#memorySet(scope, "paramHistory", history, state);
|
|
1906
1982
|
}
|
|
1907
1983
|
};
|
|
1908
1984
|
/**
|
|
@@ -1919,6 +1995,10 @@ const SNAPSHOT_SUFFIX = ".snapshot.json";
|
|
|
1919
1995
|
/**
|
|
1920
1996
|
* FileSystemProvider 后端的快照存储:<root>/<runId>.snapshot.json。
|
|
1921
1997
|
* 坏 JSON → RUN_SNAPSHOT_INCOMPATIBLE 并自动清理坏文件;runId 过路径安全校验。
|
|
1998
|
+
* save/list 时顺带清理已过 interactionExpiresAt 的过期快照。
|
|
1999
|
+
*
|
|
2000
|
+
* 数据敏感性说明:快照含完整对话历史(用户输入、工具结果、可能的凭据片段),
|
|
2001
|
+
* 以明文 JSON 落盘于宿主提供的 fs;宿主应将其视为会话数据同等保护。
|
|
1922
2002
|
* @experimental
|
|
1923
2003
|
*/
|
|
1924
2004
|
var FsRunSnapshotStore = class {
|
|
@@ -1933,6 +2013,7 @@ var FsRunSnapshotStore = class {
|
|
|
1933
2013
|
}
|
|
1934
2014
|
async save(snapshot) {
|
|
1935
2015
|
await this.#fs.writeText(this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
|
|
2016
|
+
await this.#pruneExpired();
|
|
1936
2017
|
}
|
|
1937
2018
|
async load(runId) {
|
|
1938
2019
|
const path = this.#path(runId);
|
|
@@ -1956,6 +2037,7 @@ var FsRunSnapshotStore = class {
|
|
|
1956
2037
|
if (await this.#fs.exists(path)) await this.#fs.remove(path);
|
|
1957
2038
|
}
|
|
1958
2039
|
async list() {
|
|
2040
|
+
await this.#pruneExpired();
|
|
1959
2041
|
if (!await this.#fs.exists(this.#root)) return [];
|
|
1960
2042
|
const out = [];
|
|
1961
2043
|
for (const entry of await this.#fs.list(this.#root)) {
|
|
@@ -1966,6 +2048,20 @@ var FsRunSnapshotStore = class {
|
|
|
1966
2048
|
}
|
|
1967
2049
|
return out.sort((a, b) => a.snapshotAt.localeCompare(b.snapshotAt));
|
|
1968
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
|
+
}
|
|
1969
2065
|
};
|
|
1970
2066
|
/**
|
|
1971
2067
|
* runtime 门面:组合 discovery / router / agent loop / lifecycle
|
|
@@ -1979,7 +2075,10 @@ var WebSkillRuntime = class {
|
|
|
1979
2075
|
#events;
|
|
1980
2076
|
#catalogCache;
|
|
1981
2077
|
constructor(deps) {
|
|
1982
|
-
this.#deps =
|
|
2078
|
+
this.#deps = {
|
|
2079
|
+
...deps,
|
|
2080
|
+
...deps.memory ? { memory: new SerializingMemoryStore(deps.memory) } : {}
|
|
2081
|
+
};
|
|
1983
2082
|
this.#discovery = new SkillDiscovery(deps.fs, deps.roots);
|
|
1984
2083
|
this.#router = deps.router ?? new ProgressiveRouter();
|
|
1985
2084
|
this.#events = deps.eventBus ?? new EventBus();
|
|
@@ -1991,6 +2090,10 @@ var WebSkillRuntime = class {
|
|
|
1991
2090
|
get session() {
|
|
1992
2091
|
return this.#session;
|
|
1993
2092
|
}
|
|
2093
|
+
/** 当前装配的脚本执行器(治理默认执行器断言等只读场景) */
|
|
2094
|
+
get executor() {
|
|
2095
|
+
return this.#deps.executor;
|
|
2096
|
+
}
|
|
1994
2097
|
/** 生命周期事件总线(只读观测) */
|
|
1995
2098
|
get events() {
|
|
1996
2099
|
return this.#events;
|
|
@@ -2002,6 +2105,27 @@ var WebSkillRuntime = class {
|
|
|
2002
2105
|
invalidate() {
|
|
2003
2106
|
this.#catalogCache = void 0;
|
|
2004
2107
|
}
|
|
2108
|
+
/**
|
|
2109
|
+
* 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
|
|
2110
|
+
* 既有 runtime.run(prompt) 保持无状态单次语义不变。
|
|
2111
|
+
*/
|
|
2112
|
+
createSession(options = {}) {
|
|
2113
|
+
const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
|
|
2114
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2115
|
+
let history = [];
|
|
2116
|
+
return {
|
|
2117
|
+
id: sessionId,
|
|
2118
|
+
createdAt,
|
|
2119
|
+
run: async (prompt) => {
|
|
2120
|
+
const result = await this.run(prompt, {
|
|
2121
|
+
sessionId,
|
|
2122
|
+
history
|
|
2123
|
+
});
|
|
2124
|
+
history = result.messages.slice(1);
|
|
2125
|
+
return result;
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2005
2129
|
async discover() {
|
|
2006
2130
|
const result = await this.#discovery.discover();
|
|
2007
2131
|
this.#catalogCache = {
|
|
@@ -2010,7 +2134,7 @@ var WebSkillRuntime = class {
|
|
|
2010
2134
|
};
|
|
2011
2135
|
return result;
|
|
2012
2136
|
}
|
|
2013
|
-
async run(userPrompt) {
|
|
2137
|
+
async run(userPrompt, options = {}) {
|
|
2014
2138
|
if (!this.#catalogCache) await this.discover();
|
|
2015
2139
|
const cache = this.#catalogCache;
|
|
2016
2140
|
if (!cache) throw new Error("discover() did not populate the catalog cache");
|
|
@@ -2046,9 +2170,10 @@ var WebSkillRuntime = class {
|
|
|
2046
2170
|
snapshotStore: this.#deps.snapshotStore,
|
|
2047
2171
|
skillStateGuard: this.#deps.skillStateGuard
|
|
2048
2172
|
}, this.#deps.config).run({
|
|
2049
|
-
sessionId: this.#session.id,
|
|
2173
|
+
sessionId: options.sessionId ?? this.#session.id,
|
|
2050
2174
|
userPrompt,
|
|
2051
|
-
route
|
|
2175
|
+
route,
|
|
2176
|
+
...options.history ? { history: options.history } : {}
|
|
2052
2177
|
});
|
|
2053
2178
|
for (const failure of providerFailures) result.run.trace.push({
|
|
2054
2179
|
id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2117,7 +2242,8 @@ var WebSkillRuntime = class {
|
|
|
2117
2242
|
code: "RUN_INTERACTION_TIMEOUT"
|
|
2118
2243
|
}
|
|
2119
2244
|
}]
|
|
2120
|
-
}
|
|
2245
|
+
},
|
|
2246
|
+
messages: []
|
|
2121
2247
|
};
|
|
2122
2248
|
}
|
|
2123
2249
|
if (!this.#catalogCache) await this.discover();
|
|
@@ -2219,7 +2345,7 @@ function createWebSkillApi(deps) {
|
|
|
2219
2345
|
}
|
|
2220
2346
|
};
|
|
2221
2347
|
}
|
|
2222
|
-
/** install URL 分派:http(s)
|
|
2348
|
+
/** install URL 分派:http(s) .zip → http;.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
|
|
2223
2349
|
* 其余 http(s) URL → INSTALL_FAILED(不静默当 local);非 URL → local */
|
|
2224
2350
|
function sourceFromUrl(url) {
|
|
2225
2351
|
const bare = url.split("?")[0] ?? url;
|
|
@@ -2507,16 +2633,21 @@ function networkUrlHost(url) {
|
|
|
2507
2633
|
* once-per-run(默认)在 run 内记忆已批准能力,every-call 每次调用都问。
|
|
2508
2634
|
* @stable
|
|
2509
2635
|
*/
|
|
2510
|
-
var CapabilityApproval = class {
|
|
2636
|
+
var CapabilityApproval = class CapabilityApproval {
|
|
2511
2637
|
#uiBridge;
|
|
2512
2638
|
#scope;
|
|
2513
|
-
/** runId → 已批准能力集合(once-per-run
|
|
2639
|
+
/** runId → 已批准能力集合(once-per-run 记忆;LRU 上限防长跑进程膨胀) */
|
|
2514
2640
|
#approved = /* @__PURE__ */ new Map();
|
|
2515
2641
|
#seq = 0;
|
|
2642
|
+
static #MAX_RUNS = 128;
|
|
2516
2643
|
constructor(deps = {}) {
|
|
2517
2644
|
this.#uiBridge = deps.uiBridge;
|
|
2518
2645
|
this.#scope = deps.scope ?? "once-per-run";
|
|
2519
2646
|
}
|
|
2647
|
+
/** run 终态清理授权记录(宿主在 run 结束后调用;同时有 LRU 上限兜底) */
|
|
2648
|
+
clearRun(runId) {
|
|
2649
|
+
this.#approved.delete(runId);
|
|
2650
|
+
}
|
|
2520
2651
|
async authorize(input) {
|
|
2521
2652
|
const { runId, capability, mode } = input;
|
|
2522
2653
|
if (mode === false) return "disabled";
|
|
@@ -2537,10 +2668,14 @@ var CapabilityApproval = class {
|
|
|
2537
2668
|
this.#approved.set(runId, set);
|
|
2538
2669
|
}
|
|
2539
2670
|
set.add(capability);
|
|
2671
|
+
if (this.#approved.size > CapabilityApproval.#MAX_RUNS) {
|
|
2672
|
+
const oldest = this.#approved.keys().next().value;
|
|
2673
|
+
if (oldest !== void 0) this.#approved.delete(oldest);
|
|
2674
|
+
}
|
|
2540
2675
|
}
|
|
2541
2676
|
return "allowed";
|
|
2542
2677
|
}
|
|
2543
2678
|
};
|
|
2544
2679
|
|
|
2545
2680
|
//#endregion
|
|
2546
|
-
export {
|
|
2681
|
+
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 };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
//#region ../node/dist/env--jJB-TSX.js
|
|
4
|
+
/** 解析 .env(简单 KEY=VALUE 行解析,不引依赖);文件缺失返回 undefined */
|
|
5
|
+
function readEnvVars(dotenvPath) {
|
|
6
|
+
let raw;
|
|
7
|
+
try {
|
|
8
|
+
raw = readFileSync(dotenvPath, "utf8");
|
|
9
|
+
} catch {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const vars = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
16
|
+
const eq = trimmed.indexOf("=");
|
|
17
|
+
if (eq <= 0) continue;
|
|
18
|
+
const key = trimmed.slice(0, eq).trim();
|
|
19
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
20
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
21
|
+
vars.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
return vars;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 读取 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL(优先),
|
|
27
|
+
* 向后兼容 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
|
|
28
|
+
*/
|
|
29
|
+
function loadLlmConfigFromEnv(dotenvPath = ".env") {
|
|
30
|
+
const vars = readEnvVars(dotenvPath);
|
|
31
|
+
if (!vars) return void 0;
|
|
32
|
+
const baseUrl = vars.get("LLM_BASE_URL") ?? vars.get("VITE_BASE_URL");
|
|
33
|
+
const apiKey = vars.get("LLM_API_KEY") ?? vars.get("VITE_API_KEY");
|
|
34
|
+
const model = vars.get("LLM_MODEL") ?? vars.get("VITE_MODEL_NAME");
|
|
35
|
+
if (!baseUrl || !apiKey || !model) return void 0;
|
|
36
|
+
return {
|
|
37
|
+
baseUrl,
|
|
38
|
+
apiKey,
|
|
39
|
+
model
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** ANTHROPIC_API_KEY / ANTHROPIC_MODEL(可选 ANTHROPIC_BASE_URL);缺项返回 undefined */
|
|
43
|
+
function loadAnthropicConfigFromEnv(dotenvPath = ".env") {
|
|
44
|
+
const vars = readEnvVars(dotenvPath);
|
|
45
|
+
if (!vars) return void 0;
|
|
46
|
+
const apiKey = vars.get("ANTHROPIC_API_KEY");
|
|
47
|
+
const model = vars.get("ANTHROPIC_MODEL");
|
|
48
|
+
if (!apiKey || !model) return void 0;
|
|
49
|
+
const baseUrl = vars.get("ANTHROPIC_BASE_URL");
|
|
50
|
+
return {
|
|
51
|
+
apiKey,
|
|
52
|
+
model,
|
|
53
|
+
...baseUrl ? { baseUrl } : {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** GOOGLE_API_KEY / GOOGLE_MODEL(可选 GOOGLE_BASE_URL);缺项返回 undefined */
|
|
57
|
+
function loadGoogleConfigFromEnv(dotenvPath = ".env") {
|
|
58
|
+
const vars = readEnvVars(dotenvPath);
|
|
59
|
+
if (!vars) return void 0;
|
|
60
|
+
const apiKey = vars.get("GOOGLE_API_KEY");
|
|
61
|
+
const model = vars.get("GOOGLE_MODEL");
|
|
62
|
+
if (!apiKey || !model) return void 0;
|
|
63
|
+
const baseUrl = vars.get("GOOGLE_BASE_URL");
|
|
64
|
+
return {
|
|
65
|
+
apiKey,
|
|
66
|
+
model,
|
|
67
|
+
...baseUrl ? { baseUrl } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
let capabilitiesCache;
|
|
71
|
+
/**
|
|
72
|
+
* 探测 LLM 能力(结果模块级缓存):
|
|
73
|
+
* - available:GET /models 可达(与 checkAvailability 同语义)
|
|
74
|
+
* - nonStreaming:最小非流式 chat completion(max_tokens=1,短超时);
|
|
75
|
+
* HTTP 400/404 或明确不支持非流式的错误 → false
|
|
76
|
+
* - streaming 能力由 available 推断(SSE 解析在 SDK 侧实现)
|
|
77
|
+
* 环境变量 VITE_LLM_NON_STREAMING=0 可显式强制 nonStreaming=false(模拟流式-only 回归)。
|
|
78
|
+
*/
|
|
79
|
+
function probeLlmCapabilities(config) {
|
|
80
|
+
capabilitiesCache ??= doProbeLlmCapabilities(config);
|
|
81
|
+
return capabilitiesCache;
|
|
82
|
+
}
|
|
83
|
+
async function doProbeLlmCapabilities(config) {
|
|
84
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
85
|
+
const headers = { authorization: `Bearer ${config.apiKey}` };
|
|
86
|
+
let available;
|
|
87
|
+
try {
|
|
88
|
+
available = (await fetch(`${baseUrl}/models`, {
|
|
89
|
+
headers,
|
|
90
|
+
signal: AbortSignal.timeout(1e4)
|
|
91
|
+
})).ok;
|
|
92
|
+
} catch {
|
|
93
|
+
available = false;
|
|
94
|
+
}
|
|
95
|
+
if (!available) return {
|
|
96
|
+
available: false,
|
|
97
|
+
nonStreaming: false,
|
|
98
|
+
reason: "endpoint unreachable or unauthorized"
|
|
99
|
+
};
|
|
100
|
+
if (process.env["VITE_LLM_NON_STREAMING"] === "0") return {
|
|
101
|
+
available: true,
|
|
102
|
+
nonStreaming: false,
|
|
103
|
+
reason: "non-streaming disabled via VITE_LLM_NON_STREAMING=0 (simulated)"
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
...headers,
|
|
110
|
+
"content-type": "application/json"
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
model: config.model,
|
|
114
|
+
messages: [{
|
|
115
|
+
role: "user",
|
|
116
|
+
content: "OK"
|
|
117
|
+
}],
|
|
118
|
+
max_tokens: 1
|
|
119
|
+
}),
|
|
120
|
+
signal: AbortSignal.timeout(15e3)
|
|
121
|
+
});
|
|
122
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
123
|
+
if (res.ok && !contentType.includes("text/event-stream")) return {
|
|
124
|
+
available: true,
|
|
125
|
+
nonStreaming: true
|
|
126
|
+
};
|
|
127
|
+
const detail = (await res.text().catch(() => "")).slice(0, 200);
|
|
128
|
+
return {
|
|
129
|
+
available: true,
|
|
130
|
+
nonStreaming: false,
|
|
131
|
+
reason: `endpoint rejects non-streaming chat completions (HTTP ${res.status})${detail ? `: ${detail}` : ""}`
|
|
132
|
+
};
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return {
|
|
135
|
+
available: true,
|
|
136
|
+
nonStreaming: false,
|
|
137
|
+
reason: `non-streaming probe failed: ${e instanceof Error ? e.message : String(e)}`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
export { probeLlmCapabilities as i, loadGoogleConfigFromEnv as n, loadLlmConfigFromEnv as r, loadAnthropicConfigFromEnv as t };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region ../node/dist/env-BPUBZCwJ.d.ts
|
|
2
|
+
//#region src/env.d.ts
|
|
3
|
+
interface LlmEnvConfig {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
model: string;
|
|
7
|
+
}
|
|
8
|
+
/** per-provider env 配置(baseUrl 可选,客户端自带默认) */
|
|
9
|
+
interface ProviderEnvConfig {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
model: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 读取 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL(优先),
|
|
16
|
+
* 向后兼容 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
|
|
17
|
+
*/
|
|
18
|
+
declare function loadLlmConfigFromEnv(dotenvPath?: string): LlmEnvConfig | undefined;
|
|
19
|
+
/** ANTHROPIC_API_KEY / ANTHROPIC_MODEL(可选 ANTHROPIC_BASE_URL);缺项返回 undefined */
|
|
20
|
+
declare function loadAnthropicConfigFromEnv(dotenvPath?: string): ProviderEnvConfig | undefined;
|
|
21
|
+
/** GOOGLE_API_KEY / GOOGLE_MODEL(可选 GOOGLE_BASE_URL);缺项返回 undefined */
|
|
22
|
+
declare function loadGoogleConfigFromEnv(dotenvPath?: string): ProviderEnvConfig | undefined;
|
|
23
|
+
interface LlmCapabilities {
|
|
24
|
+
available: boolean;
|
|
25
|
+
nonStreaming: boolean;
|
|
26
|
+
reason?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 探测 LLM 能力(结果模块级缓存):
|
|
30
|
+
* - available:GET /models 可达(与 checkAvailability 同语义)
|
|
31
|
+
* - nonStreaming:最小非流式 chat completion(max_tokens=1,短超时);
|
|
32
|
+
* HTTP 400/404 或明确不支持非流式的错误 → false
|
|
33
|
+
* - streaming 能力由 available 推断(SSE 解析在 SDK 侧实现)
|
|
34
|
+
* 环境变量 VITE_LLM_NON_STREAMING=0 可显式强制 nonStreaming=false(模拟流式-only 回归)。
|
|
35
|
+
*/
|
|
36
|
+
declare function probeLlmCapabilities(config: LlmEnvConfig): Promise<LlmCapabilities>;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { loadGoogleConfigFromEnv as a, loadAnthropicConfigFromEnv as i, LlmEnvConfig as n, loadLlmConfigFromEnv as o, ProviderEnvConfig as r, probeLlmCapabilities as s, LlmCapabilities as t };
|