@webskill/sdk 0.2.4 → 0.2.6

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-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
@@ -40,7 +40,7 @@ var OpenAiCompatibleClient = class {
40
40
  #fetch;
41
41
  constructor(config) {
42
42
  this.#config = config;
43
- this.#fetch = config.fetchImpl ?? fetch;
43
+ this.#fetch = config.fetchImpl ?? ((input, init) => fetch(input, init));
44
44
  }
45
45
  #requireConfig() {
46
46
  const { baseUrl, apiKey, model } = this.#config;
@@ -262,7 +262,7 @@ var AnthropicClient = class {
262
262
  #fetch;
263
263
  constructor(config) {
264
264
  this.#config = config;
265
- this.#fetch = config.fetchImpl ?? fetch;
265
+ this.#fetch = config.fetchImpl ?? ((input, init) => fetch(input, init));
266
266
  }
267
267
  #baseUrl() {
268
268
  return (this.#config.baseUrl ?? "https://api.anthropic.com").replace(/\/+$/, "");
@@ -491,7 +491,7 @@ var GoogleGenAiClient = class {
491
491
  #fetch;
492
492
  constructor(config) {
493
493
  this.#config = config;
494
- this.#fetch = config.fetchImpl ?? fetch;
494
+ this.#fetch = config.fetchImpl ?? ((input, init) => fetch(input, init));
495
495
  }
496
496
  #baseUrl() {
497
497
  return (this.#config.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
@@ -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: [],
@@ -1071,6 +1092,11 @@ const toolError = (code, message) => ({
1071
1092
  message
1072
1093
  }
1073
1094
  });
1095
+ /** 工具参数摘要(JSON 截断 100 字符;trace 与实时事件同一口径,ToolCallCard 展开详情用) */
1096
+ const summarizeArgs = (args) => {
1097
+ const json = JSON.stringify(args);
1098
+ return json.length > 100 ? `${json.slice(0, 100)}…` : json;
1099
+ };
1074
1100
  /**
1075
1101
  * 多轮 Agent 循环。
1076
1102
  * 工具失败一律转为 ToolResult{ok:false} 回喂 LLM 继续循环;
@@ -1080,6 +1106,10 @@ var AgentLoop = class {
1080
1106
  #deps;
1081
1107
  #config;
1082
1108
  #policy;
1109
+ /** 活跃 run 的 AbortController(cancel(runId) 触发;终态清理) */
1110
+ #controllers = /* @__PURE__ */ new Map();
1111
+ /** 经 cancel() 主动取消的 run(aborted 分支据此区分 cancelled 与 timeout) */
1112
+ #cancelled = /* @__PURE__ */ new Set();
1083
1113
  constructor(deps, config = {}) {
1084
1114
  this.#deps = {
1085
1115
  ...deps,
@@ -1090,6 +1120,7 @@ var AgentLoop = class {
1090
1120
  totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
1091
1121
  toolTimeoutMs: config.toolTimeoutMs ?? 3e4,
1092
1122
  toolResultMaxBytes: config.toolResultMaxBytes ?? 1e5,
1123
+ paramHistoryLimit: config.paramHistoryLimit ?? 50,
1093
1124
  temperature: config.temperature,
1094
1125
  renderResult: config.renderResult
1095
1126
  };
@@ -1099,6 +1130,18 @@ var AgentLoop = class {
1099
1130
  interactionTimeoutMs: deps.interaction?.interactionTimeoutMs ?? 3e5
1100
1131
  };
1101
1132
  }
1133
+ /**
1134
+ * 取消进行中的 run:触发该 run 的 AbortController(与 totalTimeout 硬期限同一通道),
1135
+ * run 以 cancelled(RUN_CANCELLED)终止;未找到(已终态或不属于本实例)返回 false。
1136
+ * 取消在下一个中断点生效(LLM complete/stream 调用;交互等待不强制中断)。
1137
+ */
1138
+ cancel(runId) {
1139
+ const controller = this.#controllers.get(runId);
1140
+ if (!controller) return false;
1141
+ this.#cancelled.add(runId);
1142
+ controller.abort();
1143
+ return true;
1144
+ }
1102
1145
  async run(input) {
1103
1146
  const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1104
1147
  const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
@@ -1134,6 +1177,7 @@ var AgentLoop = class {
1134
1177
  totalTimeoutMs: this.#config.totalTimeoutMs,
1135
1178
  controller: new AbortController()
1136
1179
  };
1180
+ this.#controllers.set(runId, state.controller);
1137
1181
  if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
1138
1182
  const route = this.#deps.catalogFilter ? {
1139
1183
  ...input.route,
@@ -1148,7 +1192,7 @@ var AgentLoop = class {
1148
1192
  try {
1149
1193
  return await source.listToolSpecs();
1150
1194
  } catch (e) {
1151
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf$1(e)}` });
1195
+ trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
1152
1196
  return [];
1153
1197
  }
1154
1198
  }))).flat();
@@ -1166,7 +1210,7 @@ var AgentLoop = class {
1166
1210
  try {
1167
1211
  return await this.#turnLoop(state, 1, externalSpecs);
1168
1212
  } catch (e) {
1169
- if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
1213
+ if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
1170
1214
  throw e;
1171
1215
  }
1172
1216
  }
@@ -1196,78 +1240,87 @@ var AgentLoop = class {
1196
1240
  const { llm } = this.#deps;
1197
1241
  const { trace, messages } = state;
1198
1242
  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;
1243
+ try {
1244
+ for (let turn = startTurn;; turn++) {
1245
+ state.turn = turn;
1246
+ if (turn > state.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${state.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
1247
+ if (this.#elapsed(state) > state.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1248
+ const toolSpecs = [
1249
+ toLlmToolSpec(READ_SKILL_FILE_TOOL),
1250
+ ...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
1251
+ ...[...state.activatedTools.values()].map(toLlmToolSpec),
1252
+ ...externalSpecs
1253
+ ];
1254
+ trace.record("llm.request", { data: {
1255
+ turn,
1256
+ messageCount: messages.length,
1257
+ toolCount: toolSpecs.length
1258
+ } });
1259
+ let response;
1254
1260
  try {
1255
- result = await this.#executeCall(call, state);
1261
+ response = llm.stream ? await this.#completeStreaming(llm, state, {
1262
+ model: this.#deps.model,
1263
+ messages,
1264
+ tools: toolSpecs,
1265
+ temperature: this.#config.temperature,
1266
+ signal: state.controller.signal
1267
+ }) : await llm.complete({
1268
+ model: this.#deps.model,
1269
+ messages,
1270
+ tools: toolSpecs,
1271
+ temperature: this.#config.temperature,
1272
+ signal: state.controller.signal
1273
+ });
1256
1274
  } catch (e) {
1257
- if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
1258
- throw e;
1275
+ if (state.controller.signal.aborted) {
1276
+ if (this.#cancelled.has(state.runId)) return finish("cancelled", "user-cancelled", "Run cancelled by user", "RUN_CANCELLED");
1277
+ return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${state.totalTimeoutMs}ms`, "RUN_TIMEOUT");
1278
+ }
1279
+ const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
1280
+ return finish("failed", "llm-error", messageOf(e), code);
1281
+ }
1282
+ trace.record("llm.response", { data: {
1283
+ turn,
1284
+ hasToolCalls: Boolean(response.toolCalls?.length),
1285
+ contentLength: response.content?.length ?? 0
1286
+ } });
1287
+ if (!response.toolCalls?.length) {
1288
+ messages.push({
1289
+ role: "assistant",
1290
+ content: response.content ?? ""
1291
+ });
1292
+ return finish("completed", "final-answer", response.content ?? "");
1259
1293
  }
1294
+ await this.#lifecycle("execute", state, { turn });
1260
1295
  messages.push({
1261
- role: "tool",
1262
- toolCallId: call.id,
1263
- content: await this.#serializeToolResult(call, result, state)
1296
+ role: "assistant",
1297
+ content: response.content ?? "",
1298
+ toolCalls: response.toolCalls
1264
1299
  });
1300
+ for (const call of response.toolCalls) {
1301
+ let result;
1302
+ try {
1303
+ result = await this.#executeCall(call, state);
1304
+ } catch (e) {
1305
+ if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
1306
+ throw e;
1307
+ }
1308
+ messages.push({
1309
+ role: "tool",
1310
+ toolCallId: call.id,
1311
+ content: await this.#serializeToolResult(call, result, state)
1312
+ });
1313
+ }
1265
1314
  }
1315
+ } finally {
1316
+ this.#disarmDeadline(state);
1266
1317
  }
1267
1318
  }
