@webskill/sdk 0.2.4 → 0.2.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,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-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";
1
+ import { B as SkillManifest, C as FileStat, G as SkillsLockfile, L as SkillInstallSource, _ as RenderResultRequest, a as InteractionPolicy, c as LlmClient, d as LlmResponse, f as LlmStreamEvent, o as InteractionRequest, q as VerifyResult, s as InteractionResponse, v as UiBridge, w as FileSystemProvider, y as ArchiveLimits } from "./types-CKm5G_eQ-krKWW8WV.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-DfINBEOy.js";
3
3
  //#region ../browser/dist/index.d.ts
4
4
  //#region src/fs/featureDetection.d.ts
5
5
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
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-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";
1
+ import { C as messageOf, D as readResponseWithLimit, E as parseSkillPackManifest, M as unzipWithLimits, N as validateSkills, P as verifyManifest, T as parseSkillMarkdown, b as exportSkills, c as SkillDiscovery, d as assertRemoteUrlAllowed, h as buildManifest, i as SKILL_MANIFEST_FILE, j as resolveInsideRoot, m as buildCatalog, p as atomicWriteText, r as SKILLS_LOCKFILE, s as SKILL_PACK_FILE, u as WebSkillError, x as isValidSkillName } from "./dist-BQzncxXg.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-B77plHjw.js";
3
+ import { n as MockLlmClient } from "./testing-BUoXvm1u.js";
4
4
 
5
5
  //#region ../browser/dist/index.js
