@webskill/sdk 0.2.1 → 0.2.3

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
@@ -16,6 +16,7 @@ governance.
16
16
  (missing-parameter forms, confirmations, authorization prompts), and
17
17
  resumable interrupted runs.
18
18
  - **Script sandbox** — two tiers. Isolation-grade: Node `ProcessSandboxExecutor`
19
+ (fork + `--permission`, real process isolation).
19
20
  (`child_process.fork` + `--permission`, experimental; fs scoped to the skill
20
21
  root, workers/child processes/addons denied by default — note the permission
21
22
  model has NO network dimension, so `fetch`/`WebSocket` stay patch-enforced and
@@ -25,6 +26,9 @@ governance.
25
26
  tier (NOT a security boundary): `SandboxedScriptExecutor` /
26
27
  `BrowserWorkerScriptExecutor` in blob-Worker mode, with deny-by-default
27
28
  network policy, builtin-module allowlist, forced approval, timeouts.
29
+ Known proactive escape hatches (`process.binding`, `dlopen`, `abort`, …) are
30
+ stripped in the worker entry (0.2.3); other host-shared surfaces are NOT
31
+ defended — use `ProcessSandboxExecutor` for untrusted skills.
28
32
  - **`navigator.webskill`** — a browser facade (`discover` / `read` / `validate` /
29
33
  `run` / `install` / `uninstall`) assembled explicitly in one call.
30
34
  - **MCP** — call page-provided tools and consume page-declared dynamic skills
package/dist/browser.d.ts CHANGED
@@ -164,14 +164,16 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
164
164
  //#region src/executor/iframeSandbox.d.ts
165
165
  /**
166
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 后才放行出站消息(防丢失)。
167
+ * iframe 内建 classic Worker(Blob URL;module Worker opaque origin 不可加载,实证),
168
+ * iframe window 作 postMessage 中继。opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS
169
+ * 通道鉴别(P0-C):每实例 channelId 随消息携带,宿主校验 event.source 与 channelId,
170
+ * 同页伪造消息一律丢弃;terminate 摘除监听器。
170
171
  */
171
172
  declare class IframeWorkerLike implements WorkerLike {
172
173
  #private;
173
- constructor(iframe: HTMLIFrameElement);
174
+ constructor(iframe: HTMLIFrameElement, hostWindow: Window, channelId: string);
174
175
  get ready(): boolean;
176
+ get channelId(): string;
175
177
  markReady(): void;
176
178
  postMessage(data: unknown): void;
177
179
  addEventListener(_type: 'message', listener: (event: {
@@ -181,7 +183,7 @@ declare class IframeWorkerLike implements WorkerLike {
181
183
  terminate(): void;
182
184
  }
183
185
  /**
184
- * 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 module Worker。
186
+ * 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 classic Worker。
185
187
  * 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
186
188
  */
187
189
  declare function createIframeWorker(bootstrapSource: string, doc?: Document): IframeWorkerLike;
package/dist/browser.js CHANGED
@@ -1,5 +1,5 @@
1
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";
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-NM4Mylx4.js";
3
3
  import { n as MockLlmClient } from "./testing-B4pq6JYa.js";
4
4
 
5
5
  //#region ../browser/dist/index.js
@@ -714,22 +714,38 @@ var BrowserSkillManager = class {
714
714
  }
715
715
  };
716
716
  const ENVELOPE = "__webskill_sandbox__";
717
+ let channelSeq = 0;
718
+ /** 每实例 channelId(防同页伪造消息注入;crypto.randomUUID 优先,无则随机回退) */
719
+ function nextChannelId() {
720
+ return globalThis.crypto?.randomUUID?.() ?? `ch-${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${++channelSeq}`;
721
+ }
717
722
  /**
718
723
  * 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 后才放行出站消息(防丢失)。
724
+ * iframe 内建 classic Worker(Blob URL;module Worker opaque origin 不可加载,实证),
725
+ * iframe window 作 postMessage 中继。opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS
726
+ * 通道鉴别(P0-C):每实例 channelId 随消息携带,宿主校验 event.source 与 channelId,
727
+ * 同页伪造消息一律丢弃;terminate 摘除监听器。
722
728
  */
723
729
  var IframeWorkerLike = class {
724
730
  #iframe;
725
731
  #ready = false;
726
732
  #queue = [];
727
- constructor(iframe) {
733
+ #channelId;
734
+ #hostWindow;
735
+ #hostListener;
736
+ constructor(iframe, hostWindow, channelId) {
728
737
  this.#iframe = iframe;
738
+ this.#hostWindow = hostWindow;
739
+ this.#channelId = channelId;
740
+ this.#hostListener = (event) => this.#onMessage(event);
741
+ this.#hostWindow.addEventListener("message", this.#hostListener);
729
742
  }
730
743
  get ready() {
731
744
  return this.#ready;
732
745
  }
746
+ get channelId() {
747
+ return this.#channelId;
748
+ }
733
749
  markReady() {
734
750
  this.#ready = true;
735
751
  for (const payload of this.#queue) this.#post(payload);
@@ -738,9 +754,21 @@ var IframeWorkerLike = class {
738
754
  #post(payload) {
739
755
  this.#iframe.contentWindow?.postMessage({
740
756
  [ENVELOPE]: true,
757
+ channelId: this.#channelId,
741
758
  payload
742
759
  }, "*");
743
760
  }
761
+ /** 通道鉴别:source 必须是本 iframe 的 contentWindow 且 channelId 匹配,否则丢弃 */
762
+ #onMessage(event) {
763
+ if (event.source !== this.#iframe.contentWindow) return;
764
+ const data = event.data;
765
+ if (!data || data[ENVELOPE] !== true || data.channelId !== this.#channelId) return;
766
+ if (data.payload && data.payload.type === "sandbox-ready") {
767
+ this.markReady();
768
+ return;
769
+ }
770
+ this.emit(data.payload);
771
+ }
744
772
  postMessage(data) {
745
773
  if (this.#ready) this.#post(data);
746
774
  else this.#queue.push(data);
@@ -753,15 +781,17 @@ var IframeWorkerLike = class {
753
781
  for (const listener of this.#listeners) listener({ data });
754
782
  }
755
783
  terminate() {
784
+ this.#hostWindow.removeEventListener("message", this.#hostListener);
756
785
  this.#iframe.remove();
757
786
  }
758
787
  };
759
788
  /** srcdoc 文档:内嵌 Worker 引导源码 + 双向中继(BOOTSTRAP 经 JSON 字符串注入,防 <\/script> 截断) */
760
- function sandboxDocument(bootstrapSource) {
789
+ function sandboxDocument(bootstrapSource, channelId) {
761
790
  const bootstrapJson = JSON.stringify(bootstrapSource).replace(/<\//g, "<\\/");
762
791
  return `<!doctype html>
763
792
  <html><head><meta charset="utf-8"></head><body><script>
764
793
  const ENVELOPE = ${JSON.stringify(ENVELOPE)};
794
+ const CHANNEL_ID = ${JSON.stringify(channelId)};
765
795
  const blob = new Blob([${bootstrapJson}], { type: 'text/javascript' });
766
796
  const url = URL.createObjectURL(blob);
767
797
  // 注意:opaque origin 下 module Worker 无法加载(实证),classic Worker + data: URL 动态导入可用
@@ -769,36 +799,29 @@ const worker = new Worker(url);
769
799
  URL.revokeObjectURL(url);
770
800
  window.addEventListener('message', (event) => {
771
801
  const data = event.data;
772
- if (data && data[ENVELOPE]) worker.postMessage(data.payload);
802
+ // 中继同样校验 channelId(同页脚本无法伪造实例 id)
803
+ if (data && data[ENVELOPE] && data.channelId === CHANNEL_ID) worker.postMessage(data.payload);
773
804
  });
774
805
  worker.addEventListener('message', (event) => {
775
- parent.postMessage({ [ENVELOPE]: true, payload: event.data }, '*');
806
+ parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: event.data }, '*');
776
807
  });
777
808
  worker.addEventListener('error', (event) => {
778
- parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-worker-error', message: event.message } }, '*');
809
+ parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: { type: 'sandbox-worker-error', message: event.message } }, '*');
779
810
  });
780
- parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-ready' } }, '*');
811
+ parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: { type: 'sandbox-ready' } }, '*');
781
812
  <\/script></body></html>`;
782
813
  }
783
814
  /**
784
- * 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 module Worker。
815
+ * 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 classic Worker。
785
816
  * 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
786
817
  */
787
818
  function createIframeWorker(bootstrapSource, doc = document) {
819
+ const channelId = nextChannelId();
788
820
  const iframe = doc.createElement("iframe");
789
821
  iframe.setAttribute("sandbox", "allow-scripts");
790
822
  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
- });
823
+ iframe.srcdoc = sandboxDocument(bootstrapSource, channelId);
824
+ const worker = new IframeWorkerLike(iframe, doc.defaultView ?? window, channelId);
802
825
  doc.body.appendChild(iframe);
803
826
  return worker;
804
827
  }
@@ -827,6 +850,8 @@ var BrowserWorkerScriptExecutor = class {
827
850
  #networkPolicy;
828
851
  #approval;
829
852
  #transpiler;
853
+ /** 消息 id 唯一递增(多实例/多执行不串) */
854
+ #messageSeq = 0;
830
855
  constructor(deps) {
831
856
  this.#fs = deps.fs;
832
857
  this.#workerFactory = deps.workerFactory ?? resolveWorkerFactory(deps.sandbox ?? "auto");
@@ -871,7 +896,7 @@ var BrowserWorkerScriptExecutor = class {
871
896
  try {
872
897
  const data = await this.#request(worker, {
873
898
  type: "load",
874
- id: "load-1",
899
+ id: `load-${++this.#messageSeq}`,
875
900
  source: script.source,
876
901
  networkPolicy: this.#networkPolicy
877
902
  });
@@ -906,7 +931,7 @@ var BrowserWorkerScriptExecutor = class {
906
931
  try {
907
932
  const response = await this.#request(worker, {
908
933
  type: "execute",
909
- id: "exec-1",
934
+ id: `exec-${++this.#messageSeq}`,
910
935
  skillName: context.skillName,
911
936
  runId: context.runId,
912
937
  source: script.source,
@@ -1,5 +1,5 @@
1
1
  import { A as unzipWithLimits, C as parseSkillMarkdown, M as verifyManifest, O as resolveArchiveLimits, T as readResponseWithLimit, b as isValidSkillName, f as atomicWriteText, i as SKILL_MANIFEST_FILE, j as validateSkills, k as resolveInsideRoot, m as buildManifest, 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, M as networkUrlHost, N as normalizeToolContent, P as parseBridgeRequest, c as FsArtifactStore, l as FsMemoryStore, o as CapabilityApproval } from "./dist-DrySSQ5R.js";
2
+ import { A as isNetworkAllowed, C as bridgeError, M as networkUrlHost, N as normalizeToolContent, P as parseBridgeRequest, c as FsArtifactStore, l as FsMemoryStore, o as CapabilityApproval } from "./dist-NM4Mylx4.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { unzipSync, zipSync } from "fflate";
5
5
  import { existsSync, promises, realpathSync } from "node:fs";
@@ -303,8 +303,10 @@ function workerEntryPath() {
303
303
  * 能力桥协议与浏览器同一来源(runtime/sandbox/bridgeProtocol)。
304
304
  *
305
305
  * 诚实标注:本执行器做的是**能力面收敛**(网络策略、模块 allowlist、资源限额、
306
- * 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程,仍有绕过手段;
307
- * 禁止假定其可隔离不可信脚本。
306
+ * 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程;
307
+ * 已知主动逃逸面(process.binding/_linkedBinding/dlopen/openStdin/reallyExit/abort)
308
+ * 已在入口删除(0.2.3),常规 import 拦截之外不承诺防御其它宿主共享面;
309
+ * 禁止假定其可隔离不可信脚本(隔离级需求用 ProcessSandboxExecutor)。
308
310
  */
309
311
  var SandboxedScriptExecutor = class {
310
312
  #fs;
@@ -636,18 +638,24 @@ var ProcessSandboxExecutor = class {
636
638
  async #spawnSlot(key) {
637
639
  const artifactDir = await mkdtemp(path.join(tmpdir(), "webskill-psbx-out-")).then((d) => d.split(path.sep).join("/"));
638
640
  const entry = processEntryPath();
641
+ const child = fork(entry, [], {
642
+ execArgv: [
643
+ "--permission",
644
+ ...readAllow(path.dirname(entry)),
645
+ ...readAllow(toPlatform$2(key)),
646
+ ...readAllow(artifactDir),
647
+ ...writeAllow(artifactDir)
648
+ ],
649
+ env: Object.fromEntries((this.#options.envWhitelist ?? []).filter((key) => process.env[key] !== void 0).map((key) => [key, process.env[key]])),
650
+ silent: true
651
+ });
652
+ child.stdout?.resume();
653
+ child.stderr?.resume();
654
+ child.unref();
655
+ child.channel?.unref();
639
656
  return {
640
657
  key,
641
- child: fork(entry, [], {
642
- execArgv: [
643
- "--permission",
644
- ...readAllow(path.dirname(entry)),
645
- ...readAllow(toPlatform$2(key)),
646
- ...readAllow(artifactDir),
647
- ...writeAllow(artifactDir)
648
- ],
649
- silent: false
650
- }),
658
+ child,
651
659
  busy: false,
652
660
  artifactDir
653
661
  };
@@ -1402,7 +1402,7 @@ var AgentLoop = class {
1402
1402
  state.messages.push({
1403
1403
  role: "tool",
1404
1404
  toolCallId: pendingCall.id,
1405
- content: JSON.stringify(result)
1405
+ content: await this.#serializeToolResult(pendingCall, result, state)
1406
1406
  });
1407
1407
  } else if (pending.type === "ask" && pendingCall) {
1408
1408
  const value = await this.#interact(state, pending, {
@@ -1423,14 +1423,24 @@ var AgentLoop = class {
1423
1423
  state.messages.push({
1424
1424
  role: "tool",
1425
1425
  toolCallId: pendingCall.id,
1426
- content: JSON.stringify(result)
1426
+ content: await this.#serializeToolResult(pendingCall, result, state)
1427
1427
  });
1428
1428
  } else if (pendingCall) {
1429
1429
  const result = await this.#executeCall(pendingCall, state);
1430
1430
  state.messages.push({
1431
1431
  role: "tool",
1432
1432
  toolCallId: pendingCall.id,
1433
- content: JSON.stringify(result)
1433
+ content: await this.#serializeToolResult(pendingCall, result, state)
1434
+ });
1435
+ }
1436
+ for (;;) {
1437
+ const next = this.#findPendingToolCall(state.messages);
1438
+ if (!next) break;
1439
+ const result = await this.#executeCall(next, state);
1440
+ state.messages.push({
1441
+ role: "tool",
1442
+ toolCallId: next.id,
1443
+ content: await this.#serializeToolResult(next, result, state)
1434
1444
  });
1435
1445
  }
1436
1446
  } catch (e) {
@@ -1,6 +1,6 @@
1
1
  import { F as SkillDocument, N as SkillCatalogEntry, c as LlmClient, u as LlmMessage, v as UiBridge, w as FileSystemProvider, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
2
2
  import { Q as ScriptExecutor, dt as WebSkillRuntime, ft as WebSkillRuntimeDeps, q as RuntimeRun, tt as SkillStateGuard } from "./index-gjFuBevI.js";
3
- import { d as SkillManager } from "./index-BmzysJX5.js";
3
+ import { d as SkillManager } from "./index-iqcz3_NS.js";
4
4
  //#region ../governance/dist/index.d.ts
5
5
  //#region src/types.d.ts
6
6
  type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
@@ -360,8 +360,10 @@ declare class EvaluationRunner {
360
360
  //#region src/evaluation/evaluationRuntime.d.ts
361
361
  /**
362
362
  * 治理评估专用 runtime 装配(不可信技能试用路径):
363
- * 默认注入 ProcessSandboxExecutor(fork + --permission 真实进程隔离);
364
- * 可配置 executor 切回 SandboxedScriptExecutor(worker_threads 能力面收敛形态)。
363
+ * 默认注入 ProcessSandboxExecutor(fork + --permission 真实进程隔离;子进程
364
+ * env 默认清空防密钥泄露,需透传时经 ProcessSandboxOptions.envWhitelist 显式放行)。
365
+ * 可配置 executor 切回 SandboxedScriptExecutor(worker_threads 能力面收敛形态,
366
+ * 非安全边界;envWhitelist 同样适用于该执行器)。
365
367
  */
366
368
  declare function createEvaluationRuntime(deps: WebSkillRuntimeDeps & {
367
369
  executor?: ScriptExecutor;
@@ -1,6 +1,6 @@
1
1
  import { A as unzipWithLimits, b as isValidSkillName, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, u as WebSkillError } from "./dist-D0saNPi_.js";
2
- import { S as WebSkillRuntime } from "./dist-DrySSQ5R.js";
3
- import { i as NodeFS, s as ProcessSandboxExecutor, u as exportArchive } from "./dist-BRGQcLqR.js";
2
+ import { S as WebSkillRuntime } from "./dist-NM4Mylx4.js";
3
+ import { i as NodeFS, s as ProcessSandboxExecutor, u as exportArchive } from "./dist-CvIMVwr3.js";
4
4
  import path from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { mkdtemp } from "node:fs/promises";
@@ -843,8 +843,10 @@ var EvaluationRunner = class {
843
843
  };
844
844
  /**
845
845
  * 治理评估专用 runtime 装配(不可信技能试用路径):
846
- * 默认注入 ProcessSandboxExecutor(fork + --permission 真实进程隔离);
847
- * 可配置 executor 切回 SandboxedScriptExecutor(worker_threads 能力面收敛形态)。
846
+ * 默认注入 ProcessSandboxExecutor(fork + --permission 真实进程隔离;子进程
847
+ * env 默认清空防密钥泄露,需透传时经 ProcessSandboxOptions.envWhitelist 显式放行)。
848
+ * 可配置 executor 切回 SandboxedScriptExecutor(worker_threads 能力面收敛形态,
849
+ * 非安全边界;envWhitelist 同样适用于该执行器)。
848
850
  */
849
851
  function createEvaluationRuntime(deps) {
850
852
  return new WebSkillRuntime({
@@ -72,8 +72,10 @@ interface SandboxOptions {
72
72
  * 能力桥协议与浏览器同一来源(runtime/sandbox/bridgeProtocol)。
73
73
  *
74
74
  * 诚实标注:本执行器做的是**能力面收敛**(网络策略、模块 allowlist、资源限额、
75
- * 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程,仍有绕过手段;
76
- * 禁止假定其可隔离不可信脚本。
75
+ * 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程;
76
+ * 已知主动逃逸面(process.binding/_linkedBinding/dlopen/openStdin/reallyExit/abort)
77
+ * 已在入口删除(0.2.3),常规 import 拦截之外不承诺防御其它宿主共享面;
78
+ * 禁止假定其可隔离不可信脚本(隔离级需求用 ProcessSandboxExecutor)。
77
79
  */
78
80
  declare class SandboxedScriptExecutor implements ScriptExecutor {
79
81
  #private;
@@ -92,6 +94,8 @@ declare class SandboxedScriptExecutor implements ScriptExecutor {
92
94
  interface ProcessSandboxOptions {
93
95
  /** 温池大小(并发上限;执行后 kill 并补位重生),默认 2 */
94
96
  poolSize?: number;
97
+ /** 透传给子进程的环境变量白名单(默认 []:子进程 env 为空,防密钥泄露) */
98
+ envWhitelist?: string[];
95
99
  capabilities?: BridgeCapabilities;
96
100
  /** 网络策略:默认 'deny-all'(权限模型无网络维度,补丁兜底;node:net 裸模块为已知残余面) */
97
101
  networkPolicy?: NetworkPolicy;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { A as unzipWithLimits, C as parseSkillMarkdown, D as renderCatalogJson, E as renderAvailableSkillsXml, M as verifyManifest, N as xmlRenderer, O as resolveArchiveLimits, S as normalizePath, T as readResponseWithLimit, _ as computeDigest, a as SKILL_NAME_MAX_LENGTH, b as isValidSkillName, c as SkillDiscovery, d as assertSafePathSegment, f as atomicWriteText, g as checkSkillRules, h as checkDependencyCycles, i as SKILL_MANIFEST_FILE, j as validateSkills, k as resolveInsideRoot, l as SkillReader, m as buildManifest, n as MemoryFS, o as SKILL_NAME_PATTERN, p as buildCatalog, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, t as DEFAULT_ARCHIVE_LIMITS, u as WebSkillError, v as escapeXml, w as parseSkillPackManifest, x as jsonRenderer, y as exportSkills } from "./dist-D0saNPi_.js";
2
- import { A as isNetworkAllowed, C as bridgeError, D as extractChartSpec, E as createWebSkillApi, F as resolveToolName, I as schemaToForm, L as toLlmToolSpec, M as networkUrlHost, N as normalizeToolContent, O as fromVercelResult, P as parseBridgeRequest, R as toVercelToolSpecs, S as WebSkillRuntime, T as createScriptContext, _ as READ_SKILL_FILE_TOOL, a as AnthropicClient, b as SerializingMemoryStore, c as FsArtifactStore, d as FullDisclosureRouter, f as GoogleGenAiClient, g as READ_SKILL_FILE_INPUT_SCHEMA, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, k as fromVercelStreamPart, l as FsMemoryStore, m as OpenAiCompatibleClient, n as ASK_USER_TOOL, o as CapabilityApproval, p as HookRunner, r as ASK_USER_TOOL_NAME, s as EventBus, t as ASK_USER_INPUT_SCHEMA, u as FsRunSnapshotStore, v as READ_SKILL_FILE_TOOL_NAME, w as buildRenderResult, x as TraceRecorder, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-DrySSQ5R.js";
2
+ import { A as isNetworkAllowed, C as bridgeError, D as extractChartSpec, E as createWebSkillApi, F as resolveToolName, I as schemaToForm, L as toLlmToolSpec, M as networkUrlHost, N as normalizeToolContent, O as fromVercelResult, P as parseBridgeRequest, R as toVercelToolSpecs, S as WebSkillRuntime, T as createScriptContext, _ as READ_SKILL_FILE_TOOL, a as AnthropicClient, b as SerializingMemoryStore, c as FsArtifactStore, d as FullDisclosureRouter, f as GoogleGenAiClient, g as READ_SKILL_FILE_INPUT_SCHEMA, h as ProgressiveRouter, i as AgentLoop, j as mergeCatalogEntries, k as fromVercelStreamPart, l as FsMemoryStore, m as OpenAiCompatibleClient, n as ASK_USER_TOOL, o as CapabilityApproval, p as HookRunner, r as ASK_USER_TOOL_NAME, s as EventBus, t as ASK_USER_INPUT_SCHEMA, u as FsRunSnapshotStore, v as READ_SKILL_FILE_TOOL_NAME, w as buildRenderResult, x as TraceRecorder, y as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-NM4Mylx4.js";
3
3
 
4
4
  export { ASK_USER_INPUT_SCHEMA, ASK_USER_TOOL, ASK_USER_TOOL_NAME, AgentLoop, AnthropicClient, CapabilityApproval, DEFAULT_ARCHIVE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FullDisclosureRouter, GoogleGenAiClient, HookRunner, MemoryFS, OpenAiCompatibleClient, ProgressiveRouter, READ_SKILL_FILE_INPUT_SCHEMA, READ_SKILL_FILE_TOOL, READ_SKILL_FILE_TOOL_NAME, RUN_SNAPSHOT_SCHEMA_VERSION, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SKILL_NAME_MAX_LENGTH, SKILL_NAME_PATTERN, SKILL_PACK_FILE, SerializingMemoryStore, SkillDiscovery, SkillReader, TraceRecorder, WebSkillError, WebSkillRuntime, assertSafePathSegment, atomicWriteText, bridgeError, buildCatalog, buildManifest, buildRenderResult, checkDependencyCycles, checkSkillRules, computeDigest, createScriptContext, createWebSkillApi, escapeXml, exportSkills, extractChartSpec, fromVercelResult, fromVercelStreamPart, isNetworkAllowed, isValidSkillName, jsonRenderer, mergeCatalogEntries, networkUrlHost, normalizePath, normalizeToolContent, parseBridgeRequest, parseSkillMarkdown, parseSkillPackManifest, readResponseWithLimit, renderAvailableSkillsXml, renderCatalogJson, resolveArchiveLimits, resolveInsideRoot, resolveToolName, schemaToForm, toLlmToolSpec, toVercelToolSpecs, unzipWithLimits, validateSkills, verifyManifest, xmlRenderer };
package/dist/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { u as WebSkillError } from "./dist-D0saNPi_.js";
2
- import { N as normalizeToolContent, j as mergeCatalogEntries } from "./dist-DrySSQ5R.js";
2
+ import { N as normalizeToolContent, j as mergeCatalogEntries } from "./dist-NM4Mylx4.js";
3
3
 
4
4
  //#region ../mcp/dist/index.js
5
5
  /**
package/dist/node.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { D as SKILLS_LOCKFILE, I as SkillInstallSource, K as VerifyResult, O as SKILL_MANIFEST_FILE, W as SkillsLockfile, z as SkillManifest } from "./types-CKm5G_eQ-8W8FnP4u.js";
2
2
  import { ht as createScriptContext } from "./index-gjFuBevI.js";
3
3
  import { n as LlmEnvConfig, s as probeLlmCapabilities, t as LlmCapabilities } from "./env-BPUBZCwJ-4jat_SVG.js";
4
- import { a as NodeScriptExecutor, c as ProcessSandboxOptions, d as SkillManager, f as exportArchive, i as NodeFS, l as SandboxOptions, n as FileArtifactStore, o as OxcSchemaInferer, p as readArchiveManifest, r as FileMemoryStore, s as ProcessSandboxExecutor, t as CliUiBridge, u as SandboxedScriptExecutor } from "./index-BmzysJX5.js";
4
+ import { a as NodeScriptExecutor, c as ProcessSandboxOptions, d as SkillManager, f as exportArchive, i as NodeFS, l as SandboxOptions, n as FileArtifactStore, o as OxcSchemaInferer, p as readArchiveManifest, r as FileMemoryStore, s as ProcessSandboxExecutor, t as CliUiBridge, u as SandboxedScriptExecutor } from "./index-iqcz3_NS.js";
5
5
  export { CliUiBridge, FileArtifactStore, FileMemoryStore, type LlmCapabilities, type LlmEnvConfig, NodeFS, NodeScriptExecutor, OxcSchemaInferer, ProcessSandboxExecutor, type ProcessSandboxOptions, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, type SandboxOptions, SandboxedScriptExecutor, type SkillInstallSource, SkillManager, type SkillManifest, type SkillsLockfile, type VerifyResult, createScriptContext, exportArchive, probeLlmCapabilities, readArchiveManifest };
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as SKILL_MANIFEST_FILE, r as SKILLS_LOCKFILE } from "./dist-D0saNPi_.js";
2
- import { T as createScriptContext } from "./dist-DrySSQ5R.js";
2
+ import { T as createScriptContext } from "./dist-NM4Mylx4.js";
3
3
  import { i as probeLlmCapabilities } from "./env--jJB-TSX-04klhTYi.js";
4
- import { a as NodeScriptExecutor, c as SandboxedScriptExecutor, d as readArchiveManifest, i as NodeFS, l as SkillManager, n as FileArtifactStore, o as OxcSchemaInferer, r as FileMemoryStore, s as ProcessSandboxExecutor, t as CliUiBridge, u as exportArchive } from "./dist-BRGQcLqR.js";
4
+ import { a as NodeScriptExecutor, c as SandboxedScriptExecutor, d as readArchiveManifest, i as NodeFS, l as SkillManager, n as FileArtifactStore, o as OxcSchemaInferer, r as FileMemoryStore, s as ProcessSandboxExecutor, t as CliUiBridge, u as exportArchive } from "./dist-CvIMVwr3.js";
5
5
 
6
6
  export { CliUiBridge, FileArtifactStore, FileMemoryStore, NodeFS, NodeScriptExecutor, OxcSchemaInferer, ProcessSandboxExecutor, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SandboxedScriptExecutor, SkillManager, createScriptContext, exportArchive, probeLlmCapabilities, readArchiveManifest };
@@ -197,6 +197,7 @@ process.on("message", (msg) => {
197
197
  process.exit(1);
198
198
  });
199
199
  });
200
+ process.on("disconnect", () => process.exit(0));
200
201
 
201
202
  //#endregion
202
203
  export { };
@@ -132,8 +132,29 @@ function setupNetworkGuards(task) {
132
132
  };
133
133
  register(`data:text/javascript,${encodeURIComponent(MODULE_HOOK_SOURCE.replace("%ALLOWED_MODULES%", JSON.stringify(task.allowedModules ?? [])))}`);
134
134
  }
135
+ /**
136
+ * 加载用户代码前删除 native 逃逸面(P0-A):binding/_linkedBinding/dlopen/
137
+ * openStdin/reallyExit/abort——这些直达宿主进程 internals,防御面与
138
+ * 能力收敛语义一致(常规 import 拦截之外的主动逃逸项由此封闭)。
139
+ * 注意:process.exit() 内部依赖 reallyExit(per_thread.js),故删前捕获引用,
140
+ * 关闭路径直接调用 internalExit(不经 process.exit)。
141
+ */
142
+ const internalExit = process.reallyExit.bind(process);
143
+ function stripProcessEscapeHatches() {
144
+ for (const key of [
145
+ "binding",
146
+ "_linkedBinding",
147
+ "dlopen",
148
+ "openStdin",
149
+ "reallyExit",
150
+ "abort"
151
+ ]) try {
152
+ Reflect.deleteProperty(process, key);
153
+ } catch {}
154
+ }
135
155
  async function main() {
136
156
  const task = workerData;
157
+ stripProcessEscapeHatches();
137
158
  setupNetworkGuards(task);
138
159
  port.on("message", (msg) => {
139
160
  if (msg?.type === "bridge-response" && msg.response?.id) {
@@ -222,9 +243,9 @@ async function main() {
222
243
  console.error = originals.error;
223
244
  }
224
245
  }
225
- main().then(() => process.exit(0), (e) => {
246
+ main().then(() => internalExit(0), (e) => {
226
247
  console.error(e);
227
- process.exit(1);
248
+ internalExit(1);
228
249
  });
229
250
 
230
251
  //#endregion
package/dist/ui.js CHANGED
@@ -1,4 +1,4 @@
1
- import { w as buildRenderResult } from "./dist-DrySSQ5R.js";
1
+ import { w as buildRenderResult } from "./dist-NM4Mylx4.js";
2
2
  import { C as renderRenderResult, D as toVercelToolInvocation, E as toOpenUiLang, S as renderMiniMarkdown, T as toA2uiMessages, _ as fromOpenUiAction, a as CHART_PALETTE, b as renderBlocks, c as OPENUI_SUBMIT_ACTION, d as WEBSKILL_STYLES_CSS, f as WebFormBridge, g as fromA2uiAction, h as ensureStyles, i as A2UI_VERSION, l as VERCEL_INTERACTION_TOOL_NAME, m as collectValues, n as A2UI_CANCEL_ACTION, o as LitRendererBridge, p as chartToTable, r as A2UI_SUBMIT_ACTION, s as OPENUI_CANCEL_ACTION, t as A2UI_BASIC_CATALOG_ID, u as VercelUiBridge, v as fromVercelToolResult, w as shapeInteractionValue, x as renderMiniChart, y as interactionToFormModel } from "./dist-BJobG0i-.js";
3
3
 
4
4
  export { A2UI_BASIC_CATALOG_ID, A2UI_CANCEL_ACTION, A2UI_SUBMIT_ACTION, A2UI_VERSION, CHART_PALETTE, LitRendererBridge, OPENUI_CANCEL_ACTION, OPENUI_SUBMIT_ACTION, VERCEL_INTERACTION_TOOL_NAME, VercelUiBridge, WEBSKILL_STYLES_CSS, WebFormBridge, buildRenderResult, chartToTable, collectValues, ensureStyles, fromA2uiAction, fromOpenUiAction, fromVercelToolResult, interactionToFormModel, renderBlocks, renderMiniChart, renderMiniMarkdown, renderRenderResult, shapeInteractionValue, toA2uiMessages, toOpenUiLang, toVercelToolInvocation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webskill/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "WebSkill \u2014 browser/Node agent skill runtime (skills, tools, MCP, governance, UI)",
5
5
  "license": "MIT",
6
6
  "type": "module",