@webskill/sdk 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as FileStat, I as SkillInstallSource, K as VerifyResult, W as SkillsLockfile, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits, z as SkillManifest } from "./types-CKm5G_eQ-BqyXnvoR.js";
2
- import { Et as parseBridgeRequest, N as NetworkPolicy, Q as ScriptExecutor, Z as ScriptExecutionContext, d as BridgeCapabilities, it as ToolResult, m as BridgeResponse, nt as ToolDefinition, p as BridgeRequest, pt as bridgeError, u as ApprovalScope, ut as WebSkillApi, v as ExternalSkillProvider, y as ExternalToolSource } from "./index-DZShzhon.js";
1
+ import { B as SkillManifest, C as FileStat, G as SkillsLockfile, L as SkillInstallSource, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, q as VerifyResult, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits } from "./types-CKm5G_eQ-krKWW8WV.js";
2
+ import { Et as parseBridgeRequest, N as NetworkPolicy, Q as ScriptExecutor, Z as ScriptExecutionContext, d as BridgeCapabilities, it as ToolResult, m as BridgeResponse, nt as ToolDefinition, p as BridgeRequest, pt as bridgeError, u as ApprovalScope, ut as WebSkillApi, v as ExternalSkillProvider, y as ExternalToolSource } from "./index-DJOha4b6.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 { A as unzipWithLimits, C as parseSkillMarkdown, M as verifyManifest, T as readResponseWithLimit, b as isValidSkillName, c as SkillDiscovery, f as atomicWriteText, i as SKILL_MANIFEST_FILE, j as validateSkills, k as resolveInsideRoot, m as buildManifest, p as buildCatalog, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, w as parseSkillPackManifest, y as exportSkills } from "./dist-D7MsoMPx.js";
2
- import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi, F as normalizeToolError, I as parseBridgeRequest, M as networkUrlHost, P as normalizeToolContent, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-CV64gN62.js";
3
- import { n as MockLlmClient } from "./testing-BN18eqbD.js";
1
+ import { C as messageOf, D as readResponseWithLimit, E as parseSkillPackManifest, M as unzipWithLimits, N as validateSkills, P as verifyManifest, T as parseSkillMarkdown, b as exportSkills, c as SkillDiscovery, d as assertRemoteUrlAllowed, h as buildManifest, i as SKILL_MANIFEST_FILE, j as resolveInsideRoot, m as buildCatalog, p as atomicWriteText, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, x as isValidSkillName } from "./dist-BQzncxXg.js";
2
+ import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi, F as normalizeToolError, I as parseBridgeRequest, M as networkUrlHost, P as normalizeToolContent, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-BXpDDZpR.js";
3
+ import { n as MockLlmClient } from "./testing-BUoXvm1u.js";
4
4
 
5
5
  //#region ../browser/dist/index.js
6
6
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -462,8 +462,7 @@ async function extractZipWeb(fs, data, destRoot, limits) {
462
462
  else await fs.writeBinary(target, content);
463
463
  }
464
464
  }
465
- const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
466
- const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || e.code === "TOOL_UNSUPPORTED") ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf$1(e)}`, e);
465
+ const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || e.code === "TOOL_UNSUPPORTED") ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf(e)}`, e);
467
466
  const lockfilePath = (root) => `${root}/${SKILLS_LOCKFILE}`;
468
467
  /**
469
468
  * D5:浏览器技能安装(http(s) zip / ArrayBuffer;tar/git/npm → TOOL_UNSUPPORTED)。
@@ -494,9 +493,15 @@ var BrowserSkillManager = class {
494
493
  if (source.type === "http") {
495
494
  let res;
496
495
  try {
497
- res = await (this.#fetchImpl ?? fetch)(source.url);
496
+ const absolute = new URL(source.url, typeof location !== "undefined" ? location.href : void 0).href;
497
+ const url = assertRemoteUrlAllowed(absolute, {
498
+ allowHttp: source.allowHttp ?? false,
499
+ allowPrivateHosts: source.allowPrivateHosts ?? false
500
+ });
501
+ res = await (this.#fetchImpl ?? fetch)(url.href);
498
502
  } catch (e) {
499
- throw new WebSkillError("INSTALL_FAILED", `Download failed: ${messageOf$1(e)}`, e);
503
+ if (e instanceof WebSkillError) throw e;
504
+ throw new WebSkillError("INSTALL_FAILED", `Download failed: ${messageOf(e)}`, e);
500
505
  }
501
506
  if (!res.ok) throw new WebSkillError("INSTALL_FAILED", `Download failed with HTTP ${res.status}`);
502
507
  data = await readResponseWithLimit(res, this.#archiveLimits);
@@ -526,7 +531,7 @@ var BrowserSkillManager = class {
526
531
  name = metadata.name;
527
532
  version = typeof metadata["version"] === "string" ? metadata["version"] : void 0;
528
533
  } catch (e) {
529
- throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md: ${messageOf$1(e)}`, e);
534
+ throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md: ${messageOf(e)}`, e);
530
535
  }
531
536
  if (!isValidSkillName(name)) throw new WebSkillError("INSTALL_FAILED", `Invalid skill name in SKILL.md (path traversal rejected): ${JSON.stringify(name)}`);
532
537
  const finalRoot = `${stagingRoot}/final`;
@@ -592,7 +597,7 @@ var BrowserSkillManager = class {
592
597
  name = metadata.name;
593
598
  versions.set(name, typeof metadata["version"] === "string" ? metadata["version"] : void 0);
594
599
  } catch (e) {
595
- throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf$1(e)}`, e);
600
+ throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf(e)}`, e);
596
601
  }
597
602
  if (name !== entry.name) throw new WebSkillError("INSTALL_FAILED", `Skill pack entry "${entry.name}" does not match the SKILL.md name "${name}"`);
598
603
  await copyDir(fs, skillDir, `${finalRoot}/${name}`);
@@ -674,7 +679,7 @@ var BrowserSkillManager = class {
674
679
  await this.#removeLockEntry(name);
675
680
  this.#onChanged?.();
676
681
  } catch (e) {
677
- throw new WebSkillError("UNINSTALL_FAILED", `Failed to uninstall "${name}": ${messageOf$1(e)}`, e);
682
+ throw new WebSkillError("UNINSTALL_FAILED", `Failed to uninstall "${name}": ${messageOf(e)}`, e);
678
683
  }
679
684
  }
680
685
  async verifyIntegrity(name) {
@@ -694,7 +699,7 @@ var BrowserSkillManager = class {
694
699
  try {
695
700
  return JSON.parse(await this.#fs.readText(path));
696
701
  } catch (e) {
697
- throw new WebSkillError("INTEGRITY_FAILED", `Skills lockfile at ${path} is corrupted: ${messageOf$1(e)}`, e);
702
+ throw new WebSkillError("INTEGRITY_FAILED", `Skills lockfile at ${path} is corrupted: ${messageOf(e)}`, e);
698
703
  }
699
704
  }
700
705
  async #createManifest(skillRoot, input) {
@@ -726,7 +731,7 @@ var BrowserSkillManager = class {
726
731
  try {
727
732
  return JSON.parse(await this.#fs.readText(path));
728
733
  } catch (e) {
729
- throw new WebSkillError("INTEGRITY_FAILED", `Skill manifest at ${path} is corrupted: ${messageOf$1(e)}`, e);
734
+ throw new WebSkillError("INTEGRITY_FAILED", `Skill manifest at ${path} is corrupted: ${messageOf(e)}`, e);
730
735
  }
731
736
  }
732
737
  async #upsertLockEntry(name, entry) {
@@ -864,7 +869,6 @@ const resolveWorkerFactory = (mode) => {
864
869
  return blobWorkerFactory;
865
870
  };
866
871
  const baseName = (p) => p.split("/").pop() ?? p;
867
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
868
872
  /**
869
873
  * Web Worker 脚本沙箱执行器:loadDefinition 与 execute 都在 Worker 内完成,
870
874
  * 主线程从不 import 技能脚本;每次执行独立 Worker;超时 terminate 强杀。
@@ -16,6 +16,10 @@ var WebSkillError = class extends Error {
16
16
  this.details = details;
17
17
  }
18
18
  };
19
+ /** unknown 异常取消息文本(全仓单一来源,勿再本地重复定义) */
20
+ function messageOf(e) {
21
+ return e instanceof Error ? e.message : String(e);
22
+ }
19
23
  /** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
20
24
  const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
21
25
  /** managed root 下的锁定文件名(不参与 files 列表与 digest) */
@@ -200,7 +204,12 @@ var MemoryFS = class {
200
204
  this.#entries.delete(src);
201
205
  }
202
206
  };
203
- /** 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景) */
207
+ /**
208
+ * 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景)。
209
+ * 原子性强弱取决于 provider 的 rename 实现:NodeFS/MemoryFS 为真 rename;
210
+ * OPFS 无原生 move,rename 是 copy+delete(非原子,崩溃可能残留双份),
211
+ * 对崩溃一致性要求极高的场景在浏览器侧需知悉此前提。
212
+ */
204
213
  async function atomicWriteText(fs, path, content) {
205
214
  const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
206
215
  await fs.writeText(tmp, content);
@@ -243,6 +252,65 @@ function resolveInsideRoot(root, relativePath) {
243
252
  const joined = normalizedRoot === "" ? clean.join("/") : `${normalizedRoot}/${clean.join("/")}`;
244
253
  return root.startsWith("/") || root.startsWith("\\") ? `/${joined}` : joined;
245
254
  }
255
+ const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "localhost."]);
256
+ function ipv4Octets(host) {
257
+ const parts = host.split(".");
258
+ if (parts.length !== 4) return void 0;
259
+ const octets = parts.map((p) => /^\d{1,3}$/.test(p) ? Number(p) : NaN);
260
+ return octets.every((n) => n >= 0 && n <= 255) ? octets : void 0;
261
+ }
262
+ function isPrivateIpv4(octets) {
263
+ const [a, b] = octets;
264
+ return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
265
+ }
266
+ function isPrivateIpv6(host) {
267
+ const h = host.toLowerCase();
268
+ if (h === "::1" || h === "::") return true;
269
+ const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
270
+ if (mapped) {
271
+ const octets = ipv4Octets(mapped[1]);
272
+ return octets !== void 0 && isPrivateIpv4(octets);
273
+ }
274
+ const mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
275
+ if (mappedHex) {
276
+ const hi = parseInt(mappedHex[1], 16);
277
+ const lo = parseInt(mappedHex[2], 16);
278
+ return isPrivateIpv4([
279
+ hi >> 8,
280
+ hi & 255,
281
+ lo >> 8,
282
+ lo & 255
283
+ ]);
284
+ }
285
+ const first = h.split(":")[0];
286
+ if (/^[0-9a-f]{1,4}$/.test(first)) {
287
+ const n = parseInt(first, 16);
288
+ if ((n & 65472) === 65152) return true;
289
+ if ((n & 65024) === 64512) return true;
290
+ }
291
+ return false;
292
+ }
293
+ function isPrivateHost(hostname) {
294
+ const host = hostname.toLowerCase();
295
+ if (LOOPBACK_HOSTNAMES.has(host) || host.endsWith(".localhost")) return true;
296
+ const v4 = ipv4Octets(host);
297
+ if (v4) return isPrivateIpv4(v4);
298
+ if (host.startsWith("[") && host.endsWith("]")) return isPrivateIpv6(host.slice(1, -1));
299
+ if (host.includes(":")) return isPrivateIpv6(host);
300
+ return false;
301
+ }
302
+ /**
303
+ * 校验远程 URL 是否允许连接;违规抛 NETWORK_BLOCKED。
304
+ * rawUrl 非合法 URL 时抛 TypeError(由调用方按自身错误码包装)。
305
+ */
306
+ function assertRemoteUrlAllowed(rawUrl, policy = {}) {
307
+ const url = new URL(rawUrl);
308
+ if (url.protocol === "http:") {
309
+ if (!policy.allowHttp) throw new WebSkillError("NETWORK_BLOCKED", `Insecure URL scheme "http:" is not allowed (opt in via allowHttp): ${url.href}`);
310
+ } else if (url.protocol !== "https:") throw new WebSkillError("NETWORK_BLOCKED", `URL scheme "${url.protocol}" is not allowed (https only): ${url.href}`);
311
+ if (!policy.allowPrivateHosts && isPrivateHost(url.hostname)) throw new WebSkillError("NETWORK_BLOCKED", `Refusing to connect to a private/loopback/link-local address: ${url.hostname}`);
312
+ return url;
313
+ }
246
314
  const FRONTMATTER_RE = /^---[^\S\r\n]*\r?\n([\s\S]*?)\r?\n---[^\S\r\n]*(?:\r?\n|$)([\s\S]*)$/;
247
315
  const invalid = (message, details) => {
248
316
  throw new WebSkillError("SKILL_INVALID_METADATA", message, details);
@@ -383,7 +451,6 @@ function checkDependencyCycles(adjacency) {
383
451
  */
384
452
  /** 包级清单文件名(位于 zip 根,不参与各技能 digest) */
385
453
  const SKILL_PACK_FILE = "webskill.skill-pack.json";
386
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
387
454
  const baseName$1 = (p) => p.split("/").pop() ?? p;
388
455
  async function listFilesRecursive(fs, root, prefix = "") {
389
456
  const out = [];
@@ -769,4 +836,4 @@ const xmlRenderer = {
769
836
  };
770
837
 
771
838
  //#endregion
772
- export { unzipWithLimits as A, parseSkillMarkdown as C, renderCatalogJson as D, renderAvailableSkillsXml as E, verifyManifest as M, xmlRenderer as N, resolveArchiveLimits as O, normalizePath as S, readResponseWithLimit as T, computeDigest as _, SKILL_NAME_MAX_LENGTH as a, isValidSkillName as b, SkillDiscovery as c, assertSafePathSegment as d, atomicWriteText as f, checkSkillRules as g, checkDependencyCycles as h, SKILL_MANIFEST_FILE as i, validateSkills as j, resolveInsideRoot as k, SkillReader as l, buildManifest as m, MemoryFS as n, SKILL_NAME_PATTERN as o, buildCatalog as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, escapeXml as v, parseSkillPackManifest as w, jsonRenderer as x, exportSkills as y };
839
+ export { resolveArchiveLimits as A, messageOf as C, readResponseWithLimit as D, parseSkillPackManifest as E, xmlRenderer as F, unzipWithLimits as M, validateSkills as N, renderAvailableSkillsXml as O, verifyManifest as P, jsonRenderer as S, parseSkillMarkdown as T, checkSkillRules as _, SKILL_NAME_MAX_LENGTH as a, exportSkills as b, SkillDiscovery as c, assertRemoteUrlAllowed as d, assertSafePathSegment as f, checkDependencyCycles as g, buildManifest as h, SKILL_MANIFEST_FILE as i, resolveInsideRoot as j, renderCatalogJson as k, SkillReader as l, buildCatalog as m, MemoryFS as n, SKILL_NAME_PATTERN as o, atomicWriteText as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, computeDigest as v, normalizePath as w, isValidSkillName as x, escapeXml as y };