@linnlabs/linnkit 0.8.0 → 0.10.0

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/bin/linnkit.cjs +7 -0
  5. package/dist/{agentSpec-EkmviZjy.d.cts → agentSpec-Du4Iye0q.d.cts} +16 -1
  6. package/dist/{agentSpec-EkmviZjy.d.ts → agentSpec-Du4Iye0q.d.ts} +16 -1
  7. package/dist/cli.cjs +234 -91
  8. package/dist/cli.cjs.map +1 -1
  9. package/dist/cli.js +234 -91
  10. package/dist/cli.js.map +1 -1
  11. package/dist/context-manager.cjs +230 -32
  12. package/dist/context-manager.cjs.map +1 -1
  13. package/dist/context-manager.d.cts +52 -15
  14. package/dist/context-manager.d.ts +52 -15
  15. package/dist/context-manager.js +230 -33
  16. package/dist/context-manager.js.map +1 -1
  17. package/dist/{context-trace-HE2qY5Q-.d.cts → context-trace-BHKDS-eq.d.cts} +2 -2
  18. package/dist/{context-trace-DRi5M4lX.d.ts → context-trace-CHbqHmyE.d.ts} +2 -2
  19. package/dist/contracts.cjs +3 -1
  20. package/dist/contracts.cjs.map +1 -1
  21. package/dist/contracts.d.cts +3 -3
  22. package/dist/contracts.d.ts +3 -3
  23. package/dist/contracts.js +3 -1
  24. package/dist/contracts.js.map +1 -1
  25. package/dist/{defaultGraphExecutor-BBswR8wn.d.ts → defaultGraphExecutor-B29_qTHy.d.ts} +16 -15
  26. package/dist/{defaultGraphExecutor-BIjJj7WF.d.cts → defaultGraphExecutor-C2E59v_R.d.cts} +16 -15
  27. package/dist/{index-Cm-JbzTH.d.cts → index-BAaUP9yU.d.cts} +38 -15
  28. package/dist/{index-DRBWi1fy.d.ts → index-BaVpVNi2.d.ts} +38 -15
  29. package/dist/{index-DO4dQgf2.d.cts → index-BnYCS8Zg.d.cts} +2 -2
  30. package/dist/{index-CJeWHopy.d.ts → index-C0DAjsdX.d.ts} +2 -2
  31. package/dist/{index-Dl5PLgAv.d.cts → index-CKQzzZ5Y.d.cts} +2 -2
  32. package/dist/{index-CHqwkvGp.d.ts → index-D0mKxTR5.d.ts} +2 -2
  33. package/dist/index.cjs +327 -110
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +10 -10
  36. package/dist/index.d.ts +10 -10
  37. package/dist/index.js +327 -110
  38. package/dist/index.js.map +1 -1
  39. package/dist/{ports-DnLuKfpE.d.ts → ports-DpPTFhSd.d.ts} +2 -2
  40. package/dist/{ports-DaatKJXp.d.cts → ports-s-tSp3sB.d.cts} +2 -2
  41. package/dist/quickstart.cjs +232 -88
  42. package/dist/quickstart.cjs.map +1 -1
  43. package/dist/quickstart.d.cts +7 -7
  44. package/dist/quickstart.d.ts +7 -7
  45. package/dist/quickstart.js +232 -88
  46. package/dist/quickstart.js.map +1 -1
  47. package/dist/{runAgent-CPj_9e58.d.ts → runAgent-C6F-399C.d.ts} +5 -5
  48. package/dist/{runAgent-HYKlXbVr.d.cts → runAgent-ilEj66Ik.d.cts} +5 -5
  49. package/dist/{runHandle-D3gPsD7B.d.cts → runHandle-BNOqS-Bl.d.cts} +3 -3
  50. package/dist/{runHandle-CyXvzgzk.d.ts → runHandle-BdLXOFqF.d.ts} +3 -3
  51. package/dist/runtime-kernel/events.cjs +1 -0
  52. package/dist/runtime-kernel/events.cjs.map +1 -1
  53. package/dist/runtime-kernel/events.d.cts +4 -4
  54. package/dist/runtime-kernel/events.d.ts +4 -4
  55. package/dist/runtime-kernel/events.js +1 -0
  56. package/dist/runtime-kernel/events.js.map +1 -1
  57. package/dist/runtime-kernel.cjs +318 -103
  58. package/dist/runtime-kernel.cjs.map +1 -1
  59. package/dist/runtime-kernel.d.cts +8 -8
  60. package/dist/runtime-kernel.d.ts +8 -8
  61. package/dist/runtime-kernel.js +315 -104
  62. package/dist/runtime-kernel.js.map +1 -1
  63. package/dist/testkit.cjs +331 -116
  64. package/dist/testkit.cjs.map +1 -1
  65. package/dist/testkit.d.cts +8 -8
  66. package/dist/testkit.d.ts +8 -8
  67. package/dist/testkit.js +331 -116
  68. package/dist/testkit.js.map +1 -1
  69. package/dist/{todo-B1PmDlp3.d.cts → todo-Ca8llpRQ.d.cts} +1 -1
  70. package/dist/{todo-B1PmDlp3.d.ts → todo-Ca8llpRQ.d.ts} +1 -1
  71. package/dist/{toolContracts-CLkQmhTG.d.cts → toolContracts-Bm3EZ1UM.d.cts} +13 -2
  72. package/dist/{toolContracts-Blll0241.d.ts → toolContracts-f8lzZBNa.d.ts} +13 -2
  73. package/docs/integration/README.md +1 -1
  74. package/docs/integration/agent-registration-guide.md +1 -1
  75. package/docs/integration/child-runs.md +4 -1
  76. package/docs/integration/context-engineering.md +30 -15
  77. package/docs/integration/context-fences.md +32 -3
  78. package/docs/integration/llm-provider.md +1 -1
  79. package/docs/integration/persistence.md +1 -0
  80. package/docs/integration/run-supervisor.md +3 -0
  81. package/docs/integration/tool-development-guide.md +7 -5
  82. package/docs/integration/tool-history.md +43 -17
  83. package/package.json +5 -4
@@ -168,6 +168,7 @@ var AgentSpecBudgetPolicy = zod.z.object({
168
168
  });
169
169
  var AgentSpecToolHistoryPolicy = zod.z.object({
170
170
  strategy: zod.z.enum(["per-pair", "per-run", "none"]).optional(),
171
+ retentionMode: zod.z.enum(["drop", "compress"]).optional(),
171
172
  keepLatestToolPairs: zod.z.number().int().nonnegative().optional(),
172
173
  keepLatestRuns: zod.z.number().int().nonnegative().optional(),
173
174
  maxInteractionGroups: zod.z.number().int().nonnegative().optional(),
@@ -282,12 +283,13 @@ var AgentSpecContextPolicy = zod.z.object({
282
283
  var DEFAULT_CONTEXT_POLICY = {
283
284
  profileId: "agent",
284
285
  budget: {
285
- maxTokens: 12e4,
286
+ maxTokens: 232e3,
286
287
  reservedForResponse: 2400,
287
288
  workingMemoryBudgetPercentage: 0.7
288
289
  },
289
290
  toolHistory: {
290
291
  strategy: "per-run",
292
+ retentionMode: "drop",
291
293
  keepLatestToolPairs: 2,
292
294
  keepLatestRuns: 1,
293
295
  maxInteractionGroups: 12,
@@ -1273,6 +1275,17 @@ var logger = new Logger("GraphExecutor");
1273
1275
  function asLocalRecord(local) {
1274
1276
  return local && typeof local === "object" ? { ...local } : {};
1275
1277
  }
1278
+ function readNonEmptyString(value) {
1279
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1280
+ }
1281
+ function readRuntimeConversationId(state) {
1282
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1283
+ return readNonEmptyString(local?.conversationId);
1284
+ }
1285
+ function readRuntimeTurnId(state) {
1286
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1287
+ return readNonEmptyString(local?.turnId);
1288
+ }
1276
1289
  var GraphExecutor = class {
1277
1290
  constructor(checkpointer, config = {}) {
1278
1291
  this.checkpointer = checkpointer;
@@ -1290,8 +1303,8 @@ var GraphExecutor = class {
1290
1303
  registerNode(node) {
1291
1304
  this.nodes.set(node.id, node);
1292
1305
  }
1293
- async peekCheckpoint(conversationId) {
1294
- return await this.checkpointer.load(conversationId);
1306
+ async peekCheckpoint(checkpointKey) {
1307
+ return await this.checkpointer.load(checkpointKey);
1295
1308
  }
1296
1309
  sanitize(state) {
1297
1310
  const local = asLocalRecord(state.local);
@@ -1303,8 +1316,8 @@ var GraphExecutor = class {
1303
1316
  local
1304
1317
  };
1305
1318
  }
1306
- async prime(conversationId, local, nodeId = "user") {
1307
- this.ephemeralLocals.set(conversationId, { ...local || {} });
1319
+ async prime(checkpointKey, local, nodeId = "user") {
1320
+ this.ephemeralLocals.set(checkpointKey, { ...local || {} });
1308
1321
  const localSansMemory = { ...local || {} };
1309
1322
  if ("memory" in localSansMemory) delete localSansMemory.memory;
1310
1323
  const state = {
@@ -1312,10 +1325,10 @@ var GraphExecutor = class {
1312
1325
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1313
1326
  local: localSansMemory
1314
1327
  };
1315
- await this.checkpointer.save(conversationId, state);
1328
+ await this.checkpointer.save(checkpointKey, state);
1316
1329
  }
1317
- async setNode(conversationId, nodeId, localPatch) {
1318
- const current = await this.checkpointer.load(conversationId) || {
1330
+ async setNode(checkpointKey, nodeId, localPatch) {
1331
+ const current = await this.checkpointer.load(checkpointKey) || {
1319
1332
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1320
1333
  local: {}
1321
1334
  };
@@ -1326,19 +1339,46 @@ var GraphExecutor = class {
1326
1339
  schemaVersion: current.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
1327
1340
  local: mergedLocal
1328
1341
  };
1329
- await this.checkpointer.save(conversationId, next);
1342
+ await this.checkpointer.save(checkpointKey, next);
1330
1343
  }
1331
- async runUntilYield(conversationId) {
1344
+ async runUntilYield(checkpointKey) {
1332
1345
  const runId = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1333
1346
  let lifecyclePhase = "completed";
1347
+ let initialState = null;
1348
+ try {
1349
+ initialState = await this.loadInitialState(checkpointKey);
1350
+ } catch (err) {
1351
+ lifecyclePhase = "failed";
1352
+ this.telemetryPort.emit({
1353
+ kind: "run_lifecycle",
1354
+ runId,
1355
+ phase: "spawned",
1356
+ scope: {}
1357
+ });
1358
+ this.telemetryPort.emit({
1359
+ kind: "run_lifecycle",
1360
+ runId,
1361
+ phase: lifecyclePhase,
1362
+ scope: {}
1363
+ });
1364
+ throw err;
1365
+ }
1366
+ let lifecycleConversationId = readRuntimeConversationId(initialState);
1367
+ let lifecycleTurnId = readRuntimeTurnId(initialState);
1334
1368
  this.telemetryPort.emit({
1335
1369
  kind: "run_lifecycle",
1336
1370
  runId,
1337
1371
  phase: "spawned",
1338
- scope: { conversationId: conversationId || void 0 }
1372
+ scope: {
1373
+ conversationId: lifecycleConversationId,
1374
+ turnId: lifecycleTurnId
1375
+ }
1339
1376
  });
1340
1377
  try {
1341
- return await this.runUntilYieldInternal(conversationId);
1378
+ const result = await this.runUntilYieldInternal(checkpointKey, initialState);
1379
+ lifecycleConversationId = readRuntimeConversationId(result.checkpoint);
1380
+ lifecycleTurnId = readRuntimeTurnId(result.checkpoint);
1381
+ return result;
1342
1382
  } catch (err) {
1343
1383
  lifecyclePhase = err?.name === "AbortError" ? "cancelled" : "failed";
1344
1384
  throw err;
@@ -1347,17 +1387,23 @@ var GraphExecutor = class {
1347
1387
  kind: "run_lifecycle",
1348
1388
  runId,
1349
1389
  phase: lifecyclePhase,
1350
- scope: { conversationId: conversationId || void 0 }
1390
+ scope: {
1391
+ conversationId: lifecycleConversationId,
1392
+ turnId: lifecycleTurnId
1393
+ }
1351
1394
  });
1352
1395
  }
1353
1396
  }
1354
- async runUntilYieldInternal(conversationId) {
1355
- let state = await this.checkpointer.load(conversationId) || {
1397
+ async loadInitialState(checkpointKey) {
1398
+ return await this.checkpointer.load(checkpointKey) || {
1356
1399
  nodeId: "user",
1357
1400
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1358
1401
  local: {}
1359
1402
  };
1360
- const ephemeral = this.ephemeralLocals.get(conversationId) || {};
1403
+ }
1404
+ async runUntilYieldInternal(checkpointKey, initialState) {
1405
+ let state = initialState;
1406
+ const ephemeral = this.ephemeralLocals.get(checkpointKey) || {};
1361
1407
  state = {
1362
1408
  ...state,
1363
1409
  schemaVersion: state.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
@@ -1386,7 +1432,7 @@ var GraphExecutor = class {
1386
1432
  const signalRaw = state.local?.signal;
1387
1433
  if (isAbortSignal2(signalRaw) && signalRaw.aborted) {
1388
1434
  logger.warn("[GraphExecutor] \u6536\u5230 AbortSignal\uFF0C\u7ACB\u5373\u505C\u6B62\u63A8\u7406\u5FAA\u73AF");
1389
- this.ephemeralLocals.delete(conversationId);
1435
+ this.ephemeralLocals.delete(checkpointKey);
1390
1436
  throwAbortError();
1391
1437
  }
1392
1438
  const isLastStep = cycleStepCount >= this.config.maxSteps;
@@ -1434,8 +1480,8 @@ var GraphExecutor = class {
1434
1480
  checkpointCount
1435
1481
  });
1436
1482
  const cp2 = this.sanitize(state);
1437
- await this.checkpointer.save(conversationId, cp2);
1438
- this.ephemeralLocals.delete(conversationId);
1483
+ await this.checkpointer.save(checkpointKey, cp2);
1484
+ this.ephemeralLocals.delete(checkpointKey);
1439
1485
  return { events: allEvents, checkpoint: cp2, stepCount };
1440
1486
  }
1441
1487
  logger.info("[GraphExecutor] \u8282\u70B9\u5207\u6362", {
@@ -1446,18 +1492,18 @@ var GraphExecutor = class {
1446
1492
  });
1447
1493
  const nodeRunStartedAt = Date.now();
1448
1494
  const nodeIdForTelemetry = state.nodeId;
1495
+ const conversationIdForTelemetry = readRuntimeConversationId(state);
1449
1496
  let result;
1450
1497
  try {
1451
1498
  result = await node.run(state);
1452
1499
  } finally {
1453
- const turnIdForTelemetry = typeof state.local?.turnId === "string" ? state.local.turnId : void 0;
1454
1500
  this.telemetryPort.emit({
1455
1501
  kind: "graph_node",
1456
1502
  nodeId: nodeIdForTelemetry,
1457
1503
  durationMs: Date.now() - nodeRunStartedAt,
1458
1504
  scope: {
1459
- conversationId: conversationId || void 0,
1460
- turnId: turnIdForTelemetry
1505
+ conversationId: conversationIdForTelemetry,
1506
+ turnId: readRuntimeTurnId(state)
1461
1507
  }
1462
1508
  });
1463
1509
  }
@@ -1496,7 +1542,7 @@ var GraphExecutor = class {
1496
1542
  });
1497
1543
  state = { ...state, nodeId: nextNodeId };
1498
1544
  const cp2 = this.sanitize(state);
1499
- await this.checkpointer.save(conversationId, cp2);
1545
+ await this.checkpointer.save(checkpointKey, cp2);
1500
1546
  continue;
1501
1547
  }
1502
1548
  if (result.kind === "yield") {
@@ -1507,7 +1553,7 @@ var GraphExecutor = class {
1507
1553
  checkpointCount
1508
1554
  });
1509
1555
  const cp2 = this.sanitize(state);
1510
- await this.checkpointer.save(conversationId, cp2);
1556
+ await this.checkpointer.save(checkpointKey, cp2);
1511
1557
  return { events: allEvents, checkpoint: cp2, stepCount };
1512
1558
  }
1513
1559
  if (result.kind === "pause") {
@@ -1518,7 +1564,7 @@ var GraphExecutor = class {
1518
1564
  checkpointCount
1519
1565
  });
1520
1566
  const cp2 = this.sanitize(state);
1521
- await this.checkpointer.save(conversationId, cp2);
1567
+ await this.checkpointer.save(checkpointKey, cp2);
1522
1568
  return { events: allEvents, checkpoint: cp2, stepCount };
1523
1569
  }
1524
1570
  }
@@ -1529,8 +1575,8 @@ var GraphExecutor = class {
1529
1575
  checkpointCount
1530
1576
  });
1531
1577
  const cp = this.sanitize(state);
1532
- await this.checkpointer.save(conversationId, cp);
1533
- this.ephemeralLocals.delete(conversationId);
1578
+ await this.checkpointer.save(checkpointKey, cp);
1579
+ this.ephemeralLocals.delete(checkpointKey);
1534
1580
  return { events: allEvents, checkpoint: cp, stepCount };
1535
1581
  }
1536
1582
  };
@@ -1693,15 +1739,15 @@ function splitConcatenatedJsonObjects(input) {
1693
1739
  }
1694
1740
 
1695
1741
  // src/runtime-kernel/graph-engine/tick-pipeline/helpers.ts
1696
- function readNonEmptyString(value) {
1742
+ function readNonEmptyString2(value) {
1697
1743
  if (typeof value !== "string") return void 0;
1698
1744
  const trimmed = value.trim();
1699
1745
  return trimmed.length > 0 ? trimmed : void 0;
1700
1746
  }
1701
1747
  function resolveConversationIdForRuntimeEvents(toolContext) {
1702
- const fromCamel = readNonEmptyString(toolContext?.conversationId);
1748
+ const fromCamel = readNonEmptyString2(toolContext?.conversationId);
1703
1749
  if (fromCamel) return fromCamel;
1704
- const fromSnake = toolContext ? readNonEmptyString(toolContext["conversation_id"]) : void 0;
1750
+ const fromSnake = toolContext ? readNonEmptyString2(toolContext["conversation_id"]) : void 0;
1705
1751
  if (fromSnake) return fromSnake;
1706
1752
  return generateMessageId();
1707
1753
  }
@@ -2058,7 +2104,7 @@ var runModelLockMiddleware = async (ctx, stage, next) => {
2058
2104
  if (stage.id !== "execute_llm") {
2059
2105
  return;
2060
2106
  }
2061
- const normalized = readNonEmptyString(ctx.cloudQuotaFallbackAppliedModelId);
2107
+ const normalized = readNonEmptyString2(ctx.cloudQuotaFallbackAppliedModelId);
2062
2108
  if (!normalized) {
2063
2109
  return;
2064
2110
  }
@@ -2646,7 +2692,7 @@ function createExecuteLlmStage(dependencies) {
2646
2692
  streamEventHandler,
2647
2693
  ctx.signal,
2648
2694
  (fallbackModelId) => {
2649
- ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString(fallbackModelId);
2695
+ ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString2(fallbackModelId);
2650
2696
  },
2651
2697
  (info) => {
2652
2698
  ctx.modelFallbackAudit = info;
@@ -2690,7 +2736,7 @@ function createPrepareCallStage(dependencies) {
2690
2736
  return {
2691
2737
  id: "prepare_call",
2692
2738
  async run(ctx) {
2693
- const lockedRunModelId = readNonEmptyString(ctx.executorLocal?.runLockedModelId);
2739
+ const lockedRunModelId = readNonEmptyString2(ctx.executorLocal?.runLockedModelId);
2694
2740
  const requestedModelId = lockedRunModelId ?? ctx.request.model_id;
2695
2741
  ctx.modelId = dependencies.modelResolver.resolveModelId(requestedModelId);
2696
2742
  await emitAuditEnvelope(ctx.audit, {
@@ -2762,7 +2808,7 @@ var GraphAgentExecutor = class {
2762
2808
  this.llmCaller = dependencies.llmCaller;
2763
2809
  this.toolRuntime = dependencies.toolRuntime;
2764
2810
  this.contextBuilder = dependencies.contextBuilder;
2765
- this.cloudQuotaFallbackModelId = readNonEmptyString(dependencies.cloudQuotaFallbackModelId);
2811
+ this.cloudQuotaFallbackModelId = readNonEmptyString2(dependencies.cloudQuotaFallbackModelId);
2766
2812
  this.modelCatalog = dependencies.modelCatalog ?? createEmptyModelCatalog();
2767
2813
  this.modelResolver = dependencies.modelResolver ?? new ModelResolver({
2768
2814
  modelCatalog: this.modelCatalog
@@ -2810,7 +2856,7 @@ var GraphAgentExecutor = class {
2810
2856
  llmMessages: [],
2811
2857
  mode: input.request.mode === "chat" ? "chat" : "agent",
2812
2858
  conversationId: resolveConversationIdForRuntimeEvents(input.toolContext),
2813
- turnId: readNonEmptyString(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2859
+ turnId: readNonEmptyString2(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2814
2860
  telemetry: this.telemetryPort,
2815
2861
  audit: this.auditPort
2816
2862
  };
@@ -2860,18 +2906,18 @@ function isSummarizationCallbacks(value) {
2860
2906
  function isGraphSseSink(value) {
2861
2907
  return typeof value === "function";
2862
2908
  }
2863
- function readNonEmptyString2(value) {
2909
+ function readNonEmptyString3(value) {
2864
2910
  if (typeof value !== "string") return void 0;
2865
2911
  const trimmed = value.trim();
2866
2912
  return trimmed.length > 0 ? trimmed : void 0;
2867
2913
  }
2868
2914
  function readGraphAgentLocal(local) {
2869
2915
  const source = local ?? {};
2870
- const answerId = readNonEmptyString2(source.answerId);
2916
+ const answerId = readNonEmptyString3(source.answerId);
2871
2917
  const chunkSeq = Number.isInteger(source.chunkSeq) ? Number(source.chunkSeq) : 0;
2872
2918
  return {
2873
- conversationId: readNonEmptyString2(source.conversationId) ?? "",
2874
- turnId: readNonEmptyString2(source.turnId),
2919
+ conversationId: readNonEmptyString3(source.conversationId) ?? "",
2920
+ turnId: readNonEmptyString3(source.turnId),
2875
2921
  request: isAgentInvocationRequest(source.request) ? source.request : void 0,
2876
2922
  toolContext: isToolExecutionContext(source.toolContext) ? source.toolContext : void 0,
2877
2923
  history: isRuntimeEventArray(source.history) ? source.history : [],
@@ -3636,7 +3682,7 @@ var LlmNode = class {
3636
3682
  function isRecord8(value) {
3637
3683
  return typeof value === "object" && value !== null && !Array.isArray(value);
3638
3684
  }
3639
- function readNonEmptyString3(value) {
3685
+ function readNonEmptyString4(value) {
3640
3686
  if (typeof value !== "string") {
3641
3687
  return void 0;
3642
3688
  }
@@ -3658,7 +3704,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3658
3704
  return void 0;
3659
3705
  }
3660
3706
  if (toolName === "write_report") {
3661
- const report = readNonEmptyString3(data.report);
3707
+ const report = readNonEmptyString4(data.report);
3662
3708
  if (!report) {
3663
3709
  throw new Error("[write_report] \u5DE5\u5177\u8F93\u51FA\u7F3A\u5C11 data.report\uFF08\u5FC5\u987B\u63D0\u4F9B\u5B8C\u6574\u62A5\u544A\u6B63\u6587\uFF09");
3664
3710
  }
@@ -3668,7 +3714,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3668
3714
  if (data.success !== true) {
3669
3715
  return void 0;
3670
3716
  }
3671
- const finalAnswer = readNonEmptyString3(data.final_answer);
3717
+ const finalAnswer = readNonEmptyString4(data.final_answer);
3672
3718
  if (!finalAnswer) {
3673
3719
  throw new Error("[research_run_writer] \u5DE5\u5177\u8F93\u51FA\u7F3A\u5C11 data.final_answer\uFF08success=true \u65F6\u5FC5\u987B\u63D0\u4F9B\u6700\u7EC8\u62A5\u544A\u6B63\u6587\uFF09");
3674
3720
  }
@@ -4564,6 +4610,22 @@ var ToolNode = class {
4564
4610
  });
4565
4611
  }
4566
4612
  async run(state) {
4613
+ const events = [];
4614
+ while (true) {
4615
+ const result = await this.runNextPendingToolCall(state);
4616
+ if (Array.isArray(result.events) && result.events.length > 0) {
4617
+ events.push(...result.events);
4618
+ }
4619
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4620
+ continue;
4621
+ }
4622
+ return {
4623
+ ...result,
4624
+ events
4625
+ };
4626
+ }
4627
+ }
4628
+ async runNextPendingToolCall(state) {
4567
4629
  const calls = state.local?.pendingToolCalls ?? [];
4568
4630
  const signalRaw = state.local?.signal;
4569
4631
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4771,18 +4833,25 @@ var ToolNode = class {
4771
4833
  rawArguments: context.call.function?.arguments,
4772
4834
  parsedArguments: context.toolArgs
4773
4835
  });
4836
+ const remainingCalls = context.calls.slice(1);
4774
4837
  context.state.local = buildErrorLocalState({
4775
4838
  local: context.local,
4776
- remainingCalls: context.calls.slice(1),
4839
+ remainingCalls,
4777
4840
  conversationId: context.conversationId,
4778
4841
  turnId: context.turnId,
4779
4842
  runtimeEvents: context.bridge.getRuntimeEvents(),
4780
4843
  nextProtocolErrorCount: fuse.nextCount
4781
4844
  });
4782
- if (fuse.shouldFuse) {
4845
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4783
4846
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4784
4847
  }
4785
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4848
+ return {
4849
+ kind: "route",
4850
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4851
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4852
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4853
+ events: context.bridge.getRuntimeEvents()
4854
+ };
4786
4855
  }
4787
4856
  };
4788
4857
 
@@ -4979,12 +5048,12 @@ function asRecord(value) {
4979
5048
  }
4980
5049
  return value;
4981
5050
  }
4982
- function summarizeCheckpoint(conversationId, state, savedAt) {
5051
+ function summarizeCheckpoint(checkpointKey, state, savedAt) {
4983
5052
  const local = asRecord(state.local);
4984
5053
  const executorLocal = asRecord(local?.executorLocal);
4985
5054
  const pendingToolCalls = local?.pendingToolCalls;
4986
5055
  return {
4987
- conversationId,
5056
+ checkpointKey,
4988
5057
  schemaVersion: state.schemaVersion ?? 1,
4989
5058
  savedAt,
4990
5059
  currentNode: state.nodeId,
@@ -5003,25 +5072,25 @@ var MemoryCheckpointer = class {
5003
5072
  local: state.local && typeof state.local === "object" && !Array.isArray(state.local) ? { ...state.local } : state.local
5004
5073
  };
5005
5074
  }
5006
- async load(conversationId) {
5007
- const entry = this.store.get(conversationId);
5075
+ async load(checkpointKey) {
5076
+ const entry = this.store.get(checkpointKey);
5008
5077
  return entry ? this.cloneState(entry.state) : null;
5009
5078
  }
5010
- async save(conversationId, state) {
5011
- this.store.set(conversationId, {
5079
+ async save(checkpointKey, state) {
5080
+ this.store.set(checkpointKey, {
5012
5081
  state: this.cloneState(state),
5013
5082
  savedAt: Date.now()
5014
5083
  });
5015
5084
  }
5016
- async clear(conversationId) {
5017
- this.store.delete(conversationId);
5085
+ async clear(checkpointKey) {
5086
+ this.store.delete(checkpointKey);
5018
5087
  }
5019
- async peekMeta(conversationId) {
5020
- const entry = this.store.get(conversationId);
5021
- return entry ? summarizeCheckpoint(conversationId, entry.state, entry.savedAt) : null;
5088
+ async peekMeta(checkpointKey) {
5089
+ const entry = this.store.get(checkpointKey);
5090
+ return entry ? summarizeCheckpoint(checkpointKey, entry.state, entry.savedAt) : null;
5022
5091
  }
5023
5092
  async list(filter = {}) {
5024
- const summaries = Array.from(this.store.entries()).map(([conversationId, entry]) => summarizeCheckpoint(conversationId, entry.state, entry.savedAt)).filter((summary) => filter.savedAfter === void 0 ? true : summary.savedAt > filter.savedAfter).sort((left, right) => right.savedAt - left.savedAt);
5093
+ const summaries = Array.from(this.store.entries()).map(([checkpointKey, entry]) => summarizeCheckpoint(checkpointKey, entry.state, entry.savedAt)).filter((summary) => filter.savedAfter === void 0 ? true : summary.savedAt > filter.savedAfter).sort((left, right) => right.savedAt - left.savedAt);
5025
5094
  if (filter.limit === void 0) {
5026
5095
  return summaries;
5027
5096
  }
@@ -5278,18 +5347,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5278
5347
  }
5279
5348
  var ErrorClassifier = class {
5280
5349
  static classify(error, context) {
5281
- const isRecord21 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5350
+ const isRecord22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5282
5351
  const baseMsg = (error.message || "").toLowerCase();
5283
5352
  const causeMsg = (() => {
5284
5353
  const cause = error.cause;
5285
5354
  if (typeof cause === "string") return cause.toLowerCase();
5286
5355
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5287
- if (isRecord21(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5356
+ if (isRecord22(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5288
5357
  return "";
5289
5358
  })();
5290
5359
  const causeCode = (() => {
5291
5360
  const cause = error.cause;
5292
- if (isRecord21(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5361
+ if (isRecord22(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5293
5362
  return "";
5294
5363
  })();
5295
5364
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -5965,6 +6034,67 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
5965
6034
  }
5966
6035
  }
5967
6036
 
6037
+ // src/runtime-kernel/llm/reasoning-details.ts
6038
+ var mergeableTextFields = ["reasoning_content"];
6039
+ function isRecord18(value) {
6040
+ return !!value && typeof value === "object" && !Array.isArray(value);
6041
+ }
6042
+ function findMergeableTextField(detail) {
6043
+ if (!isRecord18(detail)) return void 0;
6044
+ if (typeof detail.provider !== "string") return void 0;
6045
+ if (typeof detail.type !== "string") return void 0;
6046
+ for (const field of mergeableTextFields) {
6047
+ if (typeof detail[field] === "string") {
6048
+ return field;
6049
+ }
6050
+ }
6051
+ return void 0;
6052
+ }
6053
+ function hasOnlyStableTextDetailFields(detail, textField) {
6054
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
6055
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
6056
+ }
6057
+ function canMergeTextDetails(previous, incoming) {
6058
+ if (!isRecord18(previous) || !isRecord18(incoming)) return false;
6059
+ const previousField = findMergeableTextField(previous);
6060
+ const incomingField = findMergeableTextField(incoming);
6061
+ if (!previousField || previousField !== incomingField) return false;
6062
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
6063
+ }
6064
+ function mergeStreamingText(previous, incoming) {
6065
+ if (!previous) return incoming;
6066
+ if (!incoming) return previous;
6067
+ if (incoming.startsWith(previous)) {
6068
+ return incoming;
6069
+ }
6070
+ if (previous.endsWith(incoming)) {
6071
+ return previous;
6072
+ }
6073
+ return `${previous}${incoming}`;
6074
+ }
6075
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
6076
+ const next = [...existing];
6077
+ for (const detail of incoming) {
6078
+ const previous = next[next.length - 1];
6079
+ if (canMergeTextDetails(previous, detail)) {
6080
+ const textField = findMergeableTextField(previous);
6081
+ if (textField && isRecord18(detail)) {
6082
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
6083
+ if (mergedText === previous[textField]) {
6084
+ continue;
6085
+ }
6086
+ next[next.length - 1] = {
6087
+ ...previous,
6088
+ [textField]: mergedText
6089
+ };
6090
+ continue;
6091
+ }
6092
+ }
6093
+ next.push(detail);
6094
+ }
6095
+ return next;
6096
+ }
6097
+
5968
6098
  // src/runtime-kernel/llm/streaming-adapter.ts
5969
6099
  async function callLlmStream(params) {
5970
6100
  const {
@@ -5977,7 +6107,7 @@ async function callLlmStream(params) {
5977
6107
  } = params;
5978
6108
  let fullResponse = "";
5979
6109
  let streamError = null;
5980
- const reasoningDetails = [];
6110
+ let reasoningDetails = [];
5981
6111
  const streamAnswerId = generateMessageId();
5982
6112
  let streamChunkSeq = 0;
5983
6113
  let capturedUsage = void 0;
@@ -6047,13 +6177,21 @@ async function callLlmStream(params) {
6047
6177
  const reasoning = isRecord17(chunk) ? chunk["reasoning_details"] : void 0;
6048
6178
  if (reasoning !== void 0) {
6049
6179
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
6050
- reasoningDetails.push(...newReasoningDetails);
6051
- eventHandler({
6052
- type: "provider_sidecar",
6053
- id: generateMessageId(),
6054
- timestamp: Date.now(),
6055
- reasoning_details: newReasoningDetails
6056
- });
6180
+ const previousReasoningDetails = reasoningDetails;
6181
+ const previousLength = previousReasoningDetails.length;
6182
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6183
+ reasoningDetails = compactedReasoningDetails;
6184
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6185
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6186
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6187
+ if (emittedReasoningDetails.length > 0) {
6188
+ eventHandler({
6189
+ type: "provider_sidecar",
6190
+ id: generateMessageId(),
6191
+ timestamp: Date.now(),
6192
+ reasoning_details: emittedReasoningDetails
6193
+ });
6194
+ }
6057
6195
  }
6058
6196
  if (chunk.tool_calls) {
6059
6197
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6492,7 +6630,7 @@ function cloneRunRecord(record) {
6492
6630
  ...record,
6493
6631
  iterationBudget: record.iterationBudget ? { ...record.iterationBudget } : void 0,
6494
6632
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0,
6495
- metadata: record.metadata ? { ...record.metadata } : void 0
6633
+ metadata: record.metadata ? structuredClone(record.metadata) : void 0
6496
6634
  };
6497
6635
  }
6498
6636
  function matchesStatus(candidate, filter) {
@@ -6624,7 +6762,7 @@ function runRecordToMeta(record) {
6624
6762
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
6625
6763
  };
6626
6764
  }
6627
- function isRecord18(value) {
6765
+ function isRecord19(value) {
6628
6766
  return typeof value === "object" && value !== null && !Array.isArray(value);
6629
6767
  }
6630
6768
  function readStringField(record, key) {
@@ -6645,7 +6783,7 @@ function getRunIdFromMetadata(event) {
6645
6783
  return snakeCaseRunId;
6646
6784
  }
6647
6785
  const runContext = metadata["run_context"];
6648
- if (isRecord18(runContext)) {
6786
+ if (isRecord19(runContext)) {
6649
6787
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
6650
6788
  }
6651
6789
  return void 0;
@@ -6863,7 +7001,7 @@ function runMetaFromRecord(record) {
6863
7001
  }
6864
7002
 
6865
7003
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
6866
- function isRecord19(value) {
7004
+ function isRecord20(value) {
6867
7005
  return typeof value === "object" && value !== null && !Array.isArray(value);
6868
7006
  }
6869
7007
  function readStringField2(record, key) {
@@ -6880,7 +7018,7 @@ function readRunIdFromRuntimeEvent(event) {
6880
7018
  return directRunId;
6881
7019
  }
6882
7020
  const runContext = metadata["run_context"];
6883
- if (!isRecord19(runContext)) {
7021
+ if (!isRecord20(runContext)) {
6884
7022
  return void 0;
6885
7023
  }
6886
7024
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -6892,7 +7030,7 @@ function readAwaitingUserReason(event) {
6892
7030
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
6893
7031
  return event.prompt;
6894
7032
  }
6895
- if (isRecord19(event.form)) {
7033
+ if (isRecord20(event.form)) {
6896
7034
  const prompt = readStringField2(event.form, "prompt");
6897
7035
  if (prompt && prompt.trim().length > 0) {
6898
7036
  return prompt;
@@ -6904,7 +7042,7 @@ function isTerminalStatus(status) {
6904
7042
  return status === "completed" || status === "failed" || status === "cancelled";
6905
7043
  }
6906
7044
  function cloneMetadata(metadata) {
6907
- return metadata ? { ...metadata } : void 0;
7045
+ return metadata ? structuredClone(metadata) : void 0;
6908
7046
  }
6909
7047
  function recordToSnapshot(record) {
6910
7048
  return {
@@ -6991,7 +7129,7 @@ var DefaultRunSupervisor = class {
6991
7129
  startedAt,
6992
7130
  updatedAt: startedAt,
6993
7131
  iterationBudget: spec.iterationBudget ? { ...spec.iterationBudget } : void 0,
6994
- metadata: spec.metadata ? { ...spec.metadata } : void 0
7132
+ metadata: cloneMetadata(spec.metadata)
6995
7133
  };
6996
7134
  await this.registryStore.save(record);
6997
7135
  const handle = new DefaultRunHandle({
@@ -7165,12 +7303,13 @@ var DefaultRunSupervisor = class {
7165
7303
  }
7166
7304
  try {
7167
7305
  await handle.markRunning({ currentNode: "detached" });
7306
+ const registeredRecord = await this.registryStore.load(handle.runId);
7168
7307
  const executorOutcome = await this.executor.execute({
7169
7308
  runId: handle.runId,
7170
- parentRunId: spec.parentRunId,
7171
- conversationId: spec.conversationId,
7172
- agentSpec: spec.agentSpec,
7173
- request: spec.request,
7309
+ parentRunId: handle.parentRunId,
7310
+ conversationId: registeredRecord?.conversationId ?? spec.conversationId,
7311
+ agentSpec: await handle.spec(),
7312
+ request: await handle.request(),
7174
7313
  signal: handle.signal,
7175
7314
  eventBus: spec.eventBus,
7176
7315
  eventStore: spec.eventStore,
@@ -7179,7 +7318,7 @@ var DefaultRunSupervisor = class {
7179
7318
  contextFences: spec.contextFences,
7180
7319
  wakeSource: spec.wakeSource,
7181
7320
  ephemeral: spec.ephemeral,
7182
- metadata: spec.metadata
7321
+ metadata: cloneMetadata(registeredRecord?.metadata ?? spec.metadata)
7183
7322
  });
7184
7323
  const outcome = await this.persistExecutorOutcome(handle, executorOutcome);
7185
7324
  this.notifyTerminalWaiters(outcome);
@@ -7243,10 +7382,14 @@ var DefaultRunSupervisor = class {
7243
7382
  await this.registryStore.save(record);
7244
7383
  }
7245
7384
  const completedAt = executorOutcome?.completedAt ?? this.now();
7385
+ const fallbackMeta = record ? void 0 : await handle.meta();
7246
7386
  const outcome = recordToTerminalOutcome(record ?? {
7247
7387
  runId: handle.runId,
7248
7388
  parentRunId: handle.parentRunId,
7249
- status: executorOutcome?.status ?? "completed"}, completedAt);
7389
+ conversationId: fallbackMeta?.conversationId ?? "",
7390
+ agentSpecId: fallbackMeta?.agentSpecId,
7391
+ status: executorOutcome?.status ?? "completed",
7392
+ startedAt: fallbackMeta?.startedAt ?? completedAt}, completedAt);
7250
7393
  const nextOutcome = {
7251
7394
  ...outcome,
7252
7395
  metadata: {
@@ -7502,7 +7645,7 @@ function createQuickstartTelemetryPort(collector) {
7502
7645
  }
7503
7646
 
7504
7647
  // src/quickstart/runAgent.ts
7505
- function isRecord20(value) {
7648
+ function isRecord21(value) {
7506
7649
  return typeof value === "object" && value !== null && !Array.isArray(value);
7507
7650
  }
7508
7651
  function readString4(value) {
@@ -7516,7 +7659,7 @@ function resolveModelId(agent, options) {
7516
7659
  return modelId;
7517
7660
  }
7518
7661
  function isQuickstartStreamChunkEvent(value) {
7519
- return isRecord20(value) && value.type === "stream_chunk" && typeof value.content === "string";
7662
+ return isRecord21(value) && value.type === "stream_chunk" && typeof value.content === "string";
7520
7663
  }
7521
7664
  function createNoopObservationPreview() {
7522
7665
  return {
@@ -7556,7 +7699,7 @@ function readFinalAnswer(events, checkpointLocal) {
7556
7699
  if (chunks.length > 0) {
7557
7700
  return chunks.join("");
7558
7701
  }
7559
- if (isRecord20(checkpointLocal)) {
7702
+ if (isRecord21(checkpointLocal)) {
7560
7703
  const finalAnswer = checkpointLocal["finalAnswer"];
7561
7704
  if (typeof finalAnswer === "string") {
7562
7705
  return finalAnswer;
@@ -7565,7 +7708,7 @@ function readFinalAnswer(events, checkpointLocal) {
7565
7708
  return "";
7566
7709
  }
7567
7710
  function readContextTrace(checkpointLocal) {
7568
- if (!isRecord20(checkpointLocal)) return void 0;
7711
+ if (!isRecord21(checkpointLocal)) return void 0;
7569
7712
  return checkpointLocal["contextTrace"];
7570
7713
  }
7571
7714
  async function emitRunEvent(event, sink) {
@@ -7574,6 +7717,7 @@ async function emitRunEvent(event, sink) {
7574
7717
  async function runAgent(agent, options) {
7575
7718
  const modelId = resolveModelId(agent, options);
7576
7719
  const conversationId = options.conversationId ?? `conv_${Date.now()}`;
7720
+ const checkpointKey = conversationId;
7577
7721
  const runId = options.runId ?? generateRunId();
7578
7722
  const turnId = runId;
7579
7723
  const costCollector = new QuickstartRunCostCollector();
@@ -7628,7 +7772,7 @@ async function runAgent(agent, options) {
7628
7772
  };
7629
7773
  await handle.markRunning({ currentNode: "user" });
7630
7774
  try {
7631
- await executor.prime(conversationId, {
7775
+ await executor.prime(checkpointKey, {
7632
7776
  conversationId,
7633
7777
  turnId,
7634
7778
  request: {
@@ -7653,7 +7797,7 @@ async function runAgent(agent, options) {
7653
7797
  return void 0;
7654
7798
  }
7655
7799
  });
7656
- const result = await executor.runUntilYield(conversationId);
7800
+ const result = await executor.runUntilYield(checkpointKey);
7657
7801
  runtimeEvents.push(...result.events);
7658
7802
  for (const event of result.events) {
7659
7803
  if (event.type === "final_answer_chunk") continue;