6
6
  /** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
@@ -462,8 +462,7 @@ async function extractZipWeb(fs, data, destRoot, limits) {
462
462
  else await fs.writeBinary(target, content);
463
463
  }
464
464
  }
465
- const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
466
- const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || e.code === "TOOL_UNSUPPORTED") ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf$1(e)}`, e);
465
+ const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || e.code === "TOOL_UNSUPPORTED") ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf(e)}`, e);
467
466
  const lockfilePath = (root) => `${root}/${SKILLS_LOCKFILE}`;
468
467
  /**
469
468
  * D5:浏览器技能安装(http(s) zip / ArrayBuffer;tar/git/npm → TOOL_UNSUPPORTED)。
@@ -494,9 +493,15 @@ var BrowserSkillManager = class {
494
493
  if (source.type === "http") {
495
494
  let res;
496
495
  try {
497
- res = await (this.#fetchImpl ?? fetch)(source.url);
496
+ const absolute = new URL(source.url, typeof location !== "undefined" ? location.href : void 0).href;
497
+ const url = assertRemoteUrlAllowed(absolute, {
498
+ allowHttp: source.allowHttp ?? false,
499
+ allowPrivateHosts: source.allowPrivateHosts ?? false
500
+ });
501
+ res = await (this.#fetchImpl ?? fetch)(url.href);
498
502
  } catch (e) {
499
- throw new WebSkillError("INSTALL_FAILED", `Download failed: ${messageOf$1(e)}`, e);
503
+ if (e instanceof WebSkillError) throw e;
504
+ throw new WebSkillError("INSTALL_FAILED", `Download failed: ${messageOf(e)}`, e);
500
505
  }
501
506
  if (!res.ok) throw new WebSkillError("INSTALL_FAILED", `Download failed with HTTP ${res.status}`);
502
507
  data = await readResponseWithLimit(res, this.#archiveLimits);
@@ -526,7 +531,7 @@ var BrowserSkillManager = class {
526
531
  name = metadata.name;
527
532
  version = typeof metadata["version"] === "string" ? metadata["version"] : void 0;
528
533
  } catch (e) {
529
- throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md: ${messageOf$1(e)}`, e);
534
+ throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md: ${messageOf(e)}`, e);
530
535
  }
531
536
  if (!isValidSkillName(name)) throw new WebSkillError("INSTALL_FAILED", `Invalid skill name in SKILL.md (path traversal rejected): ${JSON.stringify(name)}`);
532
537
  const finalRoot = `${stagingRoot}/final`;
@@ -592,7 +597,7 @@ var BrowserSkillManager = class {
592
597
  name = metadata.name;
593
598
  versions.set(name, typeof metadata["version"] === "string" ? metadata["version"] : void 0);
594
599
  } catch (e) {
595
- throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf$1(e)}`, e);
600
+ throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf(e)}`, e);
596
601
  }
597
602
  if (name !== entry.name) throw new WebSkillError("INSTALL_FAILED", `Skill pack entry "${entry.name}" does not match the SKILL.md name "${name}"`);
598
603
  await copyDir(fs, skillDir, `${finalRoot}/${name}`);
@@ -674,7 +679,7 @@ var BrowserSkillManager = class {
674
679
  await this.#removeLockEntry(name);
675
680
  this.#onChanged?.();
676
681
  } catch (e) {
677
- throw new WebSkillError("UNINSTALL_FAILED", `Failed to uninstall "${name}": ${messageOf$1(e)}`, e);
682
+ throw new WebSkillError("UNINSTALL_FAILED", `Failed to uninstall "${name}": ${messageOf(e)}`, e);
678
683
  }
679
684
  }
680
685
  async verifyIntegrity(name) {
@@ -694,7 +699,7 @@ var BrowserSkillManager = class {
694
699
  try {
695
700
  return JSON.parse(await this.#fs.readText(path));
696
701
  } catch (e) {
697
- throw new WebSkillError("INTEGRITY_FAILED", `Skills lockfile at ${path} is corrupted: ${messageOf$1(e)}`, e);
702
+ throw new WebSkillError("INTEGRITY_FAILED", `Skills lockfile at ${path} is corrupted: ${messageOf(e)}`, e);
698
703
  }
699
704
  }
700
705
  async #createManifest(skillRoot, input) {
@@ -726,7 +731,7 @@ var BrowserSkillManager = class {
726
731
  try {
727
732
  return JSON.parse(await this.#fs.readText(path));
728
733
  } catch (e) {
729
- throw new WebSkillError("INTEGRITY_FAILED", `Skill manifest at ${path} is corrupted: ${messageOf$1(e)}`, e);
734
+ throw new WebSkillError("INTEGRITY_FAILED", `Skill manifest at ${path} is corrupted: ${messageOf(e)}`, e);
730
735
  }
731
736
  }
732
737
  async #upsertLockEntry(name, entry) {
@@ -864,7 +869,6 @@ const resolveWorkerFactory = (mode) => {
864
869
  return blobWorkerFactory;
865
870
  };
866
871
  const baseName = (p) => p.split("/").pop() ?? p;
867
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
868
872
  /**
869
873
  * Web Worker 脚本沙箱执行器:loadDefinition 与 execute 都在 Worker 内完成,
870
874
  * 主线程从不 import 技能脚本;每次执行独立 Worker;超时 terminate 强杀。
@@ -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-D7MsoMPx.js";
1
+ import { C as messageOf, N as validateSkills, O as renderAvailableSkillsXml, T as parseSkillMarkdown, c as SkillDiscovery, f as assertSafePathSegment, j as resolveInsideRoot, l as SkillReader, m as buildCatalog, u as WebSkillError } from "./dist-BQzncxXg.js";
2
2
  import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
3
3
 
4
4
  //#region ../runtime/dist/index.js
@@ -967,6 +967,13 @@ function mapFieldType(prop) {
967
967
  /** 生命周期事件总线:只读观测,支持按阶段或通配订阅 */
968
968
  var EventBus = class {
969
969
  #listeners = /* @__PURE__ */ new Map();
970
+ /** 监听器异常出口(默认 console.warn;单个监听器抛错不影响其他监听器与 emit 调用方) */
971
+ #onListenerError;
972
+ constructor(onListenerError) {
973
+ this.#onListenerError = onListenerError ?? ((error, event) => {
974
+ console.warn(`EventBus listener threw on phase "${event.phase}": ${error instanceof Error ? error.message : String(error)}`);
975
+ });
976
+ }
970
977
  /** 返回取消订阅函数 */
