@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
package/dist/cli.cjs CHANGED
@@ -203,6 +203,7 @@ var AgentSpecBudgetPolicy = zod.z.object({
203
203
  });
204
204
  var AgentSpecToolHistoryPolicy = zod.z.object({
205
205
  strategy: zod.z.enum(["per-pair", "per-run", "none"]).optional(),
206
+ retentionMode: zod.z.enum(["drop", "compress"]).optional(),
206
207
  keepLatestToolPairs: zod.z.number().int().nonnegative().optional(),
207
208
  keepLatestRuns: zod.z.number().int().nonnegative().optional(),
208
209
  maxInteractionGroups: zod.z.number().int().nonnegative().optional(),
@@ -1157,6 +1158,17 @@ var logger = new Logger("GraphExecutor");
1157
1158
  function asLocalRecord(local) {
1158
1159
  return local && typeof local === "object" ? { ...local } : {};
1159
1160
  }
1161
+ function readNonEmptyString(value) {
1162
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1163
+ }
1164
+ function readRuntimeConversationId(state) {
1165
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1166
+ return readNonEmptyString(local?.conversationId);
1167
+ }
1168
+ function readRuntimeTurnId(state) {
1169
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1170
+ return readNonEmptyString(local?.turnId);
1171
+ }
1160
1172
  var GraphExecutor = class {
1161
1173
  constructor(checkpointer, config = {}) {
1162
1174
  this.checkpointer = checkpointer;
@@ -1174,8 +1186,8 @@ var GraphExecutor = class {
1174
1186
  registerNode(node) {
1175
1187
  this.nodes.set(node.id, node);
1176
1188
  }
1177
- async peekCheckpoint(conversationId) {
1178
- return await this.checkpointer.load(conversationId);
1189
+ async peekCheckpoint(checkpointKey) {
1190
+ return await this.checkpointer.load(checkpointKey);
1179
1191
  }
1180
1192
  sanitize(state) {
1181
1193
  const local = asLocalRecord(state.local);
@@ -1187,8 +1199,8 @@ var GraphExecutor = class {
1187
1199
  local
1188
1200
  };
1189
1201
  }
1190
- async prime(conversationId, local, nodeId = "user") {
1191
- this.ephemeralLocals.set(conversationId, { ...local || {} });
1202
+ async prime(checkpointKey, local, nodeId = "user") {
1203
+ this.ephemeralLocals.set(checkpointKey, { ...local || {} });
1192
1204
  const localSansMemory = { ...local || {} };
1193
1205
  if ("memory" in localSansMemory) delete localSansMemory.memory;
1194
1206
  const state = {
@@ -1196,10 +1208,10 @@ var GraphExecutor = class {
1196
1208
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1197
1209
  local: localSansMemory
1198
1210
  };
1199
- await this.checkpointer.save(conversationId, state);
1211
+ await this.checkpointer.save(checkpointKey, state);
1200
1212
  }
1201
- async setNode(conversationId, nodeId, localPatch) {
1202
- const current = await this.checkpointer.load(conversationId) || {
1213
+ async setNode(checkpointKey, nodeId, localPatch) {
1214
+ const current = await this.checkpointer.load(checkpointKey) || {
1203
1215
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1204
1216
  local: {}
1205
1217
  };
@@ -1210,19 +1222,46 @@ var GraphExecutor = class {
1210
1222
  schemaVersion: current.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
1211
1223
  local: mergedLocal
1212
1224
  };
1213
- await this.checkpointer.save(conversationId, next);
1225
+ await this.checkpointer.save(checkpointKey, next);
1214
1226
  }
1215
- async runUntilYield(conversationId) {
1227
+ async runUntilYield(checkpointKey) {
1216
1228
  const runId = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1217
1229
  let lifecyclePhase = "completed";
1230
+ let initialState = null;
1231
+ try {
1232
+ initialState = await this.loadInitialState(checkpointKey);
1233
+ } catch (err) {
1234
+ lifecyclePhase = "failed";
1235
+ this.telemetryPort.emit({
1236
+ kind: "run_lifecycle",
1237
+ runId,
1238
+ phase: "spawned",
1239
+ scope: {}
1240
+ });
1241
+ this.telemetryPort.emit({
1242
+ kind: "run_lifecycle",
1243
+ runId,
1244
+ phase: lifecyclePhase,
1245
+ scope: {}
1246
+ });
1247
+ throw err;
1248
+ }
1249
+ let lifecycleConversationId = readRuntimeConversationId(initialState);
1250
+ let lifecycleTurnId = readRuntimeTurnId(initialState);
1218
1251
  this.telemetryPort.emit({
1219
1252
  kind: "run_lifecycle",
1220
1253
  runId,
1221
1254
  phase: "spawned",
1222
- scope: { conversationId: conversationId || void 0 }
1255
+ scope: {
1256
+ conversationId: lifecycleConversationId,
1257
+ turnId: lifecycleTurnId
1258
+ }
1223
1259
  });
1224
1260
  try {
1225
- return await this.runUntilYieldInternal(conversationId);
1261
+ const result = await this.runUntilYieldInternal(checkpointKey, initialState);
1262
+ lifecycleConversationId = readRuntimeConversationId(result.checkpoint);
1263
+ lifecycleTurnId = readRuntimeTurnId(result.checkpoint);
1264
+ return result;
1226
1265
  } catch (err) {
1227
1266
  lifecyclePhase = err?.name === "AbortError" ? "cancelled" : "failed";
1228
1267
  throw err;
@@ -1231,17 +1270,23 @@ var GraphExecutor = class {
1231
1270
  kind: "run_lifecycle",
1232
1271
  runId,
1233
1272
  phase: lifecyclePhase,
1234
- scope: { conversationId: conversationId || void 0 }
1273
+ scope: {
1274
+ conversationId: lifecycleConversationId,
1275
+ turnId: lifecycleTurnId
1276
+ }
1235
1277
  });
1236
1278
  }
1237
1279
  }
1238
- async runUntilYieldInternal(conversationId) {
1239
- let state = await this.checkpointer.load(conversationId) || {
1280
+ async loadInitialState(checkpointKey) {
1281
+ return await this.checkpointer.load(checkpointKey) || {
1240
1282
  nodeId: "user",
1241
1283
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1242
1284
  local: {}
1243
1285
  };
1244
- const ephemeral = this.ephemeralLocals.get(conversationId) || {};
1286
+ }
1287
+ async runUntilYieldInternal(checkpointKey, initialState) {
1288
+ let state = initialState;
1289
+ const ephemeral = this.ephemeralLocals.get(checkpointKey) || {};
1245
1290
  state = {
1246
1291
  ...state,
1247
1292
  schemaVersion: state.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
@@ -1270,7 +1315,7 @@ var GraphExecutor = class {
1270
1315
  const signalRaw = state.local?.signal;
1271
1316
  if (isAbortSignal2(signalRaw) && signalRaw.aborted) {
1272
1317
  logger.warn("[GraphExecutor] \u6536\u5230 AbortSignal\uFF0C\u7ACB\u5373\u505C\u6B62\u63A8\u7406\u5FAA\u73AF");
1273
- this.ephemeralLocals.delete(conversationId);
1318
+ this.ephemeralLocals.delete(checkpointKey);
1274
1319
  throwAbortError();
1275
1320
  }
1276
1321
  const isLastStep = cycleStepCount >= this.config.maxSteps;
@@ -1318,8 +1363,8 @@ var GraphExecutor = class {
1318
1363
  checkpointCount
1319
1364
  });
1320
1365
  const cp2 = this.sanitize(state);
1321
- await this.checkpointer.save(conversationId, cp2);
1322
- this.ephemeralLocals.delete(conversationId);
1366
+ await this.checkpointer.save(checkpointKey, cp2);
1367
+ this.ephemeralLocals.delete(checkpointKey);
1323
1368
  return { events: allEvents, checkpoint: cp2, stepCount };
1324
1369
  }
1325
1370
  logger.info("[GraphExecutor] \u8282\u70B9\u5207\u6362", {
@@ -1330,18 +1375,18 @@ var GraphExecutor = class {
1330
1375
  });
1331
1376
  const nodeRunStartedAt = Date.now();
1332
1377
  const nodeIdForTelemetry = state.nodeId;
1378
+ const conversationIdForTelemetry = readRuntimeConversationId(state);
1333
1379
  let result;
1334
1380
  try {
1335
1381
  result = await node.run(state);
1336
1382
  } finally {
1337
- const turnIdForTelemetry = typeof state.local?.turnId === "string" ? state.local.turnId : void 0;
1338
1383
  this.telemetryPort.emit({
1339
1384
  kind: "graph_node",
1340
1385
  nodeId: nodeIdForTelemetry,
1341
1386
  durationMs: Date.now() - nodeRunStartedAt,
1342
1387
  scope: {
1343
- conversationId: conversationId || void 0,
1344
- turnId: turnIdForTelemetry
1388
+ conversationId: conversationIdForTelemetry,
1389
+ turnId: readRuntimeTurnId(state)
1345
1390
  }
1346
1391
  });
1347
1392
  }
@@ -1380,7 +1425,7 @@ var GraphExecutor = class {
1380
1425
  });
1381
1426
  state = { ...state, nodeId: nextNodeId };
1382
1427
  const cp2 = this.sanitize(state);
1383
- await this.checkpointer.save(conversationId, cp2);
1428
+ await this.checkpointer.save(checkpointKey, cp2);
1384
1429
  continue;
1385
1430
  }
1386
1431
  if (result.kind === "yield") {
@@ -1391,7 +1436,7 @@ var GraphExecutor = class {
1391
1436
  checkpointCount
1392
1437
  });
1393
1438
  const cp2 = this.sanitize(state);
1394
- await this.checkpointer.save(conversationId, cp2);
1439
+ await this.checkpointer.save(checkpointKey, cp2);
1395
1440
  return { events: allEvents, checkpoint: cp2, stepCount };
1396
1441
  }
1397
1442
  if (result.kind === "pause") {
@@ -1402,7 +1447,7 @@ var GraphExecutor = class {
1402
1447
  checkpointCount
1403
1448
  });
1404
1449
  const cp2 = this.sanitize(state);
1405
- await this.checkpointer.save(conversationId, cp2);
1450
+ await this.checkpointer.save(checkpointKey, cp2);
1406
1451
  return { events: allEvents, checkpoint: cp2, stepCount };
1407
1452
  }
1408
1453
  }
@@ -1413,8 +1458,8 @@ var GraphExecutor = class {
1413
1458
  checkpointCount
1414
1459
  });
1415
1460
  const cp = this.sanitize(state);
1416
- await this.checkpointer.save(conversationId, cp);
1417
- this.ephemeralLocals.delete(conversationId);
1461
+ await this.checkpointer.save(checkpointKey, cp);
1462
+ this.ephemeralLocals.delete(checkpointKey);
1418
1463
  return { events: allEvents, checkpoint: cp, stepCount };
1419
1464
  }
1420
1465
  };
@@ -1577,15 +1622,15 @@ function splitConcatenatedJsonObjects(input) {
1577
1622
  }
1578
1623
 
1579
1624
  // src/runtime-kernel/graph-engine/tick-pipeline/helpers.ts
1580
- function readNonEmptyString(value) {
1625
+ function readNonEmptyString2(value) {
1581
1626
  if (typeof value !== "string") return void 0;
1582
1627
  const trimmed = value.trim();
1583
1628
  return trimmed.length > 0 ? trimmed : void 0;
1584
1629
  }
1585
1630
  function resolveConversationIdForRuntimeEvents(toolContext) {
1586
- const fromCamel = readNonEmptyString(toolContext?.conversationId);
1631
+ const fromCamel = readNonEmptyString2(toolContext?.conversationId);
1587
1632
  if (fromCamel) return fromCamel;
1588
- const fromSnake = toolContext ? readNonEmptyString(toolContext["conversation_id"]) : void 0;
1633
+ const fromSnake = toolContext ? readNonEmptyString2(toolContext["conversation_id"]) : void 0;
1589
1634
  if (fromSnake) return fromSnake;
1590
1635
  return generateMessageId();
1591
1636
  }
@@ -1942,7 +1987,7 @@ var runModelLockMiddleware = async (ctx, stage, next) => {
1942
1987
  if (stage.id !== "execute_llm") {
1943
1988
  return;
1944
1989
  }
1945
- const normalized = readNonEmptyString(ctx.cloudQuotaFallbackAppliedModelId);
1990
+ const normalized = readNonEmptyString2(ctx.cloudQuotaFallbackAppliedModelId);
1946
1991
  if (!normalized) {
1947
1992
  return;
1948
1993
  }
@@ -2530,7 +2575,7 @@ function createExecuteLlmStage(dependencies) {
2530
2575
  streamEventHandler,
2531
2576
  ctx.signal,
2532
2577
  (fallbackModelId) => {
2533
- ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString(fallbackModelId);
2578
+ ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString2(fallbackModelId);
2534
2579
  },
2535
2580
  (info) => {
2536
2581
  ctx.modelFallbackAudit = info;
@@ -2574,7 +2619,7 @@ function createPrepareCallStage(dependencies) {
2574
2619
  return {
2575
2620
  id: "prepare_call",
2576
2621
  async run(ctx) {
2577
- const lockedRunModelId = readNonEmptyString(ctx.executorLocal?.runLockedModelId);
2622
+ const lockedRunModelId = readNonEmptyString2(ctx.executorLocal?.runLockedModelId);
2578
2623
  const requestedModelId = lockedRunModelId ?? ctx.request.model_id;
2579
2624
  ctx.modelId = dependencies.modelResolver.resolveModelId(requestedModelId);
2580
2625
  await emitAuditEnvelope(ctx.audit, {
@@ -2646,7 +2691,7 @@ var GraphAgentExecutor = class {
2646
2691
  this.llmCaller = dependencies.llmCaller;
2647
2692
  this.toolRuntime = dependencies.toolRuntime;
2648
2693
  this.contextBuilder = dependencies.contextBuilder;
2649
- this.cloudQuotaFallbackModelId = readNonEmptyString(dependencies.cloudQuotaFallbackModelId);
2694
+ this.cloudQuotaFallbackModelId = readNonEmptyString2(dependencies.cloudQuotaFallbackModelId);
2650
2695
  this.modelCatalog = dependencies.modelCatalog ?? createEmptyModelCatalog();
2651
2696
  this.modelResolver = dependencies.modelResolver ?? new ModelResolver({
2652
2697
  modelCatalog: this.modelCatalog
@@ -2694,7 +2739,7 @@ var GraphAgentExecutor = class {
2694
2739
  llmMessages: [],
2695
2740
  mode: input.request.mode === "chat" ? "chat" : "agent",
2696
2741
  conversationId: resolveConversationIdForRuntimeEvents(input.toolContext),
2697
- turnId: readNonEmptyString(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2742
+ turnId: readNonEmptyString2(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2698
2743
  telemetry: this.telemetryPort,
2699
2744
  audit: this.auditPort
2700
2745
  };
@@ -2744,18 +2789,18 @@ function isSummarizationCallbacks(value) {
2744
2789
  function isGraphSseSink(value) {
2745
2790
  return typeof value === "function";
2746
2791
  }
2747
- function readNonEmptyString2(value) {
2792
+ function readNonEmptyString3(value) {
2748
2793
  if (typeof value !== "string") return void 0;
2749
2794
  const trimmed = value.trim();
2750
2795
  return trimmed.length > 0 ? trimmed : void 0;
2751
2796
  }
2752
2797
  function readGraphAgentLocal(local) {
2753
2798
  const source = local ?? {};
2754
- const answerId = readNonEmptyString2(source.answerId);
2799
+ const answerId = readNonEmptyString3(source.answerId);
2755
2800
  const chunkSeq = Number.isInteger(source.chunkSeq) ? Number(source.chunkSeq) : 0;
2756
2801
  return {
2757
- conversationId: readNonEmptyString2(source.conversationId) ?? "",
2758
- turnId: readNonEmptyString2(source.turnId),
2802
+ conversationId: readNonEmptyString3(source.conversationId) ?? "",
2803
+ turnId: readNonEmptyString3(source.turnId),
2759
2804
  request: isAgentInvocationRequest(source.request) ? source.request : void 0,
2760
2805
  toolContext: isToolExecutionContext(source.toolContext) ? source.toolContext : void 0,
2761
2806
  history: isRuntimeEventArray(source.history) ? source.history : [],
@@ -3520,7 +3565,7 @@ var LlmNode = class {
3520
3565
  function isRecord8(value) {
3521
3566
  return typeof value === "object" && value !== null && !Array.isArray(value);
3522
3567
  }
3523
- function readNonEmptyString3(value) {
3568
+ function readNonEmptyString4(value) {
3524
3569
  if (typeof value !== "string") {
3525
3570
  return void 0;
3526
3571
  }
@@ -3542,7 +3587,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3542
3587
  return void 0;
3543
3588
  }
3544
3589
  if (toolName === "write_report") {
3545
- const report = readNonEmptyString3(data.report);
3590
+ const report = readNonEmptyString4(data.report);
3546
3591
  if (!report) {
3547
3592
  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");
3548
3593
  }
@@ -3552,7 +3597,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3552
3597
  if (data.success !== true) {
3553
3598
  return void 0;
3554
3599
  }
3555
- const finalAnswer = readNonEmptyString3(data.final_answer);
3600
+ const finalAnswer = readNonEmptyString4(data.final_answer);
3556
3601
  if (!finalAnswer) {
3557
3602
  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");
3558
3603
  }
@@ -4448,6 +4493,22 @@ var ToolNode = class {
4448
4493
  });
4449
4494
  }
4450
4495
  async run(state) {
4496
+ const events = [];
4497
+ while (true) {
4498
+ const result = await this.runNextPendingToolCall(state);
4499
+ if (Array.isArray(result.events) && result.events.length > 0) {
4500
+ events.push(...result.events);
4501
+ }
4502
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4503
+ continue;
4504
+ }
4505
+ return {
4506
+ ...result,
4507
+ events
4508
+ };
4509
+ }
4510
+ }
4511
+ async runNextPendingToolCall(state) {
4451
4512
  const calls = state.local?.pendingToolCalls ?? [];
4452
4513
  const signalRaw = state.local?.signal;
4453
4514
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4655,18 +4716,25 @@ var ToolNode = class {
4655
4716
  rawArguments: context.call.function?.arguments,
4656
4717
  parsedArguments: context.toolArgs
4657
4718
  });
4719
+ const remainingCalls = context.calls.slice(1);
4658
4720
  context.state.local = buildErrorLocalState({
4659
4721
  local: context.local,
4660
- remainingCalls: context.calls.slice(1),
4722
+ remainingCalls,
4661
4723
  conversationId: context.conversationId,
4662
4724
  turnId: context.turnId,
4663
4725
  runtimeEvents: context.bridge.getRuntimeEvents(),
4664
4726
  nextProtocolErrorCount: fuse.nextCount
4665
4727
  });
4666
- if (fuse.shouldFuse) {
4728
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4667
4729
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4668
4730
  }
4669
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4731
+ return {
4732
+ kind: "route",
4733
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4734
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4735
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4736
+ events: context.bridge.getRuntimeEvents()
4737
+ };
4670
4738
  }
4671
4739
  };
4672
4740
 
@@ -4863,12 +4931,12 @@ function asRecord(value) {
4863
4931
  }
4864
4932
  return value;
4865
4933
  }
4866
- function summarizeCheckpoint(conversationId, state, savedAt) {
4934
+ function summarizeCheckpoint(checkpointKey, state, savedAt) {
4867
4935
  const local = asRecord(state.local);
4868
4936
  const executorLocal = asRecord(local?.executorLocal);
4869
4937
  const pendingToolCalls = local?.pendingToolCalls;
4870
4938
  return {
4871
- conversationId,
4939
+ checkpointKey,
4872
4940
  schemaVersion: state.schemaVersion ?? 1,
4873
4941
  savedAt,
4874
4942
  currentNode: state.nodeId,
@@ -4887,25 +4955,25 @@ var MemoryCheckpointer = class {
4887
4955
  local: state.local && typeof state.local === "object" && !Array.isArray(state.local) ? { ...state.local } : state.local
4888
4956
  };
4889
4957
  }
4890
- async load(conversationId) {
4891
- const entry = this.store.get(conversationId);
4958
+ async load(checkpointKey) {
4959
+ const entry = this.store.get(checkpointKey);
4892
4960
  return entry ? this.cloneState(entry.state) : null;
4893
4961
  }
4894
- async save(conversationId, state) {
4895
- this.store.set(conversationId, {
4962
+ async save(checkpointKey, state) {
4963
+ this.store.set(checkpointKey, {
4896
4964
  state: this.cloneState(state),
4897
4965
  savedAt: Date.now()
4898
4966
  });
4899
4967
  }
4900
- async clear(conversationId) {
4901
- this.store.delete(conversationId);
4968
+ async clear(checkpointKey) {
4969
+ this.store.delete(checkpointKey);
4902
4970
  }
4903
- async peekMeta(conversationId) {
4904
- const entry = this.store.get(conversationId);
4905
- return entry ? summarizeCheckpoint(conversationId, entry.state, entry.savedAt) : null;
4971
+ async peekMeta(checkpointKey) {
4972
+ const entry = this.store.get(checkpointKey);
4973
+ return entry ? summarizeCheckpoint(checkpointKey, entry.state, entry.savedAt) : null;
4906
4974
  }
4907
4975
  async list(filter = {}) {
4908
- 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);
4976
+ 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);
4909
4977
  if (filter.limit === void 0) {
4910
4978
  return summaries;
4911
4979
  }
@@ -5162,18 +5230,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5162
5230
  }
5163
5231
  var ErrorClassifier = class {
5164
5232
  static classify(error, context) {
5165
- const isRecord22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5233
+ const isRecord23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5166
5234
  const baseMsg = (error.message || "").toLowerCase();
5167
5235
  const causeMsg = (() => {
5168
5236
  const cause = error.cause;
5169
5237
  if (typeof cause === "string") return cause.toLowerCase();
5170
5238
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5171
- if (isRecord22(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5239
+ if (isRecord23(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5172
5240
  return "";
5173
5241
  })();
5174
5242
  const causeCode = (() => {
5175
5243
  const cause = error.cause;
5176
- if (isRecord22(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5244
+ if (isRecord23(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5177
5245
  return "";
5178
5246
  })();
5179
5247
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -5849,6 +5917,67 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
5849
5917
  }
5850
5918
  }
5851
5919
 
5920
+ // src/runtime-kernel/llm/reasoning-details.ts
5921
+ var mergeableTextFields = ["reasoning_content"];
5922
+ function isRecord18(value) {
5923
+ return !!value && typeof value === "object" && !Array.isArray(value);
5924
+ }
5925
+ function findMergeableTextField(detail) {
5926
+ if (!isRecord18(detail)) return void 0;
5927
+ if (typeof detail.provider !== "string") return void 0;
5928
+ if (typeof detail.type !== "string") return void 0;
5929
+ for (const field of mergeableTextFields) {
5930
+ if (typeof detail[field] === "string") {
5931
+ return field;
5932
+ }
5933
+ }
5934
+ return void 0;
5935
+ }
5936
+ function hasOnlyStableTextDetailFields(detail, textField) {
5937
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
5938
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
5939
+ }
5940
+ function canMergeTextDetails(previous, incoming) {
5941
+ if (!isRecord18(previous) || !isRecord18(incoming)) return false;
5942
+ const previousField = findMergeableTextField(previous);
5943
+ const incomingField = findMergeableTextField(incoming);
5944
+ if (!previousField || previousField !== incomingField) return false;
5945
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
5946
+ }
5947
+ function mergeStreamingText(previous, incoming) {
5948
+ if (!previous) return incoming;
5949
+ if (!incoming) return previous;
5950
+ if (incoming.startsWith(previous)) {
5951
+ return incoming;
5952
+ }
5953
+ if (previous.endsWith(incoming)) {
5954
+ return previous;
5955
+ }
5956
+ return `${previous}${incoming}`;
5957
+ }
5958
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
5959
+ const next = [...existing];
5960
+ for (const detail of incoming) {
5961
+ const previous = next[next.length - 1];
5962
+ if (canMergeTextDetails(previous, detail)) {
5963
+ const textField = findMergeableTextField(previous);
5964
+ if (textField && isRecord18(detail)) {
5965
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
5966
+ if (mergedText === previous[textField]) {
5967
+ continue;
5968
+ }
5969
+ next[next.length - 1] = {
5970
+ ...previous,
5971
+ [textField]: mergedText
5972
+ };
5973
+ continue;
5974
+ }
5975
+ }
5976
+ next.push(detail);
5977
+ }
5978
+ return next;
5979
+ }
5980
+
5852
5981
  // src/runtime-kernel/llm/streaming-adapter.ts
5853
5982
  async function callLlmStream(params) {
5854
5983
  const {
@@ -5861,7 +5990,7 @@ async function callLlmStream(params) {
5861
5990
  } = params;
5862
5991
  let fullResponse = "";
5863
5992
  let streamError = null;
5864
- const reasoningDetails = [];
5993
+ let reasoningDetails = [];
5865
5994
  const streamAnswerId = generateMessageId();
5866
5995
  let streamChunkSeq = 0;
5867
5996
  let capturedUsage = void 0;
@@ -5931,13 +6060,21 @@ async function callLlmStream(params) {
5931
6060
  const reasoning = isRecord17(chunk) ? chunk["reasoning_details"] : void 0;
5932
6061
  if (reasoning !== void 0) {
5933
6062
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
5934
- reasoningDetails.push(...newReasoningDetails);
5935
- eventHandler({
5936
- type: "provider_sidecar",
5937
- id: generateMessageId(),
5938
- timestamp: Date.now(),
5939
- reasoning_details: newReasoningDetails
5940
- });
6063
+ const previousReasoningDetails = reasoningDetails;
6064
+ const previousLength = previousReasoningDetails.length;
6065
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6066
+ reasoningDetails = compactedReasoningDetails;
6067
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6068
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6069
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6070
+ if (emittedReasoningDetails.length > 0) {
6071
+ eventHandler({
6072
+ type: "provider_sidecar",
6073
+ id: generateMessageId(),
6074
+ timestamp: Date.now(),
6075
+ reasoning_details: emittedReasoningDetails
6076
+ });
6077
+ }
5941
6078
  }
5942
6079
  if (chunk.tool_calls) {
5943
6080
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6376,7 +6513,7 @@ function cloneRunRecord(record) {
6376
6513
  ...record,
6377
6514
  iterationBudget: record.iterationBudget ? { ...record.iterationBudget } : void 0,
6378
6515
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0,
6379
- metadata: record.metadata ? { ...record.metadata } : void 0
6516
+ metadata: record.metadata ? structuredClone(record.metadata) : void 0
6380
6517
  };
6381
6518
  }
6382
6519
  function matchesStatus(candidate, filter) {
@@ -6508,7 +6645,7 @@ function runRecordToMeta(record) {
6508
6645
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
6509
6646
  };
6510
6647
  }
6511
- function isRecord18(value) {
6648
+ function isRecord19(value) {
6512
6649
  return typeof value === "object" && value !== null && !Array.isArray(value);
6513
6650
  }
6514
6651
  function readStringField(record, key) {
@@ -6529,7 +6666,7 @@ function getRunIdFromMetadata(event) {
6529
6666
  return snakeCaseRunId;
6530
6667
  }
6531
6668
  const runContext = metadata["run_context"];
6532
- if (isRecord18(runContext)) {
6669
+ if (isRecord19(runContext)) {
6533
6670
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
6534
6671
  }
6535
6672
  return void 0;
@@ -6747,7 +6884,7 @@ function runMetaFromRecord(record) {
6747
6884
  }
6748
6885
 
6749
6886
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
6750
- function isRecord19(value) {
6887
+ function isRecord20(value) {
6751
6888
  return typeof value === "object" && value !== null && !Array.isArray(value);
6752
6889
  }
6753
6890
  function readStringField2(record, key) {
@@ -6764,7 +6901,7 @@ function readRunIdFromRuntimeEvent(event) {
6764
6901
  return directRunId;
6765
6902
  }
6766
6903
  const runContext = metadata["run_context"];
6767
- if (!isRecord19(runContext)) {
6904
+ if (!isRecord20(runContext)) {
6768
6905
  return void 0;
6769
6906
  }
6770
6907
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -6776,7 +6913,7 @@ function readAwaitingUserReason(event) {
6776
6913
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
6777
6914
  return event.prompt;
6778
6915
  }
6779
- if (isRecord19(event.form)) {
6916
+ if (isRecord20(event.form)) {
6780
6917
  const prompt = readStringField2(event.form, "prompt");
6781
6918
  if (prompt && prompt.trim().length > 0) {
6782
6919
  return prompt;
@@ -6788,7 +6925,7 @@ function isTerminalStatus(status) {
6788
6925
  return status === "completed" || status === "failed" || status === "cancelled";
6789
6926
  }
6790
6927
  function cloneMetadata(metadata) {
6791
- return metadata ? { ...metadata } : void 0;
6928
+ return metadata ? structuredClone(metadata) : void 0;
6792
6929
  }
6793
6930
  function recordToSnapshot(record) {
6794
6931
  return {
@@ -6875,7 +7012,7 @@ var DefaultRunSupervisor = class {
6875
7012
  startedAt,
6876
7013
  updatedAt: startedAt,
6877
7014
  iterationBudget: spec.iterationBudget ? { ...spec.iterationBudget } : void 0,
6878
- metadata: spec.metadata ? { ...spec.metadata } : void 0
7015
+ metadata: cloneMetadata(spec.metadata)
6879
7016
  };
6880
7017
  await this.registryStore.save(record);
6881
7018
  const handle = new DefaultRunHandle({
@@ -7049,12 +7186,13 @@ var DefaultRunSupervisor = class {
7049
7186
  }
7050
7187
  try {
7051
7188
  await handle.markRunning({ currentNode: "detached" });
7189
+ const registeredRecord = await this.registryStore.load(handle.runId);
7052
7190
  const executorOutcome = await this.executor.execute({
7053
7191
  runId: handle.runId,
7054
- parentRunId: spec.parentRunId,
7055
- conversationId: spec.conversationId,
7056
- agentSpec: spec.agentSpec,
7057
- request: spec.request,
7192
+ parentRunId: handle.parentRunId,
7193
+ conversationId: registeredRecord?.conversationId ?? spec.conversationId,
7194
+ agentSpec: await handle.spec(),
7195
+ request: await handle.request(),
7058
7196
  signal: handle.signal,
7059
7197
  eventBus: spec.eventBus,
7060
7198
  eventStore: spec.eventStore,
@@ -7063,7 +7201,7 @@ var DefaultRunSupervisor = class {
7063
7201
  contextFences: spec.contextFences,
7064
7202
  wakeSource: spec.wakeSource,
7065
7203
  ephemeral: spec.ephemeral,
7066
- metadata: spec.metadata
7204
+ metadata: cloneMetadata(registeredRecord?.metadata ?? spec.metadata)
7067
7205
  });
7068
7206
  const outcome = await this.persistExecutorOutcome(handle, executorOutcome);
7069
7207
  this.notifyTerminalWaiters(outcome);
@@ -7127,10 +7265,14 @@ var DefaultRunSupervisor = class {
7127
7265
  await this.registryStore.save(record);
7128
7266
  }
7129
7267
  const completedAt = executorOutcome?.completedAt ?? this.now();
7268
+ const fallbackMeta = record ? void 0 : await handle.meta();
7130
7269
  const outcome = recordToTerminalOutcome(record ?? {
7131
7270
  runId: handle.runId,
7132
7271
  parentRunId: handle.parentRunId,
7133
- status: executorOutcome?.status ?? "completed"}, completedAt);
7272
+ conversationId: fallbackMeta?.conversationId ?? "",
7273
+ agentSpecId: fallbackMeta?.agentSpecId,
7274
+ status: executorOutcome?.status ?? "completed",
7275
+ startedAt: fallbackMeta?.startedAt ?? completedAt}, completedAt);
7134
7276
  const nextOutcome = {
7135
7277
  ...outcome,
7136
7278
  metadata: {
@@ -7386,7 +7528,7 @@ function createQuickstartTelemetryPort(collector) {
7386
7528
  }
7387
7529
 
7388
7530
  // src/quickstart/runAgent.ts
7389
- function isRecord20(value) {
7531
+ function isRecord21(value) {
7390
7532
  return typeof value === "object" && value !== null && !Array.isArray(value);
7391
7533
  }
7392
7534
  function readString4(value) {
@@ -7400,7 +7542,7 @@ function resolveModelId(agent, options) {
7400
7542
  return modelId;
7401
7543
  }
7402
7544
  function isQuickstartStreamChunkEvent(value) {
7403
- return isRecord20(value) && value.type === "stream_chunk" && typeof value.content === "string";
7545
+ return isRecord21(value) && value.type === "stream_chunk" && typeof value.content === "string";
7404
7546
  }
7405
7547
  function createNoopObservationPreview() {
7406
7548
  return {
@@ -7440,7 +7582,7 @@ function readFinalAnswer(events, checkpointLocal) {
7440
7582
  if (chunks.length > 0) {
7441
7583
  return chunks.join("");
7442
7584
  }
7443
- if (isRecord20(checkpointLocal)) {
7585
+ if (isRecord21(checkpointLocal)) {
7444
7586
  const finalAnswer = checkpointLocal["finalAnswer"];
7445
7587
  if (typeof finalAnswer === "string") {
7446
7588
  return finalAnswer;
@@ -7449,7 +7591,7 @@ function readFinalAnswer(events, checkpointLocal) {
7449
7591
  return "";
7450
7592
  }
7451
7593
  function readContextTrace(checkpointLocal) {
7452
- if (!isRecord20(checkpointLocal)) return void 0;
7594
+ if (!isRecord21(checkpointLocal)) return void 0;
7453
7595
  return checkpointLocal["contextTrace"];
7454
7596
  }
7455
7597
  async function emitRunEvent(event, sink) {
@@ -7458,6 +7600,7 @@ async function emitRunEvent(event, sink) {
7458
7600
  async function runAgent(agent, options) {
7459
7601
  const modelId = resolveModelId(agent, options);
7460
7602
  const conversationId = options.conversationId ?? `conv_${Date.now()}`;
7603
+ const checkpointKey = conversationId;
7461
7604
  const runId = options.runId ?? generateRunId();
7462
7605
  const turnId = runId;
7463
7606
  const costCollector = new QuickstartRunCostCollector();
@@ -7512,7 +7655,7 @@ async function runAgent(agent, options) {
7512
7655
  };
7513
7656
  await handle.markRunning({ currentNode: "user" });
7514
7657
  try {
7515
- await executor.prime(conversationId, {
7658
+ await executor.prime(checkpointKey, {
7516
7659
  conversationId,
7517
7660
  turnId,
7518
7661
  request: {
@@ -7537,7 +7680,7 @@ async function runAgent(agent, options) {
7537
7680
  return void 0;
7538
7681
  }
7539
7682
  });
7540
- const result = await executor.runUntilYield(conversationId);
7683
+ const result = await executor.runUntilYield(checkpointKey);
7541
7684
  runtimeEvents.push(...result.events);
7542
7685
  for (const event of result.events) {
7543
7686
  if (event.type === "final_answer_chunk") continue;
@@ -7573,11 +7716,11 @@ async function runAgent(agent, options) {
7573
7716
  }
7574
7717
 
7575
7718
  // src/cli/configLoader.ts
7576
- function isRecord21(value) {
7719
+ function isRecord22(value) {
7577
7720
  return typeof value === "object" && value !== null && !Array.isArray(value);
7578
7721
  }
7579
7722
  function readDefaultExport(moduleValue) {
7580
- if (isRecord21(moduleValue) && "default" in moduleValue) {
7723
+ if (isRecord22(moduleValue) && "default" in moduleValue) {
7581
7724
  return moduleValue.default;
7582
7725
  }
7583
7726
  return moduleValue;
@@ -7588,7 +7731,7 @@ async function loadConfig(configPath, cwd) {
7588
7731
  moduleUrl.searchParams.set("t", String(Date.now()));
7589
7732
  const loaded = await import(moduleUrl.href);
7590
7733
  const config = readDefaultExport(loaded);
7591
- if (!isRecord21(config)) {
7734
+ if (!isRecord22(config)) {
7592
7735
  throw new Error(`[linnkit] config must export an object: ${absolutePath}`);
7593
7736
  }
7594
7737
  return defineConfig(config);
@@ -7700,7 +7843,7 @@ function createQuickstartTemplateFiles(projectName) {
7700
7843
  start: 'linnkit run hello --input "\u4F60\u597D\uFF0C\u4ECB\u7ECD\u4E00\u4E0B\u4F60\u81EA\u5DF1"'
7701
7844
  },
7702
7845
  dependencies: {
7703
- "@linnlabs/linnkit": "^0.8.0",
7846
+ "@linnlabs/linnkit": "^0.10.0",
7704
7847
  zod: "^3.22.0"
7705
7848
  },
7706
7849
  devDependencies: {}