1268
1319
  /** 统一终态处理:completed/failed/cancelled;终态即删快照(快照是续命机制,审计归治理) */
1269
1320
  async #finish(state, status, reason, output, errorCode) {
1270
1321
  this.#disarmDeadline(state);
1322
+ this.#controllers.delete(state.runId);
1323
+ this.#cancelled.delete(state.runId);
1271
1324
  const { run, trace } = state;
1272
1325
  run.status = status;
1273
1326
  run.terminationReason = reason;
@@ -1292,17 +1345,17 @@ var AgentLoop = class {
1292
1345
  await bridge.renderResult(request);
1293
1346
  trace.record("ui.rendered", { data: { blockCount: request.blocks.length } });
1294
1347
  } catch (e) {
1295
- trace.record("run.warning", { message: `renderResult failed: ${messageOf$1(e)}` });
1348
+ trace.record("run.warning", { message: `renderResult failed: ${messageOf(e)}` });
1296
1349
  }
1297
1350
  if (this.#deps.snapshotStore) try {
1298
1351
  await this.#deps.snapshotStore.delete(state.runId);
1299
1352
  } catch (e) {
1300
- trace.record("run.warning", { message: `Failed to delete run snapshot: ${messageOf$1(e)}` });
1353
+ trace.record("run.warning", { message: `Failed to delete run snapshot: ${messageOf(e)}` });
1301
1354
  }
1302
1355
  try {
1303
1356
  await this.#lifecycle(status === "completed" ? "complete" : "fail", state, { reason });
1304
1357
  } catch (e) {
1305
- trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf$1(e)}` });
1358
+ trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
1306
1359
  }
1307
1360
  run.trace = trace.list();
1308
1361
  return {
@@ -1329,6 +1382,7 @@ var AgentLoop = class {
1329
1382
  interactionExpiresAt: state.run.interruptExpiresAt,
1330
1383
  renderBlocks: [...state.renderBlocks],
1331
1384
  interactionSeq: state.interactionSeq,
1385
+ pausedMs: state.pausedMs,
1332
1386
  config: {
1333
1387
  maxTurns: state.maxTurns,
1334
1388
  totalTimeoutMs: state.totalTimeoutMs,
@@ -1340,7 +1394,7 @@ var AgentLoop = class {
1340
1394
  try {
1341
1395
  await store.save(snapshot);
1342
1396
  } catch (e) {
1343
- state.trace.record("run.warning", { message: `Failed to save run snapshot: ${messageOf$1(e)}` });
1397
+ state.trace.record("run.warning", { message: `Failed to save run snapshot: ${messageOf(e)}` });
1344
1398
  }
1345
1399
  }
1346
1400
  /**
@@ -1376,11 +1430,12 @@ var AgentLoop = class {
1376
1430
  turn: snapshot.turn,
1377
1431
  renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
1378
1432
  startMs,
1379
- pausedMs: 0,
1433
+ pausedMs: snapshot.pausedMs ?? 0,
1380
1434
  maxTurns: snapshot.config.maxTurns,
1381
1435
  totalTimeoutMs: snapshot.config.totalTimeoutMs,
1382
1436
  controller: new AbortController()
1383
1437
  };
1438
+ this.#controllers.set(runId, state.controller);
1384
1439
  if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
1385
1440
  trace.record("run.resumed", { data: {
1386
1441
  snapshotAt: snapshot.snapshotAt,
@@ -1455,14 +1510,14 @@ var AgentLoop = class {
1455
1510
  try {
1456
1511
  return await source.listToolSpecs();
1457
1512
  } catch (e) {
1458
- trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf$1(e)}` });
1513
+ trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
1459
1514
  return [];
1460
1515
  }
1461
1516
  }))).flat();
1462
1517
  try {
1463
1518
  return await this.#turnLoop(state, snapshot.turn + 1, externalSpecs);
1464
1519
  } catch (e) {
1465
- if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf$1(e), "RUN_FAILED");
1520
+ if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
1466
1521
  throw e;
1467
1522
  }
1468
1523
  }
@@ -1564,8 +1619,8 @@ var AgentLoop = class {
1564
1619
  message: e.message,
1565
1620
  code: "RUN_INTERACTION_TIMEOUT"
1566
1621
  });
