@webskill/sdk 0.1.5 → 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 +3 -2
- package/dist/browser.js +10 -5
- package/dist/{dist-CfBOjJ4p.js → dist-BJobG0i-.js} +30 -7
- package/dist/{dist-DuGN5x0v.js → dist-BS5OpedX.js} +181 -50
- package/dist/{dist-fZFZaf43.js → dist-D0saNPi_.js} +22 -4
- package/dist/{dist-LIFS3ZjI.js → dist-Dj6QjHBn.js} +55 -151
- package/dist/env--jJB-TSX-04klhTYi.js +143 -0
- package/dist/env-BPUBZCwJ-4jat_SVG.d.ts +38 -0
- package/dist/governance.d.ts +32 -6
- package/dist/governance.js +155 -10
- package/dist/{index-COuOu6gw.d.ts → index-CySxIvRz.d.ts} +51 -3
- package/dist/{index-Bun1aEf4.d.ts → index-Vn63HJRO.d.ts} +11 -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 +28 -8
- package/dist/node.d.ts +5 -4
- package/dist/node.js +5 -4
- 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
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as FileStat, I as SkillInstallSource, K as VerifyResult, W as SkillsLockfile, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits, z as SkillManifest } from "./types-
|
|
2
|
-
import { N as NetworkPolicy,
|
|
1
|
+
import { C as FileStat, I as SkillInstallSource, K as VerifyResult, W as SkillsLockfile, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
|
|
2
|
+
import { N as NetworkPolicy, Q as ScriptExecutor, Z as ScriptExecutionContext, d as BridgeCapabilities, it as ToolResult, m as BridgeResponse, nt as ToolDefinition, p as BridgeRequest, pt as bridgeError, u as ApprovalScope, ut as WebSkillApi, v as ExternalSkillProvider, wt as parseBridgeRequest, y as ExternalToolSource } from "./index-CySxIvRz.js";
|
|
3
3
|
//#region ../browser/dist/index.d.ts
|
|
4
4
|
//#region src/fs/featureDetection.d.ts
|
|
5
5
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -24,6 +24,7 @@ declare class OpfsProvider implements FileSystemProvider {
|
|
|
24
24
|
remove(p: string, options?: {
|
|
25
25
|
recursive?: boolean;
|
|
26
26
|
}): Promise<void>;
|
|
27
|
+
rename(from: string, to: string): Promise<void>;
|
|
27
28
|
}
|
|
28
29
|
//#endregion
|
|
29
30
|
//#region src/executor/workerBootstrap.d.ts
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { A as
|
|
3
|
-
import { n as MockLlmClient } from "./testing-
|
|
1
|
+
import { A as unzipWithLimits, C as parseSkillMarkdown, M as verifyManifest, T as readResponseWithLimit, b as isValidSkillName, c as SkillDiscovery, f as atomicWriteText, i as SKILL_MANIFEST_FILE, j as validateSkills, k as resolveInsideRoot, m as buildManifest, p as buildCatalog, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, w as parseSkillPackManifest, y as exportSkills } from "./dist-D0saNPi_.js";
|
|
2
|
+
import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi, M as networkUrlHost, N as normalizeToolContent, P as parseBridgeRequest, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-BS5OpedX.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-B4pq6JYa.js";
|
|
4
4
|
|
|
5
5
|
//#region ../browser/dist/index.js
|
|
6
6
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -132,6 +132,11 @@ var OpfsProvider = class {
|
|
|
132
132
|
await (await this.#walkDir(`/${segments.join("/")}`, false)).removeEntry(name, { recursive: options?.recursive ?? false });
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
+
async rename(from, to) {
|
|
136
|
+
if ((await this.stat(from)).type === "directory") throw new WebSkillError("FS_PERMISSION_DENIED", `rename of directories is not supported on OPFS: ${from}`);
|
|
137
|
+
await this.writeBinary(to, await this.readBinary(from));
|
|
138
|
+
await this.remove(from);
|
|
139
|
+
}
|
|
135
140
|
};
|
|
136
141
|
/**
|
|
137
142
|
* Worker 引导源码(字符串 → Blob URL → new Worker(url, {type:'module'}))。
|
|
@@ -700,12 +705,12 @@ var BrowserSkillManager = class {
|
|
|
700
705
|
async #upsertLockEntry(name, entry) {
|
|
701
706
|
const lockfile = await this.listInstalled();
|
|
702
707
|
lockfile.skills[name] = entry;
|
|
703
|
-
await this.#fs
|
|
708
|
+
await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
|
|
704
709
|
}
|
|
705
710
|
async #removeLockEntry(name) {
|
|
706
711
|
const lockfile = await this.listInstalled();
|
|
707
712
|
delete lockfile.skills[name];
|
|
708
|
-
await this.#fs
|
|
713
|
+
await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
|
|
709
714
|
}
|
|
710
715
|
};
|
|
711
716
|
const defaultWorkerFactory = () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as WebSkillError } from "./dist-
|
|
1
|
+
import { u as WebSkillError } from "./dist-D0saNPi_.js";
|
|
2
2
|
|
|
3
3
|
//#region ../ui/dist/index.js
|
|
4
4
|
/** 提交值按请求类型归形(WebFormBridge 与框架组件库共享单一来源) */
|
|
@@ -524,6 +524,8 @@ var WebFormBridge = class {
|
|
|
524
524
|
#doc;
|
|
525
525
|
#styles;
|
|
526
526
|
#progressEl;
|
|
527
|
+
/** 等待中的 request 取消句柄(id → 触发 cancelled 并卸载 DOM) */
|
|
528
|
+
#pending = /* @__PURE__ */ new Map();
|
|
527
529
|
constructor(options) {
|
|
528
530
|
this.#mount = options.mount;
|
|
529
531
|
this.#doc = options.document ?? options.mount.ownerDocument;
|
|
@@ -535,13 +537,27 @@ var WebFormBridge = class {
|
|
|
535
537
|
const model = interactionToFormModel(input);
|
|
536
538
|
const { form, cleanup } = this.#renderForm(model);
|
|
537
539
|
return new Promise((resolve) => {
|
|
540
|
+
const cancel = () => {
|
|
541
|
+
cleanup();
|
|
542
|
+
this.#pending.delete(input.id);
|
|
543
|
+
resolve({
|
|
544
|
+
id: input.id,
|
|
545
|
+
cancelled: true
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
this.#pending.set(input.id, cancel);
|
|
538
549
|
form.addEventListener("submit", (event) => {
|
|
539
550
|
event.preventDefault();
|
|
540
551
|
const { values, missingRequired } = collectValues(model.controls, form);
|
|
541
552
|
if (missingRequired.length > 0) {
|
|
542
|
-
for (const name of missingRequired)
|
|
553
|
+
for (const name of missingRequired) {
|
|
554
|
+
const wrapper = form.querySelector(`[${CONTROL_TAG}="${name}"]`)?.closest(".webskill-form__control");
|
|
555
|
+
wrapper?.classList.add("webskill-form__control--invalid");
|
|
556
|
+
wrapper?.querySelector(".webskill-form__input")?.setAttribute("aria-invalid", "true");
|
|
557
|
+
}
|
|
543
558
|
return;
|
|
544
559
|
}
|
|
560
|
+
this.#pending.delete(input.id);
|
|
545
561
|
cleanup();
|
|
546
562
|
resolve({
|
|
547
563
|
id: input.id,
|
|
@@ -550,15 +566,15 @@ var WebFormBridge = class {
|
|
|
550
566
|
});
|
|
551
567
|
form.querySelector("[data-webskill-cancel]")?.addEventListener("click", (event) => {
|
|
552
568
|
event.preventDefault();
|
|
553
|
-
|
|
554
|
-
resolve({
|
|
555
|
-
id: input.id,
|
|
556
|
-
cancelled: true
|
|
557
|
-
});
|
|
569
|
+
cancel();
|
|
558
570
|
});
|
|
559
571
|
this.#mount.appendChild(form);
|
|
560
572
|
});
|
|
561
573
|
}
|
|
574
|
+
/** 尽力取消等待中的 request(超时后清理遗留表单) */
|
|
575
|
+
cancel(id) {
|
|
576
|
+
this.#pending.get(id)?.();
|
|
577
|
+
}
|
|
562
578
|
async renderResult(input) {
|
|
563
579
|
if (this.#styles) ensureStyles(this.#doc);
|
|
564
580
|
this.#clearProgress();
|
|
@@ -622,9 +638,12 @@ var WebFormBridge = class {
|
|
|
622
638
|
const doc = this.#doc;
|
|
623
639
|
const wrapper = doc.createElement("div");
|
|
624
640
|
wrapper.className = "webskill-form__control";
|
|
641
|
+
const controlId = `webskill-field-${control.name}`;
|
|
642
|
+
const errorId = `webskill-error-${control.name}`;
|
|
625
643
|
const label = doc.createElement("label");
|
|
626
644
|
label.className = control.required ? "webskill-form__label webskill-form__label-required" : "webskill-form__label";
|
|
627
645
|
label.textContent = control.label;
|
|
646
|
+
label.htmlFor = controlId;
|
|
628
647
|
wrapper.appendChild(label);
|
|
629
648
|
if (control.description) {
|
|
630
649
|
const desc = doc.createElement("div");
|
|
@@ -663,9 +682,13 @@ var WebFormBridge = class {
|
|
|
663
682
|
input = textInput;
|
|
664
683
|
}
|
|
665
684
|
input.setAttribute(CONTROL_TAG, control.name);
|
|
685
|
+
input.id = controlId;
|
|
686
|
+
input.setAttribute("aria-describedby", errorId);
|
|
666
687
|
wrapper.appendChild(input);
|
|
667
688
|
const error = doc.createElement("div");
|
|
668
689
|
error.className = "webskill-form__error";
|
|
690
|
+
error.id = errorId;
|
|
691
|
+
error.setAttribute("role", "alert");
|
|
669
692
|
error.textContent = "This field is required";
|
|
670
693
|
wrapper.appendChild(error);
|
|
671
694
|
return wrapper;
|
|
@@ -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();
|
|
@@ -2002,6 +2101,27 @@ var WebSkillRuntime = class {
|
|
|
2002
2101
|
invalidate() {
|
|
2003
2102
|
this.#catalogCache = void 0;
|
|
2004
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
|
+
}
|
|
2005
2125
|
async discover() {
|
|
2006
2126
|
const result = await this.#discovery.discover();
|
|
2007
2127
|
this.#catalogCache = {
|
|
@@ -2010,7 +2130,7 @@ var WebSkillRuntime = class {
|
|
|
2010
2130
|
};
|
|
2011
2131
|
return result;
|
|
2012
2132
|
}
|
|
2013
|
-
async run(userPrompt) {
|
|
2133
|
+
async run(userPrompt, options = {}) {
|
|
2014
2134
|
if (!this.#catalogCache) await this.discover();
|
|
2015
2135
|
const cache = this.#catalogCache;
|
|
2016
2136
|
if (!cache) throw new Error("discover() did not populate the catalog cache");
|
|
@@ -2046,9 +2166,10 @@ var WebSkillRuntime = class {
|
|
|
2046
2166
|
snapshotStore: this.#deps.snapshotStore,
|
|
2047
2167
|
skillStateGuard: this.#deps.skillStateGuard
|
|
2048
2168
|
}, this.#deps.config).run({
|
|
2049
|
-
sessionId: this.#session.id,
|
|
2169
|
+
sessionId: options.sessionId ?? this.#session.id,
|
|
2050
2170
|
userPrompt,
|
|
2051
|
-
route
|
|
2171
|
+
route,
|
|
2172
|
+
...options.history ? { history: options.history } : {}
|
|
2052
2173
|
});
|
|
2053
2174
|
for (const failure of providerFailures) result.run.trace.push({
|
|
2054
2175
|
id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2117,7 +2238,8 @@ var WebSkillRuntime = class {
|
|
|
2117
2238
|
code: "RUN_INTERACTION_TIMEOUT"
|
|
2118
2239
|
}
|
|
2119
2240
|
}]
|
|
2120
|
-
}
|
|
2241
|
+
},
|
|
2242
|
+
messages: []
|
|
2121
2243
|
};
|
|
2122
2244
|
}
|
|
2123
2245
|
if (!this.#catalogCache) await this.discover();
|
|
@@ -2219,7 +2341,7 @@ function createWebSkillApi(deps) {
|
|
|
2219
2341
|
}
|
|
2220
2342
|
};
|
|
2221
2343
|
}
|
|
2222
|
-
/** install URL 分派:http(s)
|
|
2344
|
+
/** install URL 分派:http(s) .zip → http;.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
|
|
2223
2345
|
* 其余 http(s) URL → INSTALL_FAILED(不静默当 local);非 URL → local */
|
|
2224
2346
|
function sourceFromUrl(url) {
|
|
2225
2347
|
const bare = url.split("?")[0] ?? url;
|
|
@@ -2507,16 +2629,21 @@ function networkUrlHost(url) {
|
|
|
2507
2629
|
* once-per-run(默认)在 run 内记忆已批准能力,every-call 每次调用都问。
|
|
2508
2630
|
* @stable
|
|
2509
2631
|
*/
|
|
2510
|
-
var CapabilityApproval = class {
|
|
2632
|
+
var CapabilityApproval = class CapabilityApproval {
|
|
2511
2633
|
#uiBridge;
|
|
2512
2634
|
#scope;
|
|
2513
|
-
/** runId → 已批准能力集合(once-per-run
|
|
2635
|
+
/** runId → 已批准能力集合(once-per-run 记忆;LRU 上限防长跑进程膨胀) */
|
|
2514
2636
|
#approved = /* @__PURE__ */ new Map();
|
|
2515
2637
|
#seq = 0;
|
|
2638
|
+
static #MAX_RUNS = 128;
|
|
2516
2639
|
constructor(deps = {}) {
|
|
2517
2640
|
this.#uiBridge = deps.uiBridge;
|
|
2518
2641
|
this.#scope = deps.scope ?? "once-per-run";
|
|
2519
2642
|
}
|
|
2643
|
+
/** run 终态清理授权记录(宿主在 run 结束后调用;同时有 LRU 上限兜底) */
|
|
2644
|
+
clearRun(runId) {
|
|
2645
|
+
this.#approved.delete(runId);
|
|
2646
|
+
}
|
|
2520
2647
|
async authorize(input) {
|
|
2521
2648
|
const { runId, capability, mode } = input;
|
|
2522
2649
|
if (mode === false) return "disabled";
|
|
@@ -2537,10 +2664,14 @@ var CapabilityApproval = class {
|
|
|
2537
2664
|
this.#approved.set(runId, set);
|
|
2538
2665
|
}
|
|
2539
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
|
+
}
|
|
2540
2671
|
}
|
|
2541
2672
|
return "allowed";
|
|
2542
2673
|
}
|
|
2543
2674
|
};
|
|
2544
2675
|
|
|
2545
2676
|
//#endregion
|
|
2546
|
-
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 };
|
|
@@ -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 };
|