@webskill/sdk 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.d.ts 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-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";
1
+ import { C as FileStat, I as SkillInstallSource, K as VerifyResult, W as SkillsLockfile, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
2
+ import { N as NetworkPolicy, Q as ScriptExecutor, Z as ScriptExecutionContext, d as BridgeCapabilities, it as ToolResult, m as BridgeResponse, nt as ToolDefinition, p as BridgeRequest, pt as bridgeError, u as ApprovalScope, ut as WebSkillApi, v as ExternalSkillProvider, wt as parseBridgeRequest, y as ExternalToolSource } from "./index-CySxIvRz.js";
3
3
  //#region ../browser/dist/index.d.ts
4
4
  //#region src/fs/featureDetection.d.ts
5
5
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -24,6 +24,7 @@ declare class OpfsProvider implements FileSystemProvider {
24
24
  remove(p: string, options?: {
25
25
  recursive?: boolean;
26
26
  }): Promise<void>;
27
+ rename(from: string, to: string): Promise<void>;
27
28
  }
28
29
  //#endregion
29
30
  //#region src/executor/workerBootstrap.d.ts
@@ -83,6 +84,10 @@ declare class BrowserSkillManager {
83
84
  fs: FileSystemProvider;
84
85
  managedRoot: string;
85
86
  fetchImpl?: typeof fetch;
87
+ /** 归档体积三重上限(缺省 DEFAULT_ARCHIVE_LIMITS) */
88
+ archiveLimits?: ArchiveLimits;
89
+ /** install/uninstall 成功后的变更回调(宿主接线缓存失效,如 WebSkillRuntime.invalidate) */
90
+ onChanged?: () => void;
86
91
  });