1567
- state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf$1(e)}` });
1568
- throw new BridgeRequestError(messageOf$1(e));
1622
+ state.trace.record("run.warning", { message: `UiBridge request failed: ${messageOf(e)}` });
1623
+ throw new BridgeRequestError(messageOf(e));
1569
1624
  }
1570
1625
  if (response.cancelled) throw new RunTerminated({
1571
1626
  status: "cancelled",
@@ -1601,10 +1656,14 @@ var AgentLoop = class {
1601
1656
  });
1602
1657
  }
1603
1658
  async #executeCall(call, state) {
1659
+ const argsSummary = summarizeArgs(call.arguments);
1660
+ const callStartMs = Date.parse(state.now());
1604
1661
  state.trace.record("tool.started", { data: {
1605
1662
  name: call.name,
1606
- callId: call.id
1663
+ callId: call.id,
1664
+ args: argsSummary
1607
1665
  } });
1666
+ this.#emitTool(state, "started", call);
1608
1667
  let result;
1609
1668
  if (call.name === "read_skill_file") result = await this.#handleReadSkillFile(call, state);
1610
1669
  else if (call.name === "ask_user") result = await this.#handleAskUser(call, state);
@@ -1616,11 +1675,15 @@ var AgentLoop = class {
1616
1675
  result = source ? await source.call(call.name, call.arguments) : toolError(resolution.code, resolution.message);
1617
1676
  }
1618
1677
  }
1678
+ const durationMs = Date.parse(state.now()) - callStartMs;
1619
1679
  if (result.ok) {
1620
1680
  state.trace.record("tool.completed", { data: {
1621
1681
  name: call.name,
1622
- callId: call.id
1682
+ callId: call.id,
1683
+ args: argsSummary,
1684
+ durationMs
1623
1685
  } });
1686
+ this.#emitTool(state, "completed", call);
1624
1687
  for (const item of result.content) {
1625
1688
  if (item.type !== "json") continue;
1626
1689
  const chart = extractChartSpec(item.data);
@@ -1629,20 +1692,45 @@ var AgentLoop = class {
1629
1692
  chart
1630
1693
  });
1631
1694
  }
1632
- } else state.trace.record("tool.failed", {
1633
- message: result.error?.message,
1634
- data: {
1635
- name: call.name,
1636
- callId: call.id,
1637
- code: result.error?.code
1638
- }
1639
- });
1695
+ } else {
1696
+ state.trace.record("tool.failed", {
1697
+ message: result.error?.message,
1698
+ data: {
1699
+ name: call.name,
1700
+ callId: call.id,
1701
+ code: result.error?.code,
1702
+ args: argsSummary,
1703
+ durationMs
1704
+ }
1705
+ });
1706
+ this.#emitTool(state, "failed", call);
1707
+ }
1640
1708
  for (const artifact of result.artifacts ?? []) state.trace.record("artifact.created", { data: {
1641
1709
  artifactId: artifact.id,
1642
1710
  path: artifact.path
1643
1711
  } });
1644
1712
  return result;
1645
1713
  }
1714
+ /**
1715
+ * 逐工具实时事件(execute 相位,data.type='tool'):chatbot ToolCallCard 等的 live 状态源;
1716
+ * 与既有 execute 相位事件({turn})并存,监听方按 data.type 区分。
1717
+ * data.args 为参数摘要(JSON 截断 100 字符,卡片展开详情用)。
1718
+ */
1719
+ #emitTool(state, status, call) {
1720
+ this.#deps.eventBus?.emit({
1721
+ phase: "execute",
1722
+ runId: state.runId,
1723
+ sessionId: state.run.sessionId,
1724
+ ts: state.now(),
1725
+ data: {
1726
+ type: "tool",
1727
+ status,
1728
+ name: call.name,
1729
+ callId: call.id,
1730
+ args: summarizeArgs(call.arguments)
1731
+ }
1732
+ });
1733
+ }
1646
1734
  async #handleAskUser(call, state) {
1647
1735
  const question = call.arguments["question"];
1648
1736
  if (typeof question !== "string" || question === "") return toolError("TOOL_EXECUTION_FAILED", "ask_user requires a non-empty \"question\" string argument");
@@ -1684,7 +1772,7 @@ var AgentLoop = class {
1684
1772
  };
1685
1773
  } catch (e) {
1686
1774
  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)}`);
1775
+ return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `read_skill_file failed: ${messageOf(e)}`);
1688
1776
  }
1689
1777
  }
