@webskill/sdk 0.13.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.
Files changed (60) hide show
  1. package/dist/agent.d.ts +3 -3
  2. package/dist/agent.js +2442 -3
  3. package/dist/{dist-ExSQky4C.js → approval-BnLLCOG0.js} +277 -2934
  4. package/dist/browser.d.ts +327 -8
  5. package/dist/browser.js +1017 -51
  6. package/dist/{openUiLibrary-BKXW7Iwx-CWWBVOkE.js → dist-CTsxblSD.js} +496 -28
  7. package/dist/{echarts-De78wXqV.js → echarts-BE7oV_Dl.js} +1 -1
  8. package/dist/{env-8cY40DXB-CGnEVZby.js → env-Bj1MI2Ww.js} +1 -1
  9. package/dist/errors-BDZNpC13.js +22 -0
  10. package/dist/{eventTypes-FllCrX-Z-DNDeHWoG.js → eventTypes-Y07N8IAC.js} +14 -2
  11. package/dist/external-_ZRQe-V9.js +53 -0
  12. package/dist/governance.d.ts +15 -3
  13. package/dist/governance.js +86 -4
  14. package/dist/{index-iBm9tJL_.d.ts → index-CQYo4tcT.d.ts} +469 -10
  15. package/dist/{index-C3XdItd_.d.ts → index-CU2md8R1.d.ts} +26 -18
  16. package/dist/{index-DkvBhJQy.d.ts → index-Oc3H89TJ.d.ts} +204 -4
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +1001 -5
  19. package/dist/kind-DxgS8LM-.js +77 -0
  20. package/dist/linkedDocument-xNXF-z8n.js +94 -0
  21. package/dist/llm-B7lLH0ZI.js +1767 -0
  22. package/dist/mcp.d.ts +56 -3
  23. package/dist/mcp.js +75 -5
  24. package/dist/memoryArtifactStore-D6dLeY55.js +48 -0
  25. package/dist/miniChart-owdCdaw-.js +533 -0
  26. package/dist/node.d.ts +3 -3
  27. package/dist/node.js +110 -13
  28. package/dist/openUiLibrary-CY_ADoe0.js +30 -0
  29. package/dist/openUiSpecLang-DuOCdw9G.js +1849 -0
  30. package/dist/pathSecurity-B1owvJAF.js +65 -0
  31. package/dist/processSandboxEntry.js +5 -1
  32. package/dist/renderResult-D9Q-Vu2x.js +143 -0
  33. package/dist/{rolldown-runtime-BOF7iYI8.js → rolldown-runtime-BHkdefai.js} +1 -2
  34. package/dist/sandboxWorkerEntry.js +5 -1
  35. package/dist/{dist-Bev6i6Ip.js → skill-CAJMsLod.js} +44 -314
  36. package/dist/{skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts} +14 -2
  37. package/dist/surface-DVGiCmwq.js +145 -0
  38. package/dist/testing.d.ts +1 -1
  39. package/dist/testing.js +64 -3
  40. package/dist/{types-DLctJep_-B5G4uk2u.d.ts → types-C3V6lAeO-BIf7UB2e.d.ts} +79 -4
  41. package/dist/types-TUydnxaj.js +78 -0
  42. package/dist/ui-react.d.ts +12 -5
  43. package/dist/ui-react.js +128548 -4729
  44. package/dist/ui-vue.d.ts +1 -1
  45. package/dist/ui-vue.js +11 -2
  46. package/dist/ui.d.ts +3 -3
  47. package/dist/ui.js +1254 -3
  48. package/dist/urlSafety-CiSuCJvX.js +65 -0
  49. package/dist/webSkillApi-DOOyz2G5.js +106 -0
  50. package/dist/{webskillLitCatalog-D_zCqeQF-C9lrvvMr.js → webskillLitCatalog-DSqVFQjK.js} +11 -3
  51. package/package.json +1 -1
  52. package/dist/catalogComponents-BgAJN0p8-CYEXSk45.js +0 -124384
  53. package/dist/client-BCM6Z3yq-qUQNkKrK.js +0 -7787
  54. package/dist/dist-DqcL6jKO.js +0 -3509
  55. package/dist/dist-DxyxmXPU-DGC-dpXf.js +0 -5621
  56. package/dist/dist-GK6dtjRv.js +0 -1493
  57. package/dist/memoryArtifactStore-52Zn9npI-LbCQaqyx.js +0 -86
  58. package/dist/stdio-CFMoANJJ-BxrTeXh7.js +0 -31
  59. package/dist/testing-WPTyXQYt.js +0 -142
  60. 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/dist/index.js
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
- * 原子写的临时文件名后缀模式;列举/打包侧据此排除中断残留(0.9.0 分册 11)。
80
- * 单一来源:不要在别处手写正则(AC-G10),复制正则必然漂移。
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$1 = "webskill.skill-signature.json";
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$1}`;
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$1} and the unsigned policy is "deny"`);
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,245 +247,9 @@ var FsTrustedKeyStore = class {
279
247
  await this.#write((await this.#read()).filter((key) => key.keyId !== keyId));
280
248
  }
281
249
  };
282
- const ROOT = "/";
283
- /** 内存文件系统:路径一律规范化为 `/` 分隔,写入自动创建父目录 */
284
- var MemoryFS = class {
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
- }
250
+
251
+ //#endregion
252
+ //#region ../core/src/skill/parser.ts
521
253
  const FRONTMATTER_RE = /^---[^\S\r\n]*\r?\n([\s\S]*?)\r?\n---[^\S\r\n]*(?:\r?\n|$)([\s\S]*)$/;
522
254
  const invalid = (message, details) => {
523
255
  throw new WebSkillError("SKILL_INVALID_METADATA", message, details);
@@ -541,12 +273,18 @@ function parseSkillMarkdown(raw) {
541
273
  body: bodyRaw.trim()
542
274
  };
543
275
  }
276
+
277
+ //#endregion
278
+ //#region ../core/src/skill/naming.ts
544
279
  const SKILL_NAME_MAX_LENGTH = 64;
545
280
  /** Agent Skills 官方命名规范:小写字母/数字/连字符,不允许首尾或连续连字符 */
546
281
  const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
547
282
  function isValidSkillName(name) {
548
283
  return name.length <= 64 && SKILL_NAME_PATTERN.test(name);
549
284
  }
285
+
286
+ //#endregion
287
+ //#region ../core/src/skill/rules.ts
550
288
  const SCRIPT_EXTENSION_RE = /\.(ts|js)$/;
551
289
  /** D2 schema sidecar(scripts/<name>.schema.json)属于协议内文件,不算非法脚本 */
552
290
  const SCHEMA_SIDECAR_RE = /\.schema\.json$/;
@@ -649,6 +387,9 @@ function checkDependencyCycles(adjacency) {
649
387
  involved
650
388
  };
651
389
  }
