@webskill/sdk 0.2.3 → 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.
@@ -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-D0saNPi_.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
@@ -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 ? `:streamGenerateContent?alt=sse&key=${encodeURIComponent(this.#config.apiKey)}` : `:generateContent?key=${encodeURIComponent(this.#config.apiKey)}`;
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: { "content-type": "application/json" },
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?key=${encodeURIComponent(this.#config.apiKey)}`)).ok;
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 fs.readText(resolveInsideRoot(skillRoot, `references/${relativePath}`));
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({
@@ -963,6 +967,13 @@ function mapFieldType(prop) {
963
967
  /** 生命周期事件总线:只读观测,支持按阶段或通配订阅 */
964
968
  var EventBus = class {
965
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
+ }
966
977
  /** 返回取消订阅函数 */
967
978
  on(phase, listener) {
968
979
  const key = phase;
@@ -982,8 +993,15 @@ var EventBus = class {
982
993
  }
983
994
  emit(event) {
984
995
  const frozen = Object.freeze(event);
985
- for (const listener of this.#listeners.get(event.phase) ?? []) listener(frozen);
986
- 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
+ }
987
1005
  }
988
1006
  };
989
1007
  /**
@@ -996,9 +1014,17 @@ var SerializingMemoryStore = class {
996
1014
  constructor(inner) {
997
1015
  this.#inner = inner;
998
1016
  }
1017
+ /** 当前链上的 scope 数(监控/测试用;空闲时应回落到 0) */
1018
+ get trackedScopeCount() {
1019
+ return this.#chains.size;
1020
+ }
999
1021
  #serialize(scope, fn) {
1000
1022
  const next = (this.#chains.get(scope) ?? Promise.resolve()).then(fn, fn);
1001
- 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
+ });
1002
1028
  return next;
1003
1029
  }
1004
1030
  get(scope, key) {
@@ -1058,7 +1084,6 @@ var RunTerminated = class extends Error {
1058
1084
  /** bridge.request 自身异常(非超时):恢复 running 后转为工具错误回喂 */
1059
1085
  var BridgeRequestError = class extends Error {};
1060
1086
  const baseName = (p) => p.split("/").pop() ?? p;
1061
- const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
1062
1087
  const toolError = (code, message) => ({
1063
1088
  ok: false,
1064
1089
  content: [],
@@ -1086,6 +1111,7 @@ var AgentLoop = class {
1086
1111
  totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
1087
1112
  toolTimeoutMs: config.toolTimeoutMs ?? 3e4,
1088
1113
  toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
1114
+ paramHistoryLimit: config.paramHistoryLimit ?? 50,
1089
1115
  temperature: config.temperature,
1090
1116
  renderResult: config.renderResult
1091
1117
  };
@@ -1144,7 +1170,7 @@ var AgentLoop = class {
1144
1170
  try {
1145
1171
  return await source.listToolSpecs();
1146
1172
  } catch (e) {
1147
- 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)}` });
1148
1174
  return [];
1149
1175
  }
1150
1176
  }))).flat();
@@ -1162,7 +1188,7 @@ var AgentLoop = class {
1162
1188
  try {
1163
1189
  return await this.#turnLoop(state, 1, externalSpecs);
1164
1190
  } catch (e) {
1165
- 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");
1166
1192
  throw e;
1167
1193
  }
1168
1194
  }
@@ -1192,73 +1218,77 @@ var AgentLoop = class {
1192
1218
  const { llm } = this.#deps;
1193
1219
  const { trace, messages } = state;
1194
1220
  this.#armDeadline(state);
1195
- for (let turn = startTurn;; turn++) {
1196
- state.turn = turn;
1197
- if (turn > state.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${state.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
1198
- if (this.#elapsed(state) > state.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1199
- const toolSpecs = [
1200
- toLlmToolSpec(READ_SKILL_FILE_TOOL),
1201
- ...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
1202
- ...[...state.activatedTools.values()].map(toLlmToolSpec),
1203
- ...externalSpecs
1204
- ];
1205
- trace.record("llm.request", { data: {
1206
- turn,
1207
- messageCount: messages.length,
1208
- toolCount: toolSpecs.length
1209
- } });
1210
- let response;
1211
- try {
1212
- response = llm.stream ? await this.#completeStreaming(llm, state, {
1213
- model: this.#deps.model,
1214
- messages,
1215
- tools: toolSpecs,
1216
- temperature: this.#config.temperature,
1217
- signal: state.controller.signal
1218
- }) : await llm.complete({
1219
- model: this.#deps.model,
1220
- messages,
1221
- tools: toolSpecs,
1222
- temperature: this.#config.temperature,
1223
- signal: state.controller.signal
1224
- });
1225
- } catch (e) {
1226
- if (state.controller.signal.aborted) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1227
- const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
1228
- return finish("failed", "llm-error", messageOf$1(e), code);
1229
- }
1230
- trace.record("llm.response", { data: {
1231
- turn,
1232
- hasToolCalls: Boolean(response.toolCalls?.length),
1233
- contentLength: response.content?.length ?? 0
1234
- } });
1235
- if (!response.toolCalls?.length) {
1236
- messages.push({
1237
- role: "assistant",
1238
- content: response.content ?? ""
1239
- });
1240
- return finish("completed", "final-answer", response.content ?? "");
1241
- }
1242
- await this.#lifecycle("execute", state, { turn });
1243
- messages.push({
1244
- role: "assistant",
1245
- content: response.content ?? "",
1246
- toolCalls: response.toolCalls
1247
- });
1248
- for (const call of response.toolCalls) {
1249
- 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;
1250
1238
  try {
1251
- 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
+ });
1252
1252
  } catch (e) {
1253
- if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
1254
- 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);
1255
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 });
1256
1270
  messages.push({
1257
- role: "tool",
1258
- toolCallId: call.id,
1259
- content: await this.#serializeToolResult(call, result, state)
1271
+ role: "assistant",
1272
+ content: response.content ?? "",
1273
+ toolCalls: response.toolCalls
1260
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
+ }
1261
1289
  }
1290
+ } finally {
1291
+ this.#disarmDeadline(state);
1262
1292
  }
1263
1293
  }
1264
1294
  /** 统一终态处理:completed/failed/cancelled;终态即删快照(快照是续命机制,审计归治理) */
@@ -1288,17 +1318,17 @@ var AgentLoop = class {
1288
1318
  await bridge.renderResult(request);
1289
1319
  trace.record("ui.rendered", { data: { blockCount: request.blocks.length } });
1290
1320
  } catch (e) {
1291
- trace.record("run.warning", { message: `renderResult failed: ${messageOf$1(e)}` });
1321
+ trace.record("run.warning", { message: `renderResult failed: ${messageOf(e)}` });
1292
1322
  }
1293
1323
  if (this.#deps.snapshotStore) try {
1294
1324
  await this.#deps.snapshotStore.delete(state.runId);
1295
1325
  } catch (e) {
1296
- 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)}` });
1297
1327
  }
1298
1328
  try {
1299
1329
  await this.#lifecycle(status === "completed" ? "complete" : "fail", state, { reason });
1300
1330
  } catch (e) {
1301
- trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf$1(e)}` });
1331
+ trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
1302
1332
  }
1303
1333
  run.trace = trace.list();
1304
1334
  return {
@@ -1325,6 +1355,7 @@ var AgentLoop = class {
1325
1355
  interactionExpiresAt: state.run.interruptExpiresAt,
1326
1356
  renderBlocks: [...state.renderBlocks],
1327
1357
  interactionSeq: state.interactionSeq,
1358
+ pausedMs: state.pausedMs,
1328
1359
  config: {
1329
1360
  maxTurns: state.maxTurns,
1330
1361
  totalTimeoutMs: state.totalTimeoutMs,
@@ -1336,7 +1367,7 @@ var AgentLoop = class {
1336
1367
  try {
1337
1368
  await store.save(snapshot);
1338
1369
  } catch (e) {
1339
- 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)}` });
1340
1371
  }
1341
1372
  }