1690
1778
  /** 经外部技能提供者读取技能文档/资源;skillKey 为技能名或 mcp:// URI */
@@ -1724,7 +1812,7 @@ var AgentLoop = class {
1724
1812
  }]
1725
1813
  };
1726
1814
  } catch (e) {
1727
- failures.push(messageOf$1(e));
1815
+ failures.push(messageOf(e));
1728
1816
  continue;
1729
1817
  }
1730
1818
  const detail = failures.length > 0 ? ` (provider errors: ${failures.join("; ")})` : "";
@@ -1746,7 +1834,7 @@ var AgentLoop = class {
1746
1834
  content: text
1747
1835
  })).id}"`;
1748
1836
  } catch (e) {
1749
- state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf$1(e)}` });
1837
+ state.trace.record("run.warning", { message: `Failed to persist oversized tool result artifact for "${call.name}": ${messageOf(e)}` });
1750
1838
  note = "full result discarded (artifact store unavailable)";
1751
1839
  }
1752
1840
  const head = text.slice(0, Math.floor(max * .6));
@@ -1784,7 +1872,7 @@ var AgentLoop = class {
1784
1872
  if (rawAllowed !== void 0) if (Array.isArray(rawAllowed)) allowedTools = rawAllowed.filter((e) => typeof e === "string");
1785
1873
  else state.trace.record("run.warning", { message: `Skill "${skillName}" has a non-array "allowed-tools" metadata entry; ignored` });
1786
1874
  } catch (e) {
1787
- state.trace.record("run.warning", { message: `Failed to read or parse SKILL.md of skill "${skillName}": ${messageOf$1(e)}` });
1875
+ state.trace.record("run.warning", { message: `Failed to read or parse SKILL.md of skill "${skillName}": ${messageOf(e)}` });
1788
1876
  }
1789
1877
  const executor = this.#deps.executor;
1790
1878
  const loaded = [];
@@ -1805,7 +1893,7 @@ var AgentLoop = class {
1805
1893
  state.activatedTools.set(def.name, def);
1806
1894
  loaded.push(def.name);
1807
1895
  } catch (e) {
1808
- state.trace.record("run.warning", { message: `Failed to load definition of script "${match[1]}" for skill "${skillName}": ${messageOf$1(e)}` });
1896
+ state.trace.record("run.warning", { message: `Failed to load definition of script "${match[1]}" for skill "${skillName}": ${messageOf(e)}` });
1809
1897
  }
1810
1898
  }
1811
1899
  }
@@ -1831,7 +1919,7 @@ var AgentLoop = class {
1831
1919
  def.inputSchema = JSON.parse(await this.#deps.fs.readText(sidecar));
1832
1920
  return;
1833
1921
  } catch (e) {
1834
- state.trace.record("run.warning", { message: `Failed to parse schema sidecar ${sidecar}: ${messageOf$1(e)}` });
1922
+ state.trace.record("run.warning", { message: `Failed to parse schema sidecar ${sidecar}: ${messageOf(e)}` });
1835
1923
  }
1836
1924
  if (!this.#deps.schemaInferer) return;
1837
1925
  for (const ext of ["ts", "js"]) {
@@ -1841,7 +1929,7 @@ var AgentLoop = class {
1841
1929
  const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
1842
1930
  if (inferred) def.inputSchema = inferred;
1843
1931
  } catch (e) {
1844
- state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf$1(e)}` });
1932
+ state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf(e)}` });
1845
1933
  }
1846
1934
  return;
1847
1935
  }
@@ -1902,7 +1990,7 @@ var AgentLoop = class {
1902
1990
  } catch (e) {
1903
1991
  if (e instanceof RunTerminated) throw e;
1904
1992
  await this.#bumpSkillStat(skillName, "failures", state);
1905
- return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `Tool "${call.name}" failed: ${messageOf$1(e)}`);
1993
+ return toolError(e instanceof WebSkillError ? e.code : "TOOL_EXECUTION_FAILED", `Tool "${call.name}" failed: ${messageOf(e)}`);
1906
1994
  }
1907
1995
  }
1908
1996
  /** context.confirm 触发点:默认真实询问;auto-approve 直通;无 bridge 降级直通 + warning */
@@ -1941,7 +2029,7 @@ var AgentLoop = class {
1941
2029
  try {
1942
2030
  await this.#deps.memory?.set(scope, key, value);
1943
2031
  } catch (e) {
1944
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
2032
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1945
2033
  }
1946
2034
  }
1947
2035
  /** read-modify-write:有 transaction 实现在 scope 原子段内执行(并发不丢计数),否则顺序执行 */
@@ -1954,7 +2042,7 @@ var AgentLoop = class {
1954
2042
  await inner.set(scope, key, mutate(await inner.get(scope, key)));
1955
2043
  });
1956
2044
  } catch (e) {
1957
- state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
2045
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf(e)}` });
1958
2046
  }