971
978
  on(phase, listener) {
972
979
  const key = phase;
@@ -986,8 +993,15 @@ var EventBus = class {
986
993
  }
987
994
  emit(event) {
988
995
  const frozen = Object.freeze(event);
989
- for (const listener of this.#listeners.get(event.phase) ?? []) listener(frozen);
990
- for (const listener of this.#listeners.get("*") ?? []) listener(frozen);
996
+ for (const listener of this.#listeners.get(event.phase) ?? []) this.#dispatch(listener, frozen);
997
+ for (const listener of this.#listeners.get("*") ?? []) this.#dispatch(listener, frozen);
998
+ }
999
+ #dispatch(listener, event) {
1000
+ try {
1001
+ listener(event);
1002
+ } catch (e) {
1003
+ this.#onListenerError(e, event);
1004
+ }
991
1005
  }
992
1006
  };
993
1007
  /**
@@ -1000,9 +1014,17 @@ var SerializingMemoryStore = class {
1000
1014
  constructor(inner) {
1001
1015
  this.#inner = inner;
1002
1016
  }
1017
+ /** 当前链上的 scope 数(监控/测试用;空闲时应回落到 0) */
1018
+ get trackedScopeCount() {
1019
+ return this.#chains.size;
1020
+ }
1003
1021
  #serialize(scope, fn) {
1004
1022
  const next = (this.#chains.get(scope) ?? Promise.resolve()).then(fn, fn);
1005
- this.#chains.set(scope, next.catch(() => void 0));
1023
+ const guarded = next.catch(() => void 0);
1024
+ this.#chains.set(scope, guarded);
1025
+ guarded.then(() => {
1026
+ if (this.#chains.get(scope) === guarded) this.#chains.delete(scope);
1027
+ });
1006
1028
  return next;
1007
1029
  }
1008
1030
  get(scope, key) {
@@ -1062,7 +1084,6 @@ var RunTerminated = class extends Error {
1062
1084
  /** bridge.request 自身异常(非超时):恢复 running 后转为工具错误回喂 */
1063
1085
  var BridgeRequestError = class extends Error {};
1064
1086
  const baseName = (p) => p.split("/").pop() ?? p;
1065
- const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
1066
1087
  const toolError = (code, message) => ({
1067
1088
  ok: false,
1068
1089
  content: [],
@@ -1090,6 +1111,7 @@ var AgentLoop = class {
1090
1111
  totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
1091
1112
  toolTimeoutMs: config.toolTimeoutMs ?? 3e4,
1092
1113
  toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
1114
+ paramHistoryLimit: config.paramHistoryLimit ?? 50,
1093
1115
  temperature: config.temperature,
1094
1116
  renderResult: config.renderResult
1095
1117
  };
@@ -1148,7 +1170,7 @@ var AgentLoop = class {
1148
1170
  try {
1149
1171
  return await source.listToolSpecs();
1150
1172
  } catch (e) {
1151
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf$1(e)}` });
1173
+ trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
1152
1174
  return [];
1153
1175
  }
1154
1176
  }))).flat();
@@ -1166,7 +1188,7 @@ var AgentLoop = class {
1166
1188
  try {
1167
1189
  return await this.#turnLoop(state, 1, externalSpecs);
1168
1190
  } catch (e) {
1169
- if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
1191
+ if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
1170
1192
  throw e;
1171
1193
  }
1172
1194
  }
@@ -1196,73 +1218,77 @@ var AgentLoop = class {
1196
1218
  const { llm } = this.#deps;
1197
1219
  const { trace, messages } = state;
1198
1220
  this.#armDeadline(state);
1199
- for (let turn = startTurn;; turn++) {
1200
- state.turn = turn;
1201
- if (turn > state.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${state.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
1202
- if (this.#elapsed(state) > state.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1203
- const toolSpecs = [
1204
- toLlmToolSpec(READ_SKILL_FILE_TOOL),
1205
- ...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
1206
- ...[...state.activatedTools.values()].map(toLlmToolSpec),
1207
- ...externalSpecs
1208
- ];
1209
- trace.record("llm.request", { data: {
1210
- turn,
1211
- messageCount: messages.length,
1212
- toolCount: toolSpecs.length
1213
- } });
1214
- let response;
1215
- try {
1216
- response = llm.stream ? await this.#completeStreaming(llm, state, {
1217
- model: this.#deps.model,
1218
- messages,
1219
- tools: toolSpecs,
1220
- temperature: this.#config.temperature,
1221
- signal: state.controller.signal
1222
- }) : await llm.complete({
1223
- model: this.#deps.model,
1224
- messages,
1225
- tools: toolSpecs,
1226
- temperature: this.#config.temperature,
1227
- signal: state.controller.signal
1228
- });
1229
- } catch (e) {
1230
- if (state.controller.signal.aborted) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1231
- const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
1232
- return finish("failed", "llm-error", messageOf$1(e), code);
1233
- }
1234
- trace.record("llm.response", { data: {
1235
- turn,
1236
- hasToolCalls: Boolean(response.toolCalls?.length),
1237
- contentLength: response.content?.length ?? 0
1238
- } });
1239
- if (!response.toolCalls?.length) {
1240
- messages.push({
1241
- role: "assistant",
1242
- content: response.content ?? ""
1243
- });
1244
- return finish("completed", "final-answer", response.content ?? "");
1245
- }
1246
- await this.#lifecycle("execute", state, { turn });
1247
- messages.push({
1248
- role: "assistant",
1249
- content: response.content ?? "",
1250
- toolCalls: response.toolCalls
1251
- });
1252
- for (const call of response.toolCalls) {
1253
- let result;
1221
+ try {
1222
+ for (let turn = startTurn;; turn++) {
1223
+ state.turn = turn;
1224
+ if (turn > state.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${state.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
1225
+ if (this.#elapsed(state) > state.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1226
+ const toolSpecs = [
1227
+ toLlmToolSpec(READ_SKILL_FILE_TOOL),
1228
+ ...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
1229
+ ...[...state.activatedTools.values()].map(toLlmToolSpec),
1230
+ ...externalSpecs
1231
+ ];
1232
+ trace.record("llm.request", { data: {
1233
+ turn,
1234
+ messageCount: messages.length,
1235
+ toolCount: toolSpecs.length
1236
+ } });
1237
+ let response;
1254
1238
  try {
1255
- result = await this.#executeCall(call, state);
1239
+ response = llm.stream ? await this.#completeStreaming(llm, state, {
1240
+ model: this.#deps.model,
1241
+ messages,
1242
+ tools: toolSpecs,
1243
+ temperature: this.#config.temperature,
1244
+ signal: state.controller.signal
1245
+ }) : await llm.complete({
1246
+ model: this.#deps.model,
1247
+ messages,
1248
+ tools: toolSpecs,
1249
+ temperature: this.#config.temperature,
1250
+ signal: state.controller.signal
1251
+ });
1256
1252
  } catch (e) {
1257
- if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
1258
- throw e;
1253
+ if (state.controller.signal.aborted) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1254
+ const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
1255
+ return finish("failed", "llm-error", messageOf(e), code);
1259
1256
  }
1257
+ trace.record("llm.response", { data: {
1258
+ turn,
1259
+ hasToolCalls: Boolean(response.toolCalls?.length),
1260
+ contentLength: response.content?.length ?? 0
1261
+ } });
1262
+ if (!response.toolCalls?.length) {
1263
+ messages.push({
1264
+ role: "assistant",
1265
+ content: response.content ?? ""
1266
+ });
1267
+ return finish("completed", "final-answer", response.content ?? "");
1268
+ }
1269
+ await this.#lifecycle("execute", state, { turn });
1260
1270
  messages.push({
1261
- role: "tool",
1262
- toolCallId: call.id,
1263
- content: await this.#serializeToolResult(call, result, state)
1271
+ role: "assistant",
1272
+ content: response.content ?? "",
1273
+ toolCalls: response.toolCalls
1264
1274
  });
1275
+ for (const call of response.toolCalls) {
1276
+ let result;
1277
+ try {
1278
+ result = await this.#executeCall(call, state);
1279
+ } catch (e) {
1280
+ if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
1281
+ throw e;
1282
+ }
1283
+ messages.push({
1284
+ role: "tool",
1285
+ toolCallId: call.id,
1286
+ content: await this.#serializeToolResult(call, result, state)
1287
+ });
1288
+ }
1265
1289
  }
1290
+ } finally {
1291
+ this.#disarmDeadline(state);
1266
1292
  }
1267
1293
  }
1268
1294
  /** 统一终态处理:completed/failed/cancelled;终态即删快照(快照是续命机制,审计归治理) */
@@ -1292,17 +1318,17 @@ var AgentLoop = class {
1292
1318
  await bridge.renderResult(request);
1293
1319
  trace.record("ui.rendered", { data: { blockCount: request.blocks.length } });
1294
1320
  } catch (e) {
1295
- trace.record("run.warning", { message: `renderResult failed: ${messageOf$1(e)}` });
1321
+ trace.record("run.warning", { message: `renderResult failed: ${messageOf(e)}` });
1296
1322
  }
1297
1323
  if (this.#deps.snapshotStore) try {
1298
1324
  await this.#deps.snapshotStore.delete(state.runId);
1299
1325
  } catch (e) {
1300
- trace.record("run.warning", { message: `Failed to delete run snapshot: ${messageOf$1(e)}` });
1326
+ trace.record("run.warning", { message: `Failed to delete run snapshot: ${messageOf(e)}` });
1301
1327
  }
1302
1328
  try {
1303
1329
  await this.#lifecycle(status === "completed" ? "complete" : "fail", state, { reason });
1304
1330
  } catch (e) {
1305
- trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf$1(e)}` });
1331
+ trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
1306
1332
  }
1307
1333
  run.trace = trace.list();
1308
1334
  return {
@@ -1329,6 +1355,7 @@ var AgentLoop = class {
1329
1355
  interactionExpiresAt: state.run.interruptExpiresAt,
1330
1356
  renderBlocks: [...state.renderBlocks],
1331
1357
  interactionSeq: state.interactionSeq,
1358
+ pausedMs: state.pausedMs,
1332
1359
  config: {
1333
1360
  maxTurns: state.maxTurns,
1334
1361
  totalTimeoutMs: state.totalTimeoutMs,
@@ -1340,7 +1367,7 @@ var AgentLoop = class {
1340
1367
  try {
1341
1368
  await store.save(snapshot);
1342
1369
  } catch (e) {
1343
- state.trace.record("run.warning", { message: `Failed to save run snapshot: ${messageOf$1(e)}` });
1370
+ state.trace.record("run.warning", { message: `Failed to save run snapshot: ${messageOf(e)}` });
1344
1371
  }
1345
1372
  }
1346
1373
  /**
@@ -1376,7 +1403,7 @@ var AgentLoop = class {
1376
1403
  turn: snapshot.turn,
1377
1404
  renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
1378
1405
  startMs,
1379
- pausedMs: 0,
1406
+ pausedMs: snapshot.pausedMs ?? 0,
1380
1407
  maxTurns: snapshot.config.maxTurns,
1381
1408
  totalTimeoutMs: snapshot.config.totalTimeoutMs,
1382
1409
  controller: new AbortController()
@@ -1455,14 +1482,14 @@ var AgentLoop = class {
1455
1482
  try {
1456
1483
  return await source.listToolSpecs();
1457
1484
  } catch (e) {
1458
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf$1(e)}` });
1485
+ trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
1459
1486
  return [];
1460
1487
  }
1461
1488
  }))).flat();
1462
1489
  try {
1463
1490
  return await this.#turnLoop(state, snapshot.turn + 1, externalSpecs);
1464
1491
  } catch (e) {
1465
- if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
1492
+ if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
1466
1493
  throw e;
1467
1494
  }
1468
1495
  }
@@ -1564,8 +1591,8 @@ var AgentLoop = class {
1564
1591
  message: e.message,
1565
1592
  code: "RUN_INTERACTION_TIMEOUT"
1566
1593
  });
1567
- state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf$1(e)}` });
1568
- throw new BridgeRequestError(messageOf$1(e));
1594
+ state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf(e)}` });
1595
+ throw new BridgeRequestError(messageOf(e));
1569
1596
  }
1570
1597
  if (response.cancelled) throw new RunTerminated({
1571
1598
  status: "cancelled",
@@ -1684,7 +1711,7 @@ var AgentLoop = class {
1684
1711
  };
1685
1712
  } catch (e) {
1686
1713
  if (e instanceof WebSkillError && e.code === "SKILL_NOT_FOUND" && (this.#deps.skillProviders?.length ?? 0) > 0) return this.#handleReadExternalSkill(skillName, pathArg, state);
1687
- return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `read_skill_file failed: ${messageOf$1(e)}`);
1714
+ return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `read_skill_file failed: ${messageOf(e)}`);
1688
1715
  }
1689
1716
  }
1690
1717
  /** 经外部技能提供者读取技能文档/资源;skillKey 为技能名或 mcp:// URI */
@@ -1724,7 +1751,7 @@ var AgentLoop = class {
1724
1751
  }]
1725
1752
  };
1726
1753
  } catch (e) {
1727
- failures.push(messageOf$1(e));
1754
+ failures.push(messageOf(e));
1728
1755
  continue;
1729
1756
  }
1730
1757
  const detail = failures.length > 0 ? ` (provider errors: ${failures.join("; ")})` : "";
@@ -1746,7 +1773,7 @@ var AgentLoop = class {
1746
1773
  content: text
1747
1774
  })).id}"`;
1748
1775
  } catch (e) {
1749
- state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf$1(e)}` });
1776
+ state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf(e)}` });
1750
1777
  note = "full result discarded (artifact store unavailable)";
