@webskill/sdk 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +3 -3
- package/dist/agent.js +2442 -3
- package/dist/{dist-Bfga1TmS.js → approval-BnLLCOG0.js} +235 -2932
- package/dist/browser.d.ts +106 -5
- package/dist/browser.js +364 -10
- package/dist/{openUiLibrary-BKXW7Iwx-DjvEnMlJ.js → dist-CTsxblSD.js} +496 -28
- package/dist/{echarts-De78wXqV.js → echarts-BE7oV_Dl.js} +1 -1
- package/dist/{env-8cY40DXB-CGnEVZby.js → env-Bj1MI2Ww.js} +1 -1
- package/dist/errors-BDZNpC13.js +22 -0
- package/dist/{eventTypes-FllCrX-Z-DNDeHWoG.js → eventTypes-Y07N8IAC.js} +14 -2
- package/dist/external-_ZRQe-V9.js +53 -0
- package/dist/governance.d.ts +15 -3
- package/dist/governance.js +86 -4
- package/dist/{index-DQwGvHAI.d.ts → index-CQYo4tcT.d.ts} +85 -6
- package/dist/{index-sd-gVKey.d.ts → index-CU2md8R1.d.ts} +26 -18
- package/dist/{index-CWoKOFBP.d.ts → index-Oc3H89TJ.d.ts} +159 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1001 -5
- package/dist/kind-DxgS8LM-.js +77 -0
- package/dist/linkedDocument-xNXF-z8n.js +94 -0
- package/dist/llm-B7lLH0ZI.js +1767 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +41 -4
- package/dist/memoryArtifactStore-D6dLeY55.js +48 -0
- package/dist/miniChart-owdCdaw-.js +533 -0
- package/dist/node.d.ts +3 -3
- package/dist/node.js +106 -9
- package/dist/openUiLibrary-CY_ADoe0.js +30 -0
- package/dist/openUiSpecLang-DuOCdw9G.js +1849 -0
- package/dist/pathSecurity-B1owvJAF.js +65 -0
- package/dist/processSandboxEntry.js +5 -1
- package/dist/renderResult-D9Q-Vu2x.js +143 -0
- package/dist/{rolldown-runtime-BOF7iYI8.js → rolldown-runtime-BHkdefai.js} +1 -2
- package/dist/sandboxWorkerEntry.js +5 -1
- package/dist/{dist-D5YhlCdY.js → skill-CAJMsLod.js} +44 -372
- package/dist/{skillVersionStore-D-qHk9ZE-C0OFDTQY.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts} +14 -2
- package/dist/surface-DVGiCmwq.js +145 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +64 -3
- package/dist/{types-C3V6lAeO-BgeOvc1Y.d.ts → types-C3V6lAeO-BIf7UB2e.d.ts} +17 -1
- package/dist/types-TUydnxaj.js +78 -0
- package/dist/ui-react.d.ts +12 -5
- package/dist/ui-react.js +128548 -4729
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +11 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +1254 -3
- package/dist/urlSafety-CiSuCJvX.js +65 -0
- package/dist/webSkillApi-DOOyz2G5.js +106 -0
- package/dist/{webskillLitCatalog-D_zCqeQF-swXsWoAe.js → webskillLitCatalog-DSqVFQjK.js} +11 -3
- package/package.json +1 -1
- package/dist/catalogComponents-BgAJN0p8-Cr55ouOg.js +0 -124384
- package/dist/client-BCM6Z3yq-qUQNkKrK.js +0 -7787
- package/dist/dist-BnqAkSS6.js +0 -2384
- package/dist/dist-CiaDIqkV.js +0 -3509
- package/dist/dist-DxyxmXPU-DGC-dpXf.js +0 -5621
- package/dist/memoryArtifactStore-52Zn9npI-BxtUkrgV.js +0 -86
- package/dist/stdio-CFMoANJJ-BxrTeXh7.js +0 -31
- package/dist/testing-Cm8MseLF.js +0 -142
- package/dist/types-WovEf4ED-CZSDiiBU.js +0 -6215
|
@@ -1,31 +1,15 @@
|
|
|
1
|
+
import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
+
import { a as atomicWriteText, o as isAtomicTempPath, r as resolveInsideRoot } from "./pathSecurity-B1owvJAF.js";
|
|
1
3
|
import { parse } from "yaml";
|
|
2
4
|
import { Unzip, UnzipInflate, zipSync } from "fflate";
|
|
3
5
|
|
|
4
|
-
//#region ../core/
|
|
5
|
-
/**
|
|
6
|
-
* 所有公开 API 抛出的结构化错误,code 供上层可编程处理
|
|
7
|
-
* @stable
|
|
8
|
-
*/
|
|
9
|
-
var WebSkillError = class extends Error {
|
|
10
|
-
code;
|
|
11
|
-
details;
|
|
12
|
-
constructor(code, message, details) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "WebSkillError";
|
|
15
|
-
this.code = code;
|
|
16
|
-
this.details = details;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/** unknown 异常取消息文本(全仓单一来源,勿再本地重复定义) */
|
|
20
|
-
function messageOf(e) {
|
|
21
|
-
return e instanceof Error ? e.message : String(e);
|
|
22
|
-
}
|
|
6
|
+
//#region ../core/src/skill/manifest.ts
|
|
23
7
|
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
24
8
|
const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
25
9
|
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
26
10
|
const SKILLS_LOCKFILE = "skills.lock.json";
|
|
27
11
|
/** 技能目录内的签名文件名(不参与 files 列表与 digest) */
|
|
28
|
-
const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
12
|
+
const SKILL_SIGNATURE_FILE$1 = "webskill.skill-signature.json";
|
|
29
13
|
/**
|
|
30
14
|
* 不参与 digest 的技能目录内文件。
|
|
31
15
|
*
|
|
@@ -36,7 +20,7 @@ const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
|
36
20
|
const MANIFEST_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
37
21
|
SKILL_MANIFEST_FILE,
|
|
38
22
|
SKILLS_LOCKFILE,
|
|
39
|
-
SKILL_SIGNATURE_FILE
|
|
23
|
+
SKILL_SIGNATURE_FILE$1
|
|
40
24
|
]);
|
|
41
25
|
/** 整体 digest:逐文件 "<path>:<sha256>\n" 拼接串(文件按 path 排序)的 sha256(hash 由调用方提供,可为 WebCrypto 异步实现) */
|
|
42
26
|
async function computeDigest(files, sha256) {
|
|
@@ -75,27 +59,11 @@ function verifyManifest(manifest, actualHashes, actualFiles = [...actualHashes.k
|
|
|
75
59
|
missing
|
|
76
60
|
};
|
|
77
61
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @stable
|
|
82
|
-
*/
|
|
83
|
-
const ATOMIC_TMP_SUFFIX_PATTERN = /\.tmp-\d+-[a-z0-9]+$/;
|
|
84
|
-
/** 该路径是否是 atomicWriteText 留下的临时文件 */
|
|
85
|
-
const isAtomicTempPath = (path) => ATOMIC_TMP_SUFFIX_PATTERN.test(path);
|
|
86
|
-
/**
|
|
87
|
-
* 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景)。
|
|
88
|
-
* 原子性强弱取决于 provider 的 rename 实现:NodeFS/MemoryFS 为真 rename;
|
|
89
|
-
* OPFS 无原生 move,rename 是 copy+delete(非原子,崩溃可能残留双份),
|
|
90
|
-
* 对崩溃一致性要求极高的场景在浏览器侧需知悉此前提。
|
|
91
|
-
*/
|
|
92
|
-
async function atomicWriteText(fs, path, content) {
|
|
93
|
-
const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
94
|
-
await fs.writeText(tmp, content);
|
|
95
|
-
await fs.rename(tmp, path);
|
|
96
|
-
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region ../core/src/skill/signature.ts
|
|
97
65
|
/** 技能目录内的签名文件名(与 manifest 同级,不参与 files 列表与 digest) */
|
|
98
|
-
const SKILL_SIGNATURE_FILE
|
|
66
|
+
const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
99
67
|
const SIGNATURE_SCHEMA_VERSION = 1;
|
|
100
68
|
const encoder = new TextEncoder();
|
|
101
69
|
const toBase64Url = (bytes) => {
|
|
@@ -171,7 +139,7 @@ function parseSignature(raw, origin) {
|
|
|
171
139
|
}
|
|
172
140
|
/** 从技能目录读签名文件;文件不存在返回 undefined(缺签名由策略层判定,不是错误) */
|
|
173
141
|
async function readSkillSignature(fs, skillRoot) {
|
|
174
|
-
const path = `${skillRoot}/${SKILL_SIGNATURE_FILE
|
|
142
|
+
const path = `${skillRoot}/${SKILL_SIGNATURE_FILE}`;
|
|
175
143
|
if (!await fs.exists(path)) return void 0;
|
|
176
144
|
let parsed;
|
|
177
145
|
try {
|
|
@@ -193,7 +161,7 @@ async function verifySkillSignature(input) {
|
|
|
193
161
|
const { manifest, signature, trustedKeys, policy } = input;
|
|
194
162
|
const skill = JSON.stringify(manifest.name);
|
|
195
163
|
if (!signature) {
|
|
196
|
-
if (policy.unsigned === "deny") throw new WebSkillError("SIGNATURE_MISSING", `Skill ${skill} carries no ${SKILL_SIGNATURE_FILE
|
|
164
|
+
if (policy.unsigned === "deny") throw new WebSkillError("SIGNATURE_MISSING", `Skill ${skill} carries no ${SKILL_SIGNATURE_FILE} and the unsigned policy is "deny"`);
|
|
197
165
|
return {
|
|
198
166
|
ok: true,
|
|
199
167
|
unsigned: true,
|
|
@@ -279,303 +247,9 @@ var FsTrustedKeyStore = class {
|
|
|
279
247
|
await this.#write((await this.#read()).filter((key) => key.keyId !== keyId));
|
|
280
248
|
}
|
|
281
249
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
kind = "memory";
|
|
286
|
-
#entries = /* @__PURE__ */ new Map();
|
|
287
|
-
constructor() {
|
|
288
|
-
this.#entries.set(ROOT, {
|
|
289
|
-
type: "directory",
|
|
290
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
291
|
-
mtimeMs: Date.now()
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
#normalize(path) {
|
|
295
|
-
const out = [];
|
|
296
|
-
for (const seg of path.replace(/\\/g, "/").split("/")) {
|
|
297
|
-
if (seg === "" || seg === ".") continue;
|
|
298
|
-
if (seg === "..") out.pop();
|
|
299
|
-
else out.push(seg);
|
|
300
|
-
}
|
|
301
|
-
return ROOT + out.join("/");
|
|
302
|
-
}
|
|
303
|
-
#parentOf(key) {
|
|
304
|
-
const i = key.lastIndexOf("/");
|
|
305
|
-
return i <= 0 ? ROOT : key.slice(0, i);
|
|
306
|
-
}
|
|
307
|
-
#ensureParents(key) {
|
|
308
|
-
const chain = [];
|
|
309
|
-
let dir = this.#parentOf(key);
|
|
310
|
-
while (!this.#entries.has(dir)) {
|
|
311
|
-
chain.push(dir);
|
|
312
|
-
dir = this.#parentOf(dir);
|
|
313
|
-
}
|
|
314
|
-
for (const d of chain.reverse()) this.#entries.set(d, {
|
|
315
|
-
type: "directory",
|
|
316
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
317
|
-
mtimeMs: Date.now()
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
#getFile(key) {
|
|
321
|
-
const entry = this.#entries.get(key);
|
|
322
|
-
if (!entry || entry.type !== "file") throw new WebSkillError("FS_NOT_FOUND", `File not found: ${key}`);
|
|
323
|
-
return entry;
|
|
324
|
-
}
|
|
325
|
-
#getDirectory(key) {
|
|
326
|
-
const entry = this.#entries.get(key);
|
|
327
|
-
if (!entry || entry.type !== "directory") throw new WebSkillError("FS_NOT_FOUND", `Directory not found: ${key}`);
|
|
328
|
-
return entry;
|
|
329
|
-
}
|
|
330
|
-
async readText(path) {
|
|
331
|
-
return new TextDecoder().decode(this.#getFile(this.#normalize(path)).content);
|
|
332
|
-
}
|
|
333
|
-
async writeText(path, content) {
|
|
334
|
-
await this.writeBinary(path, new TextEncoder().encode(content));
|
|
335
|
-
}
|
|
336
|
-
async appendText(path, content) {
|
|
337
|
-
const key = this.#normalize(path);
|
|
338
|
-
const existing = this.#entries.get(key);
|
|
339
|
-
if (existing && existing.type !== "file") throw new WebSkillError("FS_PERMISSION_DENIED", `Cannot append to a directory: ${key}`);
|
|
340
|
-
const prior = existing ? new TextDecoder().decode(existing.content) : "";
|
|
341
|
-
this.#ensureParents(key);
|
|
342
|
-
this.#entries.set(key, {
|
|
343
|
-
type: "file",
|
|
344
|
-
content: new TextEncoder().encode(prior + content),
|
|
345
|
-
mtimeMs: Date.now()
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
async readBinary(path) {
|
|
349
|
-
return this.#getFile(this.#normalize(path)).content;
|
|
350
|
-
}
|
|
351
|
-
async writeBinary(path, content) {
|
|
352
|
-
const key = this.#normalize(path);
|
|
353
|
-
this.#ensureParents(key);
|
|
354
|
-
this.#entries.set(key, {
|
|
355
|
-
type: "file",
|
|
356
|
-
content,
|
|
357
|
-
mtimeMs: Date.now()
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
async exists(path) {
|
|
361
|
-
return this.#entries.has(this.#normalize(path));
|
|
362
|
-
}
|
|
363
|
-
async stat(path) {
|
|
364
|
-
const key = this.#normalize(path);
|
|
365
|
-
const entry = this.#entries.get(key);
|
|
366
|
-
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
|
|
367
|
-
return {
|
|
368
|
-
path: key,
|
|
369
|
-
type: entry.type,
|
|
370
|
-
size: entry.type === "file" ? entry.content.length : void 0,
|
|
371
|
-
mtimeMs: entry.mtimeMs
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
async list(path) {
|
|
375
|
-
const key = this.#normalize(path);
|
|
376
|
-
this.#getDirectory(key);
|
|
377
|
-
const prefix = key === ROOT ? ROOT : key + "/";
|
|
378
|
-
const out = [];
|
|
379
|
-
for (const [k, entry] of this.#entries) {
|
|
380
|
-
if (k === key || !k.startsWith(prefix)) continue;
|
|
381
|
-
if (k.slice(prefix.length).includes("/")) continue;
|
|
382
|
-
out.push({
|
|
383
|
-
path: k,
|
|
384
|
-
type: entry.type,
|
|
385
|
-
size: entry.type === "file" ? entry.content.length : void 0,
|
|
386
|
-
mtimeMs: entry.mtimeMs
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
return out;
|
|
390
|
-
}
|
|
391
|
-
async mkdir(path) {
|
|
392
|
-
const key = this.#normalize(path);
|
|
393
|
-
if (this.#entries.has(key)) return;
|
|
394
|
-
this.#ensureParents(key);
|
|
395
|
-
this.#entries.set(key, {
|
|
396
|
-
type: "directory",
|
|
397
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
398
|
-
mtimeMs: Date.now()
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
async remove(path, options) {
|
|
402
|
-
const key = this.#normalize(path);
|
|
403
|
-
if (!this.#entries.get(key)) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
|
|
404
|
-
if (key === ROOT) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", "Cannot delete root directory");
|
|
405
|
-
const prefix = key + "/";
|
|
406
|
-
const descendants = [...this.#entries.keys()].filter((k) => k.startsWith(prefix));
|
|
407
|
-
if (descendants.length > 0 && !options?.recursive) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Directory not empty, use recursive: ${key}`);
|
|
408
|
-
for (const k of descendants) this.#entries.delete(k);
|
|
409
|
-
this.#entries.delete(key);
|
|
410
|
-
}
|
|
411
|
-
async rename(from, to) {
|
|
412
|
-
const src = this.#normalize(from);
|
|
413
|
-
const dst = this.#normalize(to);
|
|
414
|
-
const entry = this.#entries.get(src);
|
|
415
|
-
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
|
|
416
|
-
const prefix = src + "/";
|
|
417
|
-
const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
|
|
418
|
-
this.#ensureParents(dst);
|
|
419
|
-
this.#entries.set(dst, entry);
|
|
420
|
-
for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
|
|
421
|
-
for (const [k] of descendants) this.#entries.delete(k);
|
|
422
|
-
this.#entries.delete(src);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
/** 统一分隔符为 `/`,去除 `.` 段与重复分隔符(不解析 `..`) */
|
|
426
|
-
function normalizePath(path) {
|
|
427
|
-
return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* 外部标识(runId、candidateId、skillName、versionId 等)作为单一路径段使用前的统一校验:
|
|
431
|
-
* 拒绝空串、`.`、`..`、含 `/` 或 `\`、含 `:`(Windows 盘符/ADS)。
|
|
432
|
-
* 违规抛 FS_PATH_OUTSIDE_ROOT(kind 用于错误消息定位,如 "runId")。
|
|
433
|
-
*/
|
|
434
|
-
const CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/;
|
|
435
|
-
function assertSafePathSegment(segment, kind) {
|
|
436
|
-
if (CONTROL_CHARS_RE.test(segment)) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Invalid ${kind} (control characters including NUL are not allowed): ${JSON.stringify(segment)}`);
|
|
437
|
-
if (segment === "" || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes(":")) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Invalid ${kind} (must be a single safe path segment): ${JSON.stringify(segment)}`);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* 将相对路径安全地解析到 root 之内。
|
|
441
|
-
* 拒绝空路径、绝对路径(`/`、`\\`、`C:\` 形式)与任何 `..` 段。
|
|
442
|
-
*
|
|
443
|
-
* 注意:本函数仅做**词法**校验,不解析符号链接——root 内经符号链接指向
|
|
444
|
-
* 外部的目标无法被词法检查拦截(realpath 防护见 NodeFS root 模式与
|
|
445
|
-
* 安装管线的 lstat 符号链接扫描)。
|
|
446
|
-
*/
|
|
447
|
-
function resolveInsideRoot(root, relativePath) {
|
|
448
|
-
const fail = (reason) => {
|
|
449
|
-
throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Path outside root: ${reason} (path: ${JSON.stringify(relativePath)})`);
|
|
450
|
-
};
|
|
451
|
-
if (relativePath.trim() === "") fail("empty path");
|
|
452
|
-
if (relativePath.startsWith("/") || relativePath.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(relativePath)) fail("absolute paths not allowed");
|
|
453
|
-
const segments = relativePath.replace(/\\/g, "/").split("/");
|
|
454
|
-
if (CONTROL_CHARS_RE.test(relativePath)) fail("control characters including NUL are not allowed");
|
|
455
|
-
if (segments.some((s) => s === "..")) fail("`..` segments not allowed");
|
|
456
|
-
const clean = segments.filter((s) => s !== "" && s !== ".");
|
|
457
|
-
if (clean.length === 0) fail("path resolves to empty");
|
|
458
|
-
const normalizedRoot = normalizePath(root);
|
|
459
|
-
const joined = normalizedRoot === "" ? clean.join("/") : `${normalizedRoot}/${clean.join("/")}`;
|
|
460
|
-
return root.startsWith("/") || root.startsWith("\\") ? `/${joined}` : joined;
|
|
461
|
-
}
|
|
462
|
-
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "localhost."]);
|
|
463
|
-
function ipv4Octets(host) {
|
|
464
|
-
const parts = host.split(".");
|
|
465
|
-
if (parts.length !== 4) return void 0;
|
|
466
|
-
const octets = parts.map((p) => /^\d{1,3}$/.test(p) ? Number(p) : NaN);
|
|
467
|
-
return octets.every((n) => n >= 0 && n <= 255) ? octets : void 0;
|
|
468
|
-
}
|
|
469
|
-
function isPrivateIpv4(octets) {
|
|
470
|
-
const [a, b] = octets;
|
|
471
|
-
return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
472
|
-
}
|
|
473
|
-
function isPrivateIpv6(host) {
|
|
474
|
-
const h = host.toLowerCase();
|
|
475
|
-
if (h === "::1" || h === "::") return true;
|
|
476
|
-
const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
477
|
-
if (mapped) {
|
|
478
|
-
const octets = ipv4Octets(mapped[1]);
|
|
479
|
-
return octets !== void 0 && isPrivateIpv4(octets);
|
|
480
|
-
}
|
|
481
|
-
const mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
482
|
-
if (mappedHex) {
|
|
483
|
-
const hi = parseInt(mappedHex[1], 16);
|
|
484
|
-
const lo = parseInt(mappedHex[2], 16);
|
|
485
|
-
return isPrivateIpv4([
|
|
486
|
-
hi >> 8,
|
|
487
|
-
hi & 255,
|
|
488
|
-
lo >> 8,
|
|
489
|
-
lo & 255
|
|
490
|
-
]);
|
|
491
|
-
}
|
|
492
|
-
const first = h.split(":")[0];
|
|
493
|
-
if (/^[0-9a-f]{1,4}$/.test(first)) {
|
|
494
|
-
const n = parseInt(first, 16);
|
|
495
|
-
if ((n & 65472) === 65152) return true;
|
|
496
|
-
if ((n & 65024) === 64512) return true;
|
|
497
|
-
}
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
function isPrivateHost(hostname) {
|
|
501
|
-
const host = hostname.toLowerCase();
|
|
502
|
-
if (LOOPBACK_HOSTNAMES.has(host) || host.endsWith(".localhost")) return true;
|
|
503
|
-
const v4 = ipv4Octets(host);
|
|
504
|
-
if (v4) return isPrivateIpv4(v4);
|
|
505
|
-
if (host.startsWith("[") && host.endsWith("]")) return isPrivateIpv6(host.slice(1, -1));
|
|
506
|
-
if (host.includes(":")) return isPrivateIpv6(host);
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* 校验远程 URL 是否允许连接;违规抛 NETWORK_BLOCKED。
|
|
511
|
-
* rawUrl 非合法 URL 时抛 TypeError(由调用方按自身错误码包装)。
|
|
512
|
-
*/
|
|
513
|
-
function assertRemoteUrlAllowed(rawUrl, policy = {}) {
|
|
514
|
-
const url = new URL(rawUrl);
|
|
515
|
-
if (url.protocol === "http:") {
|
|
516
|
-
if (!policy.allowHttp) throw new WebSkillError("NETWORK_BLOCKED", `Insecure URL scheme "http:" is not allowed (opt in via allowHttp): ${url.href}`);
|
|
517
|
-
} else if (url.protocol !== "https:") throw new WebSkillError("NETWORK_BLOCKED", `URL scheme "${url.protocol}" is not allowed (https only): ${url.href}`);
|
|
518
|
-
if (!policy.allowPrivateHosts && isPrivateHost(url.hostname)) throw new WebSkillError("NETWORK_BLOCKED", `Refusing to connect to a private/loopback/link-local address: ${url.hostname}`);
|
|
519
|
-
return url;
|
|
520
|
-
}
|
|
521
|
-
/** 可作为图片分片外发的 MIME 类型 @experimental */
|
|
522
|
-
const IMAGE_MIME_TYPES = [
|
|
523
|
-
"image/png",
|
|
524
|
-
"image/jpeg",
|
|
525
|
-
"image/webp",
|
|
526
|
-
"image/gif"
|
|
527
|
-
];
|
|
528
|
-
/**
|
|
529
|
-
* 可按文本读取的扩展名。accept 白名单与判定白名单共用这一份——
|
|
530
|
-
* 0.5.x 两处不一致,`log`/`yaml`/`yml` 能通过判定却在文件选择器里选不到。
|
|
531
|
-
*/
|
|
532
|
-
const TEXT_EXTENSIONS = [
|
|
533
|
-
"txt",
|
|
534
|
-
"md",
|
|
535
|
-
"csv",
|
|
536
|
-
"json",
|
|
537
|
-
"log",
|
|
538
|
-
"yaml",
|
|
539
|
-
"yml"
|
|
540
|
-
];
|
|
541
|
-
/** 非文本非图片但仍可整体外发的类型(provider 的 file 分片) @experimental */
|
|
542
|
-
const FILE_MIME_TYPES = ["application/pdf"];
|
|
543
|
-
/**
|
|
544
|
-
* 需要**客户端先抽取成文本**才能外发的类型(FR-23.9 / 0.13.0 FR-12.5)。
|
|
545
|
-
* 不走直通的 `file`:docx / xlsx 发给 provider 会被当成二进制垃圾。
|
|
546
|
-
*/
|
|
547
|
-
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
548
|
-
const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
549
|
-
/** 文本类附件进模型上下文的字符上限(0.6.0) @experimental */
|
|
550
|
-
const ATTACHMENT_TEXT_LIMIT = 32 * 1024;
|
|
551
|
-
const TEXT_EXTENSION_SET = new Set(TEXT_EXTENSIONS);
|
|
552
|
-
const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
|
|
553
|
-
const FILE_MIME_SET = new Set(FILE_MIME_TYPES);
|
|
554
|
-
const EXTRACTED_MIME_SET = /* @__PURE__ */ new Set([DOCX_MIME, XLSX_MIME]);
|
|
555
|
-
const EXTRACTED_EXTENSION_SET = /* @__PURE__ */ new Set(["docx", "xlsx"]);
|
|
556
|
-
function extensionOf(fileName) {
|
|
557
|
-
return fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
558
|
-
}
|
|
559
|
-
/** 分类集中一处,不散落在各 UI 分支里 @experimental */
|
|
560
|
-
function classifyAttachment(contentType, fileName) {
|
|
561
|
-
const type = contentType.toLowerCase();
|
|
562
|
-
if (IMAGE_MIME_SET.has(type)) return "image";
|
|
563
|
-
if (EXTRACTED_MIME_SET.has(type)) return "document-text";
|
|
564
|
-
if (FILE_MIME_SET.has(type)) return "file";
|
|
565
|
-
if (type.startsWith("text/") || type === "application/json") return "text";
|
|
566
|
-
if (type === "" && TEXT_EXTENSION_SET.has(extensionOf(fileName))) return "text";
|
|
567
|
-
if (type === "" && EXTRACTED_EXTENSION_SET.has(extensionOf(fileName))) return "document-text";
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* 文本类附件进模型上下文的最终形态。两条入口共用,差别只有出处那一段(0.14.0 D-20-6)。
|
|
571
|
-
* @experimental
|
|
572
|
-
*/
|
|
573
|
-
function formatAttachmentText(input) {
|
|
574
|
-
const clipped = input.body.length > 32768 ? `${input.body.slice(0, ATTACHMENT_TEXT_LIMIT)}\n[truncated]` : input.body;
|
|
575
|
-
const bodyOnly = input.kind === "document-text" ? ", body text only" : "";
|
|
576
|
-
const from = input.origin === "download" ? ", from this device's downloads" : "";
|
|
577
|
-
return `--- Attachment: ${input.name} (${input.contentType}${bodyOnly}${from}) ---\n${clipped}`;
|
|
578
|
-
}
|
|
250
|
+
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region ../core/src/skill/parser.ts
|
|
579
253
|
const FRONTMATTER_RE = /^---[^\S\r\n]*\r?\n([\s\S]*?)\r?\n---[^\S\r\n]*(?:\r?\n|$)([\s\S]*)$/;
|
|
580
254
|
const invalid = (message, details) => {
|
|
581
255
|
throw new WebSkillError("SKILL_INVALID_METADATA", message, details);
|
|
@@ -599,12 +273,18 @@ function parseSkillMarkdown(raw) {
|
|
|
599
273
|
body: bodyRaw.trim()
|
|
600
274
|
};
|
|
601
275
|
}
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region ../core/src/skill/naming.ts
|
|
602
279
|
const SKILL_NAME_MAX_LENGTH = 64;
|
|
603
280
|
/** Agent Skills 官方命名规范:小写字母/数字/连字符,不允许首尾或连续连字符 */
|
|
604
281
|
const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
605
282
|
function isValidSkillName(name) {
|
|
606
283
|
return name.length <= 64 && SKILL_NAME_PATTERN.test(name);
|
|
607
284
|
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region ../core/src/skill/rules.ts
|
|
608
288
|
const SCRIPT_EXTENSION_RE = /\.(ts|js)$/;
|
|
609
289
|
/** D2 schema sidecar(scripts/<name>.schema.json)属于协议内文件,不算非法脚本 */
|
|
610
290
|
const SCHEMA_SIDECAR_RE = /\.schema\.json$/;
|
|
@@ -707,6 +387,9 @@ function checkDependencyCycles(adjacency) {
|
|
|
707
387
|
involved
|
|
708
388
|
};
|
|
709
389
|
}
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region ../core/src/skill/skillPack.ts
|
|
710
393
|
/**
|
|
711
394
|
* 技能包集(skill pack)格式与打包逻辑(node/browser 共用单一来源):
|
|
712
395
|
* webskill.skill-pack.json { schemaVersion: 1, skills: [{ name, digest }] }
|
|
@@ -780,6 +463,9 @@ function parseSkillPackManifest(text) {
|
|
|
780
463
|
}
|
|
781
464
|
return raw;
|
|
782
465
|
}
|
|
466
|
+
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region ../core/src/skill/archiveShape.ts
|
|
783
469
|
const SKILL_FILE = "SKILL.md";
|
|
784
470
|
function normalize(path) {
|
|
785
471
|
return path.replace(/^\.?\//, "");
|
|
@@ -812,7 +498,7 @@ function detectSkillArchiveShape(entries) {
|
|
|
812
498
|
if (!hasLooseRootFile && topLevels.size === 1) {
|
|
813
499
|
const [dir] = [...topLevels];
|
|
814
500
|
if (dir !== void 0) {
|
|
815
|
-
if (paths.includes(`${dir}
|
|
501
|
+
if (paths.includes(`${dir}/${"webskill.skill-pack.json"}`)) return {
|
|
816
502
|
shape: "pack-set",
|
|
817
503
|
rootDir: dir
|
|
818
504
|
};
|
|
@@ -856,11 +542,14 @@ async function detectSkillArchiveShapeFromFs(fs, contentDir) {
|
|
|
856
542
|
continue;
|
|
857
543
|
}
|
|
858
544
|
if (await fs.exists(`${entry.path}/${SKILL_FILE}`)) sample.push(`${name}/${SKILL_FILE}`);
|
|
859
|
-
else if (await fs.exists(`${entry.path}
|
|
545
|
+
else if (await fs.exists(`${entry.path}/${"webskill.skill-pack.json"}`)) sample.push(`${name}/${SKILL_PACK_FILE}`);
|
|
860
546
|
else sample.push(`${name}/`);
|
|
861
547
|
}
|
|
862
548
|
return detectSkillArchiveShape(sample);
|
|
863
549
|
}
|
|
550
|
+
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region ../core/src/skill/zipLimits.ts
|
|
864
553
|
const DEFAULT_ARCHIVE_LIMITS = {
|
|
865
554
|
maxDownloadBytes: 64 * 1024 * 1024,
|
|
866
555
|
maxEntryBytes: 64 * 1024 * 1024,
|
|
@@ -970,10 +659,16 @@ async function readResponseWithLimit(res, limits) {
|
|
|
970
659
|
}
|
|
971
660
|
return concat(chunks, size);
|
|
972
661
|
}
|
|
662
|
+
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region ../core/src/skill/catalog.ts
|
|
973
665
|
/** 由条目列表构建 Catalog,保证按名称排序 */
|
|
974
666
|
function buildCatalog(entries) {
|
|
975
667
|
return { entries: [...entries].sort((a, b) => a.name.localeCompare(b.name)) };
|
|
976
668
|
}
|
|
669
|
+
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region ../core/src/skill/reader.ts
|
|
977
672
|
/** 按技能名读取技能根目录内的文件,全部读取强制经过路径安全检查 */
|
|
978
673
|
var SkillReader = class {
|
|
979
674
|
#fs;
|
|
@@ -997,6 +692,9 @@ var SkillReader = class {
|
|
|
997
692
|
return this.#fs.readBinary(resolveInsideRoot(this.#rootFor(skillName), relativePath));
|
|
998
693
|
}
|
|
999
694
|
};
|
|
695
|
+
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region ../core/src/skill/discovery.ts
|
|
1000
698
|
const baseName = (path) => path.split("/").pop() ?? path;
|
|
1001
699
|
var SkillDiscovery = class {
|
|
1002
700
|
#fs;
|
|
@@ -1143,6 +841,9 @@ var SkillDiscovery = class {
|
|
|
1143
841
|
return (await this.#fs.list(scriptsDir)).filter((s) => s.type === "file").map((s) => baseName(s.path));
|
|
1144
842
|
}
|
|
1145
843
|
};
|
|
844
|
+
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region ../core/src/skill/validator.ts
|
|
1146
847
|
/**
|
|
1147
848
|
* 合规校验。内部直接复用 SkillDiscovery 的扫描逻辑,
|
|
1148
849
|
* 规则判定同样落在 checkSkillRules 单一来源上。
|
|
@@ -1157,35 +858,6 @@ async function validateSkills(fs, roots, options) {
|
|
|
1157
858
|
issues
|
|
1158
859
|
};
|
|
1159
860
|
}
|
|
1160
|
-
function renderCatalogJson(catalog) {
|
|
1161
|
-
return JSON.stringify(catalog, null, 2);
|
|
1162
|
-
}
|
|
1163
|
-
const jsonRenderer = {
|
|
1164
|
-
format: "json",
|
|
1165
|
-
render: renderCatalogJson
|
|
1166
|
-
};
|
|
1167
|
-
/** 转义 XML 五个特殊字符:& < > " ' */
|
|
1168
|
-
function escapeXml(text) {
|
|
1169
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1170
|
-
}
|
|
1171
|
-
function renderAvailableSkillsXml(catalog) {
|
|
1172
|
-
const lines = ["<available_skills>"];
|
|
1173
|
-
for (const skill of catalog.entries) {
|
|
1174
|
-
lines.push(" <skill>");
|
|
1175
|
-
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
1176
|
-
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
1177
|
-
if (skill.version !== void 0) lines.push(` <version>${escapeXml(skill.version)}</version>`);
|
|
1178
|
-
if (skill.license !== void 0) lines.push(` <license>${escapeXml(skill.license)}</license>`);
|
|
1179
|
-
lines.push(` <source>${escapeXml(skill.source)}</source>`);
|
|
1180
|
-
lines.push(" </skill>");
|
|
1181
|
-
}
|
|
1182
|
-
lines.push("</available_skills>");
|
|
1183
|
-
return lines.join("\n");
|
|
1184
|
-
}
|
|
1185
|
-
const xmlRenderer = {
|
|
1186
|
-
format: "xml",
|
|
1187
|
-
render: renderAvailableSkillsXml
|
|
1188
|
-
};
|
|
1189
861
|
|
|
1190
862
|
//#endregion
|
|
1191
|
-
export {
|
|
863
|
+
export { SKILL_MANIFEST_FILE as A, keyIdOf as C, verifySkillSignature as D, signaturePayloadBytes as E, buildManifest as M, computeDigest as N, MANIFEST_EXCLUDED_FILES as O, verifyManifest as P, SIGNATURE_SCHEMA_VERSION as S, signSkill as T, SKILL_NAME_MAX_LENGTH as _, DEFAULT_ARCHIVE_LIMITS as a, parseSkillMarkdown as b, unzipWithLimits as c, stripArchiveRoot as d, SKILL_PACK_FILE as f, checkSkillRules as g, checkDependencyCycles as h, buildCatalog as i, SKILL_SIGNATURE_FILE$1 as j, SKILLS_LOCKFILE as k, detectSkillArchiveShape as l, parseSkillPackManifest as m, SkillDiscovery as n, readResponseWithLimit as o, exportSkills as p, SkillReader as r, resolveArchiveLimits as s, validateSkills as t, detectSkillArchiveShapeFromFs as u, SKILL_NAME_PATTERN as v, readSkillSignature as w, FsTrustedKeyStore as x, isValidSkillName as y };
|
package/dist/{skillVersionStore-D-qHk9ZE-C0OFDTQY.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as FileSystemProvider, X as PageQuery, Y as Page, _t as SkillManifest, ut as SkillCatalogEntry } from "./types-C3V6lAeO-
|
|
2
|
-
//#region ../governance/dist/skillVersionStore-
|
|
1
|
+
import { U as FileSystemProvider, X as PageQuery, Y as Page, _t as SkillManifest, ut as SkillCatalogEntry } from "./types-C3V6lAeO-BIf7UB2e.js";
|
|
2
|
+
//#region ../governance/dist/skillVersionStore-B24Jjjry.d.ts
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
|
|
5
5
|
/** `generated` 是 0.5.0 的技能自动生成来源(需求 12 号 AC-9.1) */
|
|
@@ -94,6 +94,8 @@ declare class CandidateStore {
|
|
|
94
94
|
constructor(deps: {
|
|
95
95
|
root: string;
|
|
96
96
|
fs: FileSystemProvider;
|
|
97
|
+
/** 治理根下该技能是否仍装着;不注入则 published 一律拒删(见 `remove`) */
|
|
98
|
+
isSkillInstalled?: (name: string) => Promise<boolean>;
|
|
97
99
|
});
|
|
98
100
|
save(candidate: CandidateSkill): Promise<void>;
|
|
99
101
|
get(id: string): Promise<CandidateSkill>;
|
|
@@ -106,6 +108,16 @@ declare class CandidateStore {
|
|
|
106
108
|
status?: CandidateStatus;
|
|
107
109
|
} & PageQuery): Promise<CandidatePage>;
|
|
108
110
|
updateStatus(id: string, status: CandidateStatus, now?: string): Promise<CandidateSkill>;
|
|
111
|
+
/**
|
|
112
|
+
* 把候选记录从队列里删掉。
|
|
113
|
+
*
|
|
114
|
+
* `approved` 恒拒删:它是「已批准但还没发布」,删掉等于让批准过的东西凭空消失;
|
|
115
|
+
* 要清理先 `reject`(留下理由)再删。
|
|
116
|
+
* `published` 是已安装技能的来源凭证,技能还装着时同样拒删;技能卸载后它只剩堆在队列里,
|
|
117
|
+
* 此时可删(0.15.0 分册 16 D-16-2)。未注入 `isSkillInstalled` 时判断不了装没装,
|
|
118
|
+
* 退化为「published 一律拒删」——宁可留着清不掉,也不能把还装着的技能的来源凭证删了。
|
|
119
|
+
*/
|
|
120
|
+
remove(id: string): Promise<void>;
|
|
109
121
|
}
|
|
110
122
|
/** 硬门禁:仅 published 可转换为 Catalog 条目,否则 APPROVAL_REQUIRED */
|
|
111
123
|
declare function candidateToCatalogEntry(candidate: CandidateSkill): SkillCatalogEntry;
|