@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 CHANGED
@@ -1,4 +1,4 @@
1
- import { $t as VerifyResult, C as InteractionRequest, M as LlmResponse, Mt as FileStat, N as LlmStreamEvent, Nt as FileSystemProvider, S as InteractionPolicy, Tt as parseBridgeRequest, Wt as SkillInstallSource, X as RenderResultRequest, Zt as SkillsLockfile, at as ScriptExecutor, d as BridgeResponse, it as ScriptExecutionContext, k as LlmClient, l as BridgeCapabilities, lt as ToolResult, m as ExternalToolSource, mt as UiBridge, p as ExternalSkillProvider, qt as SkillManifest, st as ToolDefinition, u as BridgeRequest, vt as bridgeError, w as InteractionResponse } from "./index-DnI0BTEp.js";
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 { $ as validateSkills, B as WebSkillError, D as normalizeToolContent, E as mergeCatalogEntries, F as SKILL_MANIFEST_FILE, H as buildManifest, K as isValidSkillName, O as parseBridgeRequest, P as SKILLS_LOCKFILE, Q as resolveInsideRoot, R as SkillDiscovery, V as buildCatalog, Y as parseSkillMarkdown, et as verifyManifest, f as MockLlmClient, h as ProgressiveRouter, i as AgentLoop, m as OpenAiCompatibleClient, o as FsArtifactStore, s as FsMemoryStore, x as bridgeError } from "./dist-hMMxARXK.js";
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
- }, timeoutMs, (bridgeRequest) => this.#handleBridge(bridgeRequest, context));
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
- /** 能力桥宿主侧处理:能力关闭即 TOOL_UNSUPPORTED;readReference/writeArtifact/confirm 委托 context */
778
+ /** 能力桥宿主侧处理:能力关闭/授权拒绝即 TOOL_UNSUPPORTED;判定逻辑共用 runtime/sandbox/approval */
695
779
  async #handleBridge(request, context) {
696
- const unsupported = (capability) => bridgeError(request.id, "TOOL_UNSUPPORTED", `Capability "${capability}" is disabled`);
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
- if (!this.#capabilities.readReference) return unsupported("readReference");
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
- if (!this.#capabilities.writeArtifact) return unsupported("writeArtifact");
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 (!this.#capabilities.confirm || !context.confirm) return unsupported("confirm");
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": return isNonEmptyString(data["id"]) && isNonEmptyString(data["runId"]) ? {
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
- /** 四类 InteractionRequest → 统一中间模型(框架无关) */
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
- const message = request.type === "ask" || request.type === "confirm" || request.type === "select" ? request.message : void 0;
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
- resolve(fromA2uiAction({
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");