@webskill/sdk 0.1.2 → 0.1.4
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 +5 -2
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +54 -22
- package/dist/{dist-SarUS8EP.js → dist-BVXZF7H_.js} +177 -58
- package/dist/{dist-DRsnVw6A.js → dist-DAn07zHu.js} +452 -11
- package/dist/{dist-DS1sfgHa.js → dist-DvoBsChX.js} +13 -1
- package/dist/{dist-DxGyAznR.js → dist-MTc2KB03.js} +46 -3
- package/dist/governance.d.ts +7 -4
- package/dist/governance.js +19 -4
- package/dist/{index-fjRF4H5o.d.ts → index-BSS3EWY-.d.ts} +48 -2
- package/dist/{index-DkPK44Ji.d.ts → index-npFMt2Zw.d.ts} +27 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +25 -3
- package/dist/mcp.js +58 -13
- package/dist/node.d.ts +4 -4
- package/dist/node.js +4 -4
- package/dist/sandboxWorkerEntry.d.ts +1 -0
- package/dist/sandboxWorkerEntry.js +214 -0
- package/dist/{testing-CbM6rJ-E.js → testing-S8SN9Lc6.js} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-CgNC-oQu-Dq_A1yA-.d.ts → types-CgNC-oQu-DYrxhD3z.d.ts} +11 -1
- package/dist/ui-react.d.ts +1 -1
- package/dist/ui-react.js +1 -1
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,8 +16,11 @@ governance.
|
|
|
16
16
|
(missing-parameter forms, confirmations, authorization prompts), and
|
|
17
17
|
resumable interrupted runs.
|
|
18
18
|
- **Script sandbox** — `worker_threads` sandbox for Node, Web Worker sandbox for
|
|
19
|
-
the browser. Deny-by-default network policy,
|
|
20
|
-
timeouts kill runaway scripts.
|
|
19
|
+
the browser. Deny-by-default network policy, deny-by-default builtin-module
|
|
20
|
+
allowlist, per-capability forced approval, timeouts kill runaway scripts.
|
|
21
|
+
**Honest note:** these executors reduce the capability surface — they are NOT
|
|
22
|
+
a security boundary. Scripts share the host process/origin and bypasses
|
|
23
|
+
exist; never rely on them to isolate untrusted scripts.
|
|
21
24
|
- **`navigator.webskill`** — a browser facade (`discover` / `read` / `validate` /
|
|
22
25
|
`run` / `install` / `uninstall`) assembled explicitly in one call.
|
|
23
26
|
- **MCP** — call page-provided tools and consume page-declared dynamic skills
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as SkillsLockfile, L as SkillManifest, P as SkillInstallSource, S as FileSystemProvider, W as VerifyResult, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, x as FileStat } from "./types-CgNC-oQu-
|
|
2
|
-
import {
|
|
1
|
+
import { H as SkillsLockfile, L as SkillManifest, P as SkillInstallSource, S as FileSystemProvider, W as VerifyResult, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, x as FileStat } from "./types-CgNC-oQu-DYrxhD3z.js";
|
|
2
|
+
import { $ as ToolDefinition, N as NetworkPolicy, X as ScriptExecutionContext, Z as ScriptExecutor, d as BridgeCapabilities, m as BridgeResponse, p as BridgeRequest, st as WebSkillApi, tt as ToolResult, u as ApprovalScope, ut as bridgeError, v as ExternalSkillProvider, xt as parseBridgeRequest, y as ExternalToolSource } from "./index-BSS3EWY-.js";
|
|
3
3
|
//#region ../browser/dist/index.d.ts
|
|
4
4
|
//#region src/fs/featureDetection.d.ts
|
|
5
5
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
3
|
-
import { n as MockLlmClient } from "./testing-
|
|
1
|
+
import { D as verifyManifest, E as validateSkills, S as parseSkillPackManifest, T as resolveInsideRoot, _ as exportSkills, d as buildCatalog, f as buildManifest, l as WebSkillError, n as SKILLS_LOCKFILE, o as SKILL_PACK_FILE, r as SKILL_MANIFEST_FILE, s as SkillDiscovery, v as isValidSkillName, x as parseSkillMarkdown } from "./dist-DvoBsChX.js";
|
|
2
|
+
import { A as mergeCatalogEntries, M as normalizeToolContent, N as parseBridgeRequest, S as bridgeError, T as createWebSkillApi, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, j as networkUrlHost, k as isNetworkAllowed, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-DAn07zHu.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-S8SN9Lc6.js";
|
|
4
4
|
import { unzipSync } from "fflate";
|
|
5
5
|
|
|
6
6
|
//#region ../browser/dist/index.js
|
|
@@ -508,19 +508,31 @@ var BrowserSkillManager = class {
|
|
|
508
508
|
const errors = report.issues.filter((i) => i.severity === "error");
|
|
509
509
|
throw new WebSkillError("INSTALL_FAILED", `Skill "${name}" failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
|
|
510
510
|
}
|
|
511
|
-
|
|
512
|
-
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
513
|
-
await copyDir(fs, finalDir, targetDir);
|
|
514
|
-
const manifest = await this.#createManifest(targetDir, {
|
|
511
|
+
const manifest = await this.#createManifest(finalDir, {
|
|
515
512
|
name,
|
|
516
513
|
...version ? { version } : {},
|
|
517
514
|
source
|
|
518
515
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
516
|
+
targetDir = `${this.#managedRoot}/${name}`;
|
|
517
|
+
const backupDir = `${stagingRoot}/backup`;
|
|
518
|
+
const hadPrevious = await fs.exists(targetDir);
|
|
519
|
+
if (hadPrevious) await copyDir(fs, targetDir, backupDir);
|
|
520
|
+
let committed = false;
|
|
521
|
+
try {
|
|
522
|
+
if (hadPrevious) await fs.remove(targetDir, { recursive: true });
|
|
523
|
+
await copyDir(fs, finalDir, targetDir);
|
|
524
|
+
committed = true;
|
|
525
|
+
await this.#upsertLockEntry(name, {
|
|
526
|
+
digest: manifest.integrity.digest,
|
|
527
|
+
installedAt: manifest.installedAt,
|
|
528
|
+
source
|
|
529
|
+
});
|
|
530
|
+
} catch (e) {
|
|
531
|
+
if (committed) await removeDirQuiet(fs, targetDir);
|
|
532
|
+
if (hadPrevious) await copyDir(fs, backupDir, targetDir);
|
|
533
|
+
targetDir = void 0;
|
|
534
|
+
throw e;
|
|
535
|
+
}
|
|
524
536
|
targetDir = void 0;
|
|
525
537
|
return manifest;
|
|
526
538
|
} catch (e) {
|
|
@@ -562,11 +574,7 @@ var BrowserSkillManager = class {
|
|
|
562
574
|
}
|
|
563
575
|
const manifests = [];
|
|
564
576
|
for (const entry of pack.manifest.skills) {
|
|
565
|
-
const
|
|
566
|
-
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
567
|
-
await copyDir(fs, `${finalRoot}/${entry.name}`, targetDir);
|
|
568
|
-
installed.push(targetDir);
|
|
569
|
-
const manifest = await this.#createManifest(targetDir, {
|
|
577
|
+
const manifest = await this.#createManifest(`${finalRoot}/${entry.name}`, {
|
|
570
578
|
name: entry.name,
|
|
571
579
|
...versions.get(entry.name) ? { version: versions.get(entry.name) } : {},
|
|
572
580
|
source
|
|
@@ -574,11 +582,35 @@ var BrowserSkillManager = class {
|
|
|
574
582
|
if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
|
|
575
583
|
manifests.push(manifest);
|
|
576
584
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
585
|
+
const swapped = [];
|
|
586
|
+
try {
|
|
587
|
+
for (const manifest of manifests) {
|
|
588
|
+
const targetDir = `${this.#managedRoot}/${manifest.name}`;
|
|
589
|
+
const backupDir = `${stagingRoot}/backup/${manifest.name}`;
|
|
590
|
+
if (await fs.exists(targetDir)) {
|
|
591
|
+
await copyDir(fs, targetDir, backupDir);
|
|
592
|
+
await fs.remove(targetDir, { recursive: true });
|
|
593
|
+
}
|
|
594
|
+
await copyDir(fs, `${finalRoot}/${manifest.name}`, targetDir);
|
|
595
|
+
installed.push(targetDir);
|
|
596
|
+
swapped.push({
|
|
597
|
+
targetDir,
|
|
598
|
+
backupDir
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
for (const manifest of manifests) await this.#upsertLockEntry(manifest.name, {
|
|
602
|
+
digest: manifest.integrity.digest,
|
|
603
|
+
installedAt: manifest.installedAt,
|
|
604
|
+
source
|
|
605
|
+
});
|
|
606
|
+
} catch (e) {
|
|
607
|
+
for (const { targetDir, backupDir } of swapped) {
|
|
608
|
+
await removeDirQuiet(fs, targetDir);
|
|
609
|
+
if (await fs.exists(backupDir)) await copyDir(fs, backupDir, targetDir);
|
|
610
|
+
}
|
|
611
|
+
installed.length = 0;
|
|
612
|
+
throw e;
|
|
613
|
+
}
|
|
582
614
|
return manifests;
|
|
583
615
|
} catch (e) {
|
|
584
616
|
for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { D as verifyManifest, E as validateSkills, S as parseSkillPackManifest, T as resolveInsideRoot, _ as exportSkills, f as buildManifest, l as WebSkillError, n as SKILLS_LOCKFILE, o as SKILL_PACK_FILE, r as SKILL_MANIFEST_FILE, v as isValidSkillName, x as parseSkillMarkdown } from "./dist-DvoBsChX.js";
|
|
2
|
+
import { M as normalizeToolContent, N as parseBridgeRequest, S as bridgeError, c as FsArtifactStore, j as networkUrlHost, k as isNetworkAllowed, l as FsMemoryStore, o as CapabilityApproval } from "./dist-DAn07zHu.js";
|
|
3
3
|
import { unzipSync, zipSync } from "fflate";
|
|
4
4
|
import { existsSync, promises, readFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -15,39 +15,73 @@ import { execFile } from "node:child_process";
|
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
|
|
17
17
|
//#region ../node/dist/index.js
|
|
18
|
-
const toPlatform$
|
|
18
|
+
const toPlatform$4 = (p) => p.split("/").join(path.sep);
|
|
19
19
|
const toPosix = (p) => p.split(path.sep).join("/");
|
|
20
20
|
const isEnoent = (e) => typeof e === "object" && e !== null && e.code === "ENOENT";
|
|
21
|
-
/**
|
|
21
|
+
/** realpath 最近现存祖先并拼回剩余段(写入目标尚不存在时的等价判定) */
|
|
22
|
+
async function realpathNearest(platformPath) {
|
|
23
|
+
let current = platformPath;
|
|
24
|
+
const rest = [];
|
|
25
|
+
for (;;) try {
|
|
26
|
+
const real = await promises.realpath(current);
|
|
27
|
+
return rest.length === 0 ? real : [real, ...rest].join(path.sep);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (!isEnoent(e)) throw e;
|
|
30
|
+
rest.unshift(path.basename(current));
|
|
31
|
+
const parent = path.dirname(current);
|
|
32
|
+
if (parent === current) return platformPath;
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 基于 node:fs/promises 的 FileSystemProvider;接受 `/` 风格路径,写入自动创建父目录。
|
|
38
|
+
* 可选 root 模式:构造传入 root 后,read/write 操作先做 realpath 包含校验
|
|
39
|
+
* (root 与目标都 realpath 后前缀比对),经符号链接逃逸 root → FS_PATH_OUTSIDE_ROOT。
|
|
40
|
+
*/
|
|
22
41
|
var NodeFS = class {
|
|
23
42
|
kind = "node";
|
|
43
|
+
#root;
|
|
44
|
+
constructor(deps = {}) {
|
|
45
|
+
this.#root = deps.root;
|
|
46
|
+
}
|
|
47
|
+
/** root 模式:目标 realpath 必须落在 root realpath 前缀内(符号链接逃逸防护) */
|
|
48
|
+
async #assertContained(p) {
|
|
49
|
+
if (!this.#root) return;
|
|
50
|
+
const rootReal = await promises.realpath(toPlatform$4(this.#root));
|
|
51
|
+
const targetReal = await realpathNearest(toPlatform$4(p));
|
|
52
|
+
if (targetReal !== rootReal && !targetReal.startsWith(rootReal + path.sep)) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Path escapes root via symbolic link: ${p} (resolves outside ${this.#root})`);
|
|
53
|
+
}
|
|
24
54
|
async readText(p) {
|
|
55
|
+
await this.#assertContained(p);
|
|
25
56
|
try {
|
|
26
|
-
return await promises.readFile(toPlatform$
|
|
57
|
+
return await promises.readFile(toPlatform$4(p), "utf8");
|
|
27
58
|
} catch (e) {
|
|
28
59
|
throw this.#mapError(e, p);
|
|
29
60
|
}
|
|
30
61
|
}
|
|
31
62
|
async writeText(p, content) {
|
|
32
|
-
|
|
63
|
+
await this.#assertContained(p);
|
|
64
|
+
const target = toPlatform$4(p);
|
|
33
65
|
await promises.mkdir(path.dirname(target), { recursive: true });
|
|
34
66
|
await promises.writeFile(target, content, "utf8");
|
|
35
67
|
}
|
|
36
68
|
async readBinary(p) {
|
|
69
|
+
await this.#assertContained(p);
|
|
37
70
|
try {
|
|
38
|
-
return await promises.readFile(toPlatform$
|
|
71
|
+
return await promises.readFile(toPlatform$4(p));
|
|
39
72
|
} catch (e) {
|
|
40
73
|
throw this.#mapError(e, p);
|
|
41
74
|
}
|
|
42
75
|
}
|
|
43
76
|
async writeBinary(p, content) {
|
|
44
|
-
|
|
77
|
+
await this.#assertContained(p);
|
|
78
|
+
const target = toPlatform$4(p);
|
|
45
79
|
await promises.mkdir(path.dirname(target), { recursive: true });
|
|
46
80
|
await promises.writeFile(target, content);
|
|
47
81
|
}
|
|
48
82
|
async exists(p) {
|
|
49
83
|
try {
|
|
50
|
-
await promises.access(toPlatform$
|
|
84
|
+
await promises.access(toPlatform$4(p));
|
|
51
85
|
return true;
|
|
52
86
|
} catch {
|
|
53
87
|
return false;
|
|
@@ -55,7 +89,7 @@ var NodeFS = class {
|
|
|
55
89
|
}
|
|
56
90
|
async stat(p) {
|
|
57
91
|
try {
|
|
58
|
-
const s = await promises.stat(toPlatform$
|
|
92
|
+
const s = await promises.stat(toPlatform$4(p));
|
|
59
93
|
return {
|
|
60
94
|
path: toPosix(p),
|
|
61
95
|
type: s.isDirectory() ? "directory" : "file",
|
|
@@ -69,21 +103,21 @@ var NodeFS = class {
|
|
|
69
103
|
async list(p) {
|
|
70
104
|
let dirents;
|
|
71
105
|
try {
|
|
72
|
-
dirents = await promises.readdir(toPlatform$
|
|
106
|
+
dirents = await promises.readdir(toPlatform$4(p), { withFileTypes: true });
|
|
73
107
|
} catch (e) {
|
|
74
108
|
throw this.#mapError(e, p);
|
|
75
109
|
}
|
|
76
110
|
return dirents.map((d) => ({
|
|
77
|
-
path: toPosix(path.join(toPlatform$
|
|
111
|
+
path: toPosix(path.join(toPlatform$4(p), d.name)),
|
|
78
112
|
type: d.isDirectory() ? "directory" : "file"
|
|
79
113
|
}));
|
|
80
114
|
}
|
|
81
115
|
async mkdir(p) {
|
|
82
|
-
await promises.mkdir(toPlatform$
|
|
116
|
+
await promises.mkdir(toPlatform$4(p), { recursive: true });
|
|
83
117
|
}
|
|
84
118
|
async remove(p, options) {
|
|
85
119
|
try {
|
|
86
|
-
await promises.rm(toPlatform$
|
|
120
|
+
await promises.rm(toPlatform$4(p), { recursive: options?.recursive ?? false });
|
|
87
121
|
} catch (e) {
|
|
88
122
|
throw this.#mapError(e, p);
|
|
89
123
|
}
|
|
@@ -94,7 +128,7 @@ var NodeFS = class {
|
|
|
94
128
|
}
|
|
95
129
|
};
|
|
96
130
|
const baseName$1 = (p) => p.split("/").pop() ?? p;
|
|
97
|
-
const toPlatform$
|
|
131
|
+
const toPlatform$3 = (p) => p.split("/").join(path.sep);
|
|
98
132
|
/**
|
|
99
133
|
* 进程内脚本执行器。接口按沙箱语义设计(context 无隐式宿主访问),
|
|
100
134
|
* worker_threads 沙箱见 deferred-items D1。
|
|
@@ -114,7 +148,7 @@ var NodeScriptExecutor = class {
|
|
|
114
148
|
return hasTs ? tsPath : jsPath;
|
|
115
149
|
}
|
|
116
150
|
async #loadModule(scriptPath) {
|
|
117
|
-
return await import(`${pathToFileURL(toPlatform$
|
|
151
|
+
return await import(`${pathToFileURL(toPlatform$3(scriptPath)).href}?t=${Date.now()}`);
|
|
118
152
|
}
|
|
119
153
|
async loadDefinition(skillRoot, scriptName) {
|
|
120
154
|
const scriptPath = await this.#locateScript(skillRoot, scriptName);
|
|
@@ -223,14 +257,14 @@ var NodeScriptExecutor = class {
|
|
|
223
257
|
}
|
|
224
258
|
};
|
|
225
259
|
const baseName = (p) => p.split("/").pop() ?? p;
|
|
226
|
-
const toPlatform$
|
|
260
|
+
const toPlatform$2 = (p) => p.split("/").join(path.sep);
|
|
227
261
|
const messageOf$5 = (e) => e instanceof Error ? e.message : String(e);
|
|
228
262
|
/**
|
|
229
263
|
* 网络策略判定函数源码(注入 Worker;匹配逻辑单一来源在
|
|
230
264
|
* runtime/sandbox/networkPolicy.ts,Worker 线程不走 vitest 别名故注入而非 import)
|
|
231
265
|
*/
|
|
232
266
|
const NETWORK_POLICY_LIB = `${isNetworkAllowed.toString()}\n${networkUrlHost.toString()}`;
|
|
233
|
-
/** Worker 入口文件:src 为同目录 .ts;dist 为 dist/executor
|
|
267
|
+
/** Worker 入口文件:src 为同目录 .ts;node dist 为 dist/executor/;sdk dist 为平铺 dist/ */
|
|
234
268
|
function workerEntryPath() {
|
|
235
269
|
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
236
270
|
const candidates = [
|
|
@@ -239,13 +273,17 @@ function workerEntryPath() {
|
|
|
239
273
|
path.join(dir, "executor", "sandboxWorkerEntry.js")
|
|
240
274
|
];
|
|
241
275
|
for (const candidate of candidates) if (existsSync(candidate)) return candidate;
|
|
242
|
-
|
|
276
|
+
throw new WebSkillError("TOOL_EXECUTION_FAILED", `Sandbox worker entry not found (tried: ${candidates.join(", ")})`);
|
|
243
277
|
}
|
|
244
278
|
/**
|
|
245
279
|
* D1:worker_threads 沙箱脚本执行器。
|
|
246
280
|
* 每次执行独立 Worker(resourceLimits + env 白名单),loadDefinition 同样在
|
|
247
281
|
* Worker 内完成(禁止主进程 import 不可信脚本);超时 terminate(可杀同步死循环)。
|
|
248
282
|
* 能力桥协议与浏览器同一来源(runtime/sandbox/bridgeProtocol)。
|
|
283
|
+
*
|
|
284
|
+
* 诚实标注:本执行器做的是**能力面收敛**(网络策略、模块 allowlist、资源限额、
|
|
285
|
+
* 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程,仍有绕过手段;
|
|
286
|
+
* 禁止假定其可隔离不可信脚本。
|
|
249
287
|
*/
|
|
250
288
|
var SandboxedScriptExecutor = class {
|
|
251
289
|
#fs;
|
|
@@ -280,7 +318,7 @@ var SandboxedScriptExecutor = class {
|
|
|
280
318
|
const skillName = baseName(skillRoot);
|
|
281
319
|
const result = await this.#runWorker({
|
|
282
320
|
mode: "load",
|
|
283
|
-
scriptPath: toPlatform$
|
|
321
|
+
scriptPath: toPlatform$2(scriptPath),
|
|
284
322
|
scriptName
|
|
285
323
|
});
|
|
286
324
|
if (!result.ok) throw new WebSkillError("TOOL_EXECUTION_FAILED", `Failed to load script "${scriptName}": ${result.error?.message ?? "unknown error"}`);
|
|
@@ -310,7 +348,7 @@ var SandboxedScriptExecutor = class {
|
|
|
310
348
|
try {
|
|
311
349
|
const result = await this.#runWorker({
|
|
312
350
|
mode: "execute",
|
|
313
|
-
scriptPath: toPlatform$
|
|
351
|
+
scriptPath: toPlatform$2(scriptPath),
|
|
314
352
|
scriptName,
|
|
315
353
|
skillName: context.skillName,
|
|
316
354
|
runId: context.runId,
|
|
@@ -356,7 +394,8 @@ var SandboxedScriptExecutor = class {
|
|
|
356
394
|
workerData: {
|
|
357
395
|
...workerData,
|
|
358
396
|
networkPolicy: this.#networkPolicy,
|
|
359
|
-
networkPolicyLib: NETWORK_POLICY_LIB
|
|
397
|
+
networkPolicyLib: NETWORK_POLICY_LIB,
|
|
398
|
+
allowedModules: this.#options.allowedModules ?? []
|
|
360
399
|
},
|
|
361
400
|
env,
|
|
362
401
|
resourceLimits: this.#options.resourceLimits ?? {
|
|
@@ -776,7 +815,7 @@ var CliUiBridge = class {
|
|
|
776
815
|
}
|
|
777
816
|
};
|
|
778
817
|
const messageOf$4 = (e) => e instanceof Error ? e.message : String(e);
|
|
779
|
-
const toPlatform = (p) => p.split("/").join(path.sep);
|
|
818
|
+
const toPlatform$1 = (p) => p.split("/").join(path.sep);
|
|
780
819
|
const isZipArchive = (data) => data.length > 1 && data[0] === 80 && data[1] === 75;
|
|
781
820
|
/**
|
|
782
821
|
* zip 解包(fflate)。每个条目路径过 resolveInsideRoot:
|
|
@@ -803,15 +842,15 @@ async function extractTarFile(archivePath, destRoot) {
|
|
|
803
842
|
try {
|
|
804
843
|
const entryPaths = [];
|
|
805
844
|
await tar.t({
|
|
806
|
-
file: toPlatform(archivePath),
|
|
845
|
+
file: toPlatform$1(archivePath),
|
|
807
846
|
onentry: (entry) => {
|
|
808
847
|
entryPaths.push(entry.path);
|
|
809
848
|
}
|
|
810
849
|
});
|
|
811
850
|
for (const p of entryPaths) if (!p.replace(/\\/g, "/").split("/").every((s) => s === "" || s === ".")) resolveInsideRoot(destRoot, p);
|
|
812
851
|
await tar.x({
|
|
813
|
-
file: toPlatform(archivePath),
|
|
814
|
-
cwd: toPlatform(destRoot),
|
|
852
|
+
file: toPlatform$1(archivePath),
|
|
853
|
+
cwd: toPlatform$1(destRoot),
|
|
815
854
|
filter: (_p, entry) => !("type" in entry) || entry.type !== "SymbolicLink" && entry.type !== "Link"
|
|
816
855
|
});
|
|
817
856
|
} catch (e) {
|
|
@@ -830,8 +869,23 @@ async function listFiles(fs, root, prefix = "") {
|
|
|
830
869
|
}
|
|
831
870
|
return out;
|
|
832
871
|
}
|
|
833
|
-
|
|
872
|
+
const toPlatform = (p) => p.split("/").join(path.sep);
|
|
873
|
+
/** lstat 扫描拒绝符号链接条目(与 tar 解包 filter 策略一致;虚拟 fs 路径不存在于真实 fs 时跳过) */
|
|
874
|
+
async function assertNoSymlinks(platformRoot) {
|
|
875
|
+
let entries;
|
|
876
|
+
try {
|
|
877
|
+
entries = await promises.readdir(platformRoot, { withFileTypes: true });
|
|
878
|
+
} catch {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
for (const entry of entries) {
|
|
882
|
+
if (entry.isSymbolicLink()) throw new WebSkillError("INSTALL_FAILED", `Refusing to copy symbolic link entry: ${platformRoot}${path.sep}${entry.name}`);
|
|
883
|
+
if (entry.isDirectory()) await assertNoSymlinks(`${platformRoot}${path.sep}${entry.name}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/** 递归拷贝目录(含子目录与二进制文件);真实 fs 上先做符号链接扫描 */
|
|
834
887
|
async function copyDir(fs, from, to) {
|
|
888
|
+
await assertNoSymlinks(toPlatform(from));
|
|
835
889
|
await fs.mkdir(to);
|
|
836
890
|
for (const entry of await fs.list(from)) {
|
|
837
891
|
const name = entry.path.split("/").pop() ?? "";
|
|
@@ -854,8 +908,8 @@ async function exportArchive(fs, skillRoot, options) {
|
|
|
854
908
|
for (const rel of await listFiles(fs, skillRoot)) files[rel] = await fs.readBinary(`${skillRoot}/${rel}`);
|
|
855
909
|
await fs.writeBinary(options.outPath, zipSync(files, { level: 6 }));
|
|
856
910
|
} else await tar.c({
|
|
857
|
-
file: toPlatform(options.outPath),
|
|
858
|
-
cwd: toPlatform(skillRoot)
|
|
911
|
+
file: toPlatform$1(options.outPath),
|
|
912
|
+
cwd: toPlatform$1(skillRoot)
|
|
859
913
|
}, ["."]);
|
|
860
914
|
return options.outPath;
|
|
861
915
|
} catch (e) {
|
|
@@ -876,7 +930,7 @@ async function readArchiveManifest(fs, archivePath) {
|
|
|
876
930
|
let manifestText;
|
|
877
931
|
try {
|
|
878
932
|
await tar.t({
|
|
879
|
-
file: toPlatform(archivePath),
|
|
933
|
+
file: toPlatform$1(archivePath),
|
|
880
934
|
onentry: (entry) => {
|
|
881
935
|
if (entry.path === "webskill.skill-manifest.json" || entry.path.endsWith(`/${"webskill.skill-manifest.json"}`)) {
|
|
882
936
|
const chunks = [];
|
|
@@ -970,7 +1024,7 @@ async function stageHttp(source, ctx, expectedSha256) {
|
|
|
970
1024
|
else {
|
|
971
1025
|
const archivePath = `${ctx.stagingRoot}/archive.tar`;
|
|
972
1026
|
await ctx.fs.writeBinary(archivePath, data);
|
|
973
|
-
await extractTarFile(toPlatform(archivePath), toPlatform(contentDir));
|
|
1027
|
+
await extractTarFile(toPlatform$1(archivePath), toPlatform$1(contentDir));
|
|
974
1028
|
}
|
|
975
1029
|
const packFilePath = `${contentDir}/${SKILL_PACK_FILE}`;
|
|
976
1030
|
if (await ctx.fs.exists(packFilePath)) return {
|
|
@@ -1021,7 +1075,7 @@ async function stageNpm(source, ctx) {
|
|
|
1021
1075
|
if (!tarball || !await ctx.fs.exists(tgzPath)) throw new WebSkillError("INSTALL_FAILED", `npm pack output not found in staging directory: ${tarball ?? "(empty output)"}`);
|
|
1022
1076
|
const pkgDir = `${ctx.stagingRoot}/pkg`;
|
|
1023
1077
|
await ctx.fs.mkdir(pkgDir);
|
|
1024
|
-
await extractTarFile(toPlatform(tgzPath), toPlatform(pkgDir));
|
|
1078
|
+
await extractTarFile(toPlatform$1(tgzPath), toPlatform$1(pkgDir));
|
|
1025
1079
|
const pkgRoot = `${pkgDir}/package`;
|
|
1026
1080
|
const skillDir = await ctx.fs.exists(`${pkgRoot}/skill/SKILL.md`) ? `${pkgRoot}/skill` : pkgRoot;
|
|
1027
1081
|
let packageName = source.packageName;
|
|
@@ -1187,20 +1241,32 @@ var SkillManager = class {
|
|
|
1187
1241
|
const errors = report.issues.filter((i) => i.severity === "error");
|
|
1188
1242
|
throw new WebSkillError("INSTALL_FAILED", `Skill "${name}" failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
|
|
1189
1243
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
await copyDir(fs, finalDir, targetDir);
|
|
1193
|
-
await this.#inferSkillSchemas(targetDir);
|
|
1194
|
-
const manifest = await createManifest(fs, targetDir, {
|
|
1244
|
+
await this.#inferSkillSchemas(finalDir);
|
|
1245
|
+
const manifest = await createManifest(fs, finalDir, {
|
|
1195
1246
|
name,
|
|
1196
1247
|
...version ? { version } : {},
|
|
1197
1248
|
source: staged.source
|
|
1198
1249
|
});
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1250
|
+
targetDir = `${this.#managedRoot}/${name}`;
|
|
1251
|
+
const backupDir = `${stagingRoot}/backup`;
|
|
1252
|
+
const hadPrevious = await fs.exists(targetDir);
|
|
1253
|
+
if (hadPrevious) await copyDir(fs, targetDir, backupDir);
|
|
1254
|
+
let committed = false;
|
|
1255
|
+
try {
|
|
1256
|
+
if (hadPrevious) await fs.remove(targetDir, { recursive: true });
|
|
1257
|
+
await copyDir(fs, finalDir, targetDir);
|
|
1258
|
+
committed = true;
|
|
1259
|
+
await upsertLockEntry(fs, this.#managedRoot, name, {
|
|
1260
|
+
digest: manifest.integrity.digest,
|
|
1261
|
+
installedAt: manifest.installedAt,
|
|
1262
|
+
source: staged.source
|
|
1263
|
+
});
|
|
1264
|
+
} catch (e) {
|
|
1265
|
+
if (committed) await removeDirQuiet(fs, targetDir);
|
|
1266
|
+
if (hadPrevious) await copyDir(fs, backupDir, targetDir);
|
|
1267
|
+
targetDir = void 0;
|
|
1268
|
+
throw e;
|
|
1269
|
+
}
|
|
1204
1270
|
targetDir = void 0;
|
|
1205
1271
|
return manifest;
|
|
1206
1272
|
} catch (e) {
|
|
@@ -1243,11 +1309,7 @@ var SkillManager = class {
|
|
|
1243
1309
|
}
|
|
1244
1310
|
const manifests = [];
|
|
1245
1311
|
for (const entry of pack.manifest.skills) {
|
|
1246
|
-
const
|
|
1247
|
-
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
1248
|
-
await copyDir(fs, `${finalRoot}/${entry.name}`, targetDir);
|
|
1249
|
-
installed.push(targetDir);
|
|
1250
|
-
const manifest = await createManifest(fs, targetDir, {
|
|
1312
|
+
const manifest = await createManifest(fs, `${finalRoot}/${entry.name}`, {
|
|
1251
1313
|
name: entry.name,
|
|
1252
1314
|
...versions.get(entry.name) ? { version: versions.get(entry.name) } : {},
|
|
1253
1315
|
source
|
|
@@ -1255,11 +1317,35 @@ var SkillManager = class {
|
|
|
1255
1317
|
if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
|
|
1256
1318
|
manifests.push(manifest);
|
|
1257
1319
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1320
|
+
const swapped = [];
|
|
1321
|
+
try {
|
|
1322
|
+
for (const manifest of manifests) {
|
|
1323
|
+
const targetDir = `${this.#managedRoot}/${manifest.name}`;
|
|
1324
|
+
const backupDir = `${stagingRoot}/backup/${manifest.name}`;
|
|
1325
|
+
if (await fs.exists(targetDir)) {
|
|
1326
|
+
await copyDir(fs, targetDir, backupDir);
|
|
1327
|
+
await fs.remove(targetDir, { recursive: true });
|
|
1328
|
+
}
|
|
1329
|
+
await copyDir(fs, `${finalRoot}/${manifest.name}`, targetDir);
|
|
1330
|
+
installed.push(targetDir);
|
|
1331
|
+
swapped.push({
|
|
1332
|
+
targetDir,
|
|
1333
|
+
backupDir
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
for (const manifest of manifests) await upsertLockEntry(fs, this.#managedRoot, manifest.name, {
|
|
1337
|
+
digest: manifest.integrity.digest,
|
|
1338
|
+
installedAt: manifest.installedAt,
|
|
1339
|
+
source
|
|
1340
|
+
});
|
|
1341
|
+
} catch (e) {
|
|
1342
|
+
for (const { targetDir, backupDir } of swapped) {
|
|
1343
|
+
await removeDirQuiet(fs, targetDir);
|
|
1344
|
+
if (await fs.exists(backupDir)) await copyDir(fs, backupDir, targetDir);
|
|
1345
|
+
}
|
|
1346
|
+
installed.length = 0;
|
|
1347
|
+
throw e;
|
|
1348
|
+
}
|
|
1263
1349
|
return manifests;
|
|
1264
1350
|
} catch (e) {
|
|
1265
1351
|
for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
|
|
@@ -1328,11 +1414,8 @@ var SkillManager = class {
|
|
|
1328
1414
|
return options.outPath;
|
|
1329
1415
|
}
|
|
1330
1416
|
};
|
|
1331
|
-
/**
|
|
1332
|
-
|
|
1333
|
-
* 读取 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
|
|
1334
|
-
*/
|
|
1335
|
-
function loadLlmConfigFromEnv(dotenvPath = ".env") {
|
|
1417
|
+
/** 解析 .env(简单 KEY=VALUE 行解析,不引依赖);文件缺失返回 undefined */
|
|
1418
|
+
function readEnvVars(dotenvPath) {
|
|
1336
1419
|
let raw;
|
|
1337
1420
|
try {
|
|
1338
1421
|
raw = readFileSync(dotenvPath, "utf8");
|
|
@@ -1350,6 +1433,14 @@ function loadLlmConfigFromEnv(dotenvPath = ".env") {
|
|
|
1350
1433
|
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1351
1434
|
vars.set(key, value);
|
|
1352
1435
|
}
|
|
1436
|
+
return vars;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* 读取 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
|
|
1440
|
+
*/
|
|
1441
|
+
function loadLlmConfigFromEnv(dotenvPath = ".env") {
|
|
1442
|
+
const vars = readEnvVars(dotenvPath);
|
|
1443
|
+
if (!vars) return void 0;
|
|
1353
1444
|
const baseUrl = vars.get("VITE_BASE_URL");
|
|
1354
1445
|
const apiKey = vars.get("VITE_API_KEY");
|
|
1355
1446
|
const model = vars.get("VITE_MODEL_NAME");
|
|
@@ -1360,6 +1451,34 @@ function loadLlmConfigFromEnv(dotenvPath = ".env") {
|
|
|
1360
1451
|
model
|
|
1361
1452
|
};
|
|
1362
1453
|
}
|
|
1454
|
+
/** ANTHROPIC_API_KEY / ANTHROPIC_MODEL(可选 ANTHROPIC_BASE_URL);缺项返回 undefined */
|
|
1455
|
+
function loadAnthropicConfigFromEnv(dotenvPath = ".env") {
|
|
1456
|
+
const vars = readEnvVars(dotenvPath);
|
|
1457
|
+
if (!vars) return void 0;
|
|
1458
|
+
const apiKey = vars.get("ANTHROPIC_API_KEY");
|
|
1459
|
+
const model = vars.get("ANTHROPIC_MODEL");
|
|
1460
|
+
if (!apiKey || !model) return void 0;
|
|
1461
|
+
const baseUrl = vars.get("ANTHROPIC_BASE_URL");
|
|
1462
|
+
return {
|
|
1463
|
+
apiKey,
|
|
1464
|
+
model,
|
|
1465
|
+
...baseUrl ? { baseUrl } : {}
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
/** GOOGLE_API_KEY / GOOGLE_MODEL(可选 GOOGLE_BASE_URL);缺项返回 undefined */
|
|
1469
|
+
function loadGoogleConfigFromEnv(dotenvPath = ".env") {
|
|
1470
|
+
const vars = readEnvVars(dotenvPath);
|
|
1471
|
+
if (!vars) return void 0;
|
|
1472
|
+
const apiKey = vars.get("GOOGLE_API_KEY");
|
|
1473
|
+
const model = vars.get("GOOGLE_MODEL");
|
|
1474
|
+
if (!apiKey || !model) return void 0;
|
|
1475
|
+
const baseUrl = vars.get("GOOGLE_BASE_URL");
|
|
1476
|
+
return {
|
|
1477
|
+
apiKey,
|
|
1478
|
+
model,
|
|
1479
|
+
...baseUrl ? { baseUrl } : {}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1363
1482
|
let capabilitiesCache;
|
|
1364
1483
|
/**
|
|
1365
1484
|
* 探测 LLM 能力(结果模块级缓存):
|
|
@@ -1433,4 +1552,4 @@ async function doProbeLlmCapabilities(config) {
|
|
|
1433
1552
|
}
|
|
1434
1553
|
|
|
1435
1554
|
//#endregion
|
|
1436
|
-
export { NodeScriptExecutor as a, SkillManager as c,
|
|
1555
|
+
export { NodeScriptExecutor as a, SkillManager as c, loadLlmConfigFromEnv as d, probeLlmCapabilities as f, NodeFS as i, loadAnthropicConfigFromEnv as l, FileArtifactStore as n, OxcSchemaInferer as o, readArchiveManifest as p, FileMemoryStore as r, SandboxedScriptExecutor as s, CliUiBridge as t, loadGoogleConfigFromEnv as u };
|