larkin 0.4.18 → 0.4.19

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.
@@ -17,6 +17,7 @@ const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
17
17
  const official_lark_cli_js_1 = require("../app/official-lark-cli.cjs");
18
18
  const agent_lark_cli_workspace_js_1 = require("../app/agent-lark-cli-workspace.cjs");
19
19
  const pi_compaction_recovery_js_1 = require("./pi-compaction-recovery.cjs");
20
+ const pi_subagent_ledger_js_1 = require("./pi-subagent-ledger.cjs");
20
21
  class RuntimeSessionResetError extends Error {
21
22
  code;
22
23
  pendingCount;
@@ -41,6 +42,7 @@ class RuntimeSessionRecoveryError extends Error {
41
42
  exports.RuntimeSessionRecoveryError = RuntimeSessionRecoveryError;
42
43
  const MAX_DELIVERIES = 2048;
43
44
  const BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT = 5;
45
+ const DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS = 15_000;
44
46
  const now = () => new Date().toISOString();
45
47
  const isActiveDelivery = (status) => ["pending", "submitting", "accepted"].includes(status);
46
48
  const REPLAY_FAILURE_CODES = new Set([
@@ -150,6 +152,10 @@ function createRuntimeHost(options) {
150
152
  maxAttempts: options.retryPolicy?.maxAttempts ?? 6,
151
153
  stableWindowMs: options.retryPolicy?.stableWindowMs ?? 30_000,
152
154
  };
155
+ const subagentReconcileIntervalMs = options.subagentReconcileIntervalMs ?? DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS;
156
+ if (!Number.isFinite(subagentReconcileIntervalMs) || subagentReconcileIntervalMs < 0) {
157
+ throw new Error("subagentReconcileIntervalMs must be >= 0");
158
+ }
153
159
  const emit = (event) => {
154
160
  if (event.type === "delivery")
155
161
  telemetry?.delivery(event.agentId, event.messageId, event.status);
@@ -159,10 +165,14 @@ function createRuntimeHost(options) {
159
165
  const runtimeEnv = (config, generation) => {
160
166
  const base = {
161
167
  LARKIN_AGENT_ID: config.agentId,
162
- ...(generation ? { LARKIN_RUNTIME_OBSERVATION_GENERATION: generation } : {}),
168
+ ...(generation ? {
169
+ LARKIN_RUNTIME_OBSERVATION_GENERATION: generation,
170
+ [pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV]: generation,
171
+ } : {}),
163
172
  LARKIN_CONFIG_DIR: process.env.LARKIN_CONFIG_DIR,
164
173
  LARKIN_HOME: process.env.LARKIN_HOME,
165
174
  ...(config.piDistribution ? { LARKIN_PI_DISTRIBUTION: config.piDistribution } : {}),
175
+ LARKIN_STATE_DIR: (0, pi_subagent_ledger_js_1.effectivePiStateDir)(config),
166
176
  ...(process.env.HOME ? { HOME: process.env.HOME } : {}),
167
177
  ...(process.env.SHELL ? { SHELL: process.env.SHELL } : {}),
168
178
  ...(process.env.ZDOTDIR ? { ZDOTDIR: process.env.ZDOTDIR } : {}),
@@ -648,6 +658,7 @@ function createRuntimeHost(options) {
648
658
  }
649
659
  agent.backgroundCompletionRejectStreak = 0;
650
660
  clearBackgroundCompletionRetryTimer(agent);
661
+ // Leave the durable wake pending so a later restart can requeue it.
651
662
  };
652
663
  const scheduleBackgroundCompletionRetry = (agent) => {
653
664
  const streak = agent.backgroundCompletionRejectStreak;
@@ -714,6 +725,7 @@ function createRuntimeHost(options) {
714
725
  agent.backgroundCompletionWakeInputId = null;
715
726
  agent.backgroundCompletionRejectStreak = 0;
716
727
  clearBackgroundCompletionRetryTimer(agent);
728
+ acknowledgeSubagentWake(agent, completionKey);
717
729
  };
718
730
  const wakeOnBackgroundCompletion = async (agent) => {
719
731
  // Caller already reserved submitting/busy so a concurrent idle drain cannot
@@ -722,7 +734,15 @@ function createRuntimeHost(options) {
722
734
  const session = await ensureSession(agent);
723
735
  if (agent.session !== session || agent.stopped)
724
736
  return { status: "dropped" };
725
- const input = options.promptBuilder.buildRuntimeInput("wake", node_crypto_1.default.randomUUID(), { wakeReason: "background subagent completed" });
737
+ const headKey = agent.backgroundCompletionQueue[0];
738
+ const tasks = (0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(headKey ?? "")
739
+ .map((taskId) => (0, pi_subagent_ledger_js_1.getDispatchedSubagent)(agent.subagentLedger, taskId))
740
+ .filter((task) => Boolean(task));
741
+ const details = tasks.map((task) => `task ${task.taskId} status=${task.status}`).join("; ");
742
+ const wakeReason = details
743
+ ? `background subagent completed; ${details}`
744
+ : "background subagent completed";
745
+ const input = options.promptBuilder.buildRuntimeInput("wake", node_crypto_1.default.randomUUID(), { wakeReason });
726
746
  const result = await session.prompt(input);
727
747
  if (agent.session !== session || agent.stopped)
728
748
  return { status: "dropped" };
@@ -800,10 +820,62 @@ function createRuntimeHost(options) {
800
820
  agent.submitting = false;
801
821
  }
802
822
  };
823
+ const persistSubagentLedger = (agent) => {
824
+ (0, pi_subagent_ledger_js_1.writeDispatchedSubagentLedger)((0, pi_subagent_ledger_js_1.ledgerFilePath)((0, pi_subagent_ledger_js_1.effectivePiStateDir)(agent.config)), agent.subagentLedger);
825
+ };
826
+ const dropQueuedBackgroundCompletionFallback = (agent, completionKey) => {
827
+ const keys = new Set([completionKey, ...(0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(completionKey)]);
828
+ for (let index = agent.backgroundCompletionQueue.length - 1; index >= 0; index -= 1) {
829
+ const queued = agent.backgroundCompletionQueue[index];
830
+ if (queued && keys.has(queued))
831
+ agent.backgroundCompletionQueue.splice(index, 1);
832
+ }
833
+ for (const key of keys)
834
+ agent.backgroundCompletionKeys.add(key);
835
+ };
836
+ const acknowledgeSubagentWake = (agent, completionKey) => {
837
+ const next = (0, pi_subagent_ledger_js_1.noteDispatchedSubagentWakeAcknowledged)(agent.subagentLedger, { completionKey });
838
+ if (next === agent.subagentLedger)
839
+ return;
840
+ agent.subagentLedger = next;
841
+ persistSubagentLedger(agent);
842
+ for (const taskId of (0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(completionKey)) {
843
+ (0, pi_subagent_ledger_js_1.retirePiSubagentRecord)((0, pi_subagent_ledger_js_1.getDispatchedSubagent)(agent.subagentLedger, taskId)?.recordFile);
844
+ }
845
+ };
846
+ const enqueueUndeliveredTerminalWakes = (agent) => {
847
+ for (const key of (0, pi_subagent_ledger_js_1.undeliveredTerminalWakeKeys)(agent.subagentLedger)) {
848
+ agent.backgroundCompletionKeys.delete(key);
849
+ for (const taskId of (0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(key))
850
+ agent.backgroundCompletionKeys.delete(taskId);
851
+ noteBackgroundCompletion(agent, key);
852
+ }
853
+ };
854
+ const reconcileAfterSessionSwap = (agent) => {
855
+ reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
856
+ enqueueUndeliveredTerminalWakes(agent);
857
+ if (agent.backgroundCompletionQueue.length > 0)
858
+ scheduleBackgroundCompletionDrain(agent);
859
+ };
860
+ const loadSubagentLedger = (config) => {
861
+ return (0, pi_subagent_ledger_js_1.readDispatchedSubagentLedger)((0, pi_subagent_ledger_js_1.ledgerFilePath)((0, pi_subagent_ledger_js_1.effectivePiStateDir)(config)));
862
+ };
863
+ const clearSubagentReconcileTimer = (agent) => {
864
+ if (!agent.subagentReconcileTimer)
865
+ return;
866
+ clearInterval(agent.subagentReconcileTimer);
867
+ agent.subagentReconcileTimer = null;
868
+ };
869
+ const hasActiveDispatchedSubagent = (agent) => agent.subagentLedger.tasks.some((task) => task.status === "dispatched");
803
870
  const noteBackgroundCompletion = (agent, completionKey) => {
871
+ const taskIds = (0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(completionKey);
804
872
  if (agent.backgroundCompletionKeys.has(completionKey))
805
873
  return;
874
+ if (taskIds.length > 0 && taskIds.every((taskId) => agent.backgroundCompletionKeys.has(taskId)))
875
+ return;
806
876
  agent.backgroundCompletionKeys.add(completionKey);
877
+ for (const taskId of taskIds)
878
+ agent.backgroundCompletionKeys.add(taskId);
807
879
  if (!agent.backgroundCompletionQueue.includes(completionKey)) {
808
880
  agent.backgroundCompletionQueue.push(completionKey);
809
881
  }
@@ -811,6 +883,62 @@ function createRuntimeHost(options) {
811
883
  scheduleBackgroundCompletionDrain(agent);
812
884
  }
813
885
  };
886
+ const recordDispatchedSubagent = (agent, taskId, outputFile) => {
887
+ const recordFile = (0, pi_subagent_ledger_js_1.writeDispatchedSubagentRecordFile)((0, pi_subagent_ledger_js_1.effectivePiStateDir)(agent.config), taskId, agent.piSessionOwner);
888
+ agent.subagentLedger = (0, pi_subagent_ledger_js_1.noteDispatchedSubagent)(agent.subagentLedger, { taskId, outputFile, recordFile });
889
+ persistSubagentLedger(agent);
890
+ armSubagentReconcileTimer(agent);
891
+ };
892
+ const recordTerminalSubagentNotification = (agent, completionKey, statuses) => {
893
+ let next = agent.subagentLedger;
894
+ for (const taskId of (0, pi_subagent_ledger_js_1.taskIdsFromCompletionKey)(completionKey)) {
895
+ const status = statuses?.[taskId];
896
+ next = (0, pi_subagent_ledger_js_1.noteDispatchedSubagentTerminal)(next, {
897
+ taskId,
898
+ status: status && status !== "dispatched" && status !== "orphaned" ? status : "completed",
899
+ wakeKey: completionKey,
900
+ });
901
+ }
902
+ agent.subagentLedger = next;
903
+ persistSubagentLedger(agent);
904
+ if (!hasActiveDispatchedSubagent(agent))
905
+ clearSubagentReconcileTimer(agent);
906
+ };
907
+ const reconcileSubagentLedger = (agent, input = {}) => {
908
+ if (agent.stopped)
909
+ return;
910
+ const result = (0, pi_subagent_ledger_js_1.reconcileDispatchedSubagents)(agent.subagentLedger, {
911
+ probe: options.subagentRecordProbe ?? pi_subagent_ledger_js_1.probePiSubagentRecord,
912
+ forceMissing: input.forceMissing === true,
913
+ missingReason: input.missingReason,
914
+ });
915
+ const previousLedger = agent.subagentLedger;
916
+ agent.subagentLedger = result.ledger;
917
+ if (result.orphaned.length > 0 || result.terminals.length > 0 || result.ledger !== previousLedger) {
918
+ persistSubagentLedger(agent);
919
+ }
920
+ for (const record of [...result.orphaned, ...result.terminals]) {
921
+ noteBackgroundCompletion(agent, record.wakeKey ?? record.taskId);
922
+ }
923
+ if (!hasActiveDispatchedSubagent(agent))
924
+ clearSubagentReconcileTimer(agent);
925
+ else
926
+ armSubagentReconcileTimer(agent);
927
+ };
928
+ const armSubagentReconcileTimer = (agent) => {
929
+ if (agent.stopped || agent.subagentReconcileTimer || subagentReconcileIntervalMs === 0)
930
+ return;
931
+ if (!hasActiveDispatchedSubagent(agent))
932
+ return;
933
+ agent.subagentReconcileTimer = setInterval(() => {
934
+ if (agent.stopped) {
935
+ clearSubagentReconcileTimer(agent);
936
+ return;
937
+ }
938
+ reconcileSubagentLedger(agent);
939
+ }, subagentReconcileIntervalMs);
940
+ agent.subagentReconcileTimer.unref?.();
941
+ };
814
942
  const scheduleRecreate = (agent, reason) => {
815
943
  if (agent.stopped || agent.retryTimer || agent.starting || agent.session)
816
944
  return;
@@ -832,6 +960,8 @@ function createRuntimeHost(options) {
832
960
  const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
833
961
  if (proactive && await proactive === "failed")
834
962
  return;
963
+ reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
964
+ enqueueUndeliveredTerminalWakes(agent);
835
965
  await retryPending(agent);
836
966
  if (agent.backgroundCompletionQueue.length > 0)
837
967
  scheduleBackgroundCompletionDrain(agent);
@@ -862,13 +992,21 @@ function createRuntimeHost(options) {
862
992
  if (agent.stabilityTimer)
863
993
  clearTimeout(agent.stabilityTimer);
864
994
  agent.stabilityTimer = null;
865
- void session.close(`replace after ${reason}`).catch((error) => log("runtime close after replacement failed", String(error)));
866
995
  for (const record of agent.records.values())
867
996
  if (isActiveDelivery(record.status))
868
997
  record.status = "pending";
869
998
  emitConsumed(agent, persist(agent));
870
999
  emit({ type: "agent-status", agentId: agent.config.agentId, status: "error", error: reason });
871
- scheduleRecreate(agent, reason);
1000
+ // Close first so the shutdown sweep can bridge consumed or terminal state
1001
+ // before force-missing reconcile treats a plain sidecar as orphaned.
1002
+ void session.close(`replace after ${reason}`)
1003
+ .catch((error) => log("runtime close after replacement failed", String(error)))
1004
+ .finally(() => {
1005
+ if (agent.stopped)
1006
+ return;
1007
+ reconcileAfterSessionSwap(agent);
1008
+ scheduleRecreate(agent, reason);
1009
+ });
872
1010
  };
873
1011
  const recoverConfiguration = (agent, session, message) => {
874
1012
  if (agent.session !== session || agent.stopped)
@@ -893,6 +1031,8 @@ function createRuntimeHost(options) {
893
1031
  const recovery = (async () => {
894
1032
  try {
895
1033
  await session.close("runtime configuration error");
1034
+ if (!agent.stopped)
1035
+ reconcileAfterSessionSwap(agent);
896
1036
  const result = agent.adapter.recoverConfigurationError
897
1037
  ? await agent.adapter.recoverConfigurationError(message)
898
1038
  : { recovered: false, reason: "runtime adapter does not support automatic configuration recovery" };
@@ -1180,6 +1320,7 @@ function createRuntimeHost(options) {
1180
1320
  if (agent.backgroundCompletionQueue.length > 0 && !agent.backgroundCompletionRetryTimer) {
1181
1321
  scheduleBackgroundCompletionDrain(agent);
1182
1322
  }
1323
+ reconcileSubagentLedger(agent);
1183
1324
  telemetry?.runtimeEvent(agent.config.agentId, event);
1184
1325
  if (recoveredAuthentication) {
1185
1326
  agent.authFailureActive = false;
@@ -1200,8 +1341,18 @@ function createRuntimeHost(options) {
1200
1341
  queueMicrotask(() => { void scanAndPromoteAcceptedInboxUpdates(agent); });
1201
1342
  }
1202
1343
  else if (event.type === "runtime-observation") {
1203
- if (event.phase === "completed" && typeof event.completionKey === "string") {
1204
- noteBackgroundCompletion(agent, event.completionKey);
1344
+ if (event.phase === "background_dispatched" && typeof event.taskId === "string" && event.taskId) {
1345
+ recordDispatchedSubagent(agent, event.taskId, typeof event.outputFile === "string" ? event.outputFile : undefined);
1346
+ }
1347
+ else if (event.phase === "completed" && typeof event.completionKey === "string") {
1348
+ recordTerminalSubagentNotification(agent, event.completionKey, event.completionStatuses);
1349
+ if (event.handledInTurn === true) {
1350
+ acknowledgeSubagentWake(agent, event.completionKey);
1351
+ dropQueuedBackgroundCompletionFallback(agent, event.completionKey);
1352
+ }
1353
+ else {
1354
+ noteBackgroundCompletion(agent, event.completionKey);
1355
+ }
1205
1356
  }
1206
1357
  }
1207
1358
  else if (event.type === "activity") {
@@ -1319,11 +1470,12 @@ function createRuntimeHost(options) {
1319
1470
  });
1320
1471
  const generation = ++agent.generation;
1321
1472
  let completedSession = null;
1473
+ const sessionEnv = runtimeEnv(agent.config, `${agent.launchId}:${generation}`);
1322
1474
  agent.starting = agent.adapter.createSession({
1323
1475
  agentId: agent.config.agentId, model: agent.config.model, reasoningEffort: agent.config.effort || null,
1324
1476
  workspaceDir: agent.config.workspaceDir, stateDir: agent.config.stateDir,
1325
1477
  resumeSessionId: agent.config.sessionId || null, standingPrompt,
1326
- env: runtimeEnv(agent.config, `${agent.launchId}:${generation}`),
1478
+ env: sessionEnv,
1327
1479
  }).then((session) => {
1328
1480
  completedSession = session;
1329
1481
  if (agent.stopped || generation !== agent.generation) {
@@ -1331,6 +1483,7 @@ function createRuntimeHost(options) {
1331
1483
  throw new Error("stale runtime session creation");
1332
1484
  }
1333
1485
  agent.session = session;
1486
+ agent.piSessionOwner = sessionEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
1334
1487
  session.subscribe((event) => observe(agent, session, event));
1335
1488
  if (agent.session !== session)
1336
1489
  return session;
@@ -1470,7 +1623,9 @@ function createRuntimeHost(options) {
1470
1623
  ...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}) });
1471
1624
  }
1472
1625
  emit({ type: "agent-status", agentId, status: "active", readiness });
1626
+ agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
1473
1627
  await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
1628
+ reconcileAfterSessionSwap(agent);
1474
1629
  const proactive = proactivelyCompactPiAtIdle(agent, fresh);
1475
1630
  void (proactive ?? Promise.resolve("noop")).then((outcome) => {
1476
1631
  if (outcome !== "failed")
@@ -1545,11 +1700,13 @@ function createRuntimeHost(options) {
1545
1700
  clearTimeout(previous.stabilityTimer);
1546
1701
  if (previous.backgroundCompletionRetryTimer)
1547
1702
  clearTimeout(previous.backgroundCompletionRetryTimer);
1703
+ if (previous.subagentReconcileTimer)
1704
+ clearInterval(previous.subagentReconcileTimer);
1548
1705
  const candidate = {
1549
1706
  ...previous, config, adapter, session, launchId: node_crypto_1.default.randomUUID(), busy: false, submitting: false,
1550
1707
  starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
1551
1708
  recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
1552
- backgroundCompletionRetryTimer: null,
1709
+ backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
1553
1710
  disabledReason: null, configurationRecovery: null,
1554
1711
  readiness: previous.authFailureActive ? previous.readiness : readiness,
1555
1712
  };
@@ -1566,13 +1723,14 @@ function createRuntimeHost(options) {
1566
1723
  if (!candidate.authFailureActive) {
1567
1724
  emit({ type: "agent-status", agentId: config.agentId, status: "active", readiness: candidate.readiness ?? readiness });
1568
1725
  }
1569
- // Commit clears any inherited backoff timer; reschedule so a queued
1570
- // completion still drains on the new session.
1571
- if (candidate.backgroundCompletionQueue.length > 0) {
1572
- scheduleBackgroundCompletionDrain(candidate);
1573
- }
1726
+ candidate.piSessionOwner = stageEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
1727
+ // Close first so the old session's shutdown sweep can bridge consumed
1728
+ // terminals before force-missing reconcile. Commit also clears any
1729
+ // inherited backoff timer; reschedule so a queued completion still
1730
+ // drains on the new session.
1574
1731
  await previous.session?.close("runtime candidate committed")
1575
1732
  .catch((error) => log("previous runtime close after candidate commit failed", String(error)));
1733
+ reconcileAfterSessionSwap(candidate);
1576
1734
  },
1577
1735
  rollback,
1578
1736
  };
@@ -1665,8 +1823,10 @@ function createRuntimeHost(options) {
1665
1823
  ...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}) });
1666
1824
  }
1667
1825
  emit({ type: "agent-status", agentId, status: "active", readiness });
1826
+ agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
1668
1827
  await oldSession.close("fresh session reset committed")
1669
1828
  .catch((error) => log("previous runtime close after fresh reset failed", String(error)));
1829
+ reconcileAfterSessionSwap(agent);
1670
1830
  return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
1671
1831
  runtimeReady: true, pendingCount: 0, sessionId: fresh.sessionId };
1672
1832
  },
@@ -1815,8 +1975,10 @@ function createRuntimeHost(options) {
1815
1975
  }
1816
1976
  emit({ type: "agent-status", agentId, status: "active", readiness });
1817
1977
  durableRollback = null;
1978
+ agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
1818
1979
  await oldSession.close("context-window recovery committed")
1819
1980
  .catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
1981
+ reconcileAfterSessionSwap(agent);
1820
1982
  const proactive = proactivelyCompactPiAtIdle(agent, fresh);
1821
1983
  void (proactive ?? Promise.resolve("noop")).then((outcome) => {
1822
1984
  if (outcome !== "failed")
@@ -1906,7 +2068,8 @@ function createRuntimeHost(options) {
1906
2068
  promotedInboxUpdateIds: new Set(),
1907
2069
  backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
1908
2070
  backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
1909
- backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null };
2071
+ backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
2072
+ subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
1910
2073
  const startupConsumed = [];
1911
2074
  const startupQuarantined = [];
1912
2075
  for (const record of agent.records.values()) {
@@ -1937,6 +2100,8 @@ function createRuntimeHost(options) {
1937
2100
  emitConsumed(agent, [...startupConsumed, ...persist(agent)]);
1938
2101
  try {
1939
2102
  await ensureSession(agent);
2103
+ reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
2104
+ enqueueUndeliveredTerminalWakes(agent);
1940
2105
  await recoverStalePiCompaction(agent);
1941
2106
  const startupSession = agent.session;
1942
2107
  const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
@@ -1949,6 +2114,8 @@ function createRuntimeHost(options) {
1949
2114
  const reason = error instanceof Error ? error.message : String(error);
1950
2115
  const transient = error instanceof runtime_readiness_js_1.RuntimePrerequisiteError && error.readiness.state === "unavailable";
1951
2116
  agent.disabledReason = transient ? null : reason;
2117
+ reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
2118
+ enqueueUndeliveredTerminalWakes(agent);
1952
2119
  if (transient) {
1953
2120
  recoveringCount += 1;
1954
2121
  scheduleRecreate(agent, reason);
@@ -2059,6 +2226,8 @@ function createRuntimeHost(options) {
2059
2226
  clearTimeout(agent.stabilityTimer);
2060
2227
  if (agent.backgroundCompletionRetryTimer)
2061
2228
  clearTimeout(agent.backgroundCompletionRetryTimer);
2229
+ if (agent.subagentReconcileTimer)
2230
+ clearInterval(agent.subagentReconcileTimer);
2062
2231
  if (agent.busy)
2063
2232
  await agent.session?.cancel(reason);
2064
2233
  await agent.session?.close(reason);
@@ -2067,5 +2236,9 @@ function createRuntimeHost(options) {
2067
2236
  },
2068
2237
  async shutdown(reason) { await Promise.allSettled([...managed.keys()].map((id) => this.stop(id, reason))); },
2069
2238
  subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
2239
+ getDispatchedSubagent(agentId, taskId) {
2240
+ const agent = managed.get(agentId);
2241
+ return agent ? (0, pi_subagent_ledger_js_1.getDispatchedSubagent)(agent.subagentLedger, taskId) : null;
2242
+ },
2070
2243
  };
2071
2244
  }