@webskill/sdk 0.0.3 → 0.0.5
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/dist/browser.d.ts +26 -4
- package/dist/browser.js +233 -20
- package/dist/{dist-RcqvzAGF.js → dist-BM-VcQ90.js} +31 -7
- package/dist/{dist-hMMxARXK.js → dist-D405AlPD.js} +503 -113
- package/dist/{dist-C1p26Ap9.js → dist-Ixnb-hNR.js} +60 -12
- package/dist/governance.d.ts +2 -2
- package/dist/governance.js +2 -2
- package/dist/{index-DnI0BTEp.d.ts → index-BQDc-U1x.d.ts} +141 -7
- package/dist/{index-D65Jk0ju.d.ts → index-aEple804.d.ts} +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/dist/ui-react.d.ts +1 -1
- package/dist/ui-react.js +2 -2
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +2 -2
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as InteractionResponse, F as LlmClient, Gt as FileSystemProvider, Lt as parseBridgeRequest, O as InteractionPolicy, Ot as bridgeError, R as LlmResponse, Wt as FileStat, Y as NetworkPolicy, _t as ToolDefinition, an as SkillManifest, c as ApprovalScope, d as BridgeCapabilities, dn as VerifyResult, ht as ScriptExecutor, it as RenderResultRequest, k as InteractionRequest, ln as SkillsLockfile, m as BridgeResponse, mt as ScriptExecutionContext, nn as SkillInstallSource, p as BridgeRequest, v as ExternalSkillProvider, wt as UiBridge, y as ExternalToolSource, yt as ToolResult, z as LlmStreamEvent } from "./index-BQDc-U1x.js";
|
|
2
2
|
//#region ../browser/dist/index.d.ts
|
|
3
3
|
//#region src/fs/featureDetection.d.ts
|
|
4
4
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -28,17 +28,22 @@ declare class OpfsProvider implements FileSystemProvider {
|
|
|
28
28
|
//#region src/executor/workerBootstrap.d.ts
|
|
29
29
|
/**
|
|
30
30
|
* Worker 引导源码(字符串 → Blob URL → new Worker(url, {type:'module'}))。
|
|
31
|
-
* 注意:内容为纯 JS,不能含反引号与 ${
|
|
31
|
+
* 注意:内容为纯 JS,不能含反引号与 ${ 占位(外层用模板字符串内嵌);
|
|
32
|
+
* isNetworkAllowed/networkUrlHost 经函数源码注入(匹配逻辑单一来源在
|
|
33
|
+
* runtime/sandbox/networkPolicy.ts,此处禁止复制实现)。
|
|
32
34
|
*
|
|
33
35
|
* 协议(宿主 ↔ Worker):
|
|
34
|
-
* - 宿主 → Worker:{type:'load', id, source} 请求加载脚本定义
|
|
35
|
-
* - 宿主 → Worker:{type:'execute', id, skillName, runId, source, args} 请求执行
|
|
36
|
+
* - 宿主 → Worker:{type:'load', id, source, networkPolicy?} 请求加载脚本定义
|
|
37
|
+
* - 宿主 → Worker:{type:'execute', id, skillName, runId, source, args, networkPolicy?} 请求执行
|
|
36
38
|
* - Worker → 宿主:{type:'load-result', id, ok, definition?, error?}
|
|
37
39
|
* - Worker → 宿主:{type:'execute-result', id, ok, value?, stdout, stderr, error?}
|
|
38
40
|
* - Worker → 宿主:{type:'bridge', request} 能力桥 RPC(readReference/writeArtifact/confirm)
|
|
39
41
|
* - 宿主 → Worker:{type:'bridge-response', response}
|
|
42
|
+
* - Worker → 宿主:{type:'network-blocked', host} 网络阻断通知(宿主记 run.warning,URL 脱敏只记 host)
|
|
40
43
|
*
|
|
41
44
|
* .js 源码经 data: URL 动态 import;主线程从不 import 技能脚本。
|
|
45
|
+
* 网络管控:包装 fetch / XMLHttpRequest / WebSocket / EventSource,
|
|
46
|
+
* 策略默认 deny-all,由 load/execute 消息携带更新。
|
|
42
47
|
*/
|
|
43
48
|
declare const WORKER_BOOTSTRAP_SOURCE: string;
|
|
44
49
|
//#endregion
|
|
@@ -116,6 +121,12 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
|
|
|
116
121
|
fs: FileSystemProvider;
|
|
117
122
|
workerFactory?: WorkerFactory;
|
|
118
123
|
capabilities?: BridgeCapabilities;
|
|
124
|
+
/** 网络策略:默认 'deny-all'(白名单见 runtime/sandbox/networkPolicy) */
|
|
125
|
+
networkPolicy?: NetworkPolicy;
|
|
126
|
+
/** require-approval 模式的授权询问出口(缺失时 require-approval 一律拒绝) */
|
|
127
|
+
uiBridge?: UiBridge;
|
|
128
|
+
/** 授权粒度:默认 'once-per-run' */
|
|
129
|
+
approvalScope?: ApprovalScope;
|
|
119
130
|
/** D4 opt-in:配置 esbuild-wasm 资产 URL 后 .ts 经宿主侧转译执行(未配置 .ts → TOOL_UNSUPPORTED) */
|
|
120
131
|
typescript?: TypeScriptSupportOptions;
|
|
121
132
|
});
|
|
@@ -165,6 +176,13 @@ type WorkerRequest = {
|
|
|
165
176
|
type: 'cancel';
|
|
166
177
|
id: string;
|
|
167
178
|
runId: string;
|
|
179
|
+
} | {
|
|
180
|
+
type: 'list-interrupted';
|
|
181
|
+
id: string;
|
|
182
|
+
} | {
|
|
183
|
+
type: 'resume';
|
|
184
|
+
id: string;
|
|
185
|
+
runId: string;
|
|
168
186
|
} | {
|
|
169
187
|
type: 'status';
|
|
170
188
|
id: string;
|
|
@@ -260,6 +278,10 @@ declare class WorkerRuntimeClient {
|
|
|
260
278
|
run(prompt: string, runId?: string): Promise<WorkerEvent>;
|
|
261
279
|
status(runId: string): Promise<WorkerEvent>;
|
|
262
280
|
cancel(runId: string): Promise<WorkerEvent>;
|
|
281
|
+
/** D3:列出可恢复的 interrupted run */
|
|
282
|
+
listInterrupted(): Promise<WorkerEvent>;
|
|
283
|
+
/** D3:恢复 interrupted run(新 Worker 重建后重新发起等待中的交互) */
|
|
284
|
+
resume(runId: string): Promise<WorkerEvent>;
|
|
263
285
|
dispose(): void;
|
|
264
286
|
}
|
|
265
287
|
//#endregion
|
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as mergeCatalogEntries, B as SKILL_MANIFEST_FILE, G as WebSkillError, K as buildCatalog, M as normalizeToolContent, N as parseBridgeRequest, U as SkillDiscovery, Z as isValidSkillName, _ as ProgressiveRouter, a as CapabilityApproval, at as verifyManifest, c as FsMemoryStore, et as parseSkillMarkdown, g as OpenAiCompatibleClient, i as AgentLoop, it as validateSkills, j as networkUrlHost, k as isNetworkAllowed, l as FsRunSnapshotStore, m as MockLlmClient, q as buildManifest, rt as resolveInsideRoot, s as FsArtifactStore, w as bridgeError, x as RUN_SNAPSHOT_SCHEMA_VERSION, z as SKILLS_LOCKFILE } from "./dist-D405AlPD.js";
|
|
2
2
|
import { unzipSync } from "fflate";
|
|
3
3
|
|
|
4
4
|
//#region ../browser/dist/index.js
|
|
@@ -134,23 +134,92 @@ var OpfsProvider = class {
|
|
|
134
134
|
};
|
|
135
135
|
/**
|
|
136
136
|
* Worker 引导源码(字符串 → Blob URL → new Worker(url, {type:'module'}))。
|
|
137
|
-
* 注意:内容为纯 JS,不能含反引号与 ${
|
|
137
|
+
* 注意:内容为纯 JS,不能含反引号与 ${ 占位(外层用模板字符串内嵌);
|
|
138
|
+
* isNetworkAllowed/networkUrlHost 经函数源码注入(匹配逻辑单一来源在
|
|
139
|
+
* runtime/sandbox/networkPolicy.ts,此处禁止复制实现)。
|
|
138
140
|
*
|
|
139
141
|
* 协议(宿主 ↔ Worker):
|
|
140
|
-
* - 宿主 → Worker:{type:'load', id, source} 请求加载脚本定义
|
|
141
|
-
* - 宿主 → Worker:{type:'execute', id, skillName, runId, source, args} 请求执行
|
|
142
|
+
* - 宿主 → Worker:{type:'load', id, source, networkPolicy?} 请求加载脚本定义
|
|
143
|
+
* - 宿主 → Worker:{type:'execute', id, skillName, runId, source, args, networkPolicy?} 请求执行
|
|
142
144
|
* - Worker → 宿主:{type:'load-result', id, ok, definition?, error?}
|
|
143
145
|
* - Worker → 宿主:{type:'execute-result', id, ok, value?, stdout, stderr, error?}
|
|
144
146
|
* - Worker → 宿主:{type:'bridge', request} 能力桥 RPC(readReference/writeArtifact/confirm)
|
|
145
147
|
* - 宿主 → Worker:{type:'bridge-response', response}
|
|
148
|
+
* - Worker → 宿主:{type:'network-blocked', host} 网络阻断通知(宿主记 run.warning,URL 脱敏只记 host)
|
|
146
149
|
*
|
|
147
150
|
* .js 源码经 data: URL 动态 import;主线程从不 import 技能脚本。
|
|
151
|
+
* 网络管控:包装 fetch / XMLHttpRequest / WebSocket / EventSource,
|
|
152
|
+
* 策略默认 deny-all,由 load/execute 消息携带更新。
|
|
148
153
|
*/
|
|
149
154
|
const WORKER_BOOTSTRAP_SOURCE = String.raw`
|
|
150
155
|
'use strict';
|
|
151
156
|
|
|
152
157
|
var pending = new Map();
|
|
153
158
|
var bridgeSeq = 0;
|
|
159
|
+
var networkPolicy = 'deny-all';
|
|
160
|
+
|
|
161
|
+
${isNetworkAllowed.toString()}
|
|
162
|
+
|
|
163
|
+
${networkUrlHost.toString()}
|
|
164
|
+
|
|
165
|
+
function resolveUrl(raw) {
|
|
166
|
+
try {
|
|
167
|
+
return new URL(raw, self.location.href).href;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return String(raw);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function networkGate(rawUrl) {
|
|
174
|
+
var url = resolveUrl(rawUrl);
|
|
175
|
+
if (isNetworkAllowed(networkPolicy, url)) return null;
|
|
176
|
+
var host = networkUrlHost(url);
|
|
177
|
+
self.postMessage({ type: 'network-blocked', host: host });
|
|
178
|
+
var err = new Error('Network request blocked by sandbox network policy: ' + host);
|
|
179
|
+
err.code = 'NETWORK_BLOCKED';
|
|
180
|
+
return err;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (typeof self.fetch === 'function') {
|
|
184
|
+
var originalFetch = self.fetch;
|
|
185
|
+
self.fetch = function (input, init) {
|
|
186
|
+
var raw = typeof input === 'string' ? input : input && input.url ? input.url : String(input);
|
|
187
|
+
var blocked = networkGate(raw);
|
|
188
|
+
if (blocked) return Promise.reject(blocked);
|
|
189
|
+
return originalFetch.call(this, input, init);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof XMLHttpRequest !== 'undefined') {
|
|
194
|
+
var originalXhrOpen = XMLHttpRequest.prototype.open;
|
|
195
|
+
XMLHttpRequest.prototype.open = function (method, url) {
|
|
196
|
+
var blocked = networkGate(url);
|
|
197
|
+
if (blocked) throw blocked;
|
|
198
|
+
return originalXhrOpen.apply(this, arguments);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof WebSocket !== 'undefined') {
|
|
203
|
+
var OriginalWebSocket = WebSocket;
|
|
204
|
+
self.WebSocket = function (url, protocols) {
|
|
205
|
+
var blocked = networkGate(url);
|
|
206
|
+
if (blocked) throw blocked;
|
|
207
|
+
return protocols === undefined
|
|
208
|
+
? new OriginalWebSocket(url)
|
|
209
|
+
: new OriginalWebSocket(url, protocols);
|
|
210
|
+
};
|
|
211
|
+
self.WebSocket.prototype = OriginalWebSocket.prototype;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (typeof EventSource !== 'undefined') {
|
|
215
|
+
var OriginalEventSource = EventSource;
|
|
216
|
+
self.EventSource = function (url, config) {
|
|
217
|
+
var blocked = networkGate(url);
|
|
218
|
+
if (blocked) throw blocked;
|
|
219
|
+
return config === undefined ? new OriginalEventSource(url) : new OriginalEventSource(url, config);
|
|
220
|
+
};
|
|
221
|
+
self.EventSource.prototype = OriginalEventSource.prototype;
|
|
222
|
+
}
|
|
154
223
|
|
|
155
224
|
function loadModule(source) {
|
|
156
225
|
var url = 'data:text/javascript;charset=utf-8,' + encodeURIComponent(source);
|
|
@@ -212,6 +281,7 @@ self.onmessage = async function (event) {
|
|
|
212
281
|
}
|
|
213
282
|
|
|
214
283
|
if (msg.type === 'load') {
|
|
284
|
+
if (msg.networkPolicy !== undefined) networkPolicy = msg.networkPolicy;
|
|
215
285
|
try {
|
|
216
286
|
var mod = await loadModule(msg.source);
|
|
217
287
|
self.postMessage({
|
|
@@ -225,12 +295,13 @@ self.onmessage = async function (event) {
|
|
|
225
295
|
},
|
|
226
296
|
});
|
|
227
297
|
} catch (e) {
|
|
228
|
-
postError('load-result', msg.id, 'TOOL_EXECUTION_FAILED', String((e && e.message) || e));
|
|
298
|
+
postError('load-result', msg.id, (e && e.code) || 'TOOL_EXECUTION_FAILED', String((e && e.message) || e));
|
|
229
299
|
}
|
|
230
300
|
return;
|
|
231
301
|
}
|
|
232
302
|
|
|
233
303
|
if (msg.type === 'execute') {
|
|
304
|
+
if (msg.networkPolicy !== undefined) networkPolicy = msg.networkPolicy;
|
|
234
305
|
var stdout = [];
|
|
235
306
|
var stderr = [];
|
|
236
307
|
var originals = {
|
|
@@ -543,6 +614,8 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
543
614
|
#fs;
|
|
544
615
|
#workerFactory;
|
|
545
616
|
#capabilities;
|
|
617
|
+
#networkPolicy;
|
|
618
|
+
#approval;
|
|
546
619
|
#transpiler;
|
|
547
620
|
constructor(deps) {
|
|
548
621
|
this.#fs = deps.fs;
|
|
@@ -556,6 +629,11 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
556
629
|
writeArtifact: deps.capabilities?.writeArtifact ?? true,
|
|
557
630
|
confirm: deps.capabilities?.confirm ?? true
|
|
558
631
|
};
|
|
632
|
+
this.#networkPolicy = deps.networkPolicy ?? "deny-all";
|
|
633
|
+
this.#approval = new CapabilityApproval({
|
|
634
|
+
...deps.uiBridge ? { uiBridge: deps.uiBridge } : {},
|
|
635
|
+
...deps.approvalScope ? { scope: deps.approvalScope } : {}
|
|
636
|
+
});
|
|
559
637
|
}
|
|
560
638
|
/** 定位 scripts/<name>.js;.ts → TOOL_UNSUPPORTED(D4);冲突/缺失结构化报错 */
|
|
561
639
|
async #locateScript(skillRoot, scriptName) {
|
|
@@ -584,7 +662,8 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
584
662
|
const data = await this.#request(worker, {
|
|
585
663
|
type: "load",
|
|
586
664
|
id: "load-1",
|
|
587
|
-
source: script.source
|
|
665
|
+
source: script.source,
|
|
666
|
+
networkPolicy: this.#networkPolicy
|
|
588
667
|
});
|
|
589
668
|
if (!data.ok) throw new WebSkillError("TOOL_EXECUTION_FAILED", `Failed to load script "${scriptName}": ${data.error?.message ?? "unknown error"}`);
|
|
590
669
|
return {
|
|
@@ -621,8 +700,9 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
621
700
|
skillName: context.skillName,
|
|
622
701
|
runId: context.runId,
|
|
623
702
|
source: script.source,
|
|
624
|
-
args
|
|
625
|
-
|
|
703
|
+
args,
|
|
704
|
+
networkPolicy: this.#networkPolicy
|
|
705
|
+
}, timeoutMs, (bridgeRequest) => this.#handleBridge(bridgeRequest, context), (host) => context.onWarning?.(`Network request blocked by sandbox network policy: ${host}`));
|
|
626
706
|
if (!response.ok) {
|
|
627
707
|
const stderrSummary = response.stderr?.length ? ` | stderr: ${response.stderr.join(" | ").slice(0, 500)}` : "";
|
|
628
708
|
return {
|
|
@@ -656,8 +736,8 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
656
736
|
worker.terminate();
|
|
657
737
|
}
|
|
658
738
|
}
|
|
659
|
-
/** 发消息并按 id 等回包;可选超时;onBridge
|
|
660
|
-
#request(worker, message, timeoutMs, onBridge) {
|
|
739
|
+
/** 发消息并按 id 等回包;可选超时;onBridge 处理执行期间的能力桥请求;onNetworkBlocked 收网络阻断通知 */
|
|
740
|
+
#request(worker, message, timeoutMs, onBridge, onNetworkBlocked) {
|
|
661
741
|
const id = String(message["id"]);
|
|
662
742
|
return new Promise((resolve, reject) => {
|
|
663
743
|
let settled = false;
|
|
@@ -670,6 +750,10 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
670
750
|
worker.addEventListener("message", (event) => {
|
|
671
751
|
const data = event.data;
|
|
672
752
|
if (!data || typeof data !== "object") return;
|
|
753
|
+
if (data.type === "network-blocked") {
|
|
754
|
+
if (typeof data.host === "string") onNetworkBlocked?.(data.host);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
673
757
|
if (data.type === "bridge" && onBridge) {
|
|
674
758
|
const request = parseBridgeRequest(data.request);
|
|
675
759
|
const respond = (response) => worker.postMessage({
|
|
@@ -691,20 +775,36 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
691
775
|
worker.postMessage(message);
|
|
692
776
|
});
|
|
693
777
|
}
|
|
694
|
-
/**
|
|
778
|
+
/** 能力桥宿主侧处理:能力关闭/授权拒绝即 TOOL_UNSUPPORTED;判定逻辑共用 runtime/sandbox/approval */
|
|
695
779
|
async #handleBridge(request, context) {
|
|
696
|
-
const
|
|
780
|
+
const gate = async (capability, message, details) => {
|
|
781
|
+
const decision = await this.#approval.authorize({
|
|
782
|
+
runId: context.runId,
|
|
783
|
+
capability,
|
|
784
|
+
mode: this.#capabilities[capability],
|
|
785
|
+
message,
|
|
786
|
+
...details !== void 0 ? { details } : {}
|
|
787
|
+
});
|
|
788
|
+
if (decision === "allowed") return void 0;
|
|
789
|
+
return bridgeError(request.id, "TOOL_UNSUPPORTED", decision === "disabled" ? `Capability "${capability}" is disabled` : `Capability "${capability}" was denied by the user`);
|
|
790
|
+
};
|
|
697
791
|
try {
|
|
698
792
|
switch (request.kind) {
|
|
699
|
-
case "readReference":
|
|
700
|
-
|
|
793
|
+
case "readReference": {
|
|
794
|
+
const denied = await gate("readReference", `Script "${context.skillName}" wants to read reference "${request.path}"`, { path: request.path });
|
|
795
|
+
if (denied) return denied;
|
|
701
796
|
return {
|
|
702
797
|
id: request.id,
|
|
703
798
|
ok: true,
|
|
704
799
|
value: await context.readReference(request.path)
|
|
705
800
|
};
|
|
801
|
+
}
|
|
706
802
|
case "writeArtifact": {
|
|
707
|
-
|
|
803
|
+
const denied = await gate("writeArtifact", `Script "${context.skillName}" wants to write artifact "${request.path}"`, {
|
|
804
|
+
path: request.path,
|
|
805
|
+
mimeType: request.mimeType
|
|
806
|
+
});
|
|
807
|
+
if (denied) return denied;
|
|
708
808
|
const content = typeof request.content === "string" ? request.content : new Uint8Array(request.content);
|
|
709
809
|
const artifact = await context.writeArtifact(request.path, content, { ...request.mimeType ? { mimeType: request.mimeType } : {} });
|
|
710
810
|
return {
|
|
@@ -713,13 +813,16 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
713
813
|
value: artifact
|
|
714
814
|
};
|
|
715
815
|
}
|
|
716
|
-
case "confirm":
|
|
717
|
-
if (!
|
|
816
|
+
case "confirm": {
|
|
817
|
+
if (!context.confirm) return bridgeError(request.id, "TOOL_UNSUPPORTED", "Capability \"confirm\" is disabled");
|
|
818
|
+
const denied = await gate("confirm", `Script "${context.skillName}" asks for confirmation: ${request.message}`);
|
|
819
|
+
if (denied) return denied;
|
|
718
820
|
return {
|
|
719
821
|
id: request.id,
|
|
720
822
|
ok: true,
|
|
721
823
|
value: await context.confirm(request.message)
|
|
722
824
|
};
|
|
825
|
+
}
|
|
723
826
|
}
|
|
724
827
|
} catch (e) {
|
|
725
828
|
return bridgeError(request.id, e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", messageOf(e));
|
|
@@ -744,11 +847,16 @@ function parseMainMessage(data) {
|
|
|
744
847
|
...isNonEmptyString(data["runId"]) ? { runId: data["runId"] } : {}
|
|
745
848
|
} : void 0;
|
|
746
849
|
case "cancel":
|
|
747
|
-
case "status":
|
|
850
|
+
case "status":
|
|
851
|
+
case "resume": return isNonEmptyString(data["id"]) && isNonEmptyString(data["runId"]) ? {
|
|
748
852
|
type: data["type"],
|
|
749
853
|
id: data["id"],
|
|
750
854
|
runId: data["runId"]
|
|
751
855
|
} : void 0;
|
|
856
|
+
case "list-interrupted": return isNonEmptyString(data["id"]) ? {
|
|
857
|
+
type: "list-interrupted",
|
|
858
|
+
id: data["id"]
|
|
859
|
+
} : void 0;
|
|
752
860
|
case "interaction-response": {
|
|
753
861
|
const response = data["response"];
|
|
754
862
|
return isNonEmptyString(data["workerMessageId"]) && isRecord(response) && isNonEmptyString(response["id"]) ? {
|
|
@@ -890,7 +998,11 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
|
|
|
890
998
|
...config.typescript ? { typescript: config.typescript } : {}
|
|
891
999
|
}),
|
|
892
1000
|
...config.interaction ? { interaction: config.interaction } : {},
|
|
893
|
-
roots: config.roots ?? ["/skills"]
|
|
1001
|
+
roots: config.roots ?? ["/skills"],
|
|
1002
|
+
snapshotStore: new FsRunSnapshotStore({
|
|
1003
|
+
root: "snapshots",
|
|
1004
|
+
fs
|
|
1005
|
+
})
|
|
894
1006
|
};
|
|
895
1007
|
post({
|
|
896
1008
|
type: "ready",
|
|
@@ -936,7 +1048,8 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
|
|
|
936
1048
|
uiBridge,
|
|
937
1049
|
...state.interaction ? { interaction: state.interaction } : {},
|
|
938
1050
|
...overrides.externalTools ? { externalTools: overrides.externalTools } : {},
|
|
939
|
-
...overrides.skillProviders ? { skillProviders: overrides.skillProviders } : {}
|
|
1051
|
+
...overrides.skillProviders ? { skillProviders: overrides.skillProviders } : {},
|
|
1052
|
+
snapshotStore: state.snapshotStore
|
|
940
1053
|
}).run({
|
|
941
1054
|
sessionId: `session-${runId}`,
|
|
942
1055
|
userPrompt: prompt,
|
|
@@ -977,6 +1090,91 @@ function startWorkerRuntimeHost(scope, overrides = {}) {
|
|
|
977
1090
|
} } });
|
|
978
1091
|
return;
|
|
979
1092
|
}
|
|
1093
|
+
case "list-interrupted":
|
|
1094
|
+
if (!state) {
|
|
1095
|
+
result(message.id, { error: {
|
|
1096
|
+
code: "RUN_FAILED",
|
|
1097
|
+
message: "Host is not initialized"
|
|
1098
|
+
} });
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
state.snapshotStore.list().then((snapshots) => result(message.id, { result: { snapshots } }), (e) => result(message.id, { error: serializeError(e) }));
|
|
1102
|
+
return;
|
|
1103
|
+
case "resume":
|
|
1104
|
+
if (!state) {
|
|
1105
|
+
result(message.id, { error: {
|
|
1106
|
+
code: "RUN_FAILED",
|
|
1107
|
+
message: "Host is not initialized"
|
|
1108
|
+
} });
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
(async () => {
|
|
1112
|
+
const snapshot = await state.snapshotStore.load(message.runId);
|
|
1113
|
+
if (!snapshot) throw new WebSkillError("RUN_SNAPSHOT_NOT_FOUND", `No snapshot found for run "${message.runId}"`);
|
|
1114
|
+
if (snapshot.schemaVersion !== 1) {
|
|
1115
|
+
await state.snapshotStore.delete(message.runId);
|
|
1116
|
+
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${message.runId}" has incompatible schemaVersion ${String(snapshot.schemaVersion)}`);
|
|
1117
|
+
}
|
|
1118
|
+
if (Date.now() > Date.parse(snapshot.interactionExpiresAt)) {
|
|
1119
|
+
await state.snapshotStore.delete(message.runId);
|
|
1120
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1121
|
+
return {
|
|
1122
|
+
output: "Interaction timed out before the run was resumed",
|
|
1123
|
+
run: {
|
|
1124
|
+
id: message.runId,
|
|
1125
|
+
sessionId: snapshot.sessionId,
|
|
1126
|
+
status: "failed",
|
|
1127
|
+
phase: "fail",
|
|
1128
|
+
userPrompt: snapshot.userPrompt,
|
|
1129
|
+
startedAt: snapshot.startedAt,
|
|
1130
|
+
endedAt,
|
|
1131
|
+
terminationReason: "interaction-timeout",
|
|
1132
|
+
activeSkillNames: snapshot.activeSkillNames,
|
|
1133
|
+
artifacts: [],
|
|
1134
|
+
trace: [{
|
|
1135
|
+
id: `evt-expired-${message.runId}`,
|
|
1136
|
+
runId: message.runId,
|
|
1137
|
+
ts: endedAt,
|
|
1138
|
+
type: "run.failed",
|
|
1139
|
+
message: `Interaction timed out before the run was resumed (expired at ${snapshot.interactionExpiresAt})`,
|
|
1140
|
+
data: {
|
|
1141
|
+
reason: "interaction-timeout",
|
|
1142
|
+
code: "RUN_INTERACTION_TIMEOUT"
|
|
1143
|
+
}
|
|
1144
|
+
}]
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
const resumeUiBridge = new WorkerUiBridge(scope);
|
|
1149
|
+
activeUiBridge = resumeUiBridge;
|
|
1150
|
+
try {
|
|
1151
|
+
return await new AgentLoop({
|
|
1152
|
+
llm: state.llm,
|
|
1153
|
+
executor: state.executor,
|
|
1154
|
+
artifactStore: new FsArtifactStore({
|
|
1155
|
+
root: "artifacts",
|
|
1156
|
+
fs: state.fs
|
|
1157
|
+
}),
|
|
1158
|
+
memory: new FsMemoryStore({
|
|
1159
|
+
root: "memory",
|
|
1160
|
+
fs: state.fs
|
|
1161
|
+
}),
|
|
1162
|
+
fs: state.fs,
|
|
1163
|
+
skillIndex: /* @__PURE__ */ new Map(),
|
|
1164
|
+
uiBridge: resumeUiBridge,
|
|
1165
|
+
...state.interaction ? { interaction: state.interaction } : {},
|
|
1166
|
+
...overrides.externalTools ? { externalTools: overrides.externalTools } : {},
|
|
1167
|
+
...overrides.skillProviders ? { skillProviders: overrides.skillProviders } : {},
|
|
1168
|
+
snapshotStore: state.snapshotStore
|
|
1169
|
+
}).resume(snapshot);
|
|
1170
|
+
} finally {
|
|
1171
|
+
activeUiBridge = void 0;
|
|
1172
|
+
}
|
|
1173
|
+
})().then((runResult) => {
|
|
1174
|
+
runs.set(message.runId, { status: runResult.run.status });
|
|
1175
|
+
result(message.id, { result: runResult });
|
|
1176
|
+
}, (e) => result(message.id, { error: serializeError(e) }));
|
|
1177
|
+
return;
|
|
980
1178
|
case "cancel":
|
|
981
1179
|
if (!runs.get(message.runId)) {
|
|
982
1180
|
result(message.id, { error: {
|
|
@@ -1042,6 +1240,21 @@ var WorkerRuntimeClient = class {
|
|
|
1042
1240
|
runId
|
|
1043
1241
|
});
|
|
1044
1242
|
}
|
|
1243
|
+
/** D3:列出可恢复的 interrupted run */
|
|
1244
|
+
listInterrupted() {
|
|
1245
|
+
return this.#call({
|
|
1246
|
+
type: "list-interrupted",
|
|
1247
|
+
id: this.#nextId()
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
/** D3:恢复 interrupted run(新 Worker 重建后重新发起等待中的交互) */
|
|
1251
|
+
resume(runId) {
|
|
1252
|
+
return this.#call({
|
|
1253
|
+
type: "resume",
|
|
1254
|
+
id: this.#nextId(),
|
|
1255
|
+
runId
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1045
1258
|
dispose() {
|
|
1046
1259
|
for (const [id, pending] of this.#pending) {
|
|
1047
1260
|
clearTimeout(pending.timer);
|
|
@@ -5,10 +5,11 @@ function shapeInteractionValue(model, values) {
|
|
|
5
5
|
case "ask": return values["answer"];
|
|
6
6
|
case "confirm": return values["confirmed"] === true;
|
|
7
7
|
case "select": return values["selected"];
|
|
8
|
+
case "authorize": return true;
|
|
8
9
|
case "form": return values;
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
|
-
/**
|
|
12
|
+
/** 五类 InteractionRequest → 统一中间模型(框架无关) */
|
|
12
13
|
function interactionToFormModel(request) {
|
|
13
14
|
switch (request.type) {
|
|
14
15
|
case "ask": return {
|
|
@@ -63,6 +64,14 @@ function interactionToFormModel(request) {
|
|
|
63
64
|
submitLabel: "Select",
|
|
64
65
|
cancelLabel: "Cancel"
|
|
65
66
|
};
|
|
67
|
+
case "authorize": return {
|
|
68
|
+
kind: "authorize",
|
|
69
|
+
title: "Authorization required",
|
|
70
|
+
message: request.message,
|
|
71
|
+
controls: [],
|
|
72
|
+
submitLabel: "Allow",
|
|
73
|
+
cancelLabel: "Deny"
|
|
74
|
+
};
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
const isEmpty = (v) => v === void 0 || v === "";
|
|
@@ -551,6 +560,15 @@ function toOpenUiLang(request) {
|
|
|
551
560
|
options: request.options
|
|
552
561
|
}];
|
|
553
562
|
break;
|
|
563
|
+
case "authorize":
|
|
564
|
+
title = "Authorization required";
|
|
565
|
+
fields = [{
|
|
566
|
+
name: "approved",
|
|
567
|
+
label: request.message,
|
|
568
|
+
control: "boolean",
|
|
569
|
+
defaultValue: false
|
|
570
|
+
}];
|
|
571
|
+
break;
|
|
554
572
|
}
|
|
555
573
|
lines.push(...fieldStatements(fields));
|
|
556
574
|
const children = fields.map((_, i) => `f${i}`);
|
|
@@ -617,7 +635,11 @@ function toA2uiMessages(request) {
|
|
|
617
635
|
components.push(text("title", request.title, "h3"));
|
|
618
636
|
children.push("title");
|
|
619
637
|
}
|
|
620
|
-
|
|
638
|
+
if (request.type === "authorize") {
|
|
639
|
+
components.push(text("title", "Authorization required", "h3"));
|
|
640
|
+
children.push("title");
|
|
641
|
+
}
|
|
642
|
+
const message = request.type === "ask" || request.type === "confirm" || request.type === "select" || request.type === "authorize" ? request.message : void 0;
|
|
621
643
|
if (message) {
|
|
622
644
|
components.push(text("message", message));
|
|
623
645
|
children.push("message");
|
|
@@ -632,7 +654,7 @@ function toA2uiMessages(request) {
|
|
|
632
654
|
label: request.message,
|
|
633
655
|
type: "boolean",
|
|
634
656
|
defaultValue: request.defaultValue ?? true
|
|
635
|
-
}] : [{
|
|
657
|
+
}] : request.type === "authorize" ? [] : [{
|
|
636
658
|
name: "selected",
|
|
637
659
|
label: request.message,
|
|
638
660
|
type: "select",
|
|
@@ -675,7 +697,7 @@ function toA2uiMessages(request) {
|
|
|
675
697
|
});
|
|
676
698
|
children.push(`field_${field.name}`);
|
|
677
699
|
}
|
|
678
|
-
components.push(text("submit_label", "Submit"));
|
|
700
|
+
components.push(text("submit_label", request.type === "authorize" ? "Allow" : "Submit"));
|
|
679
701
|
components.push({
|
|
680
702
|
id: "submit",
|
|
681
703
|
component: "Button",
|
|
@@ -686,7 +708,7 @@ function toA2uiMessages(request) {
|
|
|
686
708
|
context: { requestId: request.id }
|
|
687
709
|
} }
|
|
688
710
|
});
|
|
689
|
-
components.push(text("cancel_label", "Cancel"));
|
|
711
|
+
components.push(text("cancel_label", request.type === "authorize" ? "Deny" : "Cancel"));
|
|
690
712
|
components.push({
|
|
691
713
|
id: "cancel",
|
|
692
714
|
component: "Button",
|
|
@@ -15359,13 +15381,15 @@ var LitRendererBridge = class {
|
|
|
15359
15381
|
const surfaces = processor.getClientDataModel("v0.9.1")?.surfaces;
|
|
15360
15382
|
const formValues = typeof surfaces?.[surfaceId] === "object" && surfaces[surfaceId] !== null ? surfaces[surfaceId]["form"] ?? action.context : action.context;
|
|
15361
15383
|
cleanup(surfaceEl);
|
|
15362
|
-
|
|
15384
|
+
const response = fromA2uiAction({
|
|
15363
15385
|
...action,
|
|
15364
15386
|
context: {
|
|
15365
15387
|
...action.context,
|
|
15366
15388
|
values: formValues
|
|
15367
15389
|
}
|
|
15368
|
-
})
|
|
15390
|
+
});
|
|
15391
|
+
if (input.type === "authorize" && !response.cancelled) response.value = true;
|
|
15392
|
+
resolve(response);
|
|
15369
15393
|
}, { version: "v0.9.1" });
|
|
15370
15394
|
processor.onSurfaceCreated((surface) => {
|
|
15371
15395
|
const el = this.#doc.createElement("a2ui-surface");
|