1342
1373
  /**
@@ -1372,7 +1403,7 @@ var AgentLoop = class {
1372
1403
  turn: snapshot.turn,
1373
1404
  renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
1374
1405
  startMs,
1375
- pausedMs: 0,
1406
+ pausedMs: snapshot.pausedMs ?? 0,
1376
1407
  maxTurns: snapshot.config.maxTurns,
1377
1408
  totalTimeoutMs: snapshot.config.totalTimeoutMs,
1378
1409
  controller: new AbortController()
@@ -1451,14 +1482,14 @@ var AgentLoop = class {
1451
1482
  try {
1452
1483
  return await source.listToolSpecs();
1453
1484
  } catch (e) {
1454
- 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)}` });
1455
1486
  return [];
1456
1487
  }
1457
1488
  }))).flat();
1458
1489
  try {
1459
1490
  return await this.#turnLoop(state, snapshot.turn + 1, externalSpecs);
1460
1491
  } catch (e) {
1461
- 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");
1462
1493
  throw e;
1463
1494
  }
1464
1495
  }
@@ -1560,8 +1591,8 @@ var AgentLoop = class {
1560
1591
  message: e.message,
1561
1592
  code: "RUN_INTERACTION_TIMEOUT"
1562
1593
  });
1563
- state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf$1(e)}` });
1564
- throw new BridgeRequestError(messageOf$1(e));
1594
+ state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf(e)}` });
1595
+ throw new BridgeRequestError(messageOf(e));
1565
1596
  }
1566
1597
  if (response.cancelled) throw new RunTerminated({
1567
1598
  status: "cancelled",
@@ -1680,7 +1711,7 @@ var AgentLoop = class {
1680
1711
  };
1681
1712
  } catch (e) {
1682
1713
  if (e instanceof WebSkillError && e.code === "SKILL_NOT_FOUND" && (this.#deps.skillProviders?.length ?? 0) > 0) return this.#handleReadExternalSkill(skillName, pathArg, state);
1683
- 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)}`);
1684
1715
  }
1685
1716
  }
1686
1717
  /** 经外部技能提供者读取技能文档/资源;skillKey 为技能名或 mcp:// URI */
@@ -1720,7 +1751,7 @@ var AgentLoop = class {
1720
1751
  }]
1721
1752
  };
1722
1753
  } catch (e) {
1723
- failures.push(messageOf$1(e));
1754
+ failures.push(messageOf(e));
1724
1755
  continue;
1725
1756
  }
1726
1757
  const detail = failures.length > 0 ? ` (provider errors: ${failures.join("; ")})` : "";
@@ -1734,14 +1765,20 @@ var AgentLoop = class {
1734
1765
  const text = JSON.stringify(result);
1735
1766
  const max = this.#config.toolResultMaxBytes;
1736
1767
  if (text.length <= max) return text;
1737
- const artifact = await this.#deps.artifactStore.createTextArtifact({
1738
- runId: state.runId,
1739
- path: `tool-results/${call.id}.json`,
1740
- content: text
1741
- });
1768
+ let note;
1769
+ try {
1770
+ note = `full tool result saved as artifact "${(await this.#deps.artifactStore.createTextArtifact({
1771
+ runId: state.runId,
1772
+ path: `tool-results/${call.id}.json`,
1773
+ content: text
1774
+ })).id}"`;
1775
+ } catch (e) {
1776
+ state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf(e)}` });
1777
+ note = "full result discarded (artifact store unavailable)";
1778
+ }
1742
1779
  const head = text.slice(0, Math.floor(max * .6));
1743
1780
  const tail = text.slice(-Math.floor(max * .3));
1744
- return `${head}\n...[truncated ${text.length - head.length - tail.length} chars; full tool result saved as artifact "${artifact.id}"]...\n${tail}`;
1781
+ return `${head}\n...[truncated ${text.length - head.length - tail.length} chars; ${note}]...\n${tail}`;
1745
1782
  }
1746
1783
  /** SkillStateGuard 判定(无注入默认全放行;仅显式 false 拦截) */
1747
1784
  async #guardDenied(kind, skillName) {
@@ -1774,7 +1811,7 @@ var AgentLoop = class {
1774
1811
  if (rawAllowed !== void 0) if (Array.isArray(rawAllowed)) allowedTools = rawAllowed.filter((e) => typeof e === "string");
1775
1812
  else state.trace.record("run.warning", { message: `Skill "${skillName}" has a non-array "allowed-tools" metadata entry; ignored` });
1776
1813
  } catch (e) {
1777
- 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)}` });
1778
1815
  }
1779
1816
  const executor = this.#deps.executor;
1780
1817
  const loaded = [];
@@ -1795,7 +1832,7 @@ var AgentLoop = class {
1795
1832
  state.activatedTools.set(def.name, def);
1796
1833
  loaded.push(def.name);
1797
1834
  } catch (e) {
1798
- 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)}` });
1799
1836
  }
1800
1837
  }
1801
1838
  }
@@ -1821,7 +1858,7 @@ var AgentLoop = class {
1821
1858
  def.inputSchema = JSON.parse(await this.#deps.fs.readText(sidecar));
1822
1859
  return;
1823
1860
  } catch (e) {
1824
- 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)}` });
1825
1862
  }
1826
1863
  if (!this.#deps.schemaInferer) return;
1827
1864
  for (const ext of ["ts", "js"]) {
@@ -1831,7 +1868,7 @@ var AgentLoop = class {
1831
1868
  const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
1832
1869
  if (inferred) def.inputSchema = inferred;
1833
1870
  } catch (e) {
1834
- 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)}` });
1835
1872
  }
1836
1873
  return;
1837
1874
  }
@@ -1892,7 +1929,7 @@ var AgentLoop = class {
1892
1929
  } catch (e) {
1893
1930
  if (e instanceof RunTerminated) throw e;
1894
1931
  await this.#bumpSkillStat(skillName, "failures", state);
1895
- 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)}`);
1896
1933
  }
1897
1934
  }
1898
1935
  /** context.confirm 触发点:默认真实询问;auto-approve 直通;无 bridge 降级直通 + warning */
@@ -1931,7 +1968,7 @@ var AgentLoop = class {
1931
1968
  try {
1932
1969
  await this.#deps.memory?.set(scope, key, value);
1933
1970
  } catch (e) {
1934
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1971
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1935
1972
  }
1936
1973
  }
1937
1974
  /** read-modify-write:有 transaction 实现在 scope 原子段内执行(并发不丢计数),否则顺序执行 */
@@ -1944,7 +1981,7 @@ var AgentLoop = class {
1944
1981
  await inner.set(scope, key, mutate(await inner.get(scope, key)));
1945
1982
  });
1946
1983
  } catch (e) {
1947
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1984
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1948
1985
  }
1949
1986
  return;
1950
1987
  }
@@ -1979,6 +2016,7 @@ var AgentLoop = class {
1979
2016
  async #appendParamHistory(state, request, value) {
1980
2017
  if (!this.#deps.memory) return;
1981
2018
  const scope = `session:${state.run.sessionId}`;
2019
+ const limit = this.#config.paramHistoryLimit;
1982
2020
  await this.#memoryMutate(scope, "paramHistory", state, (current) => {
1983
2021
  const history = current ?? [];
1984
2022
  history.push({
@@ -1987,7 +2025,7 @@ var AgentLoop = class {
1987
2025
  type: request.type,
1988
2026
  value
1989
2027
  });
1990
- return history;
2028
+ return history.slice(-limit);
1991
2029
  });
1992
2030
  }
1993
2031
  };
@@ -2074,6 +2112,24 @@ var FsRunSnapshotStore = class {
2074
2112
  }
2075
2113
  };
2076
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
+ /**
2077
2133
  * runtime 门面:组合 discovery / router / agent loop / lifecycle
2078
2134
  * @stable
2079
2135
  */
@@ -2118,20 +2174,28 @@ var WebSkillRuntime = class {
2118
2174
  /**
2119
2175
  * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
2120
2176
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
2177
+ * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
2178
+ * maxHistoryMessages(默认 100)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
2121
2179
  */
2122
2180
  createSession(options = {}) {
2123
2181
  const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
2124
2182
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2183
+ const maxHistory = options.maxHistoryMessages ?? 100;
2125
2184
  let history = [];
2185
+ let queue = Promise.resolve();
2126
2186
  return {
2127
2187
  id: sessionId,
2128
2188
  createdAt,
2129
- run: async (prompt) => {
2130
- const result = await this.run(prompt, {
2131
- sessionId,
2132
- 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;
2133
2197
  });
2134
- history = result.messages.slice(1);
2198
+ queue = result.then(() => void 0, () => void 0);
2135
2199
  return result;
2136
2200
  }
2137
2201
  };
@@ -2379,7 +2443,6 @@ function sourceFromUrl(url) {
2379
2443
  path: url
2380
2444
  };
2381
2445
  }
2382
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
2383
2446
  /** 受控钩子执行器:逐个执行,超时/异常默认降级为 warning,可切严格模式 */
2384
2447
  var HookRunner = class {
2385
2448
  #hooks = /* @__PURE__ */ new Map();
@@ -2637,6 +2700,67 @@ function networkUrlHost(url) {
2637
2700
  return "(unparseable-url)";
2638
2701
  }
2639
2702
  }
2703
+ /** WebSkillErrorCode 全量白名单(错误码归一用;与 core errors.ts 保持同步) */
2704
+ const WHITELIST = /* @__PURE__ */ new Set([
2705
+ "FS_NOT_FOUND",
2706
+ "FS_PATH_OUTSIDE_ROOT",
2707
+ "SKILL_NOT_FOUND",
2708
+ "SKILL_INVALID_METADATA",
2709
+ "SKILL_INVALID_NAME",
2710
+ "SKILL_DUPLICATE_NAME",
2711
+ "SKILL_UNSUPPORTED_SCRIPT",
2712
+ "VALIDATION_FAILED",
2713
+ "TOOL_NOT_FOUND",
2714
+ "TOOL_EXECUTION_FAILED",
2715
+ "NETWORK_BLOCKED",
2716
+ "TOOL_UNSUPPORTED",
2717
+ "TOOL_SCHEMA_UNAVAILABLE",
2718
+ "RUN_TIMEOUT",
2719
+ "RUN_MAX_TURNS_EXCEEDED",
2720
+ "RUN_FAILED",
2721
+ "RUN_CANCELLED",
2722
+ "RUN_INTERACTION_TIMEOUT",
2723
+ "UI_UNAVAILABLE",
2724
+ "LLM_UNAVAILABLE",
2725
+ "LLM_REQUEST_FAILED",
2726
+ "INSTALL_FAILED",
2727
+ "UNINSTALL_FAILED",
2728
+ "EXPORT_FAILED",
2729
+ "INTEGRITY_FAILED",
2730
+ "FS_PERMISSION_DENIED",
2731
+ "MCP_ENDPOINT_UNAVAILABLE",
2732
+ "MCP_TOOL_NOT_FOUND",
2733
+ "CANDIDATE_INVALID",
2734
+ "APPROVAL_REQUIRED",
2735
+ "SKILL_QUARANTINED",
2736
+ "SKILL_DISABLED",
2737
+ "SKILL_UNKNOWN_ALLOWED_TOOL",
2738
+ "SKILL_UNKNOWN_DEPENDENCY",
2739
+ "SKILL_CIRCULAR_DEPENDENCY",
2740
+ "GOVERNANCE_FAILED",
2741
+ "RUN_SNAPSHOT_NOT_FOUND",
2742
+ "RUN_SNAPSHOT_EXPIRED",
2743
+ "RUN_SNAPSHOT_INCOMPATIBLE"
2744
+ ]);
2745
+ /**
2746
+ * 错误码白名单归一:沙箱/桥消息里出现的非白名单码(DOMException 数值码、
2747
+ * Node 任意 ERR_* 码等)一律归为 TOOL_EXECUTION_FAILED。
2748
+ */
2749
+ function normalizeErrorCode(code) {
2750
+ return typeof code === "string" && WHITELIST.has(code) ? code : "TOOL_EXECUTION_FAILED";
2751
+ }
2752
+ /** 归一化后的 (code, message):非白名单码保留在 message 尾部([original code: X]) */
2753
+ function normalizeToolError(code, message) {
2754
+ const normalized = normalizeErrorCode(code);
2755
+ if (typeof code === "string" && normalized === code) return {
2756
+ code: normalized,
2757
+ message
2758
+ };
2759
+ return {
2760
+ code: normalized,
2761
+ message: `${message} [original code: ${String(code)}]`
2762
+ };
2763
+ }
2640
2764
  /**
2641
2765
  * Per-capability 强制授权判定(宿主侧,browser/node 执行器共用单一来源)。
2642
2766
  * 'require-approval' 模式下经注入的 UiBridge 发 authorize 交互;
@@ -2661,6 +2785,7 @@ var CapabilityApproval = class CapabilityApproval {
2661
2785
  async authorize(input) {
2662
2786
  const { runId, capability, mode } = input;
2663
2787
  if (mode === false) return "disabled";
2788
+ if (mode !== true && mode !== "require-approval") return "disabled";
2664
2789
  if (mode !== "require-approval") return "allowed";
2665
2790
  if (this.#scope === "once-per-run" && this.#approved.get(runId)?.has(capability)) return "allowed";
2666
2791
  if (!this.#uiBridge) return "denied";
@@ -2688,4 +2813,4 @@ var CapabilityApproval = class CapabilityApproval {
2688
2813
  };
2689
2814
 
2690
2815
  //#endregion
2691
- export { isNetworkAllowed as A, bridgeError as C, extractChartSpec as D, createWebSkillApi as E, resolveToolName as F, schemaToForm as I, toLlmToolSpec as L, networkUrlHost as M, normalizeToolContent as N, fromVercelResult as O, parseBridgeRequest as P, toVercelToolSpecs 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 };
2816
+ 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 };