@webskill/sdk 0.2.2 → 0.2.4
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 +4 -0
- package/dist/browser.d.ts +10 -7
- package/dist/browser.js +81 -28
- package/dist/{dist-DrySSQ5R.js → dist-CV64gN62.js} +98 -16
- package/dist/{dist-BJobG0i-.js → dist-CeNAzFYi.js} +1 -1
- package/dist/{dist-Cn9GpW6Q.js → dist-Chgf2tcy.js} +35 -12
- package/dist/{dist-D0saNPi_.js → dist-D7MsoMPx.js} +28 -2
- package/dist/governance.d.ts +13 -6
- package/dist/governance.js +37 -17
- package/dist/{index-gjFuBevI.d.ts → index-DZShzhon.d.ts} +19 -2
- package/dist/{index-BmzysJX5.d.ts → index-DrHelz72.d.ts} +11 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +24 -7
- package/dist/node.d.ts +3 -3
- package/dist/node.js +3 -3
- package/dist/processSandboxEntry.js +11 -0
- package/dist/sandboxWorkerEntry.js +34 -2
- package/dist/{testing-B4pq6JYa.js → testing-BN18eqbD.js} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-CKm5G_eQ-8W8FnP4u.d.ts → types-CKm5G_eQ-BqyXnvoR.d.ts} +8 -5
- package/dist/ui-react.d.ts +1 -1
- package/dist/ui-react.js +1 -1
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +2 -2
- package/package.json +1 -1
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
|
@@ -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-CKm5G_eQ-
|
|
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,
|
|
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-BqyXnvoR.js";
|
|
2
|
+
import { Et as parseBridgeRequest, 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, y as ExternalToolSource } from "./index-DZShzhon.js";
|
|
3
3
|
//#region ../browser/dist/index.d.ts
|
|
4
4
|
//#region src/fs/featureDetection.d.ts
|
|
5
5
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -15,6 +15,7 @@ declare class OpfsProvider implements FileSystemProvider {
|
|
|
15
15
|
});
|
|
16
16
|
readText(p: string): Promise<string>;
|
|
17
17
|
writeText(p: string, content: string): Promise<void>;
|
|
18
|
+
appendText(p: string, content: string): Promise<void>;
|
|
18
19
|
readBinary(p: string): Promise<Uint8Array>;
|
|
19
20
|
writeBinary(p: string, content: Uint8Array): Promise<void>;
|
|
20
21
|
exists(p: string): Promise<boolean>;
|
|
@@ -164,14 +165,16 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
|
|
|
164
165
|
//#region src/executor/iframeSandbox.d.ts
|
|
165
166
|
/**
|
|
166
167
|
* opaque origin 沙箱 iframe(sandbox="allow-scripts" srcdoc):
|
|
167
|
-
* iframe 内建
|
|
168
|
-
* opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS
|
|
169
|
-
*
|
|
168
|
+
* iframe 内建 classic Worker(Blob URL;module Worker 在 opaque origin 不可加载,实证),
|
|
169
|
+
* iframe window 作 postMessage 中继。opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS。
|
|
170
|
+
* 通道鉴别(P0-C):每实例 channelId 随消息携带,宿主校验 event.source 与 channelId,
|
|
171
|
+
* 同页伪造消息一律丢弃;terminate 摘除监听器。
|
|
170
172
|
*/
|
|
171
173
|
declare class IframeWorkerLike implements WorkerLike {
|
|
172
174
|
#private;
|
|
173
|
-
constructor(iframe: HTMLIFrameElement);
|
|
175
|
+
constructor(iframe: HTMLIFrameElement, hostWindow: Window, channelId: string);
|
|
174
176
|
get ready(): boolean;
|
|
177
|
+
get channelId(): string;
|
|
175
178
|
markReady(): void;
|
|
176
179
|
postMessage(data: unknown): void;
|
|
177
180
|
addEventListener(_type: 'message', listener: (event: {
|
|
@@ -181,7 +184,7 @@ declare class IframeWorkerLike implements WorkerLike {
|
|
|
181
184
|
terminate(): void;
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
|
-
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载
|
|
187
|
+
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 classic Worker。
|
|
185
188
|
* 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
|
|
186
189
|
*/
|
|
187
190
|
declare function createIframeWorker(bootstrapSource: string, doc?: Document): IframeWorkerLike;
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
2
|
-
import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi,
|
|
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-D7MsoMPx.js";
|
|
2
|
+
import { A as isNetworkAllowed, C as bridgeError, E as createWebSkillApi, F as normalizeToolError, I as parseBridgeRequest, M as networkUrlHost, P as normalizeToolContent, 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-CV64gN62.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-BN18eqbD.js";
|
|
4
4
|
|
|
5
5
|
//#region ../browser/dist/index.js
|
|
6
6
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -57,6 +57,22 @@ var OpfsProvider = class {
|
|
|
57
57
|
async writeText(p, content) {
|
|
58
58
|
await this.writeBinary(p, new TextEncoder().encode(content));
|
|
59
59
|
}
|
|
60
|
+
async appendText(p, content) {
|
|
61
|
+
await this.#wrap(p, async () => {
|
|
62
|
+
const segments = this.#segments(p);
|
|
63
|
+
const name = segments.pop();
|
|
64
|
+
if (!name) throw new WebSkillError("FS_NOT_FOUND", `Invalid file path: ${p}`);
|
|
65
|
+
const handle = await (await this.#walkDir(`/${segments.join("/")}`, true)).getFileHandle(name, { create: true });
|
|
66
|
+
const size = (await handle.getFile()).size;
|
|
67
|
+
const writable = await handle.createWritable({ keepExistingData: true });
|
|
68
|
+
await writable.write({
|
|
69
|
+
type: "write",
|
|
70
|
+
position: size,
|
|
71
|
+
data: content
|
|
72
|
+
});
|
|
73
|
+
await writable.close();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
60
76
|
async readBinary(p) {
|
|
61
77
|
return this.#wrap(p, async () => {
|
|
62
78
|
const handle = await this.#walkFile(p);
|
|
@@ -232,6 +248,16 @@ function loadModule(source) {
|
|
|
232
248
|
return import(url);
|
|
233
249
|
}
|
|
234
250
|
|
|
251
|
+
function assertSerializable(value) {
|
|
252
|
+
try {
|
|
253
|
+
structuredClone(value);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
var err = new Error('Script returned a non-serializable value: ' + String((e && e.message) || e));
|
|
256
|
+
err.code = 'TOOL_EXECUTION_FAILED';
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
235
261
|
function postError(type, id, code, message, extra) {
|
|
236
262
|
var msg = { type: type, id: id, ok: false, error: { code: code, message: message } };
|
|
237
263
|
if (extra) Object.assign(msg, extra);
|
|
@@ -326,6 +352,7 @@ self.onmessage = async function (event) {
|
|
|
326
352
|
throw new Error('Script does not export a run function');
|
|
327
353
|
}
|
|
328
354
|
var value = await mod2.run(msg.args, makeContext(msg));
|
|
355
|
+
assertSerializable(value);
|
|
329
356
|
self.postMessage({
|
|
330
357
|
type: 'execute-result', id: msg.id, ok: true,
|
|
331
358
|
value: value === undefined ? null : value,
|
|
@@ -714,22 +741,38 @@ var BrowserSkillManager = class {
|
|
|
714
741
|
}
|
|
715
742
|
};
|
|
716
743
|
const ENVELOPE = "__webskill_sandbox__";
|
|
744
|
+
let channelSeq = 0;
|
|
745
|
+
/** 每实例 channelId(防同页伪造消息注入;crypto.randomUUID 优先,无则随机回退) */
|
|
746
|
+
function nextChannelId() {
|
|
747
|
+
return globalThis.crypto?.randomUUID?.() ?? `ch-${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${++channelSeq}`;
|
|
748
|
+
}
|
|
717
749
|
/**
|
|
718
750
|
* opaque origin 沙箱 iframe(sandbox="allow-scripts" srcdoc):
|
|
719
|
-
* iframe 内建
|
|
720
|
-
* opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS
|
|
721
|
-
*
|
|
751
|
+
* iframe 内建 classic Worker(Blob URL;module Worker 在 opaque origin 不可加载,实证),
|
|
752
|
+
* iframe window 作 postMessage 中继。opaque origin:沙箱内摸不到页面 DOM/localStorage/OPFS。
|
|
753
|
+
* 通道鉴别(P0-C):每实例 channelId 随消息携带,宿主校验 event.source 与 channelId,
|
|
754
|
+
* 同页伪造消息一律丢弃;terminate 摘除监听器。
|
|
722
755
|
*/
|
|
723
756
|
var IframeWorkerLike = class {
|
|
724
757
|
#iframe;
|
|
725
758
|
#ready = false;
|
|
726
759
|
#queue = [];
|
|
727
|
-
|
|
760
|
+
#channelId;
|
|
761
|
+
#hostWindow;
|
|
762
|
+
#hostListener;
|
|
763
|
+
constructor(iframe, hostWindow, channelId) {
|
|
728
764
|
this.#iframe = iframe;
|
|
765
|
+
this.#hostWindow = hostWindow;
|
|
766
|
+
this.#channelId = channelId;
|
|
767
|
+
this.#hostListener = (event) => this.#onMessage(event);
|
|
768
|
+
this.#hostWindow.addEventListener("message", this.#hostListener);
|
|
729
769
|
}
|
|
730
770
|
get ready() {
|
|
731
771
|
return this.#ready;
|
|
732
772
|
}
|
|
773
|
+
get channelId() {
|
|
774
|
+
return this.#channelId;
|
|
775
|
+
}
|
|
733
776
|
markReady() {
|
|
734
777
|
this.#ready = true;
|
|
735
778
|
for (const payload of this.#queue) this.#post(payload);
|
|
@@ -738,9 +781,21 @@ var IframeWorkerLike = class {
|
|
|
738
781
|
#post(payload) {
|
|
739
782
|
this.#iframe.contentWindow?.postMessage({
|
|
740
783
|
[ENVELOPE]: true,
|
|
784
|
+
channelId: this.#channelId,
|
|
741
785
|
payload
|
|
742
786
|
}, "*");
|
|
743
787
|
}
|
|
788
|
+
/** 通道鉴别:source 必须是本 iframe 的 contentWindow 且 channelId 匹配,否则丢弃 */
|
|
789
|
+
#onMessage(event) {
|
|
790
|
+
if (event.source !== this.#iframe.contentWindow) return;
|
|
791
|
+
const data = event.data;
|
|
792
|
+
if (!data || data[ENVELOPE] !== true || data.channelId !== this.#channelId) return;
|
|
793
|
+
if (data.payload && data.payload.type === "sandbox-ready") {
|
|
794
|
+
this.markReady();
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
this.emit(data.payload);
|
|
798
|
+
}
|
|
744
799
|
postMessage(data) {
|
|
745
800
|
if (this.#ready) this.#post(data);
|
|
746
801
|
else this.#queue.push(data);
|
|
@@ -753,15 +808,17 @@ var IframeWorkerLike = class {
|
|
|
753
808
|
for (const listener of this.#listeners) listener({ data });
|
|
754
809
|
}
|
|
755
810
|
terminate() {
|
|
811
|
+
this.#hostWindow.removeEventListener("message", this.#hostListener);
|
|
756
812
|
this.#iframe.remove();
|
|
757
813
|
}
|
|
758
814
|
};
|
|
759
815
|
/** srcdoc 文档:内嵌 Worker 引导源码 + 双向中继(BOOTSTRAP 经 JSON 字符串注入,防 <\/script> 截断) */
|
|
760
|
-
function sandboxDocument(bootstrapSource) {
|
|
816
|
+
function sandboxDocument(bootstrapSource, channelId) {
|
|
761
817
|
const bootstrapJson = JSON.stringify(bootstrapSource).replace(/<\//g, "<\\/");
|
|
762
818
|
return `<!doctype html>
|
|
763
819
|
<html><head><meta charset="utf-8"></head><body><script>
|
|
764
820
|
const ENVELOPE = ${JSON.stringify(ENVELOPE)};
|
|
821
|
+
const CHANNEL_ID = ${JSON.stringify(channelId)};
|
|
765
822
|
const blob = new Blob([${bootstrapJson}], { type: 'text/javascript' });
|
|
766
823
|
const url = URL.createObjectURL(blob);
|
|
767
824
|
// 注意:opaque origin 下 module Worker 无法加载(实证),classic Worker + data: URL 动态导入可用
|
|
@@ -769,36 +826,29 @@ const worker = new Worker(url);
|
|
|
769
826
|
URL.revokeObjectURL(url);
|
|
770
827
|
window.addEventListener('message', (event) => {
|
|
771
828
|
const data = event.data;
|
|
772
|
-
|
|
829
|
+
// 中继同样校验 channelId(同页脚本无法伪造实例 id)
|
|
830
|
+
if (data && data[ENVELOPE] && data.channelId === CHANNEL_ID) worker.postMessage(data.payload);
|
|
773
831
|
});
|
|
774
832
|
worker.addEventListener('message', (event) => {
|
|
775
|
-
parent.postMessage({ [ENVELOPE]: true, payload: event.data }, '*');
|
|
833
|
+
parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: event.data }, '*');
|
|
776
834
|
});
|
|
777
835
|
worker.addEventListener('error', (event) => {
|
|
778
|
-
parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-worker-error', message: event.message } }, '*');
|
|
836
|
+
parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: { type: 'sandbox-worker-error', message: event.message } }, '*');
|
|
779
837
|
});
|
|
780
|
-
parent.postMessage({ [ENVELOPE]: true, payload: { type: 'sandbox-ready' } }, '*');
|
|
838
|
+
parent.postMessage({ [ENVELOPE]: true, channelId: CHANNEL_ID, payload: { type: 'sandbox-ready' } }, '*');
|
|
781
839
|
<\/script></body></html>`;
|
|
782
840
|
}
|
|
783
841
|
/**
|
|
784
|
-
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载
|
|
842
|
+
* 创建 opaque iframe 沙箱 Worker:sandbox="allow-scripts" srcdoc iframe 承载 classic Worker。
|
|
785
843
|
* 返回 IframeWorkerLike(协议与 blob Worker 完全一致:load/execute/bridge/network-blocked)。
|
|
786
844
|
*/
|
|
787
845
|
function createIframeWorker(bootstrapSource, doc = document) {
|
|
846
|
+
const channelId = nextChannelId();
|
|
788
847
|
const iframe = doc.createElement("iframe");
|
|
789
848
|
iframe.setAttribute("sandbox", "allow-scripts");
|
|
790
849
|
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
|
-
});
|
|
850
|
+
iframe.srcdoc = sandboxDocument(bootstrapSource, channelId);
|
|
851
|
+
const worker = new IframeWorkerLike(iframe, doc.defaultView ?? window, channelId);
|
|
802
852
|
doc.body.appendChild(iframe);
|
|
803
853
|
return worker;
|
|
804
854
|
}
|
|
@@ -827,6 +877,8 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
827
877
|
#networkPolicy;
|
|
828
878
|
#approval;
|
|
829
879
|
#transpiler;
|
|
880
|
+
/** 消息 id 唯一递增(多实例/多执行不串) */
|
|
881
|
+
#messageSeq = 0;
|
|
830
882
|
constructor(deps) {
|
|
831
883
|
this.#fs = deps.fs;
|
|
832
884
|
this.#workerFactory = deps.workerFactory ?? resolveWorkerFactory(deps.sandbox ?? "auto");
|
|
@@ -871,7 +923,7 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
871
923
|
try {
|
|
872
924
|
const data = await this.#request(worker, {
|
|
873
925
|
type: "load",
|
|
874
|
-
id:
|
|
926
|
+
id: `load-${++this.#messageSeq}`,
|
|
875
927
|
source: script.source,
|
|
876
928
|
networkPolicy: this.#networkPolicy
|
|
877
929
|
});
|
|
@@ -906,7 +958,7 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
906
958
|
try {
|
|
907
959
|
const response = await this.#request(worker, {
|
|
908
960
|
type: "execute",
|
|
909
|
-
id:
|
|
961
|
+
id: `exec-${++this.#messageSeq}`,
|
|
910
962
|
skillName: context.skillName,
|
|
911
963
|
runId: context.runId,
|
|
912
964
|
source: script.source,
|
|
@@ -915,12 +967,13 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
915
967
|
}, timeoutMs, (bridgeRequest) => this.#handleBridge(bridgeRequest, context), (host) => context.onWarning?.(`Network request blocked by sandbox network policy: ${host}`));
|
|
916
968
|
if (!response.ok) {
|
|
917
969
|
const stderrSummary = response.stderr?.length ? ` | stderr: ${response.stderr.join(" | ").slice(0, 500)}` : "";
|
|
970
|
+
const normalized = normalizeToolError(response.error?.code, response.error?.message ?? "script execution failed");
|
|
918
971
|
return {
|
|
919
972
|
ok: false,
|
|
920
973
|
content: [],
|
|
921
974
|
error: {
|
|
922
|
-
code:
|
|
923
|
-
message: `${
|
|
975
|
+
code: normalized.code,
|
|
976
|
+
message: `${normalized.message}${stderrSummary}`
|
|
924
977
|
}
|
|
925
978
|
};
|
|
926
979
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as parseSkillMarkdown, E as renderAvailableSkillsXml, c as SkillDiscovery, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, l as SkillReader, p as buildCatalog, u as WebSkillError } from "./dist-
|
|
1
|
+
import { C as parseSkillMarkdown, E as renderAvailableSkillsXml, c as SkillDiscovery, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, l as SkillReader, p as buildCatalog, u as WebSkillError } from "./dist-D7MsoMPx.js";
|
|
2
2
|
import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
|
|
3
3
|
|
|
4
4
|
//#region ../runtime/dist/index.js
|
|
@@ -272,7 +272,7 @@ var AnthropicClient = class {
|
|
|
272
272
|
"content-type": "application/json",
|
|
273
273
|
"x-api-key": this.#config.apiKey,
|
|
274
274
|
"anthropic-version": ANTHROPIC_VERSION,
|
|
275
|
-
"anthropic-dangerous-direct-browser-access": "true"
|
|
275
|
+
...this.#config.dangerouslyAllowDirectBrowserAccess ? { "anthropic-dangerous-direct-browser-access": "true" } : {}
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
278
|
async complete(input) {
|
|
@@ -571,7 +571,7 @@ var GoogleGenAiClient = class {
|
|
|
571
571
|
}
|
|
572
572
|
async #post(input, stream) {
|
|
573
573
|
const model = input.model ?? this.#config.model;
|
|
574
|
-
const action = stream ?
|
|
574
|
+
const action = stream ? ":streamGenerateContent?alt=sse" : ":generateContent";
|
|
575
575
|
const { systemInstruction, contents } = toGenAiContents(input.messages);
|
|
576
576
|
const body = { contents };
|
|
577
577
|
if (systemInstruction) body["systemInstruction"] = systemInstruction;
|
|
@@ -581,7 +581,10 @@ var GoogleGenAiClient = class {
|
|
|
581
581
|
try {
|
|
582
582
|
res = await this.#fetch(`${this.#baseUrl()}/v1beta/models/${encodeURIComponent(model)}${action}`, {
|
|
583
583
|
method: "POST",
|
|
584
|
-
headers: {
|
|
584
|
+
headers: {
|
|
585
|
+
"content-type": "application/json",
|
|
586
|
+
"x-goog-api-key": this.#config.apiKey
|
|
587
|
+
},
|
|
585
588
|
body: JSON.stringify(body),
|
|
586
589
|
signal: input.signal ?? (this.#config.requestTimeoutMs ? AbortSignal.timeout(this.#config.requestTimeoutMs) : null)
|
|
587
590
|
});
|
|
@@ -597,7 +600,7 @@ var GoogleGenAiClient = class {
|
|
|
597
600
|
/** 轻量探测(GET /v1beta/models),集成测试据此决定 skip */
|
|
598
601
|
async checkAvailability() {
|
|
599
602
|
try {
|
|
600
|
-
return (await this.#fetch(`${this.#baseUrl()}/v1beta/models
|
|
603
|
+
return (await this.#fetch(`${this.#baseUrl()}/v1beta/models`, { headers: { "x-goog-api-key": this.#config.apiKey } })).ok;
|
|
601
604
|
} catch {
|
|
602
605
|
return false;
|
|
603
606
|
}
|
|
@@ -846,11 +849,12 @@ const ASK_USER_TOOL = {
|
|
|
846
849
|
*/
|
|
847
850
|
function createScriptContext(deps) {
|
|
848
851
|
const { fs, artifactStore, skillName, skillRoot, runId, confirm, onWarning, onArtifactCreated } = deps;
|
|
852
|
+
const readFs = fs.withRoot?.(skillRoot) ?? fs;
|
|
849
853
|
return {
|
|
850
854
|
skillName,
|
|
851
855
|
runId,
|
|
852
856
|
async readReference(relativePath) {
|
|
853
|
-
return
|
|
857
|
+
return readFs.readText(resolveInsideRoot(skillRoot, `references/${relativePath}`));
|
|
854
858
|
},
|
|
855
859
|
async writeArtifact(path, content, options) {
|
|
856
860
|
const artifact = typeof content === "string" ? await artifactStore.createTextArtifact({
|
|
@@ -1402,7 +1406,7 @@ var AgentLoop = class {
|
|
|
1402
1406
|
state.messages.push({
|
|
1403
1407
|
role: "tool",
|
|
1404
1408
|
toolCallId: pendingCall.id,
|
|
1405
|
-
content:
|
|
1409
|
+
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1406
1410
|
});
|
|
1407
1411
|
} else if (pending.type === "ask" && pendingCall) {
|
|
1408
1412
|
const value = await this.#interact(state, pending, {
|
|
@@ -1423,14 +1427,24 @@ var AgentLoop = class {
|
|
|
1423
1427
|
state.messages.push({
|
|
1424
1428
|
role: "tool",
|
|
1425
1429
|
toolCallId: pendingCall.id,
|
|
1426
|
-
content:
|
|
1430
|
+
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1427
1431
|
});
|
|
1428
1432
|
} else if (pendingCall) {
|
|
1429
1433
|
const result = await this.#executeCall(pendingCall, state);
|
|
1430
1434
|
state.messages.push({
|
|
1431
1435
|
role: "tool",
|
|
1432
1436
|
toolCallId: pendingCall.id,
|
|
1433
|
-
content:
|
|
1437
|
+
content: await this.#serializeToolResult(pendingCall, result, state)
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
for (;;) {
|
|
1441
|
+
const next = this.#findPendingToolCall(state.messages);
|
|
1442
|
+
if (!next) break;
|
|
1443
|
+
const result = await this.#executeCall(next, state);
|
|
1444
|
+
state.messages.push({
|
|
1445
|
+
role: "tool",
|
|
1446
|
+
toolCallId: next.id,
|
|
1447
|
+
content: await this.#serializeToolResult(next, result, state)
|
|
1434
1448
|
});
|
|
1435
1449
|
}
|
|
1436
1450
|
} catch (e) {
|
|
@@ -1724,14 +1738,20 @@ var AgentLoop = class {
|
|
|
1724
1738
|
const text = JSON.stringify(result);
|
|
1725
1739
|
const max = this.#config.toolResultMaxBytes;
|
|
1726
1740
|
if (text.length <= max) return text;
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1741
|
+
let note;
|
|
1742
|
+
try {
|
|
1743
|
+
note = `full tool result saved as artifact "${(await this.#deps.artifactStore.createTextArtifact({
|
|
1744
|
+
runId: state.runId,
|
|
1745
|
+
path: `tool-results/${call.id}.json`,
|
|
1746
|
+
content: text
|
|
1747
|
+
})).id}"`;
|
|
1748
|
+
} catch (e) {
|
|
1749
|
+
state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf$1(e)}` });
|
|
1750
|
+
note = "full result discarded (artifact store unavailable)";
|
|
1751
|
+
}
|
|
1732
1752
|
const head = text.slice(0, Math.floor(max * .6));
|
|
1733
1753
|
const tail = text.slice(-Math.floor(max * .3));
|
|
1734
|
-
return `${head}\n...[truncated ${text.length - head.length - tail.length} chars;
|
|
1754
|
+
return `${head}\n...[truncated ${text.length - head.length - tail.length} chars; ${note}]...\n${tail}`;
|
|
1735
1755
|
}
|
|
1736
1756
|
/** SkillStateGuard 判定(无注入默认全放行;仅显式 false 拦截) */
|
|
1737
1757
|
async #guardDenied(kind, skillName) {
|
|
@@ -2627,6 +2647,67 @@ function networkUrlHost(url) {
|
|
|
2627
2647
|
return "(unparseable-url)";
|
|
2628
2648
|
}
|
|
2629
2649
|
}
|
|
2650
|
+
/** WebSkillErrorCode 全量白名单(错误码归一用;与 core errors.ts 保持同步) */
|
|
2651
|
+
const WHITELIST = /* @__PURE__ */ new Set([
|
|
2652
|
+
"FS_NOT_FOUND",
|
|
2653
|
+
"FS_PATH_OUTSIDE_ROOT",
|
|
2654
|
+
"SKILL_NOT_FOUND",
|
|
2655
|
+
"SKILL_INVALID_METADATA",
|
|
2656
|
+
"SKILL_INVALID_NAME",
|
|
2657
|
+
"SKILL_DUPLICATE_NAME",
|
|
2658
|
+
"SKILL_UNSUPPORTED_SCRIPT",
|
|
2659
|
+
"VALIDATION_FAILED",
|
|
2660
|
+
"TOOL_NOT_FOUND",
|
|
2661
|
+
"TOOL_EXECUTION_FAILED",
|
|
2662
|
+
"NETWORK_BLOCKED",
|
|
2663
|
+
"TOOL_UNSUPPORTED",
|
|
2664
|
+
"TOOL_SCHEMA_UNAVAILABLE",
|
|
2665
|
+
"RUN_TIMEOUT",
|
|
2666
|
+
"RUN_MAX_TURNS_EXCEEDED",
|
|
2667
|
+
"RUN_FAILED",
|
|
2668
|
+
"RUN_CANCELLED",
|
|
2669
|
+
"RUN_INTERACTION_TIMEOUT",
|
|
2670
|
+
"UI_UNAVAILABLE",
|
|
2671
|
+
"LLM_UNAVAILABLE",
|
|
2672
|
+
"LLM_REQUEST_FAILED",
|
|
2673
|
+
"INSTALL_FAILED",
|
|
2674
|
+
"UNINSTALL_FAILED",
|
|
2675
|
+
"EXPORT_FAILED",
|
|
2676
|
+
"INTEGRITY_FAILED",
|
|
2677
|
+
"FS_PERMISSION_DENIED",
|
|
2678
|
+
"MCP_ENDPOINT_UNAVAILABLE",
|
|
2679
|
+
"MCP_TOOL_NOT_FOUND",
|
|
2680
|
+
"CANDIDATE_INVALID",
|
|
2681
|
+
"APPROVAL_REQUIRED",
|
|
2682
|
+
"SKILL_QUARANTINED",
|
|
2683
|
+
"SKILL_DISABLED",
|
|
2684
|
+
"SKILL_UNKNOWN_ALLOWED_TOOL",
|
|
2685
|
+
"SKILL_UNKNOWN_DEPENDENCY",
|
|
2686
|
+
"SKILL_CIRCULAR_DEPENDENCY",
|
|
2687
|
+
"GOVERNANCE_FAILED",
|
|
2688
|
+
"RUN_SNAPSHOT_NOT_FOUND",
|
|
2689
|
+
"RUN_SNAPSHOT_EXPIRED",
|
|
2690
|
+
"RUN_SNAPSHOT_INCOMPATIBLE"
|
|
2691
|
+
]);
|
|
2692
|
+
/**
|
|
2693
|
+
* 错误码白名单归一:沙箱/桥消息里出现的非白名单码(DOMException 数值码、
|
|
2694
|
+
* Node 任意 ERR_* 码等)一律归为 TOOL_EXECUTION_FAILED。
|
|
2695
|
+
*/
|
|
2696
|
+
function normalizeErrorCode(code) {
|
|
2697
|
+
return typeof code === "string" && WHITELIST.has(code) ? code : "TOOL_EXECUTION_FAILED";
|
|
2698
|
+
}
|
|
2699
|
+
/** 归一化后的 (code, message):非白名单码保留在 message 尾部([original code: X]) */
|
|
2700
|
+
function normalizeToolError(code, message) {
|
|
2701
|
+
const normalized = normalizeErrorCode(code);
|
|
2702
|
+
if (typeof code === "string" && normalized === code) return {
|
|
2703
|
+
code: normalized,
|
|
2704
|
+
message
|
|
2705
|
+
};
|
|
2706
|
+
return {
|
|
2707
|
+
code: normalized,
|
|
2708
|
+
message: `${message} [original code: ${String(code)}]`
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2630
2711
|
/**
|
|
2631
2712
|
* Per-capability 强制授权判定(宿主侧,browser/node 执行器共用单一来源)。
|
|
2632
2713
|
* 'require-approval' 模式下经注入的 UiBridge 发 authorize 交互;
|
|
@@ -2651,6 +2732,7 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
2651
2732
|
async authorize(input) {
|
|
2652
2733
|
const { runId, capability, mode } = input;
|
|
2653
2734
|
if (mode === false) return "disabled";
|
|
2735
|
+
if (mode !== true && mode !== "require-approval") return "disabled";
|
|
2654
2736
|
if (mode !== "require-approval") return "allowed";
|
|
2655
2737
|
if (this.#scope === "once-per-run" && this.#approved.get(runId)?.has(capability)) return "allowed";
|
|
2656
2738
|
if (!this.#uiBridge) return "denied";
|
|
@@ -2678,4 +2760,4 @@ var CapabilityApproval = class CapabilityApproval {
|
|
|
2678
2760
|
};
|
|
2679
2761
|
|
|
2680
2762
|
//#endregion
|
|
2681
|
-
export { isNetworkAllowed as A, bridgeError as C, extractChartSpec as D, createWebSkillApi as E,
|
|
2763
|
+
export { isNetworkAllowed as A, toVercelToolSpecs as B, bridgeError as C, extractChartSpec as D, createWebSkillApi as E, normalizeToolError as F, parseBridgeRequest as I, resolveToolName as L, networkUrlHost as M, normalizeErrorCode as N, fromVercelResult as O, normalizeToolContent as P, schemaToForm as R, WebSkillRuntime as S, createScriptContext as T, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, SerializingMemoryStore as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, mergeCatalogEntries as j, fromVercelStreamPart as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, buildRenderResult as w, TraceRecorder as x, RUN_SNAPSHOT_SCHEMA_VERSION as y, toLlmToolSpec as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
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-
|
|
2
|
-
import { A as isNetworkAllowed, C as bridgeError,
|
|
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-D7MsoMPx.js";
|
|
2
|
+
import { A as isNetworkAllowed, C as bridgeError, F as normalizeToolError, I as parseBridgeRequest, M as networkUrlHost, P as normalizeToolContent, c as FsArtifactStore, l as FsMemoryStore, o as CapabilityApproval } from "./dist-CV64gN62.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { unzipSync, zipSync } from "fflate";
|
|
5
5
|
import { existsSync, promises, realpathSync } from "node:fs";
|
|
@@ -37,13 +37,16 @@ async function realpathNearest(platformPath) {
|
|
|
37
37
|
* 可选 root 模式:构造传入 root 后,read/write 操作先做 realpath 包含校验
|
|
38
38
|
* (root 与目标都 realpath 后前缀比对),经符号链接逃逸 root → FS_PATH_OUTSIDE_ROOT。
|
|
39
39
|
*/
|
|
40
|
-
var NodeFS = class {
|
|
40
|
+
var NodeFS = class NodeFS {
|
|
41
41
|
kind = "node";
|
|
42
42
|
#root;
|
|
43
43
|
constructor(deps = {}) {
|
|
44
44
|
this.#root = deps.root;
|
|
45
45
|
}
|
|
46
|
-
/** root
|
|
46
|
+
/** root 模式:全部方法(read/write/exists/stat/list/mkdir/remove/rename)目标 realpath 必须落在 root realpath 前缀内 */
|
|
47
|
+
withRoot(root) {
|
|
48
|
+
return new NodeFS({ root });
|
|
49
|
+
}
|
|
47
50
|
async #assertContained(p) {
|
|
48
51
|
if (!this.#root) return;
|
|
49
52
|
const rootReal = await promises.realpath(toPlatform$4(this.#root));
|
|
@@ -64,6 +67,12 @@ var NodeFS = class {
|
|
|
64
67
|
await promises.mkdir(path.dirname(target), { recursive: true });
|
|
65
68
|
await promises.writeFile(target, content, "utf8");
|
|
66
69
|
}
|
|
70
|
+
async appendText(p, content) {
|
|
71
|
+
await this.#assertContained(p);
|
|
72
|
+
const target = toPlatform$4(p);
|
|
73
|
+
await promises.mkdir(path.dirname(target), { recursive: true });
|
|
74
|
+
await promises.appendFile(target, content, "utf8");
|
|
75
|
+
}
|
|
67
76
|
async readBinary(p) {
|
|
68
77
|
await this.#assertContained(p);
|
|
69
78
|
try {
|
|
@@ -79,6 +88,7 @@ var NodeFS = class {
|
|
|
79
88
|
await promises.writeFile(target, content);
|
|
80
89
|
}
|
|
81
90
|
async exists(p) {
|
|
91
|
+
await this.#assertContained(p);
|
|
82
92
|
try {
|
|
83
93
|
await promises.access(toPlatform$4(p));
|
|
84
94
|
return true;
|
|
@@ -87,6 +97,7 @@ var NodeFS = class {
|
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
async stat(p) {
|
|
100
|
+
await this.#assertContained(p);
|
|
90
101
|
try {
|
|
91
102
|
const s = await promises.stat(toPlatform$4(p));
|
|
92
103
|
return {
|
|
@@ -100,6 +111,7 @@ var NodeFS = class {
|
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
async list(p) {
|
|
114
|
+
await this.#assertContained(p);
|
|
103
115
|
let dirents;
|
|
104
116
|
try {
|
|
105
117
|
dirents = await promises.readdir(toPlatform$4(p), { withFileTypes: true });
|
|
@@ -112,9 +124,11 @@ var NodeFS = class {
|
|
|
112
124
|
}));
|
|
113
125
|
}
|
|
114
126
|
async mkdir(p) {
|
|
127
|
+
await this.#assertContained(p);
|
|
115
128
|
await promises.mkdir(toPlatform$4(p), { recursive: true });
|
|
116
129
|
}
|
|
117
130
|
async remove(p, options) {
|
|
131
|
+
await this.#assertContained(p);
|
|
118
132
|
try {
|
|
119
133
|
await promises.rm(toPlatform$4(p), { recursive: options?.recursive ?? false });
|
|
120
134
|
} catch (e) {
|
|
@@ -122,6 +136,8 @@ var NodeFS = class {
|
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
138
|
async rename(from, to) {
|
|
139
|
+
await this.#assertContained(from);
|
|
140
|
+
await this.#assertContained(to);
|
|
125
141
|
await promises.mkdir(path.dirname(toPlatform$4(to)), { recursive: true });
|
|
126
142
|
try {
|
|
127
143
|
await promises.rename(toPlatform$4(from), toPlatform$4(to));
|
|
@@ -303,8 +319,10 @@ function workerEntryPath() {
|
|
|
303
319
|
* 能力桥协议与浏览器同一来源(runtime/sandbox/bridgeProtocol)。
|
|
304
320
|
*
|
|
305
321
|
* 诚实标注:本执行器做的是**能力面收敛**(网络策略、模块 allowlist、资源限额、
|
|
306
|
-
* 超时强杀),**不是安全边界**——Worker
|
|
307
|
-
*
|
|
322
|
+
* 超时强杀),**不是安全边界**——Worker 内脚本与宿主共享进程;
|
|
323
|
+
* 已知主动逃逸面(process.binding/_linkedBinding/dlopen/openStdin/reallyExit/abort)
|
|
324
|
+
* 已在入口删除(0.2.3),常规 import 拦截之外不承诺防御其它宿主共享面;
|
|
325
|
+
* 禁止假定其可隔离不可信脚本(隔离级需求用 ProcessSandboxExecutor)。
|
|
308
326
|
*/
|
|
309
327
|
var SandboxedScriptExecutor = class {
|
|
310
328
|
#fs;
|
|
@@ -379,12 +397,13 @@ var SandboxedScriptExecutor = class {
|
|
|
379
397
|
}, timeoutMs, (request) => this.#handleBridge(request, context), (host) => context.onWarning?.(`Network request blocked by sandbox network policy: ${host}`));
|
|
380
398
|
if (!result.ok) {
|
|
381
399
|
const stderrSummary = result.stderr?.length ? ` | stderr: ${result.stderr.join(" | ").slice(0, 500)}` : "";
|
|
400
|
+
const normalized = normalizeToolError(result.error?.code, result.error?.message ?? "script execution failed");
|
|
382
401
|
return {
|
|
383
402
|
ok: false,
|
|
384
403
|
content: [],
|
|
385
404
|
error: {
|
|
386
|
-
code:
|
|
387
|
-
message: `${
|
|
405
|
+
code: normalized.code,
|
|
406
|
+
message: `${normalized.message}${stderrSummary}`
|
|
388
407
|
}
|
|
389
408
|
};
|
|
390
409
|
}
|
|
@@ -461,7 +480,7 @@ var SandboxedScriptExecutor = class {
|
|
|
461
480
|
});
|
|
462
481
|
worker.on("error", (e) => done(() => reject(e)));
|
|
463
482
|
worker.on("exit", (code) => {
|
|
464
|
-
if (
|
|
483
|
+
if (!settled) done(() => reject(new WebSkillError("TOOL_EXECUTION_FAILED", `Sandbox worker exited ${code === null ? "by signal" : `with code ${code}`} without producing a result`)));
|
|
465
484
|
});
|
|
466
485
|
});
|
|
467
486
|
}
|
|
@@ -644,8 +663,11 @@ var ProcessSandboxExecutor = class {
|
|
|
644
663
|
...readAllow(artifactDir),
|
|
645
664
|
...writeAllow(artifactDir)
|
|
646
665
|
],
|
|
647
|
-
|
|
666
|
+
env: Object.fromEntries((this.#options.envWhitelist ?? []).filter((key) => process.env[key] !== void 0).map((key) => [key, process.env[key]])),
|
|
667
|
+
silent: true
|
|
648
668
|
});
|
|
669
|
+
child.stdout?.resume();
|
|
670
|
+
child.stderr?.resume();
|
|
649
671
|
child.unref();
|
|
650
672
|
child.channel?.unref();
|
|
651
673
|
return {
|
|
@@ -708,12 +730,13 @@ var ProcessSandboxExecutor = class {
|
|
|
708
730
|
}, timeoutMs, (request) => this.#handleBridge(request, context), (host) => context.onWarning?.(`Network request blocked by sandbox network policy: ${host}`));
|
|
709
731
|
if (!result.ok) {
|
|
710
732
|
const stderrSummary = result.stderr?.length ? ` | stderr: ${result.stderr.join(" | ").slice(0, 500)}` : "";
|
|
733
|
+
const normalized = normalizeToolError(result.error?.code, result.error?.message ?? "script execution failed");
|
|
711
734
|
return {
|
|
712
735
|
ok: false,
|
|
713
736
|
content: [],
|
|
714
737
|
error: {
|
|
715
|
-
code:
|
|
716
|
-
message: `${
|
|
738
|
+
code: normalized.code,
|
|
739
|
+
message: `${normalized.message}${stderrSummary}`
|
|
717
740
|
}
|
|
718
741
|
};
|
|
719
742
|
}
|