1751
1778
  }
1752
1779
  const head = text.slice(0, Math.floor(max * .6));
@@ -1784,7 +1811,7 @@ var AgentLoop = class {
1784
1811
  if (rawAllowed !== void 0) if (Array.isArray(rawAllowed)) allowedTools = rawAllowed.filter((e) => typeof e === "string");
1785
1812
  else state.trace.record("run.warning", { message: `Skill "${skillName}" has a non-array "allowed-tools" metadata entry; ignored` });
1786
1813
  } catch (e) {
1787
- state.trace.record("run.warning", { message: `Failed to read or parse SKILL.md of skill "${skillName}": ${messageOf$1(e)}` });
1814
+ state.trace.record("run.warning", { message: `Failed to read or parse SKILL.md of skill "${skillName}": ${messageOf(e)}` });
1788
1815
  }
1789
1816
  const executor = this.#deps.executor;
1790
1817
  const loaded = [];
@@ -1805,7 +1832,7 @@ var AgentLoop = class {
1805
1832
  state.activatedTools.set(def.name, def);
1806
1833
  loaded.push(def.name);
1807
1834
  } catch (e) {
1808
- state.trace.record("run.warning", { message: `Failed to load definition of script "${match[1]}" for skill "${skillName}": ${messageOf$1(e)}` });
1835
+ state.trace.record("run.warning", { message: `Failed to load definition of script "${match[1]}" for skill "${skillName}": ${messageOf(e)}` });
1809
1836
  }
1810
1837
  }
1811
1838
  }
@@ -1831,7 +1858,7 @@ var AgentLoop = class {
1831
1858
  def.inputSchema = JSON.parse(await this.#deps.fs.readText(sidecar));
1832
1859
  return;
1833
1860
  } catch (e) {
1834
- state.trace.record("run.warning", { message: `Failed to parse schema sidecar ${sidecar}: ${messageOf$1(e)}` });
1861
+ state.trace.record("run.warning", { message: `Failed to parse schema sidecar ${sidecar}: ${messageOf(e)}` });
1835
1862
  }
1836
1863
  if (!this.#deps.schemaInferer) return;
1837
1864
  for (const ext of ["ts", "js"]) {
@@ -1841,7 +1868,7 @@ var AgentLoop = class {
1841
1868
  const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
1842
1869
  if (inferred) def.inputSchema = inferred;
1843
1870
  } catch (e) {
1844
- state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf$1(e)}` });
1871
+ state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf(e)}` });
1845
1872
  }
1846
1873
  return;
1847
1874
  }
@@ -1902,7 +1929,7 @@ var AgentLoop = class {
1902
1929
  } catch (e) {
1903
1930
  if (e instanceof RunTerminated) throw e;
1904
1931
  await this.#bumpSkillStat(skillName, "failures", state);
1905
- return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `Tool "${call.name}" failed: ${messageOf$1(e)}`);
1932
+ return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `Tool "${call.name}" failed: ${messageOf(e)}`);
1906
1933
  }
1907
1934
  }
1908
1935
  /** context.confirm 触发点:默认真实询问;auto-approve 直通;无 bridge 降级直通 + warning */
@@ -1941,7 +1968,7 @@ var AgentLoop = class {
1941
1968
  try {
1942
1969
  await this.#deps.memory?.set(scope, key, value);
1943
1970
  } catch (e) {
1944
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1971
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1945
1972
  }
1946
1973
  }
1947
1974
  /** read-modify-write:有 transaction 实现在 scope 原子段内执行(并发不丢计数),否则顺序执行 */
@@ -1954,7 +1981,7 @@ var AgentLoop = class {
1954
1981
  await inner.set(scope, key, mutate(await inner.get(scope, key)));
1955
1982
  });
1956
1983
  } catch (e) {
1957
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1984
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1958
1985
  }
1959
1986
  return;
1960
1987
  }
@@ -1989,6 +2016,7 @@ var AgentLoop = class {
1989
2016
  async #appendParamHistory(state, request, value) {
1990
2017
  if (!this.#deps.memory) return;
1991
2018
  const scope = `session:${state.run.sessionId}`;
2019
+ const limit = this.#config.paramHistoryLimit;
1992
2020
  await this.#memoryMutate(scope, "paramHistory", state, (current) => {
1993
2021
  const history = current ?? [];
1994
2022
  history.push({
@@ -1997,7 +2025,7 @@ var AgentLoop = class {
1997
2025
  type: request.type,
1998
2026
  value
1999
2027
  });
2000
- return history;
2028
+ return history.slice(-limit);
2001
2029
  });
2002
2030
  }
