@webskill/sdk 0.0.5 → 0.1.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/README.md CHANGED
@@ -9,22 +9,25 @@ governance.
9
9
  ## Features
10
10
 
11
11
  - **Skill protocol** — Agent Skills compatible discovery, validation, cataloging
12
- (JSON / XML), and safe per-skill file access.
12
+ (JSON / XML), declarative `dependencies` and `allowed-tools`, skill packs
13
+ (multi-skill zip export/import with integrity digests).
13
14
  - **Agent loop** — multi-turn tool calling with structured errors fed back to
14
- the LLM, guardrails, lifecycle events, memory, and human interaction
15
- (missing-parameter forms, confirmations, ask-user).
16
- - **Script sandbox** — in-process Node executor plus a `worker_threads` sandbox;
17
- Web Worker sandbox for the browser. Timeouts kill runaway scripts.
18
- - **Streaming** — OpenAI-compatible SSE streaming end to end, with incremental
19
- UI updates.
15
+ the LLM, guardrails, lifecycle events, memory, human interaction
16
+ (missing-parameter forms, confirmations, authorization prompts), and
17
+ resumable interrupted runs.
18
+ - **Script sandbox** — `worker_threads` sandbox for Node, Web Worker sandbox for
19
+ the browser. Deny-by-default network policy, per-capability forced approval,
20
+ timeouts kill runaway scripts.
21
+ - **`navigator.webskill`** — a browser facade (`discover` / `read` / `validate` /
22
+ `run` / `install` / `uninstall`) assembled explicitly in one call.
20
23
  - **MCP** — call page-provided tools and consume page-declared dynamic skills
21
24
  over standard MCP transports (MessageChannel).
22
25
  - **Governance** — LLM-generated skill candidates with mandatory review,
23
26
  approval workflows, audit log, versioning/rollback, quarantine, evaluation,
24
27
  and scoring.
25
- - **UI** — framework-agnostic web forms, result rendering, generative UI
26
- adapters (Vercel AI SDK, OpenUI, A2UI), plus React and Vue component
27
- primitives.
28
+ - **UI** — framework-agnostic forms, result rendering (markdown, tables, SVG
29
+ charts), generative UI adapters (Vercel AI SDK, OpenUI, A2UI), plus React and
30
+ Vue component primitives.
28
31
 
29
32
  ## Installation
30
33
 
@@ -35,53 +38,184 @@ npm install @webskill/sdk
35
38
  React/Vue integrations require their respective peer dependencies
36
39
  (`react` + `react-dom`, or `vue`), which are optional.
37
40
 
41
+ ## Requirements
42
+
43
+ - Node.js >= 22.18 (native type stripping for `.ts` skill scripts)
44
+ - Modern browsers for the browser host (OPFS, Web Workers, ES modules)
45
+
38
46
  ## Quick start (Node)
39
47
 
40
- ```ts
41
- import { OpenAiCompatibleClient, WebSkillRuntime } from '@webskill/sdk';
42
- import { NodeFS, NodeScriptExecutor, FileArtifactStore } from '@webskill/sdk/node';
43
-
44
- const runtime = new WebSkillRuntime({
45
- fs: new NodeFS(),
46
- roots: ['./skills'],
47
- llm: new OpenAiCompatibleClient({
48
- baseUrl: 'https://your-llm-endpoint/v1',
49
- apiKey: process.env.LLM_API_KEY!,
50
- model: 'your-model'
51
- }),
52
- executor: new NodeScriptExecutor(new NodeFS()),
53
- artifactStore: new FileArtifactStore({ root: './.webskill/artifacts' })
54
- });
48
+ Runs as-is with the bundled mock LLM — see
49
+ [`examples/quickstart-node`](../examples/quickstart-node) for the full project.
50
+
51
+ ```js
52
+ import { WebSkillRuntime } from '@webskill/sdk';
53
+ import { NodeFS, SandboxedScriptExecutor } from '@webskill/sdk/node';
54
+ import { MockLlmClient } from '@webskill/sdk/testing';
55
+
56
+ const fs = new NodeFS();
57
+ const llm = new MockLlmClient([
58
+ { toolCalls: [{ id: 'c1', name: 'read_skill_file', arguments: { skillName: 'greeter' } }] },
59
+ { toolCalls: [{ id: 'c2', name: 'greeter__greet', arguments: { text: 'world' } }] },
60
+ { content: 'Greeted the world.' }
61
+ ]);
55
62
 
