@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 +180 -46
- package/dist/browser.d.ts +37 -2
- package/dist/browser.js +112 -3
- package/dist/{dist-D405AlPD.js → dist-CiYRkm71.js} +840 -1291
- package/dist/{dist-Ixnb-hNR.js → dist-DfKKj86A.js} +84 -2
- package/dist/{dist-BM-VcQ90.js → dist-Dv4a1Jkm.js} +226 -4
- package/dist/governance.d.ts +5 -3
- package/dist/governance.js +13 -4
- package/dist/{index-aEple804.d.ts → index-DuodMfQw.d.ts} +7 -1
- package/dist/{index-BQDc-U1x.d.ts → index-S8uza3ld.d.ts} +65 -540
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/mcp.d.ts +3 -1
- package/dist/mcp.js +3 -1
- package/dist/memoryArtifactStore-C9lFVqPF-U8nXvqL9.js +637 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +3 -2
- package/dist/testing-pn3NhXSV.js +117 -0
- package/dist/testing.d.ts +73 -0
- package/dist/testing.js +4 -0
- package/dist/types-CgNC-oQu-DCklnS0h.d.ts +539 -0
- package/dist/ui-react.d.ts +2 -2
- package/dist/ui-react.js +12 -2
- package/dist/ui-vue.d.ts +2 -2
- package/dist/ui-vue.js +9 -2
- package/dist/ui.d.ts +30 -5
- package/dist/ui.js +3 -3
- package/package.json +5 -1
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
|
|
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,
|
|
15
|
-
(missing-parameter forms, confirmations,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
26
|
-
adapters (Vercel AI SDK, OpenUI, A2UI), plus React and
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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 {
|
|
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 {
|
|
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 ? {
|
|
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 };
|