2003
2031
  };
@@ -2084,6 +2112,24 @@ var FsRunSnapshotStore = class {
2084
2112
  }
2085
2113
  };
2086
2114
  /**
2115
+ * session history 滚动裁剪:超出预算时裁掉中段,保留首尾。
2116
+ * 边界对齐 LLM tool 契约:head 不以未应答的 assistant toolCalls 结尾,
2117
+ * tail 不以孤儿 tool 消息开头(其 assistant 调用已随中段裁掉)。
2118
+ */
2119
+ function trimSessionHistory(messages, max) {
2120
+ if (max < 2 || messages.length <= max) return messages;
2121
+ let head = Math.ceil(max / 2);
2122
+ while (head > 0) {
2123
+ const last = messages[head - 1];
2124
+ if (last.role === "assistant" && last.toolCalls?.length) head -= 1;
2125
+ else break;
2126
+ }
2127
+ let tailStart = Math.max(head, messages.length - (max - head));
2128
+ while (tailStart < messages.length && messages[tailStart].role === "tool") tailStart += 1;
2129
+ if (tailStart >= messages.length) return messages.slice(-max);
2130
+ return [...messages.slice(0, head), ...messages.slice(tailStart)];
2131
+ }
2132
+ /**
2087
2133
  * runtime 门面:组合 discovery / router / agent loop / lifecycle
2088
2134
  * @stable
2089
2135
  */
@@ -2128,20 +2174,28 @@ var WebSkillRuntime = class {
2128
2174
  /**
2129
2175
  * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
2130
2176
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
2177
+ * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
2178
+ * maxHistoryMessages(默认 100)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
2131
2179
  */
2132
2180
  createSession(options = {}) {
2133
2181
  const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
2134
2182
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2183
+ const maxHistory = options.maxHistoryMessages ?? 100;
2135
2184
  let history = [];
2185
+ let queue = Promise.resolve();
2136
2186
  return {
2137
2187
  id: sessionId,
2138
2188
  createdAt,
2139
- run: async (prompt) => {
2140
- const result = await this.run(prompt, {
2141
- sessionId,
2142
- history
2189
+ run: (prompt) => {
2190
+ const result = queue.then(async () => {
2191
+ const r = await this.run(prompt, {
2192
+ sessionId,
2193
+ history
2194
+ });
2195
+ history = trimSessionHistory(r.messages.slice(1), maxHistory);
2196
+ return r;
2143
2197
  });
2144
- history = result.messages.slice(1);
2198
+ queue = result.then(() => void 0, () => void 0);
2145
2199
  return result;
2146
2200
  }
2147
2201
  };
@@ -2389,7 +2443,6 @@ function sourceFromUrl(url) {
2389
2443
  path: url
2390
2444
  };
2391
2445
  }
2392
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
2393
2446
  /** 受控钩子执行器:逐个执行,超时/异常默认降级为 warning,可切严格模式 */
2394
2447
  var HookRunner = class {
2395
2448
  #hooks = /* @__PURE__ */ new Map();