87
92
  install(source: SkillInstallSource, options?: {
88
93
  expectedSha256?: string;
@@ -99,10 +104,10 @@ declare class BrowserSkillManager {
99
104
  //#endregion
100
105
  //#region src/skillManagement/zipExtract.d.ts
101
106
  /**
102
- * zip 解包(fflate,浏览器无依赖)。
107
+ * zip 解包(fflate 流式,单条目/总解压体积上限,浏览器无依赖)。
103
108
  * 每个条目路径过 resolveInsideRoot:拒绝 `..` 段、绝对路径、反斜杠穿越(与 node 规则一致)。
104
109
  */
105
- declare function extractZipWeb(fs: FileSystemProvider, data: Uint8Array, destRoot: string): Promise<void>;
110
+ declare function extractZipWeb(fs: FileSystemProvider, data: Uint8Array, destRoot: string, limits?: ArchiveLimits): Promise<void>;
106
111
  //#endregion
107
112
  //#region src/skillManagement/checksumWeb.d.ts
108
113
  /** WebCrypto sha256(浏览器/Worker 通用;Node 20+ 全局 crypto 同样可用) */
package/dist/browser.js CHANGED
@@ -1,7 +1,6 @@
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
- import { unzipSync } from "fflate";
1
+ import { A as unzipWithLimits, C as parseSkillMarkdown, M as verifyManifest, T as readResponseWithLimit, b as isValidSkillName, c as SkillDiscovery, f as atomicWriteText, i as SKILL_MANIFEST_FILE, j as validateSkills, k as resolveInsideRoot, m as buildManifest, p as buildCatalog, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, w as parseSkillPackManifest, y as exportSkills } from "./dist-D0saNPi_.js";
2
+ import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi, M as networkUrlHost, N as normalizeToolContent, P as parseBridgeRequest, c as FsArtifactStore, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, l as FsMemoryStore, m as OpenAiCompatibleClient, o as CapabilityApproval, u as FsRunSnapshotStore, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-BS5OpedX.js";
3
+ import { n as MockLlmClient } from "./testing-B4pq6JYa.js";
5
4
 
6
5
  //#region ../browser/dist/index.js
7
6
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -133,6 +132,11 @@ var OpfsProvider = class {
133
132
  await (await this.#walkDir(`/${segments.join("/")}`, false)).removeEntry(name, { recursive: options?.recursive ?? false });
134
133
  });
135
134
  }
135
+ async rename(from, to) {
136
+ if ((await this.stat(from)).type === "directory") throw new WebSkillError("FS_PERMISSION_DENIED", `rename of directories is not supported on OPFS: ${from}`);
137
+ await this.writeBinary(to, await this.readBinary(from));
138
+ await this.remove(from);
139
+ }
136
140
  };
137
141
  /**
138
142
  * Worker 引导源码(字符串 → Blob URL → new Worker(url, {type:'module'}))。
@@ -421,17 +425,11 @@ async function removeDirQuiet(fs, dir) {
421
425
  } catch {}
422
426
  }
423
427
  /**
424
- * zip 解包(fflate,浏览器无依赖)。
428
+ * zip 解包(fflate 流式,单条目/总解压体积上限,浏览器无依赖)。
425
429
  * 每个条目路径过 resolveInsideRoot:拒绝 `..` 段、绝对路径、反斜杠穿越(与 node 规则一致)。
426
430
  */
427
- async function extractZipWeb(fs, data, destRoot) {
428
- let entries;
429
- try {
430
- entries = unzipSync(data);
431
- } catch (e) {
432
- throw new WebSkillError("INSTALL_FAILED", `Failed to read zip archive: ${e instanceof Error ? e.message : String(e)}`, e);
433
- }
434
- for (const [entryPath, content] of Object.entries(entries)) {
431
+ async function extractZipWeb(fs, data, destRoot, limits) {
432
+ for (const [entryPath, content] of await unzipWithLimits(data, limits)) {
435
433
  const target = resolveInsideRoot(destRoot, entryPath);
436
434
  if (entryPath.endsWith("/")) await fs.mkdir(target);
437
435
  else await fs.writeBinary(target, content);
@@ -450,10 +448,14 @@ var BrowserSkillManager = class {
450
448
  #fs;
451
449
  #managedRoot;
452
450
  #fetchImpl;
451
+ #archiveLimits;
452
+ #onChanged;
453
453
  constructor(deps) {
454
454
  this.#fs = deps.fs;
455
455
  this.#managedRoot = deps.managedRoot.replace(/\/+$/, "");
456
456
  this.#fetchImpl = deps.fetchImpl;
457
+ this.#archiveLimits = deps.archiveLimits;
458
+ this.#onChanged = deps.onChanged;
457
459
  }
458
460
  async install(source, options) {
459
461
  const fs = this.#fs;
@@ -470,7 +472,7 @@ var BrowserSkillManager = class {
470
472
  throw new WebSkillError("INSTALL_FAILED", `Download failed: ${messageOf$1(e)}`, e);
471
473
  }
472
474
  if (!res.ok) throw new WebSkillError("INSTALL_FAILED", `Download failed with HTTP ${res.status}`);
473
- data = new Uint8Array(await res.arrayBuffer());
475
+ data = await readResponseWithLimit(res, this.#archiveLimits);
474
476
  } else data = new Uint8Array(source.data);
475
477
  if (options?.expectedSha256 !== void 0) {
476
478
  const actual = await sha256HexWeb(data);
@@ -478,7 +480,7 @@ var BrowserSkillManager = class {
478
480
  }
479
481
  const contentDir = `${stagingRoot}/content`;
480
482
  await fs.mkdir(contentDir);
481
- await extractZipWeb(fs, data, contentDir);
483
+ await extractZipWeb(fs, data, contentDir, this.#archiveLimits);
482
484
  const packFilePath = `${contentDir}/${SKILL_PACK_FILE}`;
483
485
  if (await fs.exists(packFilePath)) return (await this.#installPack({
484
486
  contentDir,
@@ -534,6 +536,7 @@ var BrowserSkillManager = class {
534
536
  throw e;
535
537
  }
536
538
  targetDir = void 0;
539
+ this.#onChanged?.();
537
540
  return manifest;
538
541
  } catch (e) {
539
542
  if (targetDir) await removeDirQuiet(fs, targetDir);
@@ -611,6 +614,7 @@ var BrowserSkillManager = class {
611
614
  installed.length = 0;
612
615
  throw e;
613
616
  }
617
+ this.#onChanged?.();
614
618
  return manifests;
615
619
  } catch (e) {
616
620
  for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
@@ -641,6 +645,7 @@ var BrowserSkillManager = class {
641
645
  try {
642
646
  await this.#fs.remove(targetDir, { recursive: true });
643
647
  await this.#removeLockEntry(name);
648
+ this.#onChanged?.();
644
649
  } catch (e) {
645
650
  throw new WebSkillError("UNINSTALL_FAILED", `Failed to uninstall "${name}": ${messageOf$1(e)}`, e);
646
651
  }
@@ -649,11 +654,9 @@ var BrowserSkillManager = class {
649
654
  if (!isValidSkillName(name)) throw new WebSkillError("INTEGRITY_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
650
655
  const manifest = await this.#readManifest(`${this.#managedRoot}/${name}`);
651
656
  const actualHashes = /* @__PURE__ */ new Map();
652
- for (const file of manifest.files) {
653
- const path = `${this.#managedRoot}/${name}/${file.path}`;
654
- if (await this.#fs.exists(path)) actualHashes.set(file.path, await sha256HexWeb(await this.#fs.readBinary(path)));
655
- }
656
- return verifyManifest(manifest, actualHashes);
657
+ const actualFiles = await listFiles(this.#fs, `${this.#managedRoot}/${name}`);
658
+ for (const rel of actualFiles) actualHashes.set(rel, await sha256HexWeb(await this.#fs.readBinary(`${this.#managedRoot}/${name}/${rel}`)));
659
+ return verifyManifest(manifest, actualHashes, actualFiles);
657
660
  }
658
661
  async listInstalled() {
659
662
  const path = lockfilePath(this.#managedRoot);
@@ -702,12 +705,12 @@ var BrowserSkillManager = class {
702
705
  async #upsertLockEntry(name, entry) {
703
706
  const lockfile = await this.listInstalled();
704
707
  lockfile.skills[name] = entry;
705
- await this.#fs.writeText(lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
708
+ await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
706
709
  }
707
710
  async #removeLockEntry(name) {
708
711
  const lockfile = await this.listInstalled();
709
712
  delete lockfile.skills[name];
710
- await this.#fs.writeText(lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
713
+ await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
711
714
  }
712
715
  };
713
716
  const defaultWorkerFactory = () => {
@@ -1,4 +1,4 @@
1
- import { l as WebSkillError } from "./dist-DvoBsChX.js";
1
+ import { u as WebSkillError } from "./dist-D0saNPi_.js";
2
2
 
3
3
  //#region ../ui/dist/index.js
4
4
  /** 提交值按请求类型归形(WebFormBridge 与框架组件库共享单一来源) */
@@ -524,6 +524,8 @@ var WebFormBridge = class {
524
524
  #doc;
525
525
  #styles;
526
526
  #progressEl;
527
+ /** 等待中的 request 取消句柄(id → 触发 cancelled 并卸载 DOM) */
528
+ #pending = /* @__PURE__ */ new Map();
527
529
  constructor(options) {
528
530
  this.#mount = options.mount;
529
531
  this.#doc = options.document ?? options.mount.ownerDocument;
@@ -535,13 +537,27 @@ var WebFormBridge = class {
535
537
  const model = interactionToFormModel(input);
536
538
  const { form, cleanup } = this.#renderForm(model);
537
539
  return new Promise((resolve) => {
540
+ const cancel = () => {
541
+ cleanup();
542
+ this.#pending.delete(input.id);
543
+ resolve({
544
+ id: input.id,
545
+ cancelled: true
546
+ });
547
+ };
548
+ this.#pending.set(input.id, cancel);
538
549
  form.addEventListener("submit", (event) => {
539
550
  event.preventDefault();
540
551
  const { values, missingRequired } = collectValues(model.controls, form);
541
552
  if (missingRequired.length > 0) {
542
- for (const name of missingRequired) form.querySelector(`[${CONTROL_TAG}="${name}"]`)?.closest(".webskill-form__control")?.classList.add("webskill-form__control--invalid");
553
+ for (const name of missingRequired) {
554
+ const wrapper = form.querySelector(`[${CONTROL_TAG}="${name}"]`)?.closest(".webskill-form__control");
555
+ wrapper?.classList.add("webskill-form__control--invalid");
556
+ wrapper?.querySelector(".webskill-form__input")?.setAttribute("aria-invalid", "true");
557
+ }
543
558
  return;
544
559
  }
560
+ this.#pending.delete(input.id);
545
561
  cleanup();
546
562
  resolve({
547
563
  id: input.id,
@@ -550,15 +566,15 @@ var WebFormBridge = class {
550
566
  });
551
567
  form.querySelector("[data-webskill-cancel]")?.addEventListener("click", (event) => {
552
568
  event.preventDefault();
553
- cleanup();
554
- resolve({
555
- id: input.id,
556
- cancelled: true
557
- });
569
+ cancel();
558
570
  });
559
571
  this.#mount.appendChild(form);
560
572
  });
561
573
  }
574
+ /** 尽力取消等待中的 request(超时后清理遗留表单) */
575
+ cancel(id) {
576
+ this.#pending.get(id)?.();
577
+ }
562
578
  async renderResult(input) {
563
579
  if (this.#styles) ensureStyles(this.#doc);
564
580
  this.#clearProgress();
@@ -622,9 +638,12 @@ var WebFormBridge = class {
622
638
  const doc = this.#doc;
623
639
  const wrapper = doc.createElement("div");
624
640
  wrapper.className = "webskill-form__control";
641
+ const controlId = `webskill-field-${control.name}`;
642
+ const errorId = `webskill-error-${control.name}`;
625
643
  const label = doc.createElement("label");
626
644
  label.className = control.required ? "webskill-form__label webskill-form__label-required" : "webskill-form__label";
627
645
  label.textContent = control.label;
646
+ label.htmlFor = controlId;
628
647
  wrapper.appendChild(label);
629
648
  if (control.description) {
630
649
  const desc = doc.createElement("div");
@@ -663,9 +682,13 @@ var WebFormBridge = class {
663
682
  input = textInput;
664
683
  }
665
684
  input.setAttribute(CONTROL_TAG, control.name);
685
+ input.id = controlId;
686
+ input.setAttribute("aria-describedby", errorId);
666
687
  wrapper.appendChild(input);
667
688
  const error = doc.createElement("div");
668
689
  error.className = "webskill-form__error";
690
+ error.id = errorId;
691
+ error.setAttribute("role", "alert");
669
692
  error.textContent = "This field is required";
670
693
  wrapper.appendChild(error);
671
694
  return wrapper;