390
+
391
+ //#endregion
392
+ //#region ../core/src/skill/skillPack.ts
652
393
  /**
653
394
  * 技能包集(skill pack)格式与打包逻辑(node/browser 共用单一来源):
654
395
  * webskill.skill-pack.json { schemaVersion: 1, skills: [{ name, digest }] }
@@ -722,6 +463,9 @@ function parseSkillPackManifest(text) {
722
463
  }
723
464
  return raw;
724
465
  }
466
+
467
+ //#endregion
468
+ //#region ../core/src/skill/archiveShape.ts
725
469
  const SKILL_FILE = "SKILL.md";
726
470
  function normalize(path) {
727
471
  return path.replace(/^\.?\//, "");
@@ -754,7 +498,7 @@ function detectSkillArchiveShape(entries) {
754
498
  if (!hasLooseRootFile && topLevels.size === 1) {
755
499
  const [dir] = [...topLevels];
756
500
  if (dir !== void 0) {
757
- if (paths.includes(`${dir}/webskill.skill-pack.json`)) return {
501
+ if (paths.includes(`${dir}/${"webskill.skill-pack.json"}`)) return {
758
502
  shape: "pack-set",
759
503
  rootDir: dir
760
504
  };
@@ -798,11 +542,14 @@ async function detectSkillArchiveShapeFromFs(fs, contentDir) {
798
542
  continue;
799
543
  }
800
544
  if (await fs.exists(`${entry.path}/${SKILL_FILE}`)) sample.push(`${name}/${SKILL_FILE}`);
801
- else if (await fs.exists(`${entry.path}/webskill.skill-pack.json`)) sample.push(`${name}/${SKILL_PACK_FILE}`);
545
+ else if (await fs.exists(`${entry.path}/${"webskill.skill-pack.json"}`)) sample.push(`${name}/${SKILL_PACK_FILE}`);
802
546
  else sample.push(`${name}/`);
803
547
  }
804
548
  return detectSkillArchiveShape(sample);
805
549
  }
550
+
551
+ //#endregion
552
+ //#region ../core/src/skill/zipLimits.ts
806
553
  const DEFAULT_ARCHIVE_LIMITS = {
807
554
  maxDownloadBytes: 64 * 1024 * 1024,
808
555
  maxEntryBytes: 64 * 1024 * 1024,
@@ -912,10 +659,16 @@ async function readResponseWithLimit(res, limits) {
912
659
  }
913
660
  return concat(chunks, size);
914
661
  }
662
+
663
+ //#endregion
664
+ //#region ../core/src/skill/catalog.ts
915
665
  /** 由条目列表构建 Catalog,保证按名称排序 */
916
666
  function buildCatalog(entries) {
917
667
  return { entries: [...entries].sort((a, b) => a.name.localeCompare(b.name)) };
918
668
  }
669
+
670
+ //#endregion
671
+ //#region ../core/src/skill/reader.ts
919
672
  /** 按技能名读取技能根目录内的文件,全部读取强制经过路径安全检查 */
920
673
  var SkillReader = class {
921
674
  #fs;
@@ -939,6 +692,9 @@ var SkillReader = class {
939
692
  return this.#fs.readBinary(resolveInsideRoot(this.#rootFor(skillName), relativePath));
940
693
  }
941
694
  };
695
+
696
+ //#endregion
697
+ //#region ../core/src/skill/discovery.ts
942
698
  const baseName = (path) => path.split("/").pop() ?? path;
943
699
  var SkillDiscovery = class {
944
700
  #fs;
@@ -1085,6 +841,9 @@ var SkillDiscovery = class {
1085
841
  return (await this.#fs.list(scriptsDir)).filter((s) => s.type === "file").map((s) => baseName(s.path));
1086
842
  }
1087
843
  };
844
+
845
+ //#endregion
846
+ //#region ../core/src/skill/validator.ts
1088
847
  /**
1089
848
  * 合规校验。内部直接复用 SkillDiscovery 的扫描逻辑,
1090
849
  * 规则判定同样落在 checkSkillRules 单一来源上。
@@ -1099,35 +858,6 @@ async function validateSkills(fs, roots, options) {
1099
858
  issues
1100
859
  };
1101
860
  }
1102
- function renderCatalogJson(catalog) {
1103
- return JSON.stringify(catalog, null, 2);
1104
- }
1105
- const jsonRenderer = {
1106
- format: "json",
1107
- render: renderCatalogJson
1108
- };
1109
- /** 转义 XML 五个特殊字符:& < > " ' */
1110
- function escapeXml(text) {
1111
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1112
- }
1113
- function renderAvailableSkillsXml(catalog) {
1114
- const lines = ["<available_skills>"];
1115
- for (const skill of catalog.entries) {
1116
- lines.push(" <skill>");
1117
- lines.push(` <name>${escapeXml(skill.name)}</name>`);
1118
- lines.push(` <description>${escapeXml(skill.description)}</description>`);
1119
- if (skill.version !== void 0) lines.push(` <version>${escapeXml(skill.version)}</version>`);
1120
- if (skill.license !== void 0) lines.push(` <license>${escapeXml(skill.license)}</license>`);
1121
- lines.push(` <source>${escapeXml(skill.source)}</source>`);
1122
- lines.push(" </skill>");
1123
- }
1124
- lines.push("</available_skills>");
1125
- return lines.join("\n");
1126
- }
1127
- const xmlRenderer = {
1128
- format: "xml",
1129
- render: renderAvailableSkillsXml
1130
- };
1131
861
 
1132
862
  //#endregion
1133
- export { jsonRenderer as A, resolveArchiveLimits as B, computeDigest as C, exportSkills as D, escapeXml as E, parseSkillPackManifest as F, unzipWithLimits as G, signSkill as H, readResponseWithLimit as I, verifySkillSignature as J, validateSkills as K, readSkillSignature as L, messageOf as M, normalizePath as N, isAtomicTempPath as O, parseSkillMarkdown as P, renderAvailableSkillsXml as R, checkSkillRules as S, detectSkillArchiveShapeFromFs as T, signaturePayloadBytes as U, resolveInsideRoot as V, stripArchiveRoot as W, xmlRenderer as Y, assertSafePathSegment as _, MemoryFS as a, buildManifest as b, SKILL_MANIFEST_FILE as c, SKILL_PACK_FILE as d, SKILL_SIGNATURE_FILE as f, assertRemoteUrlAllowed as g, WebSkillError as h, MANIFEST_EXCLUDED_FILES as i, keyIdOf as j, isValidSkillName as k, SKILL_NAME_MAX_LENGTH as l, SkillReader as m, DEFAULT_ARCHIVE_LIMITS as n, SIGNATURE_SCHEMA_VERSION as o, SkillDiscovery as p, verifyManifest as q, FsTrustedKeyStore as r, SKILLS_LOCKFILE as s, ATOMIC_TMP_SUFFIX_PATTERN as t, SKILL_NAME_PATTERN as u, atomicWriteText as v, detectSkillArchiveShape as w, checkDependencyCycles as x, buildCatalog as y, renderCatalogJson as z };
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 };
@@ -1,5 +1,5 @@
1
- import { R as FileSystemProvider, U as Page, W as PageQuery, dt as SkillManifest, it as SkillCatalogEntry } from "./types-DLctJep_-B5G4uk2u.js";
2
- //#region ../governance/dist/skillVersionStore-D-qHk9ZE.d.ts
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;
@@ -0,0 +1,145 @@
1
+ import { t as WebSkillError } from "./errors-BDZNpC13.js";
2
+
3
+ //#region ../runtime/src/interaction/surface.ts
4
+ const MAX_SURFACE_BYTES = 256 * 1024;
5
+ const MAX_ACTIONS = 32;
6
+ const MAX_NODES = 2e3;
7
+ const MAX_NODE_DEPTH = 32;
8
+ const MAX_JSON_DEPTH = 16;
9
+ const MAX_PATCH_OPERATIONS = 128;
10
+ const actionIntents = /* @__PURE__ */ new Set([
11
+ "submit",
12
+ "cancel",
13
+ "select",
14
+ "download",
15
+ "refresh"
16
+ ]);
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19
+ }
20
+ function reject(message) {
21
+ throw new WebSkillError("VALIDATION_FAILED", message);
22
+ }
23
+ function requireString(value, name) {
24
+ if (typeof value !== "string" || value.trim() === "") reject(`${name} must be a non-empty string`);
25
+ }
26
+ function requireNonNegativeInteger(value, name) {
27
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) reject(`${name} must be a non-negative integer`);
28
+ }
29
+ function isJsonValue(value, depth = 0) {
30
+ if (depth > MAX_JSON_DEPTH || value === void 0 || typeof value === "function" || typeof value === "symbol") return false;
31
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
32
+ if (typeof value === "number") return Number.isFinite(value);
33
+ if (Array.isArray(value)) return value.every((item) => isJsonValue(item, depth + 1));
34
+ if (!isRecord(value)) return false;
35
+ return Object.keys(value).every((key) => key !== "__proto__" && key !== "constructor" && isJsonValue(value[key], depth + 1));
36
+ }
37
+ /**
38
+ * Runtime 只做结构校验;组件名白名单与 props schema 属于 catalog 语义,
39
+ * 留在 `@webskill/ui`——runtime 不得为了校验反向依赖 ui。
40
+ */
41
+ function assertNode(value, path, depth, counter) {
42
+ if (depth > MAX_NODE_DEPTH) reject(`A UI spec tree must not nest deeper than ${MAX_NODE_DEPTH} levels`);
43
+ if (++counter.nodes > MAX_NODES) reject(`A UI spec tree must contain at most ${MAX_NODES} nodes`);
44
+ if (!isRecord(value)) reject(`${path} must be an object`);
45
+ requireString(value["component"], `${path}.component`);
46
+ if (value["id"] !== void 0) requireString(value["id"], `${path}.id`);
47
+ if (value["props"] !== void 0 && (!isRecord(value["props"]) || !isJsonValue(value["props"]))) reject(`${path}.props must be a JSON object`);
48
+ const children = value["children"];
49
+ if (children === void 0) return;
50
+ if (!Array.isArray(children)) reject(`${path}.children must be an array`);
51
+ children.forEach((child, index) => assertNode(child, `${path}.children[${index}]`, depth + 1, counter));
52
+ }
53
+ function assertActions(value) {
54
+ if (value === void 0) return;
55
+ if (!Array.isArray(value) || value.length > MAX_ACTIONS) reject(`Surface actions must contain at most ${MAX_ACTIONS} items`);
56
+ for (const action of value) {
57
+ if (!isRecord(action)) reject("A surface action must be an object");
58
+ requireString(action["id"], "Surface action ID");
59
+ const intent = action["intent"];
60
+ if (typeof intent !== "string" || !actionIntents.has(intent)) reject("Surface action intent is invalid");
61
+ if (action["awaitResponse"] !== void 0 && typeof action["awaitResponse"] !== "boolean") reject("Surface action awaitResponse must be a boolean");
62
+ if (action["nonce"] !== void 0 && (typeof action["nonce"] !== "string" || action["nonce"] === "")) reject("Surface action nonce must be a non-empty string");
63
+ }
64
+ }
65
+ /** Validates the allowlisted, data-only node tree accepted by a UI surface renderer. @experimental */
66
+ function validateUiSpecNode(value) {
67
+ assertNode(value, "root", 0, { nodes: 0 });
68
+ if (!isJsonValue(value)) reject("A UI spec tree must contain JSON data only");
69
+ if (JSON.stringify(value).length > MAX_SURFACE_BYTES) reject(`A UI spec tree exceeds the ${MAX_SURFACE_BYTES}-byte limit`);
70
+ return structuredClone(value);
71
+ }
72
+ function assertPatch(value) {
73
+ if (!isRecord(value)) reject("A surface patch operation must be an object");
74
+ if (value["op"] !== "replace" && value["op"] !== "merge" && value["op"] !== "append") reject("Surface patch operation is invalid");
75
+ requireString(value["path"], "Surface patch path");
76
+ if (!value["path"].startsWith("/")) reject("Surface patch path must be a JSON pointer");
77
+ if (!isJsonValue(value["value"])) reject("Surface patch value must be JSON data");
78
+ }
79
+ /** Validates an individual event in the framework-neutral surface stream. @experimental */
80
+ function validateUiSpecEvent(value) {
81
+ if (!isRecord(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
82
+ if (value["runId"] !== void 0) requireString(value["runId"], "Surface event run ID");
83
+ switch (value["type"]) {
84
+ case "open":
85
+ requireString(value["id"], "UI surface ID");
86
+ assertActions(value["actions"]);
87
+ return {
88
+ type: "open",
89
+ ...value["runId"] ? { runId: value["runId"] } : {},
90
+ id: value["id"],
91
+ node: validateUiSpecNode(value["node"]),
92
+ ...value["actions"] ? { actions: structuredClone(value["actions"]) } : {}
93
+ };
94
+ case "patch":
95
+ requireString(value["id"], "Surface patch ID");
96
+ requireNonNegativeInteger(value["revision"], "Surface patch revision");
97
+ if (!Array.isArray(value["operations"]) || value["operations"].length === 0) reject("A surface patch requires operations");
98
+ if (value["operations"].length > MAX_PATCH_OPERATIONS) reject(`A surface patch must contain at most ${MAX_PATCH_OPERATIONS} operations`);
99
+ for (const operation of value["operations"]) assertPatch(operation);
100
+ return {
101
+ type: "patch",
102
+ ...value["runId"] ? { runId: value["runId"] } : {},
103
+ id: value["id"],
104
+ revision: value["revision"],
105
+ operations: structuredClone(value["operations"])
106
+ };
107
+ case "complete":
108
+ requireString(value["id"], "Surface completion ID");
109
+ requireNonNegativeInteger(value["revision"], "Surface completion revision");
110
+ return {
111
+ type: "complete",
112
+ ...value["runId"] ? { runId: value["runId"] } : {},
113
+ id: value["id"],
114
+ revision: value["revision"]
115
+ };
116
+ case "error":
117
+ requireString(value["id"], "Surface error ID");
118
+ requireString(value["code"], "Surface error code");
119
+ requireString(value["message"], "Surface error message");
120
+ return {
121
+ type: "error",
122
+ ...value["runId"] ? { runId: value["runId"] } : {},
123
+ id: value["id"],
124
+ code: value["code"],
125
+ message: value["message"]
126
+ };
127
+ case "cancel":
128
+ requireString(value["id"], "Surface cancellation ID");
129
+ return {
130
+ type: "cancel",
131
+ ...value["runId"] ? { runId: value["runId"] } : {},
132
+ id: value["id"]
133
+ };
134
+ default: return reject("UI surface event type is invalid");
135
+ }
136
+ }
137
+ /** Extracts validated surface stream events from structured tool output. @experimental */
138
+ function extractUiSpecEvents(data) {
139
+ if (!isRecord(data) || data["$surface"] === void 0) return [];
140
+ const raw = data["$surface"];
141
+ return (Array.isArray(raw) ? raw : [raw]).map((event) => validateUiSpecEvent(event));
142
+ }
143
+
144
+ //#endregion
145
+ export { validateUiSpecEvent as n, validateUiSpecNode as r, extractUiSpecEvents as t };
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as UiBridge, c as InteractionResponse, l as LlmClient, m as LlmStreamEvent, n as ArtifactStore, p as LlmResponse, s as InteractionRequest, t as Artifact, u as LlmCompleteInput, v as MemoryStore } from "./types-DLctJep_-B5G4uk2u.js";
1
+ import { S as UiBridge, c as InteractionResponse, l as LlmClient, m as LlmStreamEvent, n as ArtifactStore, p as LlmResponse, s as InteractionRequest, t as Artifact, u as LlmCompleteInput, v as MemoryStore } from "./types-C3V6lAeO-BIf7UB2e.js";
2
2
  import { a as loadGoogleConfigFromEnv, i as loadAnthropicConfigFromEnv, n as LlmEnvConfig, o as loadLlmConfigFromEnv, r as ProviderEnvConfig } from "./env-AK3cSMEA-Dli6QU5E.js";
3
3
  //#region ../runtime/dist/testing.d.ts
4
4
  //#region src/llm/mockLlmClient.d.ts