56
- // Discover skills and render the catalog
57
- const { entries, issues } = await runtime.discover();
58
- console.log(
59
- entries.map((e) => e.name),
60
- issues
61
- );
63
+ const runtime = new WebSkillRuntime({ fs, roots: ['./skills'], llm, executor: new SandboxedScriptExecutor(fs) });
62
64
 
63
- // Run the agent loop
64
- const { output, run } = await runtime.run('Use the calculator skill to compute 2+3.');
65
+ const catalog = await runtime.discover();
66
+ console.log(catalog.entries.map((e) => e.name));
67
+
68
+ const { output, run } = await runtime.run('Greet the world.');
65
69
  console.log(output, run.status, run.trace.length);
66
70
  ```
67
71
 
72
+ For a real LLM, replace the mock with `OpenAiCompatibleClient`:
73
+
74
+ ```js
75
+ import { OpenAiCompatibleClient } from '@webskill/sdk';
76
+
77
+ const llm = new OpenAiCompatibleClient({
78
+ baseUrl: 'https://your-llm-endpoint/v1',
79
+ apiKey: process.env.LLM_API_KEY,
80
+ model: 'your-model'
81
+ });
82
+ ```
83
+
68
84
  ## Subpath exports
69
85
 
70
- | Subpath | Contents |
71
- | -------------------------- | -------------------------------------------------------------------------------------------------------------------- |
72
- | `@webskill/sdk` | Core protocol + runtime engine (discovery, agent loop, LLM clients, routing, interaction, memory, artifacts, trace) |
73
- | `@webskill/sdk/node` | Node host: NodeFS, script executors (in-process + sandboxed), file stores, skill manager, CLI UI bridge, env helpers |
74
- | `@webskill/sdk/browser` | Browser host: OPFS provider, Web Worker script sandbox, worker runtime host/client |
75
- | `@webskill/sdk/mcp` | MCP transports, endpoint registry, tool resolver, temporary skills, WebMCP adapter, runtime plugin |
76
- | `@webskill/sdk/ui` | Framework-agnostic forms, result rendering, mini markdown, Vercel/OpenUI/A2UI adapters, A2UI Lit runtime |
77
- | `@webskill/sdk/ui-react` | React bridge state + InteractionForm/ResultBlocks/StreamingText components |
78
- | `@webskill/sdk/ui-vue` | Vue bridge state + equivalent components |
79
- | `@webskill/sdk/governance` | Candidates, approval, audit, versioning, state policy, evaluation, scoring, documents |
86
+ ### `@webskill/sdk` (main entry)
80
87
 
81
- ## Requirements
88
+ Core protocol + runtime engine: discovery, catalog, validation, agent loop, LLM
89
+ clients, routing, interaction types, memory, artifacts, tracing, network policy,
90
+ capability approval, and the environment-agnostic `createWebSkillApi` facade.
82
91
 
83
- - Node.js >= 22.18 (native type stripping for `.ts` skill scripts)
84
- - Modern browsers for the browser host (OPFS, Web Workers, ES modules)
92
+ ```js
93
+ import { SkillDiscovery, validateSkills, createWebSkillApi } from '@webskill/sdk';
94
+
95
+ const api = createWebSkillApi({ fs, roots: ['./skills'], llm, skillManager });
96
+ const report = await api.validate('./skills');
97
+ const run = await api.run('Summarize the references of skill greeter.');
98
+ ```
99
+
100
+ ### `@webskill/sdk/node`
101
+
102
+ Node host: `NodeFS`, script executors (in-process + `worker_threads` sandbox),
103
+ file-backed stores, `SkillManager` (install/export incl. multi-skill packs),
104
+ CLI UI bridge, schema inference.
105
+
106
+ ```js
107
+ import { NodeFS, SkillManager } from '@webskill/sdk/node';
108
+
109
+ const manager = new SkillManager({ managedRoot: './.webskill/skills' });
110
+ await manager.install({ type: 'http', url: 'https://example.com/greeter.zip' });
111
+ await manager.exportPack(['greeter', 'calculator'], { outPath: './pack.zip' });
112
+ console.log((await manager.verifyIntegrity('greeter')).ok);
113
+ ```
114
+
115
+ ### `@webskill/sdk/browser`
116
+
117
+ Browser host: OPFS provider, Web Worker script sandbox (deny-by-default network
118
+ policy), worker runtime host/client, `BrowserSkillManager`, and the
119
+ `navigator.webskill` assembler. See
120
+ [`examples/quickstart-browser`](../examples/quickstart-browser).
121
+
122
+ ```js
123
+ import { installWebSkillNavigator } from '@webskill/browser';
124
+
125
+ const api = installWebSkillNavigator({ roots: ['/skills'], llm });
126
+ const catalog = await navigator.webskill.discover('/skills');
127
+ const run = await navigator.webskill.run('Greet the world.');
128
+ ```
129
+
130
+ ### `@webskill/sdk/mcp`
131
+
132
+ MCP integration: MessageChannel transport, endpoint registry, tool resolver,
133
+ temporary (page-declared) skills, WebMCP adapter, runtime plugin.
134
+
135
+ ```js
136
+ import { MessageChannelTransport, serveSkillAsMcp } from '@webskill/sdk/mcp';
137
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
138
+
139
+ const server = new McpServer({ name: 'page', version: '0.1.0' });
140
+ serveSkillAsMcp(server, { name: 'greeter', description: 'Greet someone', body: '# Greeter' });
141
+ await server.connect(new MessageChannelTransport(port));
142
+ ```
143
+
144
+ ### `@webskill/sdk/ui`
145
+
146
+ Framework-agnostic UI: form models + `WebFormBridge`, result rendering
147
+ (markdown / table / mini-chart SVG), mini markdown, generative UI adapters
148
+ (Vercel AI SDK, OpenUI, A2UI).
149
+
150
+ ```js
151
+ import { WebFormBridge } from '@webskill/sdk/ui';
152
+
153
+ const bridge = new WebFormBridge({ mount: document.querySelector('#ui') });
154
+ // pass as uiBridge to the runtime: forms, confirmations, and authorization
155
+ // prompts render as real DOM, results (incl. charts) render on completion.
156
+ ```
157
+
158
+ ### `@webskill/sdk/ui-react`
159
+
160
+ React bridge state + `InteractionForm` / `ResultBlocks` / `StreamingText`
161
+ components (requires `react` + `react-dom`).
162
+
163
+ ```jsx
164
+ import { ReactBridgeState, InteractionForm, ResultBlocks } from '@webskill/sdk/ui-react';
165
+
166
+ const bridge = new ReactBridgeState();
167
+ // <InteractionForm bridge={bridge} /> <ResultBlocks bridge={bridge} />
168
+ ```
169
+
170
+ ### `@webskill/sdk/ui-vue`
171
+
172
+ Vue bridge state + equivalent components (requires `vue`).
173
+
174
+ ```js
175
+ import { VueBridgeState, InteractionForm, ResultBlocks } from '@webskill/sdk/ui-vue';
176
+
177
+ const bridge = new VueBridgeState();
178
+ // <InteractionForm :bridge="bridge" /> <ResultBlocks :bridge="bridge" />
179
+ ```
180
+
181
+ ### `@webskill/sdk/governance`
182
+
183
+ Governance: skill candidates with mandatory review, approval workflows, audit
184
+ log, versioning/rollback, quarantine, evaluation, scoring, dependency graph.
185
+
186
+ ```js
187
+ import { DependencyGraph } from '@webskill/sdk/governance';
188
+
189
+ const graph = DependencyGraph.buildFromCatalog(catalog.entries, documents);
190
+ console.log(graph.dependenciesOf('greeter'));
191
+ ```
192
+
193
+ ### `@webskill/sdk/testing`
194
+
195
+ Test facilities (moved out of the main entry in 0.1.0): `MockLlmClient`,
196
+ `MockUiBridge`, `InMemoryStore`, `MemoryArtifactStore`.
197
+
198
+ ```js
199
+ import { MockLlmClient, MockUiBridge, InMemoryStore, MemoryArtifactStore } from '@webskill/sdk/testing';
200
+
201
+ const llm = new MockLlmClient([{ content: 'deterministic answer' }]);
202
+ ```
203
+
204
+ ## Stability
205
+
206
+ This package follows semver starting at 0.1.0. Public symbols carry JSDoc
207
+ stability tags:
208
+
209
+ - **`@stable`** — covered by the semver contract: bug fixes ship in patch,
210
+ backwards-compatible capabilities in minor, breaking changes only in major
211
+ (with release notes).
212
+ - **`@experimental`** — may change in minor/patch releases. Currently:
213
+ `ExperimentalWebMcpAdapter`, `resumeRun` and the `RunSnapshot` format,
214
+ the A2UI/OpenUI adapters, and the `authorize` interaction type.
215
+
216
+ The public API surface of every subpath is pinned by checked-in `.d.ts`
217
+ snapshots (`api-snapshots/`); any intentional change requires
218
+ `pnpm api:update` and is reviewable in the diff.
85
219
 
86
220
  ## License
87
221
 
package/dist/browser.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { A as InteractionResponse, F as LlmClient, Gt as FileSystemProvider, Lt as parseBridgeRequest, O as InteractionPolicy, Ot as bridgeError, R as LlmResponse, Wt as FileStat, Y as NetworkPolicy, _t as ToolDefinition, an as SkillManifest, c as ApprovalScope, d as BridgeCapabilities, dn as VerifyResult, ht as ScriptExecutor, it as RenderResultRequest, k as InteractionRequest, ln as SkillsLockfile, m as BridgeResponse, mt as ScriptExecutionContext, nn as SkillInstallSource, p as BridgeRequest, v as ExternalSkillProvider, wt as UiBridge, y as ExternalToolSource, yt as ToolResult, z as LlmStreamEvent } from "./index-BQDc-U1x.js";
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-DCklnS0h.js";
2
+ import { K as ScriptExecutionContext, Y as ToolDefinition, Z as ToolResult, _ as ExternalToolSource, _t as parseBridgeRequest, c as ApprovalScope, d as BridgeRequest, f as BridgeResponse, g as ExternalSkillProvider, k as NetworkPolicy, l as BridgeCapabilities, ot as bridgeError, q as ScriptExecutor, rt as WebSkillApi } from "./index-S8uza3ld.js";
2
3
  //#region ../browser/dist/index.d.ts
3
4
  //#region src/fs/featureDetection.d.ts
4
5
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -74,6 +75,7 @@ declare class TsTranspiler {
74
75
  * D5:浏览器技能安装(http(s) zip / ArrayBuffer;tar/git/npm → TOOL_UNSUPPORTED)。
75
76
  * 流程与 Node 对齐:staging → 解析 name → validateSkills(失败零残留)→ managed root →
76
77
  * manifest(WebCrypto sha256)→ skills.lock.json;zip-slip 防护;manifest/lockfile 与 Node 同格式。
78
+ * @stable
77
79
  */
78
80
  declare class BrowserSkillManager {
79
81
  #private;
@@ -85,6 +87,11 @@ declare class BrowserSkillManager {
85
87
  install(source: SkillInstallSource, options?: {
86
88
  expectedSha256?: string;
87
89
  }): Promise<SkillManifest>;
90
+ /**
91
+ * 浏览器导出(单技能与包集同构:始终产出含 webskill.skill-pack.json 的 zip 字节,
92
+ * 统一安装路径;保存/下载由应用层处理)。
93
+ */
94
+ exportArchive(names: string | string[]): Promise<Uint8Array>;
88
95
  uninstall(name: string): Promise<void>;
89
96
  verifyIntegrity(name: string): Promise<VerifyResult>;
90
97
  listInstalled(): Promise<SkillsLockfile>;
@@ -114,6 +121,7 @@ type WorkerFactory = () => WorkerLike;
114
121
  /**
115
122
  * Web Worker 脚本沙箱执行器:loadDefinition 与 execute 都在 Worker 内完成,
116
123
  * 主线程从不 import 技能脚本;每次执行独立 Worker;超时 terminate 强杀。
124
+ * @stable
117
125
  */
118
126
  declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
119
127
  #private;
@@ -140,6 +148,33 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
140
148
  }): Promise<ToolResult>;
141
149
  }
142
150
  //#endregion
151
+ //#region src/navigator.d.ts
152
+ declare global {
153
+ /** 0.0.6:installWebSkillNavigator 显式装配后可用(不自动挂全局) */
154
+ interface Navigator {
155
+ webskill?: WebSkillApi;
156
+ }
157
+ }
158
+ /**
159
+ * navigator.webskill 显式装配:OPFS 默认存储 + BrowserWorkerScriptExecutor +
160
+ * BrowserSkillManager → createWebSkillApi;target 提供时挂 target.navigator
161
+ * (缺失则补空对象),否则挂真实 navigator.webskill。
162
+ * target 可注入(测试);返回值即门面本体(挂载只是副作用)。
163
+ * @stable
164
+ */
165
+ declare function installWebSkillNavigator(config: {
166
+ roots?: string[];
167
+ llm: LlmClient;
168
+ typescript?: TypeScriptSupportOptions;
169
+ /** 测试注入:替换 OPFS 默认 fs(jsdom 无 OPFS) */
170
+ fs?: FileSystemProvider;
171
+ /** 安装目标根(默认与 roots[0] 一致) */
172
+ managedRoot?: string;
173
+ target?: {
174
+ navigator?: Record<string, unknown>;
175
+ };
176
+ }): WebSkillApi;
177
+ //#endregion
143
178
  //#region src/host/protocol.d.ts
144
179
  /** 主线程 → Worker 的 LLM 配置;mockResponses 供 E2E/测试注入确定性 Mock */
145
180
  interface LlmClientConfig {
@@ -303,4 +338,4 @@ declare class WorkerUiBridge implements UiBridge {
303
338
  onTextDelta(runId: string, delta: string): Promise<void>;
304
339
  }
305
340
  //#endregion
306
- export { type BridgeCapabilities, type BridgeRequest, type BridgeResponse, BrowserSkillManager, BrowserWorkerScriptExecutor, type LlmClientConfig, type MainToWorkerMessage, OpfsProvider, type SerializedError, TsTranspiler, type TypeScriptSupportOptions, WORKER_BOOTSTRAP_SOURCE, type WorkerEvent, type WorkerFactory, type WorkerLike, type WorkerRequest, WorkerRuntimeClient, type WorkerRuntimeClientDeps, type WorkerRuntimeHostOverrides, type WorkerScopeLike, WorkerUiBridge, bridgeError, extractZipWeb, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
341
+ export { type BridgeCapabilities, type BridgeRequest, type BridgeResponse, BrowserSkillManager, BrowserWorkerScriptExecutor, type LlmClientConfig, type MainToWorkerMessage, OpfsProvider, type SerializedError, TsTranspiler, type TypeScriptSupportOptions, WORKER_BOOTSTRAP_SOURCE, type WorkerEvent, type WorkerFactory, type WorkerLike, type WorkerRequest, WorkerRuntimeClient, type WorkerRuntimeClientDeps, type WorkerRuntimeHostOverrides, type WorkerScopeLike, WorkerUiBridge, bridgeError, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
package/dist/browser.js CHANGED
@@ -1,4 +1,6 @@
1
- import { A as mergeCatalogEntries, B as SKILL_MANIFEST_FILE, G as WebSkillError, K as buildCatalog, M as normalizeToolContent, N as parseBridgeRequest, U as SkillDiscovery, Z as isValidSkillName, _ as ProgressiveRouter, a as CapabilityApproval, at as verifyManifest, c as FsMemoryStore, et as parseSkillMarkdown, g as OpenAiCompatibleClient, i as AgentLoop, it as validateSkills, j as networkUrlHost, k as isNetworkAllowed, l as FsRunSnapshotStore, m as MockLlmClient, q as buildManifest, rt as resolveInsideRoot, s as FsArtifactStore, w as bridgeError, x as RUN_SNAPSHOT_SCHEMA_VERSION, z as SKILLS_LOCKFILE } from "./dist-D405AlPD.js";
1
+ import { D as verifyManifest, E as validateSkills, S as parseSkillPackManifest, T as resolveInsideRoot, _ as exportSkills, c as SkillDiscovery, d as buildCatalog, f as buildManifest, i as SKILL_MANIFEST_FILE, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, v as isValidSkillName, x as parseSkillMarkdown } from "./memoryArtifactStore-C9lFVqPF-U8nXvqL9.js";
2
+ import { A as normalizeToolContent, C as createWebSkillApi, D as isNetworkAllowed, O as mergeCatalogEntries, _ as RUN_SNAPSHOT_SCHEMA_VERSION, a as CapabilityApproval, b as bridgeError, c as FsMemoryStore, f as OpenAiCompatibleClient, i as AgentLoop, j as parseBridgeRequest, k as networkUrlHost, l as FsRunSnapshotStore, p as ProgressiveRouter, s as FsArtifactStore } from "./dist-CiYRkm71.js";
3
+ import { n as MockLlmClient } from "./testing-pn3NhXSV.js";
2
4
  import { unzipSync } from "fflate";
3
5
 
4
6
  //#region ../browser/dist/index.js
@@ -442,6 +444,7 @@ const lockfilePath = (root) => `${root}/${SKILLS_LOCKFILE}`;
442
444
  * D5:浏览器技能安装(http(s) zip / ArrayBuffer;tar/git/npm → TOOL_UNSUPPORTED)。
443
445
  * 流程与 Node 对齐:staging → 解析 name → validateSkills(失败零残留)→ managed root →
444
446
  * manifest(WebCrypto sha256)→ skills.lock.json;zip-slip 防护;manifest/lockfile 与 Node 同格式。
447
+ * @stable
445
448
  */
446
449
  var BrowserSkillManager = class {
447
450
  #fs;
@@ -476,6 +479,11 @@ var BrowserSkillManager = class {
476
479
  const contentDir = `${stagingRoot}/content`;
477
480
  await fs.mkdir(contentDir);
478
481
  await extractZipWeb(fs, data, contentDir);
482
+ const packFilePath = `${contentDir}/${SKILL_PACK_FILE}`;
483
+ if (await fs.exists(packFilePath)) return (await this.#installPack({
484
+ contentDir,
485
+ manifest: parseSkillPackManifest(await fs.readText(packFilePath))
486
+ }, source, stagingRoot))[0];
479
487
  let skillDir = contentDir;
480
488
  if (!await fs.exists(`${contentDir}/SKILL.md`)) {
481
489
  const dirs = (await fs.list(contentDir)).filter((e) => e.type === "directory");
@@ -521,6 +529,77 @@ var BrowserSkillManager = class {
521
529
  await removeDirQuiet(fs, stagingRoot);
522
530
  }
523
531
  }
532
+ /**
533
+ * 包集安装:逐技能走单技能管线(归一 → 汇总校验 → 落 managed root → manifest → digest 比对)。
534
+ * 任一失败整体回滚(删除本次已落目录;lockfile 在全部成功后才写入,天然零残留)。
535
+ */
536
+ async #installPack(pack, source, stagingRoot) {
537
+ const fs = this.#fs;
538
+ const finalRoot = `${stagingRoot}/final`;
539
+ const installed = [];
540
+ try {
541
+ const versions = /* @__PURE__ */ new Map();
542
+ for (const entry of pack.manifest.skills) {
543
+ const skillDir = `${pack.contentDir}/${entry.name}`;
544
+ if (!await fs.exists(`${skillDir}/SKILL.md`)) throw new WebSkillError("INSTALL_FAILED", `Skill pack is missing a directory for skill "${entry.name}"`);
545
+ let name;
546
+ try {
547
+ const { metadata } = parseSkillMarkdown(await fs.readText(`${skillDir}/SKILL.md`));
548
+ name = metadata.name;
549
+ versions.set(name, typeof metadata["version"] === "string" ? metadata["version"] : void 0);
550
+ } catch (e) {
551
+ throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf$1(e)}`, e);
552
+ }
553
+ if (name !== entry.name) throw new WebSkillError("INSTALL_FAILED", `Skill pack entry "${entry.name}" does not match the SKILL.md name "${name}"`);
554
+ await copyDir(fs, skillDir, `${finalRoot}/${name}`);
555
+ }
556
+ const report = await validateSkills(fs, [finalRoot]);
557
+ if (!report.ok) {
558
+ const errors = report.issues.filter((i) => i.severity === "error");
559
+ throw new WebSkillError("INSTALL_FAILED", `Skill pack failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
560
+ }
561
+ const manifests = [];
562
+ for (const entry of pack.manifest.skills) {
563
+ const targetDir = `${this.#managedRoot}/${entry.name}`;
564
+ if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
565
+ await copyDir(fs, `${finalRoot}/${entry.name}`, targetDir);
566
+ installed.push(targetDir);
567
+ const manifest = await this.#createManifest(targetDir, {
568
+ name: entry.name,
569
+ ...versions.get(entry.name) ? { version: versions.get(entry.name) } : {},
570
+ source
571
+ });
572
+ if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
573
+ manifests.push(manifest);
574
+ }
575
+ for (const manifest of manifests) await this.#upsertLockEntry(manifest.name, {
576
+ digest: manifest.integrity.digest,
577
+ installedAt: manifest.installedAt,
578
+ source
579
+ });
580
+ return manifests;
581
+ } catch (e) {
582
+ for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
583
+ throw asInstallFailed(e);
584
+ }
585
+ }
586
+ /**
587
+ * 浏览器导出(单技能与包集同构:始终产出含 webskill.skill-pack.json 的 zip 字节,
588
+ * 统一安装路径;保存/下载由应用层处理)。
589
+ */
590
+ async exportArchive(names) {
591
+ const list = Array.isArray(names) ? names : [names];
592
+ if (list.length === 0) throw new WebSkillError("EXPORT_FAILED", "exportArchive requires at least one skill name");
593
+ const roots = list.map((name) => {
594
+ if (!isValidSkillName(name)) throw new WebSkillError("EXPORT_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
595
+ return `${this.#managedRoot}/${name}`;
596
+ });
597
+ for (const [index, root] of roots.entries()) if (!await this.#fs.exists(root)) throw new WebSkillError("EXPORT_FAILED", `Skill "${list[index]}" is not installed`);
598
+ return exportSkills(this.#fs, {
599
+ roots,
600
+ manifestBuilder: (root) => this.#readManifest(root)
601
+ });
602
+ }
524
603
  async uninstall(name) {
525
604
  if (!isValidSkillName(name)) throw new WebSkillError("UNINSTALL_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
526
605
  const targetDir = `${this.#managedRoot}/${name}`;
@@ -609,6 +688,7 @@ const messageOf = (e) => e instanceof Error ? e.message : String(e);
609
688
  /**
610
689
  * Web Worker 脚本沙箱执行器:loadDefinition 与 execute 都在 Worker 内完成,
611
690
  * 主线程从不 import 技能脚本;每次执行独立 Worker;超时 terminate 强杀。
691
+ * @stable
612
692
  */
613
693
  var BrowserWorkerScriptExecutor = class {
614
694
  #fs;
@@ -829,6 +909,35 @@ var BrowserWorkerScriptExecutor = class {
829
909
  }
830
910
  }
831
911
  };
912
+ /**
913
+ * navigator.webskill 显式装配:OPFS 默认存储 + BrowserWorkerScriptExecutor +
914
+ * BrowserSkillManager → createWebSkillApi;target 提供时挂 target.navigator
915
+ * (缺失则补空对象),否则挂真实 navigator.webskill。
916
+ * target 可注入(测试);返回值即门面本体(挂载只是副作用)。
917
+ * @stable
918
+ */
919
+ function installWebSkillNavigator(config) {
920
+ const roots = config.roots ?? ["/skills"];
921
+ const fs = config.fs ?? new OpfsProvider();
922
+ const executor = new BrowserWorkerScriptExecutor({
923
+ fs,
924
+ ...config.typescript ? { typescript: config.typescript } : {}
925
+ });
926
+ const skillManager = new BrowserSkillManager({
927
+ fs,
928
+ managedRoot: config.managedRoot ?? roots[0] ?? "/skills"
929
+ });
930
+ const api = createWebSkillApi({
931
+ fs,
932
+ roots,
933
+ llm: config.llm,
934
+ executor,
935
+ skillManager
936
+ });
937
+ const nav = config.target ? config.target.navigator ??= {} : navigator;
938
+ nav["webskill"] = api;
939
+ return api;
940
+ }
832
941
  const isRecord = (v) => typeof v === "object" && v !== null;
833
942
  const isNonEmptyString = (v) => typeof v === "string" && v !== "";
834
943
  /** Worker 侧入站校验:非法消息返回 undefined(忽略) */
@@ -1028,7 +1137,7 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
1028
1137
  return [];
1029
1138
  }
1030
1139
  }))).flat();
1031
- const catalog = providerEntries.length > 0 ? { skills: mergeCatalogEntries(buildCatalog(entries).skills, providerEntries) } : buildCatalog(entries);
1140
+ const catalog = providerEntries.length > 0 ? { entries: mergeCatalogEntries(buildCatalog(entries).entries, providerEntries) } : buildCatalog(entries);
1032
1141
  const route = await new ProgressiveRouter().route(catalog);
1033
1142
  const uiBridge = new WorkerUiBridge(scope);
1034
1143
  activeUiBridge = uiBridge;
@@ -1337,4 +1446,4 @@ var WorkerRuntimeClient = class {
1337
1446
  };
1338
1447
 
1339
1448
  //#endregion
1340
- export { BrowserSkillManager, BrowserWorkerScriptExecutor, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, extractZipWeb, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
1449
+ export { BrowserSkillManager, BrowserWorkerScriptExecutor, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };