@webskill/sdk 0.1.5 → 0.2.1
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 +16 -10
- package/dist/browser.d.ts +37 -3
- package/dist/browser.js +106 -8
- package/dist/{dist-CfBOjJ4p.js → dist-BJobG0i-.js} +30 -7
- package/dist/{dist-LIFS3ZjI.js → dist-BRGQcLqR.js} +405 -179
- package/dist/{dist-fZFZaf43.js → dist-D0saNPi_.js} +22 -4
- package/dist/{dist-DuGN5x0v.js → dist-DrySSQ5R.js} +185 -50
- package/dist/env--jJB-TSX-04klhTYi.js +143 -0
- package/dist/env-BPUBZCwJ-4jat_SVG.d.ts +38 -0
- package/dist/governance.d.ts +43 -7
- package/dist/governance.js +168 -11
- package/dist/{index-Bun1aEf4.d.ts → index-BmzysJX5.d.ts} +48 -38
- package/dist/{index-COuOu6gw.d.ts → index-gjFuBevI.d.ts} +53 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +28 -8
- package/dist/node.d.ts +5 -4
- package/dist/node.js +5 -4
- package/dist/processSandboxEntry.d.ts +1 -0
- package/dist/processSandboxEntry.js +202 -0
- package/dist/{testing-BmR48Pn1.js → testing-B4pq6JYa.js} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{types-CgNC-oQu-DEcIBJKG.d.ts → types-CKm5G_eQ-8W8FnP4u.d.ts} +13 -2
- package/dist/ui-react.d.ts +4 -2
- package/dist/ui-react.js +25 -4
- package/dist/ui-vue.d.ts +3 -2
- package/dist/ui-vue.js +44 -15
- package/dist/ui.d.ts +5 -3
- package/dist/ui.js +2 -2
- package/package.json +21 -9
package/README.md
CHANGED
|
@@ -15,12 +15,16 @@ governance.
|
|
|
15
15
|
the LLM, guardrails, lifecycle events, memory, human interaction
|
|
16
16
|
(missing-parameter forms, confirmations, authorization prompts), and
|
|
17
17
|
resumable interrupted runs.
|
|
18
|
-
- **Script sandbox** —
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
- **Script sandbox** — two tiers. Isolation-grade: Node `ProcessSandboxExecutor`
|
|
19
|
+
(`child_process.fork` + `--permission`, experimental; fs scoped to the skill
|
|
20
|
+
root, workers/child processes/addons denied by default — note the permission
|
|
21
|
+
model has NO network dimension, so `fetch`/`WebSocket` stay patch-enforced and
|
|
22
|
+
bare `node:net` imports remain a documented residual), and the browser opaque
|
|
23
|
+
origin sandbox (`sandbox="allow-scripts"` iframe hosting a classic Worker —
|
|
24
|
+
`type:"module"` workers cannot load in opaque origins, verified). Capability
|
|
25
|
+
tier (NOT a security boundary): `SandboxedScriptExecutor` /
|
|
26
|
+
`BrowserWorkerScriptExecutor` in blob-Worker mode, with deny-by-default
|
|
27
|
+
network policy, builtin-module allowlist, forced approval, timeouts.
|
|
24
28
|
- **`navigator.webskill`** — a browser facade (`discover` / `read` / `validate` /
|
|
25
29
|
`run` / `install` / `uninstall`) assembled explicitly in one call.
|
|
26
30
|
- **MCP** — call page-provided tools and consume page-declared dynamic skills
|
|
@@ -102,7 +106,8 @@ const run = await api.run('Summarize the references of skill greeter.');
|
|
|
102
106
|
|
|
103
107
|
### `@webskill/sdk/node`
|
|
104
108
|
|
|
105
|
-
Node host: `NodeFS`, script executors (in-process
|
|
109
|
+
Node host: `NodeFS`, script executors (in-process, `worker_threads` sandbox, and
|
|
110
|
+
`ProcessSandboxExecutor` for real process isolation),
|
|
106
111
|
file-backed stores, `SkillManager` (install/export incl. multi-skill packs),
|
|
107
112
|
CLI UI bridge, schema inference.
|
|
108
113
|
|
|
@@ -117,9 +122,10 @@ console.log((await manager.verifyIntegrity('greeter')).ok);
|
|
|
117
122
|
|
|
118
123
|
### `@webskill/sdk/browser`
|
|
119
124
|
|
|
120
|
-
Browser host: OPFS provider,
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
Browser host: OPFS provider, opaque origin iframe script sandbox (default on
|
|
126
|
+
the main thread; engines running inside a Worker automatically fall back to the
|
|
127
|
+
blob Worker form — no iframe is available there, documented), worker runtime
|
|
128
|
+
host/client, `BrowserSkillManager`, and the `navigator.webskill` assembler. See
|
|
123
129
|
[`examples/quickstart-browser`](../examples/quickstart-browser).
|
|
124
130
|
|
|
125
131
|
```js
|
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-
|
|
2
|
-
import { N as NetworkPolicy,
|
|
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-gjFuBevI.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
|
|
@@ -122,6 +123,8 @@ interface WorkerLike {
|
|
|
122
123
|
terminate(): void;
|
|
123
124
|
}
|
|
124
125
|
type WorkerFactory = () => WorkerLike;
|
|
126
|
+
/** 沙箱形态:opaque iframe(默认,主线程可用 document 时)/ blob Worker(Worker 内引擎回退) */
|
|
127
|
+
type SandboxMode = 'iframe' | 'worker' | 'auto';
|
|
125
128
|
/**
|
|
126
129
|
* Web Worker 脚本沙箱执行器:loadDefinition 与 execute 都在 Worker 内完成,
|
|
127
130
|
* 主线程从不 import 技能脚本;每次执行独立 Worker;超时 terminate 强杀。
|
|
@@ -132,6 +135,12 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
|
|
|
132
135
|
constructor(deps: {
|
|
133
136
|
fs: FileSystemProvider;
|
|
134
137
|
workerFactory?: WorkerFactory;
|
|
138
|
+
/**
|
|
139
|
+
* 沙箱形态:'auto'(默认)——主线程用 opaque origin iframe(sandbox=allow-scripts srcdoc,
|
|
140
|
+
* 真实隔离);Worker 内引擎自动回退 blob Worker(无 iframe 可用,文档明示)。
|
|
141
|
+
* 显式 'worker' 强制 blob Worker(0.1.x 旧形态)。
|
|
142
|
+
*/
|
|
143
|
+
sandbox?: SandboxMode;
|
|
135
144
|
capabilities?: BridgeCapabilities;
|
|
136
145
|
/** 网络策略:默认 'deny-all'(白名单见 runtime/sandbox/networkPolicy) */
|
|
137
146
|
networkPolicy?: NetworkPolicy;
|
|
@@ -152,6 +161,31 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
|
|
|
152
161
|
}): Promise<ToolResult>;
|
|
153
162
|
}
|
|
154
163
|
//#endregion
|
|
164
|
+
//#region src/executor/iframeSandbox.d.ts
|
|
165
|
+
/**
|
|
166
|
+
* opaque origin 沙箱 iframe(sandbox="allow-scripts" srcdoc):
|
|
167
|
+
* iframe 内建 module Worker(Blob URL),iframe window 作 postMessage 中继。
|
|
168
|
+
* opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS(独立 storage 分区)。
|
|
169
|
+
* 就绪握手:iframe 加载完成发 sandbox-ready 后才放行出站消息(防丢失)。
|
|
170
|
+
*/
|
|
171
|
+
declare class IframeWorkerLike implements WorkerLike {
|
|
172
|
+
#private;
|
|
173
|
+
constructor(iframe: HTMLIFrameElement);
|
|
174
|
+
get ready(): boolean;
|
|
175
|
+
markReady(): void;
|
|
176
|
+
postMessage(data: unknown): void;
|
|
177
|
+
addEventListener(_type: 'message', listener: (event: {
|
|
178
|
+
data: unknown;
|
|
179
|
+
}) => void): void;
|
|
180
|
+
emit(data: unknown): void;
|
|
181
|
+
terminate(): void;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 module Worker。
|
|
185
|
+
* 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
|
|
186
|
+
*/
|
|
187
|
+
declare function createIframeWorker(bootstrapSource: string, doc?: Document): IframeWorkerLike;
|
|
188
|
+
//#endregion
|
|
155
189
|
//#region src/navigator.d.ts
|
|
156
190
|
declare global {
|
|
157
191
|
/** 0.0.6:installWebSkillNavigator 显式装配后可用(不自动挂全局) */
|
|
@@ -342,4 +376,4 @@ declare class WorkerUiBridge implements UiBridge {
|
|
|
342
376
|
onTextDelta(runId: string, delta: string): Promise<void>;
|
|
343
377
|
}
|
|
344
378
|
//#endregion
|
|
345
|
-
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 };
|
|
379
|
+
export { type BridgeCapabilities, type BridgeRequest, type BridgeResponse, BrowserSkillManager, BrowserWorkerScriptExecutor, IframeWorkerLike, type LlmClientConfig, type MainToWorkerMessage, OpfsProvider, type SandboxMode, 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, createIframeWorker, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { A as
|
|
3
|
-
import { n as MockLlmClient } from "./testing-
|
|
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-DrySSQ5R.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-B4pq6JYa.js";
|
|
4
4
|
|
|
5
5
|
//#region ../browser/dist/index.js
|
|
6
6
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -132,6 +132,11 @@ var OpfsProvider = class {
|
|
|
132
132
|
await (await this.#walkDir(`/${segments.join("/")}`, false)).removeEntry(name, { recursive: options?.recursive ?? false });
|
|
133
133
|
});
|
|
134
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
|
+
}
|
|
135
140
|
};
|
|
136
141
|
/**
|
|
137
142
|
* Worker 引导源码(字符串 → Blob URL → new Worker(url, {type:'module'}))。
|
|
@@ -700,21 +705,114 @@ var BrowserSkillManager = class {
|
|
|
700
705
|
async #upsertLockEntry(name, entry) {
|
|
701
706
|
const lockfile = await this.listInstalled();
|
|
702
707
|
lockfile.skills[name] = entry;
|
|
703
|
-
await this.#fs
|
|
708
|
+
await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
|
|
704
709
|
}
|
|
705
710
|
async #removeLockEntry(name) {
|
|
706
711
|
const lockfile = await this.listInstalled();
|
|
707
712
|
delete lockfile.skills[name];
|
|
708
|
-
await this.#fs
|
|
713
|
+
await atomicWriteText(this.#fs, lockfilePath(this.#managedRoot), JSON.stringify(lockfile, null, 2));
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
const ENVELOPE = "__webskill_sandbox__";
|
|
717
|
+
/**
|
|
718
|
+
* opaque origin 沙箱 iframe(sandbox="allow-scripts" srcdoc):
|
|
719
|
+
* iframe 内建 module Worker(Blob URL),iframe window 作 postMessage 中继。
|
|
720
|
+
* opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS(独立 storage 分区)。
|
|
721
|
+
* 就绪握手:iframe 加载完成发 sandbox-ready 后才放行出站消息(防丢失)。
|
|
722
|
+
*/
|
|
723
|
+
var IframeWorkerLike = class {
|
|
724
|
+
#iframe;
|
|
725
|
+
#ready = false;
|
|
726
|
+
#queue = [];
|
|
727
|
+
constructor(iframe) {
|
|
728
|
+
this.#iframe = iframe;
|
|
729
|
+
}
|
|
730
|
+
get ready() {
|
|
731
|
+
return this.#ready;
|
|
732
|
+
}
|
|
733
|
+
markReady() {
|
|
734
|
+
this.#ready = true;
|
|
735
|
+
for (const payload of this.#queue) this.#post(payload);
|
|
736
|
+
this.#queue = [];
|
|
737
|
+
}
|
|
738
|
+
#post(payload) {
|
|
739
|
+
this.#iframe.contentWindow?.postMessage({
|
|
740
|
+
[ENVELOPE]: true,
|
|
741
|
+
payload
|
|
742
|
+
}, "*");
|
|
743
|
+
}
|
|
744
|
+
postMessage(data) {
|
|
745
|
+
if (this.#ready) this.#post(data);
|
|
746
|
+
else this.#queue.push(data);
|
|
747
|
+
}
|
|
748
|
+
addEventListener(_type, listener) {
|
|
749
|
+
this.#listeners.add(listener);
|
|
750
|
+
}
|
|
751
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
752
|
+
emit(data) {
|
|
753
|
+
for (const listener of this.#listeners) listener({ data });
|
|
754
|
+
}
|
|
755
|
+
terminate() {
|
|
756
|
+
this.#iframe.remove();
|
|
709
757
|
}
|
|
710
758
|
};
|
|
711
|
-
|
|
759
|
+
/** srcdoc 文档:内嵌 Worker 引导源码 + 双向中继(BOOTSTRAP 经 JSON 字符串注入,防 <\/script> 截断) */
|
|
760
|
+
function sandboxDocument(bootstrapSource) {
|
|
761
|
+
const bootstrapJson = JSON.stringify(bootstrapSource).replace(/<\//g, "<\\/");
|
|
762
|
+
return `<!doctype html>
|
|
763
|
+
<html><head><meta charset="utf-8"></head><body><script>
|
|
764
|
+
const ENVELOPE = ${JSON.stringify(ENVELOPE)};
|
|
765
|
+
const blob = new Blob([${bootstrapJson}], { type: 'text/javascript' });
|
|
766
|
+
const url = URL.createObjectURL(blob);
|
|
767
|
+
// 注意:opaque origin 下 module Worker 无法加载(实证),classic Worker + data: URL 动态导入可用
|
|
768
|
+
const worker = new Worker(url);
|
|
769
|
+
URL.revokeObjectURL(url);
|
|
770
|
+
window.addEventListener('message', (event) => {
|
|
771
|
+
const data = event.data;
|
|
772
|
+
if (data && data[ENVELOPE]) worker.postMessage(data.payload);
|
|
773
|
+
});
|
|
774
|
+
worker.addEventListener('message', (event) => {
|
|
775
|
+
parent.postMessage({ [ENVELOPE]: true, payload: event.data }, '*');
|
|
776
|
+
});
|
|
777
|
+
worker.addEventListener('error', (event) => {
|
|
778
|
+
parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-worker-error', message: event.message } }, '*');
|
|
779
|
+
});
|
|
780
|
+
parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-ready' } }, '*');
|
|
781
|
+
<\/script></body></html>`;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 module Worker。
|
|
785
|
+
* 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
|
|
786
|
+
*/
|
|
787
|
+
function createIframeWorker(bootstrapSource, doc = document) {
|
|
788
|
+
const iframe = doc.createElement("iframe");
|
|
789
|
+
iframe.setAttribute("sandbox", "allow-scripts");
|
|
790
|
+
iframe.style.display = "none";
|
|
791
|
+
iframe.srcdoc = sandboxDocument(bootstrapSource);
|
|
792
|
+
const worker = new IframeWorkerLike(iframe);
|
|
793
|
+
(doc.defaultView ?? window).addEventListener("message", (event) => {
|
|
794
|
+
const data = event.data;
|
|
795
|
+
if (!data || data[ENVELOPE] !== true) return;
|
|
796
|
+
if (data.payload && data.payload.type === "sandbox-ready") {
|
|
797
|
+
worker.markReady();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
worker.emit(data.payload);
|
|
801
|
+
});
|
|
802
|
+
doc.body.appendChild(iframe);
|
|
803
|
+
return worker;
|
|
804
|
+
}
|
|
805
|
+
const blobWorkerFactory = () => {
|
|
712
806
|
const blob = new Blob([WORKER_BOOTSTRAP_SOURCE], { type: "text/javascript" });
|
|
713
807
|
const url = URL.createObjectURL(blob);
|
|
714
808
|
const worker = new Worker(url, { type: "module" });
|
|
715
809
|
URL.revokeObjectURL(url);
|
|
716
810
|
return worker;
|
|
717
811
|
};
|
|
812
|
+
const resolveWorkerFactory = (mode) => {
|
|
813
|
+
if (mode === "iframe" || mode === "auto" && typeof document !== "undefined") return () => createIframeWorker(WORKER_BOOTSTRAP_SOURCE);
|
|
814
|
+
return blobWorkerFactory;
|
|
815
|
+
};
|
|
718
816
|
const baseName = (p) => p.split("/").pop() ?? p;
|
|
719
817
|
const messageOf = (e) => e instanceof Error ? e.message : String(e);
|
|
720
818
|
/**
|
|
@@ -731,7 +829,7 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
731
829
|
#transpiler;
|
|
732
830
|
constructor(deps) {
|
|
733
831
|
this.#fs = deps.fs;
|
|
734
|
-
this.#workerFactory = deps.workerFactory ??
|
|
832
|
+
this.#workerFactory = deps.workerFactory ?? resolveWorkerFactory(deps.sandbox ?? "auto");
|
|
735
833
|
this.#transpiler = deps.typescript ? new TsTranspiler({
|
|
736
834
|
fs: deps.fs,
|
|
737
835
|
options: deps.typescript
|
|
@@ -1478,4 +1576,4 @@ var WorkerRuntimeClient = class {
|
|
|
1478
1576
|
};
|
|
1479
1577
|
|
|
1480
1578
|
//#endregion
|
|
1481
|
-
export { BrowserSkillManager, BrowserWorkerScriptExecutor, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
|
1579
|
+
export { BrowserSkillManager, BrowserWorkerScriptExecutor, IframeWorkerLike, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, createIframeWorker, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as WebSkillError } from "./dist-
|
|
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)
|
|
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
|
-
|
|
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;
|