1959
2047
  return;
1960
2048
  }
@@ -1989,6 +2077,7 @@ var AgentLoop = class {
1989
2077
  async #appendParamHistory(state, request, value) {
1990
2078
  if (!this.#deps.memory) return;
1991
2079
  const scope = `session:${state.run.sessionId}`;
2080
+ const limit = this.#config.paramHistoryLimit;
1992
2081
  await this.#memoryMutate(scope, "paramHistory", state, (current) => {
1993
2082
  const history = current ?? [];
1994
2083
  history.push({
@@ -1997,7 +2086,7 @@ var AgentLoop = class {
1997
2086
  type: request.type,
1998
2087
  value
1999
2088
  });
2000
- return history;
2089
+ return history.slice(-limit);
2001
2090
  });
2002
2091
  }
2003
2092
  };
@@ -2084,6 +2173,24 @@ var FsRunSnapshotStore = class {
2084
2173
  }
2085
2174
  };
2086
2175
  /**
2176
+ * session history 滚动裁剪:超出预算时裁掉中段,保留首尾。
2177
+ * 边界对齐 LLM tool 契约:head 不以未应答的 assistant toolCalls 结尾,
2178
+ * tail 不以孤儿 tool 消息开头(其 assistant 调用已随中段裁掉)。
2179
+ */
2180
+ function trimSessionHistory(messages, max) {
2181
+ if (max < 2 || messages.length <= max) return messages;
2182
+ let head = Math.ceil(max / 2);
2183
+ while (head > 0) {
2184
+ const last = messages[head - 1];
2185
+ if (last.role === "assistant" && last.toolCalls?.length) head -= 1;
2186
+ else break;
2187
+ }
2188
+ let tailStart = Math.max(head, messages.length - (max - head));
2189
+ while (tailStart < messages.length && messages[tailStart].role === "tool") tailStart += 1;
2190
+ if (tailStart >= messages.length) return messages.slice(-max);
2191
+ return [...messages.slice(0, head), ...messages.slice(tailStart)];
2192
+ }
2193
+ /**
2087
2194
  * runtime 门面:组合 discovery / router / agent loop / lifecycle
2088
2195
  * @stable
2089
2196
  */
@@ -2094,6 +2201,8 @@ var WebSkillRuntime = class {
2094
2201
  #session;
2095
2202
  #events;
2096
2203
  #catalogCache;
2204
+ /** 活跃 run 的 loop 实例(cancel(runId) 路由;终态清理) */
2205
+ #loops = /* @__PURE__ */ new Map();
2097
2206
  constructor(deps) {
2098
2207
  this.#deps = {
2099
2208
  ...deps,
@@ -2128,20 +2237,28 @@ var WebSkillRuntime = class {
2128
2237
  /**
2129
2238
  * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
2130
2239
  * 既有 runtime.run(prompt) 保持无状态单次语义不变。
2240
+ * 同 handle 并发 run 经 per-handle 队列串行化(防历史 last-writer-wins 丢轮次);
2241
+ * maxHistoryMessages(默认 100)超出时滚动裁剪中段(保留首尾;边界对齐 tool 契约)。
2131
2242
  */
2132
2243
  createSession(options = {}) {
2133
2244
  const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
2134
2245
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2246
+ const maxHistory = options.maxHistoryMessages ?? 100;
2135
2247
  let history = [];
2248
+ let queue = Promise.resolve();
2136
2249
  return {
2137
2250
  id: sessionId,
2138
2251
  createdAt,
2139
- run: async (prompt) => {
2140
- const result = await this.run(prompt, {
2141
- sessionId,
2142
- history
2252
+ run: (prompt) => {
2253
+ const result = queue.then(async () => {
2254
+ const r = await this.run(prompt, {
2255
+ sessionId,
2256
+ history
2257
+ });
2258
+ history = trimSessionHistory(r.messages.slice(1), maxHistory);
2259
+ return r;
2143
2260
  });
2144
- history = result.messages.slice(1);
2261
+ queue = result.then(() => void 0, () => void 0);
2145
2262
  return result;
2146
2263
  }
2147
2264
  };
@@ -2170,7 +2287,7 @@ var WebSkillRuntime = class {
2170
2287
  const catalog = providerEntries.length > 0 ? { entries: mergeCatalogEntries(cache.catalog.entries, providerEntries) } : cache.catalog;
2171
2288
  const filteredCatalog = this.#deps.catalogFilter ? { entries: await this.#deps.catalogFilter(catalog.entries) } : catalog;
2172
2289
  const route = await this.#router.route(filteredCatalog);
2173
- const result = await new AgentLoop({
2290
+ const loop = new AgentLoop({
2174
2291
  llm: this.#deps.llm,
2175
2292
  executor: this.#deps.executor,
2176
2293
  schemaInferer: this.#deps.schemaInferer,
@@ -2189,12 +2306,21 @@ var WebSkillRuntime = class {
2189
2306
  catalogFilter: this.#deps.catalogFilter,
2190
2307
  snapshotStore: this.#deps.snapshotStore,
2191
2308
  skillStateGuard: this.#deps.skillStateGuard
2192
- }, this.#deps.config).run({
2193
- sessionId: options.sessionId ?? this.#session.id,
2194
- userPrompt,
2195
- route,
2196
- ...options.history ? { history: options.history } : {}
2197
- });
2309
+ }, this.#deps.config);
2310
+ const runId = `run-${Math.random().toString(36).slice(2, 10)}`;
2311
+ this.#loops.set(runId, loop);
2312
+ let result;
2313
+ try {
2314
+ result = await loop.run({
2315
+ sessionId: options.sessionId ?? this.#session.id,
2316
+ userPrompt,
2317
+ route,
2318
+ runId,
2319
+ ...options.history ? { history: options.history } : {}
2320
+ });
2321
+ } finally {
2322
+ this.#loops.delete(runId);
2323
+ }
2198
2324
  for (const failure of providerFailures) result.run.trace.push({
2199
2325
  id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
2200
2326
  runId: result.run.id,
@@ -2269,7 +2395,7 @@ var WebSkillRuntime = class {
2269
2395
  if (!this.#catalogCache) await this.discover();
2270
2396
  const cache = this.#catalogCache;
2271
2397
  if (!cache) throw new WebSkillError("RUN_FAILED", "discover() did not populate the catalog cache");
2272
- return new AgentLoop({
2398
+ const loop = new AgentLoop({
2273
2399
  llm: this.#deps.llm,
2274
2400
  executor: this.#deps.executor,
2275
2401
  schemaInferer: this.#deps.schemaInferer,
@@ -2288,7 +2414,21 @@ var WebSkillRuntime = class {
2288
2414
  catalogFilter: this.#deps.catalogFilter,
2289
2415
  snapshotStore: store,
2290
2416
  skillStateGuard: this.#deps.skillStateGuard
2291
- }, this.#deps.config).resume(snapshot);
2417
+ }, this.#deps.config);
2418
+ this.#loops.set(runId, loop);
2419
+ try {
2420
+ return await loop.resume(snapshot);
2421
+ } finally {
2422
+ this.#loops.delete(runId);
2423
+ }
2424
+ }
2425
+ /**
2426
+ * 取消进行中的 run(chatbot Stop 按钮等):触发该 run 的 AbortController,
2427
+ * run 以 cancelled(RUN_CANCELLED)终止;未找到活跃 run 返回 false。
2428
+ * 取消在下一个中断点生效(LLM complete/stream;交互等待不强制中断)。
2429
+ */
2430
+ cancel(runId) {
2431
+ return this.#loops.get(runId)?.cancel(runId) ?? false;
2292
2432
  }
2293
2433
  };
2294
2434
  /**
@@ -2389,7 +2529,6 @@ function sourceFromUrl(url) {
2389
2529
  path: url
2390
2530
  };
2391
2531
  }
2392
- const messageOf = (e) => e instanceof Error ? e.message : String(e);
2393
2532
  /** 受控钩子执行器:逐个执行,超时/异常默认降级为 warning,可切严格模式 */
2394
2533
  var HookRunner = class {
2395
2534
  #hooks = /* @__PURE__ */ new Map();