@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.js CHANGED
@@ -200,6 +200,7 @@ var AgentSpecBudgetPolicy = z.object({
200
200
  });
201
201
  var AgentSpecToolHistoryPolicy = z.object({
202
202
  strategy: z.enum(["per-pair", "per-run", "none"]).optional(),
203
+ retentionMode: z.enum(["drop", "compress"]).optional(),
203
204
  keepLatestToolPairs: z.number().int().nonnegative().optional(),
204
205
  keepLatestRuns: z.number().int().nonnegative().optional(),
205
206
  maxInteractionGroups: z.number().int().nonnegative().optional(),
@@ -1154,6 +1155,17 @@ var logger = new Logger("GraphExecutor");
1154
1155
  function asLocalRecord(local) {
1155
1156
  return local && typeof local === "object" ? { ...local } : {};
1156
1157
  }
1158
+ function readNonEmptyString(value) {
1159
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1160
+ }
1161
+ function readRuntimeConversationId(state) {
1162
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1163
+ return readNonEmptyString(local?.conversationId);
1164
+ }
1165
+ function readRuntimeTurnId(state) {
1166
+ const local = state?.local && typeof state.local === "object" ? state.local : void 0;
1167
+ return readNonEmptyString(local?.turnId);
1168
+ }
1157
1169
  var GraphExecutor = class {
1158
1170
  constructor(checkpointer, config = {}) {
1159
1171
  this.checkpointer = checkpointer;
@@ -1171,8 +1183,8 @@ var GraphExecutor = class {
1171
1183
  registerNode(node) {
1172
1184
  this.nodes.set(node.id, node);
1173
1185
  }
1174
- async peekCheckpoint(conversationId) {
1175
- return await this.checkpointer.load(conversationId);
1186
+ async peekCheckpoint(checkpointKey) {
1187
+ return await this.checkpointer.load(checkpointKey);
1176
1188
  }
1177
1189
  sanitize(state) {
1178
1190
  const local = asLocalRecord(state.local);
@@ -1184,8 +1196,8 @@ var GraphExecutor = class {
1184
1196
  local
1185
1197
  };
1186
1198
  }
1187
- async prime(conversationId, local, nodeId = "user") {
1188
- this.ephemeralLocals.set(conversationId, { ...local || {} });
1199
+ async prime(checkpointKey, local, nodeId = "user") {
1200
+ this.ephemeralLocals.set(checkpointKey, { ...local || {} });
1189
1201
  const localSansMemory = { ...local || {} };
1190
1202
  if ("memory" in localSansMemory) delete localSansMemory.memory;
1191
1203
  const state = {
@@ -1193,10 +1205,10 @@ var GraphExecutor = class {
1193
1205
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1194
1206
  local: localSansMemory
1195
1207
  };
1196
- await this.checkpointer.save(conversationId, state);
1208
+ await this.checkpointer.save(checkpointKey, state);
1197
1209
  }
1198
- async setNode(conversationId, nodeId, localPatch) {
1199
- const current = await this.checkpointer.load(conversationId) || {
1210
+ async setNode(checkpointKey, nodeId, localPatch) {
1211
+ const current = await this.checkpointer.load(checkpointKey) || {
1200
1212
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1201
1213
  local: {}
1202
1214
  };
@@ -1207,19 +1219,46 @@ var GraphExecutor = class {
1207
1219
  schemaVersion: current.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
1208
1220
  local: mergedLocal
1209
1221
  };
1210
- await this.checkpointer.save(conversationId, next);
1222
+ await this.checkpointer.save(checkpointKey, next);
1211
1223
  }
1212
- async runUntilYield(conversationId) {
1224
+ async runUntilYield(checkpointKey) {
1213
1225
  const runId = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1214
1226
  let lifecyclePhase = "completed";
1227
+ let initialState = null;
1228
+ try {
1229
+ initialState = await this.loadInitialState(checkpointKey);
1230
+ } catch (err) {
1231
+ lifecyclePhase = "failed";
1232
+ this.telemetryPort.emit({
1233
+ kind: "run_lifecycle",
1234
+ runId,
1235
+ phase: "spawned",
1236
+ scope: {}
1237
+ });
1238
+ this.telemetryPort.emit({
1239
+ kind: "run_lifecycle",
1240
+ runId,
1241
+ phase: lifecyclePhase,
1242
+ scope: {}
1243
+ });
1244
+ throw err;
1245
+ }
1246
+ let lifecycleConversationId = readRuntimeConversationId(initialState);
1247
+ let lifecycleTurnId = readRuntimeTurnId(initialState);
1215
1248
  this.telemetryPort.emit({
1216
1249
  kind: "run_lifecycle",
1217
1250
  runId,
1218
1251
  phase: "spawned",
1219
- scope: { conversationId: conversationId || void 0 }
1252
+ scope: {
1253
+ conversationId: lifecycleConversationId,
1254
+ turnId: lifecycleTurnId
1255
+ }
1220
1256
  });
1221
1257
  try {
1222
- return await this.runUntilYieldInternal(conversationId);
1258
+ const result = await this.runUntilYieldInternal(checkpointKey, initialState);
1259
+ lifecycleConversationId = readRuntimeConversationId(result.checkpoint);
1260
+ lifecycleTurnId = readRuntimeTurnId(result.checkpoint);
1261
+ return result;
1223
1262
  } catch (err) {
1224
1263
  lifecyclePhase = err?.name === "AbortError" ? "cancelled" : "failed";
1225
1264
  throw err;
@@ -1228,17 +1267,23 @@ var GraphExecutor = class {
1228
1267
  kind: "run_lifecycle",
1229
1268
  runId,
1230
1269
  phase: lifecyclePhase,
1231
- scope: { conversationId: conversationId || void 0 }
1270
+ scope: {
1271
+ conversationId: lifecycleConversationId,
1272
+ turnId: lifecycleTurnId
1273
+ }
1232
1274
  });
1233
1275
  }
1234
1276
  }
1235
- async runUntilYieldInternal(conversationId) {
1236
- let state = await this.checkpointer.load(conversationId) || {
1277
+ async loadInitialState(checkpointKey) {
1278
+ return await this.checkpointer.load(checkpointKey) || {
1237
1279
  nodeId: "user",
1238
1280
  schemaVersion: ENGINE_STATE_SCHEMA_VERSION,
1239
1281
  local: {}
1240
1282
  };
1241
- const ephemeral = this.ephemeralLocals.get(conversationId) || {};
1283
+ }
1284
+ async runUntilYieldInternal(checkpointKey, initialState) {
1285
+ let state = initialState;
1286
+ const ephemeral = this.ephemeralLocals.get(checkpointKey) || {};
1242
1287
  state = {
1243
1288
  ...state,
1244
1289
  schemaVersion: state.schemaVersion ?? ENGINE_STATE_SCHEMA_VERSION,
@@ -1267,7 +1312,7 @@ var GraphExecutor = class {
1267
1312
  const signalRaw = state.local?.signal;
1268
1313
  if (isAbortSignal2(signalRaw) && signalRaw.aborted) {
1269
1314
  logger.warn("[GraphExecutor] \u6536\u5230 AbortSignal\uFF0C\u7ACB\u5373\u505C\u6B62\u63A8\u7406\u5FAA\u73AF");
1270
- this.ephemeralLocals.delete(conversationId);
1315
+ this.ephemeralLocals.delete(checkpointKey);
1271
1316
  throwAbortError();
1272
1317
  }
1273
1318
  const isLastStep = cycleStepCount >= this.config.maxSteps;
@@ -1315,8 +1360,8 @@ var GraphExecutor = class {
1315
1360
  checkpointCount
1316
1361
  });
1317
1362
  const cp2 = this.sanitize(state);
1318
- await this.checkpointer.save(conversationId, cp2);
1319
- this.ephemeralLocals.delete(conversationId);
1363
+ await this.checkpointer.save(checkpointKey, cp2);
1364
+ this.ephemeralLocals.delete(checkpointKey);
1320
1365
  return { events: allEvents, checkpoint: cp2, stepCount };
1321
1366
  }
1322
1367
  logger.info("[GraphExecutor] \u8282\u70B9\u5207\u6362", {
@@ -1327,18 +1372,18 @@ var GraphExecutor = class {
1327
1372
  });
1328
1373
  const nodeRunStartedAt = Date.now();
1329
1374
  const nodeIdForTelemetry = state.nodeId;
1375
+ const conversationIdForTelemetry = readRuntimeConversationId(state);
1330
1376
  let result;
1331
1377
  try {
1332
1378
  result = await node.run(state);
1333
1379
  } finally {
1334
- const turnIdForTelemetry = typeof state.local?.turnId === "string" ? state.local.turnId : void 0;
1335
1380
  this.telemetryPort.emit({
1336
1381
  kind: "graph_node",
1337
1382
  nodeId: nodeIdForTelemetry,
1338
1383
  durationMs: Date.now() - nodeRunStartedAt,
1339
1384
  scope: {
1340
- conversationId: conversationId || void 0,
1341
- turnId: turnIdForTelemetry
1385
+ conversationId: conversationIdForTelemetry,
1386
+ turnId: readRuntimeTurnId(state)
1342
1387
  }
1343
1388
  });
1344
1389
  }
@@ -1377,7 +1422,7 @@ var GraphExecutor = class {
1377
1422
  });
1378
1423
  state = { ...state, nodeId: nextNodeId };
1379
1424
  const cp2 = this.sanitize(state);
1380
- await this.checkpointer.save(conversationId, cp2);
1425
+ await this.checkpointer.save(checkpointKey, cp2);
1381
1426
  continue;
1382
1427
  }
1383
1428
  if (result.kind === "yield") {
@@ -1388,7 +1433,7 @@ var GraphExecutor = class {
1388
1433
  checkpointCount
1389
1434
  });
1390
1435
  const cp2 = this.sanitize(state);
1391
- await this.checkpointer.save(conversationId, cp2);
1436
+ await this.checkpointer.save(checkpointKey, cp2);
1392
1437
  return { events: allEvents, checkpoint: cp2, stepCount };
1393
1438
  }
1394
1439
  if (result.kind === "pause") {
@@ -1399,7 +1444,7 @@ var GraphExecutor = class {
1399
1444
  checkpointCount
1400
1445
  });
1401
1446
  const cp2 = this.sanitize(state);
1402
- await this.checkpointer.save(conversationId, cp2);
1447
+ await this.checkpointer.save(checkpointKey, cp2);
1403
1448
  return { events: allEvents, checkpoint: cp2, stepCount };
1404
1449
  }
1405
1450
  }
@@ -1410,8 +1455,8 @@ var GraphExecutor = class {
1410
1455
  checkpointCount
1411
1456
  });
1412
1457
  const cp = this.sanitize(state);
1413
- await this.checkpointer.save(conversationId, cp);
1414
- this.ephemeralLocals.delete(conversationId);
1458
+ await this.checkpointer.save(checkpointKey, cp);
1459
+ this.ephemeralLocals.delete(checkpointKey);
1415
1460
  return { events: allEvents, checkpoint: cp, stepCount };
1416
1461
  }
1417
1462
  };
@@ -1574,15 +1619,15 @@ function splitConcatenatedJsonObjects(input) {
1574
1619
  }
1575
1620
 
1576
1621
  // src/runtime-kernel/graph-engine/tick-pipeline/helpers.ts
1577
- function readNonEmptyString(value) {
1622
+ function readNonEmptyString2(value) {
1578
1623
  if (typeof value !== "string") return void 0;
1579
1624
  const trimmed = value.trim();
1580
1625
  return trimmed.length > 0 ? trimmed : void 0;
1581
1626
  }
1582
1627
  function resolveConversationIdForRuntimeEvents(toolContext) {
1583
- const fromCamel = readNonEmptyString(toolContext?.conversationId);
1628
+ const fromCamel = readNonEmptyString2(toolContext?.conversationId);
1584
1629
  if (fromCamel) return fromCamel;
1585
- const fromSnake = toolContext ? readNonEmptyString(toolContext["conversation_id"]) : void 0;
1630
+ const fromSnake = toolContext ? readNonEmptyString2(toolContext["conversation_id"]) : void 0;
1586
1631
  if (fromSnake) return fromSnake;
1587
1632
  return generateMessageId();
1588
1633
  }
@@ -1939,7 +1984,7 @@ var runModelLockMiddleware = async (ctx, stage, next) => {
1939
1984
  if (stage.id !== "execute_llm") {
1940
1985
  return;
1941
1986
  }
1942
- const normalized = readNonEmptyString(ctx.cloudQuotaFallbackAppliedModelId);
1987
+ const normalized = readNonEmptyString2(ctx.cloudQuotaFallbackAppliedModelId);
1943
1988
  if (!normalized) {
1944
1989
  return;
1945
1990
  }
@@ -2527,7 +2572,7 @@ function createExecuteLlmStage(dependencies) {
2527
2572
  streamEventHandler,
2528
2573
  ctx.signal,
2529
2574
  (fallbackModelId) => {
2530
- ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString(fallbackModelId);
2575
+ ctx.cloudQuotaFallbackAppliedModelId = readNonEmptyString2(fallbackModelId);
2531
2576
  },
2532
2577
  (info) => {
2533
2578
  ctx.modelFallbackAudit = info;
@@ -2571,7 +2616,7 @@ function createPrepareCallStage(dependencies) {
2571
2616
  return {
2572
2617
  id: "prepare_call",
2573
2618
  async run(ctx) {
2574
- const lockedRunModelId = readNonEmptyString(ctx.executorLocal?.runLockedModelId);
2619
+ const lockedRunModelId = readNonEmptyString2(ctx.executorLocal?.runLockedModelId);
2575
2620
  const requestedModelId = lockedRunModelId ?? ctx.request.model_id;
2576
2621
  ctx.modelId = dependencies.modelResolver.resolveModelId(requestedModelId);
2577
2622
  await emitAuditEnvelope(ctx.audit, {
@@ -2643,7 +2688,7 @@ var GraphAgentExecutor = class {
2643
2688
  this.llmCaller = dependencies.llmCaller;
2644
2689
  this.toolRuntime = dependencies.toolRuntime;
2645
2690
  this.contextBuilder = dependencies.contextBuilder;
2646
- this.cloudQuotaFallbackModelId = readNonEmptyString(dependencies.cloudQuotaFallbackModelId);
2691
+ this.cloudQuotaFallbackModelId = readNonEmptyString2(dependencies.cloudQuotaFallbackModelId);
2647
2692
  this.modelCatalog = dependencies.modelCatalog ?? createEmptyModelCatalog();
2648
2693
  this.modelResolver = dependencies.modelResolver ?? new ModelResolver({
2649
2694
  modelCatalog: this.modelCatalog
@@ -2691,7 +2736,7 @@ var GraphAgentExecutor = class {
2691
2736
  llmMessages: [],
2692
2737
  mode: input.request.mode === "chat" ? "chat" : "agent",
2693
2738
  conversationId: resolveConversationIdForRuntimeEvents(input.toolContext),
2694
- turnId: readNonEmptyString(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2739
+ turnId: readNonEmptyString2(input.toolContext?.turnId) ?? `turn_${Date.now()}`,
2695
2740
  telemetry: this.telemetryPort,
2696
2741
  audit: this.auditPort
2697
2742
  };
@@ -2741,18 +2786,18 @@ function isSummarizationCallbacks(value) {
2741
2786
  function isGraphSseSink(value) {
2742
2787
  return typeof value === "function";
2743
2788
  }
2744
- function readNonEmptyString2(value) {
2789
+ function readNonEmptyString3(value) {
2745
2790
  if (typeof value !== "string") return void 0;
2746
2791
  const trimmed = value.trim();
2747
2792
  return trimmed.length > 0 ? trimmed : void 0;
2748
2793
  }
2749
2794
  function readGraphAgentLocal(local) {
2750
2795
  const source = local ?? {};
2751
- const answerId = readNonEmptyString2(source.answerId);
2796
+ const answerId = readNonEmptyString3(source.answerId);
2752
2797
  const chunkSeq = Number.isInteger(source.chunkSeq) ? Number(source.chunkSeq) : 0;
2753
2798
  return {
2754
- conversationId: readNonEmptyString2(source.conversationId) ?? "",
2755
- turnId: readNonEmptyString2(source.turnId),
2799
+ conversationId: readNonEmptyString3(source.conversationId) ?? "",
2800
+ turnId: readNonEmptyString3(source.turnId),
2756
2801
  request: isAgentInvocationRequest(source.request) ? source.request : void 0,
2757
2802
  toolContext: isToolExecutionContext(source.toolContext) ? source.toolContext : void 0,
2758
2803
  history: isRuntimeEventArray(source.history) ? source.history : [],
@@ -3517,7 +3562,7 @@ var LlmNode = class {
3517
3562
  function isRecord8(value) {
3518
3563
  return typeof value === "object" && value !== null && !Array.isArray(value);
3519
3564
  }
3520
- function readNonEmptyString3(value) {
3565
+ function readNonEmptyString4(value) {
3521
3566
  if (typeof value !== "string") {
3522
3567
  return void 0;
3523
3568
  }
@@ -3539,7 +3584,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3539
3584
  return void 0;
3540
3585
  }
3541
3586
  if (toolName === "write_report") {
3542
- const report = readNonEmptyString3(data.report);
3587
+ const report = readNonEmptyString4(data.report);
3543
3588
  if (!report) {
3544
3589
  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");
3545
3590
  }
@@ -3549,7 +3594,7 @@ function resolveFinalAnswerFromToolResult(toolName, parsedResult) {
3549
3594
  if (data.success !== true) {
3550
3595
  return void 0;
3551
3596
  }
3552
- const finalAnswer = readNonEmptyString3(data.final_answer);
3597
+ const finalAnswer = readNonEmptyString4(data.final_answer);
3553
3598
  if (!finalAnswer) {
3554
3599
  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");
3555
3600
  }
@@ -4445,6 +4490,22 @@ var ToolNode = class {
4445
4490
  });
4446
4491
  }
4447
4492
  async run(state) {
4493
+ const events = [];
4494
+ while (true) {
4495
+ const result = await this.runNextPendingToolCall(state);
4496
+ if (Array.isArray(result.events) && result.events.length > 0) {
4497
+ events.push(...result.events);
4498
+ }
4499
+ if (result.kind === "route" && result.nextNodeId === "tool") {
4500
+ continue;
4501
+ }
4502
+ return {
4503
+ ...result,
4504
+ events
4505
+ };
4506
+ }
4507
+ }
4508
+ async runNextPendingToolCall(state) {
4448
4509
  const calls = state.local?.pendingToolCalls ?? [];
4449
4510
  const signalRaw = state.local?.signal;
4450
4511
  if (isAbortSignal(signalRaw) && signalRaw.aborted) {
@@ -4652,18 +4713,25 @@ var ToolNode = class {
4652
4713
  rawArguments: context.call.function?.arguments,
4653
4714
  parsedArguments: context.toolArgs
4654
4715
  });
4716
+ const remainingCalls = context.calls.slice(1);
4655
4717
  context.state.local = buildErrorLocalState({
4656
4718
  local: context.local,
4657
- remainingCalls: context.calls.slice(1),
4719
+ remainingCalls,
4658
4720
  conversationId: context.conversationId,
4659
4721
  turnId: context.turnId,
4660
4722
  runtimeEvents: context.bridge.getRuntimeEvents(),
4661
4723
  nextProtocolErrorCount: fuse.nextCount
4662
4724
  });
4663
- if (fuse.shouldFuse) {
4725
+ if (fuse.shouldFuse && remainingCalls.length === 0) {
4664
4726
  throw createToolProtocolFuseError(fuse.nextCount, context.exec.error);
4665
4727
  }
4666
- return { kind: "route", nextNodeId: "llm", events: context.bridge.getRuntimeEvents() };
4728
+ return {
4729
+ kind: "route",
4730
+ // 同一个 assistant.tool_calls batch 必须为每个 call 产出 tool_output。
4731
+ // 出错时也继续消费剩余 call,ToolNode.run 会在本节点内 drain 完 batch 再回 LLM。
4732
+ nextNodeId: remainingCalls.length > 0 ? "tool" : "llm",
4733
+ events: context.bridge.getRuntimeEvents()
4734
+ };
4667
4735
  }
4668
4736
  };
4669
4737
 
@@ -4860,12 +4928,12 @@ function asRecord(value) {
4860
4928
  }
4861
4929
  return value;
4862
4930
  }
4863
- function summarizeCheckpoint(conversationId, state, savedAt) {
4931
+ function summarizeCheckpoint(checkpointKey, state, savedAt) {
4864
4932
  const local = asRecord(state.local);
4865
4933
  const executorLocal = asRecord(local?.executorLocal);
4866
4934
  const pendingToolCalls = local?.pendingToolCalls;
4867
4935
  return {
4868
- conversationId,
4936
+ checkpointKey,
4869
4937
  schemaVersion: state.schemaVersion ?? 1,
4870
4938
  savedAt,
4871
4939
  currentNode: state.nodeId,
@@ -4884,25 +4952,25 @@ var MemoryCheckpointer = class {
4884
4952
  local: state.local && typeof state.local === "object" && !Array.isArray(state.local) ? { ...state.local } : state.local
4885
4953
  };
4886
4954
  }
4887
- async load(conversationId) {
4888
- const entry = this.store.get(conversationId);
4955
+ async load(checkpointKey) {
4956
+ const entry = this.store.get(checkpointKey);
4889
4957
  return entry ? this.cloneState(entry.state) : null;
4890
4958
  }
4891
- async save(conversationId, state) {
4892
- this.store.set(conversationId, {
4959
+ async save(checkpointKey, state) {
4960
+ this.store.set(checkpointKey, {
4893
4961
  state: this.cloneState(state),
4894
4962
  savedAt: Date.now()
4895
4963
  });
4896
4964
  }
4897
- async clear(conversationId) {
4898
- this.store.delete(conversationId);
4965
+ async clear(checkpointKey) {
4966
+ this.store.delete(checkpointKey);
4899
4967
  }
4900
- async peekMeta(conversationId) {
4901
- const entry = this.store.get(conversationId);
4902
- return entry ? summarizeCheckpoint(conversationId, entry.state, entry.savedAt) : null;
4968
+ async peekMeta(checkpointKey) {
4969
+ const entry = this.store.get(checkpointKey);
4970
+ return entry ? summarizeCheckpoint(checkpointKey, entry.state, entry.savedAt) : null;
4903
4971
  }
4904
4972
  async list(filter = {}) {
4905
- 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);
4973
+ 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);
4906
4974
  if (filter.limit === void 0) {
4907
4975
  return summaries;
4908
4976
  }
@@ -5159,18 +5227,18 @@ function createClassification(category, reason, suggestedDelay, extras) {
5159
5227
  }
5160
5228
  var ErrorClassifier = class {
5161
5229
  static classify(error, context) {
5162
- const isRecord22 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5230
+ const isRecord23 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
5163
5231
  const baseMsg = (error.message || "").toLowerCase();
5164
5232
  const causeMsg = (() => {
5165
5233
  const cause = error.cause;
5166
5234
  if (typeof cause === "string") return cause.toLowerCase();
5167
5235
  if (cause instanceof Error) return (cause.message || "").toLowerCase();
5168
- if (isRecord22(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5236
+ if (isRecord23(cause) && typeof cause["message"] === "string") return String(cause["message"]).toLowerCase();
5169
5237
  return "";
5170
5238
  })();
5171
5239
  const causeCode = (() => {
5172
5240
  const cause = error.cause;
5173
- if (isRecord22(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5241
+ if (isRecord23(cause) && typeof cause["code"] === "string") return String(cause["code"]);
5174
5242
  return "";
5175
5243
  })();
5176
5244
  const errorMessage = `${baseMsg} ${causeMsg}`.trim();
@@ -5846,6 +5914,67 @@ function assertToolCallsHaveValidJsonArguments(toolCalls) {
5846
5914
  }
5847
5915
  }
5848
5916
 
5917
+ // src/runtime-kernel/llm/reasoning-details.ts
5918
+ var mergeableTextFields = ["reasoning_content"];
5919
+ function isRecord18(value) {
5920
+ return !!value && typeof value === "object" && !Array.isArray(value);
5921
+ }
5922
+ function findMergeableTextField(detail) {
5923
+ if (!isRecord18(detail)) return void 0;
5924
+ if (typeof detail.provider !== "string") return void 0;
5925
+ if (typeof detail.type !== "string") return void 0;
5926
+ for (const field of mergeableTextFields) {
5927
+ if (typeof detail[field] === "string") {
5928
+ return field;
5929
+ }
5930
+ }
5931
+ return void 0;
5932
+ }
5933
+ function hasOnlyStableTextDetailFields(detail, textField) {
5934
+ const allowedKeys = /* @__PURE__ */ new Set(["provider", "type", textField]);
5935
+ return Object.keys(detail).every((key) => allowedKeys.has(key));
5936
+ }
5937
+ function canMergeTextDetails(previous, incoming) {
5938
+ if (!isRecord18(previous) || !isRecord18(incoming)) return false;
5939
+ const previousField = findMergeableTextField(previous);
5940
+ const incomingField = findMergeableTextField(incoming);
5941
+ if (!previousField || previousField !== incomingField) return false;
5942
+ return previous.provider === incoming.provider && previous.type === incoming.type && hasOnlyStableTextDetailFields(previous, previousField) && hasOnlyStableTextDetailFields(incoming, incomingField);
5943
+ }
5944
+ function mergeStreamingText(previous, incoming) {
5945
+ if (!previous) return incoming;
5946
+ if (!incoming) return previous;
5947
+ if (incoming.startsWith(previous)) {
5948
+ return incoming;
5949
+ }
5950
+ if (previous.endsWith(incoming)) {
5951
+ return previous;
5952
+ }
5953
+ return `${previous}${incoming}`;
5954
+ }
5955
+ function appendStreamingProviderReasoningDetails(existing, incoming) {
5956
+ const next = [...existing];
5957
+ for (const detail of incoming) {
5958
+ const previous = next[next.length - 1];
5959
+ if (canMergeTextDetails(previous, detail)) {
5960
+ const textField = findMergeableTextField(previous);
5961
+ if (textField && isRecord18(detail)) {
5962
+ const mergedText = mergeStreamingText(String(previous[textField]), String(detail[textField]));
5963
+ if (mergedText === previous[textField]) {
5964
+ continue;
5965
+ }
5966
+ next[next.length - 1] = {
5967
+ ...previous,
5968
+ [textField]: mergedText
5969
+ };
5970
+ continue;
5971
+ }
5972
+ }
5973
+ next.push(detail);
5974
+ }
5975
+ return next;
5976
+ }
5977
+
5849
5978
  // src/runtime-kernel/llm/streaming-adapter.ts
5850
5979
  async function callLlmStream(params) {
5851
5980
  const {
@@ -5858,7 +5987,7 @@ async function callLlmStream(params) {
5858
5987
  } = params;
5859
5988
  let fullResponse = "";
5860
5989
  let streamError = null;
5861
- const reasoningDetails = [];
5990
+ let reasoningDetails = [];
5862
5991
  const streamAnswerId = generateMessageId();
5863
5992
  let streamChunkSeq = 0;
5864
5993
  let capturedUsage = void 0;
@@ -5928,13 +6057,21 @@ async function callLlmStream(params) {
5928
6057
  const reasoning = isRecord17(chunk) ? chunk["reasoning_details"] : void 0;
5929
6058
  if (reasoning !== void 0) {
5930
6059
  const newReasoningDetails = Array.isArray(reasoning) ? reasoning : [reasoning];
5931
- reasoningDetails.push(...newReasoningDetails);
5932
- eventHandler({
5933
- type: "provider_sidecar",
5934
- id: generateMessageId(),
5935
- timestamp: Date.now(),
5936
- reasoning_details: newReasoningDetails
5937
- });
6060
+ const previousReasoningDetails = reasoningDetails;
6061
+ const previousLength = previousReasoningDetails.length;
6062
+ const compactedReasoningDetails = appendStreamingProviderReasoningDetails(reasoningDetails, newReasoningDetails);
6063
+ reasoningDetails = compactedReasoningDetails;
6064
+ const previousLastChanged = previousLength > 0 && compactedReasoningDetails[previousLength - 1] !== previousReasoningDetails[previousLength - 1];
6065
+ const emitFromIndex = previousLastChanged ? previousLength - 1 : previousLength;
6066
+ const emittedReasoningDetails = compactedReasoningDetails.slice(Math.max(0, emitFromIndex));
6067
+ if (emittedReasoningDetails.length > 0) {
6068
+ eventHandler({
6069
+ type: "provider_sidecar",
6070
+ id: generateMessageId(),
6071
+ timestamp: Date.now(),
6072
+ reasoning_details: emittedReasoningDetails
6073
+ });
6074
+ }
5938
6075
  }
5939
6076
  if (chunk.tool_calls) {
5940
6077
  emitThoughtComplete(thoughtSegmenter.onBoundary());
@@ -6373,7 +6510,7 @@ function cloneRunRecord(record) {
6373
6510
  ...record,
6374
6511
  iterationBudget: record.iterationBudget ? { ...record.iterationBudget } : void 0,
6375
6512
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0,
6376
- metadata: record.metadata ? { ...record.metadata } : void 0
6513
+ metadata: record.metadata ? structuredClone(record.metadata) : void 0
6377
6514
  };
6378
6515
  }
6379
6516
  function matchesStatus(candidate, filter) {
@@ -6505,7 +6642,7 @@ function runRecordToMeta(record) {
6505
6642
  errorIfAny: record.errorIfAny ? { ...record.errorIfAny } : void 0
6506
6643
  };
6507
6644
  }
6508
- function isRecord18(value) {
6645
+ function isRecord19(value) {
6509
6646
  return typeof value === "object" && value !== null && !Array.isArray(value);
6510
6647
  }
6511
6648
  function readStringField(record, key) {
@@ -6526,7 +6663,7 @@ function getRunIdFromMetadata(event) {
6526
6663
  return snakeCaseRunId;
6527
6664
  }
6528
6665
  const runContext = metadata["run_context"];
6529
- if (isRecord18(runContext)) {
6666
+ if (isRecord19(runContext)) {
6530
6667
  return readStringField(runContext, "runId") ?? readStringField(runContext, "run_id");
6531
6668
  }
6532
6669
  return void 0;
@@ -6744,7 +6881,7 @@ function runMetaFromRecord(record) {
6744
6881
  }
6745
6882
 
6746
6883
  // src/runtime-kernel/run-supervisor/runSupervisor.ts
6747
- function isRecord19(value) {
6884
+ function isRecord20(value) {
6748
6885
  return typeof value === "object" && value !== null && !Array.isArray(value);
6749
6886
  }
6750
6887
  function readStringField2(record, key) {
@@ -6761,7 +6898,7 @@ function readRunIdFromRuntimeEvent(event) {
6761
6898
  return directRunId;
6762
6899
  }
6763
6900
  const runContext = metadata["run_context"];
6764
- if (!isRecord19(runContext)) {
6901
+ if (!isRecord20(runContext)) {
6765
6902
  return void 0;
6766
6903
  }
6767
6904
  return readStringField2(runContext, "runId") ?? readStringField2(runContext, "run_id");
@@ -6773,7 +6910,7 @@ function readAwaitingUserReason(event) {
6773
6910
  if (typeof event.prompt === "string" && event.prompt.trim().length > 0) {
6774
6911
  return event.prompt;
6775
6912
  }
6776
- if (isRecord19(event.form)) {
6913
+ if (isRecord20(event.form)) {
6777
6914
  const prompt = readStringField2(event.form, "prompt");
6778
6915
  if (prompt && prompt.trim().length > 0) {
6779
6916
  return prompt;
@@ -6785,7 +6922,7 @@ function isTerminalStatus(status) {
6785
6922
  return status === "completed" || status === "failed" || status === "cancelled";
6786
6923
  }
6787
6924
  function cloneMetadata(metadata) {
6788
- return metadata ? { ...metadata } : void 0;
6925
+ return metadata ? structuredClone(metadata) : void 0;
6789
6926
  }
6790
6927
  function recordToSnapshot(record) {
6791
6928
  return {
@@ -6872,7 +7009,7 @@ var DefaultRunSupervisor = class {
6872
7009
  startedAt,
6873
7010
  updatedAt: startedAt,
6874
7011
  iterationBudget: spec.iterationBudget ? { ...spec.iterationBudget } : void 0,
6875
- metadata: spec.metadata ? { ...spec.metadata } : void 0
7012
+ metadata: cloneMetadata(spec.metadata)
6876
7013
  };
6877
7014
  await this.registryStore.save(record);
6878
7015
  const handle = new DefaultRunHandle({
@@ -7046,12 +7183,13 @@ var DefaultRunSupervisor = class {
7046
7183
  }
7047
7184
  try {
7048
7185
  await handle.markRunning({ currentNode: "detached" });
7186
+ const registeredRecord = await this.registryStore.load(handle.runId);
7049
7187
  const executorOutcome = await this.executor.execute({
7050
7188
  runId: handle.runId,
7051
- parentRunId: spec.parentRunId,
7052
- conversationId: spec.conversationId,
7053
- agentSpec: spec.agentSpec,
7054
- request: spec.request,
7189
+ parentRunId: handle.parentRunId,
7190
+ conversationId: registeredRecord?.conversationId ?? spec.conversationId,
7191
+ agentSpec: await handle.spec(),
7192
+ request: await handle.request(),
7055
7193
  signal: handle.signal,
7056
7194
  eventBus: spec.eventBus,
7057
7195
  eventStore: spec.eventStore,
@@ -7060,7 +7198,7 @@ var DefaultRunSupervisor = class {
7060
7198
  contextFences: spec.contextFences,
7061
7199
  wakeSource: spec.wakeSource,
7062
7200
  ephemeral: spec.ephemeral,
7063
- metadata: spec.metadata
7201
+ metadata: cloneMetadata(registeredRecord?.metadata ?? spec.metadata)
7064
7202
  });
7065
7203
  const outcome = await this.persistExecutorOutcome(handle, executorOutcome);
7066
7204
  this.notifyTerminalWaiters(outcome);
@@ -7124,10 +7262,14 @@ var DefaultRunSupervisor = class {
7124
7262
  await this.registryStore.save(record);
7125
7263
  }
7126
7264
  const completedAt = executorOutcome?.completedAt ?? this.now();
7265
+ const fallbackMeta = record ? void 0 : await handle.meta();
7127
7266
  const outcome = recordToTerminalOutcome(record ?? {
7128
7267
  runId: handle.runId,
7129
7268
  parentRunId: handle.parentRunId,
7130
- status: executorOutcome?.status ?? "completed"}, completedAt);
7269
+ conversationId: fallbackMeta?.conversationId ?? "",
7270
+ agentSpecId: fallbackMeta?.agentSpecId,
7271
+ status: executorOutcome?.status ?? "completed",
7272
+ startedAt: fallbackMeta?.startedAt ?? completedAt}, completedAt);
7131
7273
  const nextOutcome = {
7132
7274
  ...outcome,
7133
7275
  metadata: {
@@ -7383,7 +7525,7 @@ function createQuickstartTelemetryPort(collector) {
7383
7525
  }
7384
7526
 
7385
7527
  // src/quickstart/runAgent.ts
7386
- function isRecord20(value) {
7528
+ function isRecord21(value) {
7387
7529
  return typeof value === "object" && value !== null && !Array.isArray(value);
7388
7530
  }
7389
7531
  function readString4(value) {
@@ -7397,7 +7539,7 @@ function resolveModelId(agent, options) {
7397
7539
  return modelId;
7398
7540
  }
7399
7541
  function isQuickstartStreamChunkEvent(value) {
7400
- return isRecord20(value) && value.type === "stream_chunk" && typeof value.content === "string";
7542
+ return isRecord21(value) && value.type === "stream_chunk" && typeof value.content === "string";
7401
7543
  }
7402
7544
  function createNoopObservationPreview() {
7403
7545
  return {
@@ -7437,7 +7579,7 @@ function readFinalAnswer(events, checkpointLocal) {
7437
7579
  if (chunks.length > 0) {
7438
7580
  return chunks.join("");
7439
7581
  }
7440
- if (isRecord20(checkpointLocal)) {
7582
+ if (isRecord21(checkpointLocal)) {
7441
7583
  const finalAnswer = checkpointLocal["finalAnswer"];
7442
7584
  if (typeof finalAnswer === "string") {
7443
7585
  return finalAnswer;
@@ -7446,7 +7588,7 @@ function readFinalAnswer(events, checkpointLocal) {
7446
7588
  return "";
7447
7589
  }
7448
7590
  function readContextTrace(checkpointLocal) {
7449
- if (!isRecord20(checkpointLocal)) return void 0;
7591
+ if (!isRecord21(checkpointLocal)) return void 0;
7450
7592
  return checkpointLocal["contextTrace"];
7451
7593
  }
7452
7594
  async function emitRunEvent(event, sink) {
@@ -7455,6 +7597,7 @@ async function emitRunEvent(event, sink) {
7455
7597
  async function runAgent(agent, options) {
7456
7598
  const modelId = resolveModelId(agent, options);
7457
7599
  const conversationId = options.conversationId ?? `conv_${Date.now()}`;
7600
+ const checkpointKey = conversationId;
7458
7601
  const runId = options.runId ?? generateRunId();
7459
7602
  const turnId = runId;
7460
7603
  const costCollector = new QuickstartRunCostCollector();
@@ -7509,7 +7652,7 @@ async function runAgent(agent, options) {
7509
7652
  };
7510
7653
  await handle.markRunning({ currentNode: "user" });
7511
7654
  try {
7512
- await executor.prime(conversationId, {
7655
+ await executor.prime(checkpointKey, {
7513
7656
  conversationId,
7514
7657
  turnId,
7515
7658
  request: {
@@ -7534,7 +7677,7 @@ async function runAgent(agent, options) {
7534
7677
  return void 0;
7535
7678
  }
7536
7679
  });
7537
- const result = await executor.runUntilYield(conversationId);
7680
+ const result = await executor.runUntilYield(checkpointKey);
7538
7681
  runtimeEvents.push(...result.events);
7539
7682
  for (const event of result.events) {
7540
7683
  if (event.type === "final_answer_chunk") continue;
@@ -7570,11 +7713,11 @@ async function runAgent(agent, options) {
7570
7713
  }
7571
7714
 
7572
7715
  // src/cli/configLoader.ts
7573
- function isRecord21(value) {
7716
+ function isRecord22(value) {
7574
7717
  return typeof value === "object" && value !== null && !Array.isArray(value);
7575
7718
  }
7576
7719
  function readDefaultExport(moduleValue) {
7577
- if (isRecord21(moduleValue) && "default" in moduleValue) {
7720
+ if (isRecord22(moduleValue) && "default" in moduleValue) {
7578
7721
  return moduleValue.default;
7579
7722
  }
7580
7723
  return moduleValue;
@@ -7585,7 +7728,7 @@ async function loadConfig(configPath, cwd) {
7585
7728
  moduleUrl.searchParams.set("t", String(Date.now()));
7586
7729
  const loaded = await import(moduleUrl.href);
7587
7730
  const config = readDefaultExport(loaded);
7588
- if (!isRecord21(config)) {
7731
+ if (!isRecord22(config)) {
7589
7732
  throw new Error(`[linnkit] config must export an object: ${absolutePath}`);
7590
7733
  }
7591
7734
  return defineConfig(config);
@@ -7697,7 +7840,7 @@ function createQuickstartTemplateFiles(projectName) {
7697
7840
  start: 'linnkit run hello --input "\u4F60\u597D\uFF0C\u4ECB\u7ECD\u4E00\u4E0B\u4F60\u81EA\u5DF1"'
7698
7841
  },
7699
7842
  dependencies: {
7700
- "@linnlabs/linnkit": "^0.8.0",
7843
+ "@linnlabs/linnkit": "^0.10.0",
7701
7844
  zod: "^3.22.0"
7702
7845
  },
7703
7846
  devDependencies: {}