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.
- package/dist/runtime/pi-inline-extensions.mjs +5 -0
- package/dist/runtime/pi-subagent-ledger.cjs +632 -0
- package/dist/runtime/pi-subagent-ledger.mjs +594 -0
- package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +43 -0
- package/dist/runtime/pi-subagent-record-watchdog.bundle.js +183 -0
- package/dist/runtime/pi-subagent-record-watchdog.mjs +46 -0
- package/dist/runtime/pi-subagents-notification.mjs +14 -0
- package/dist/runtime/runtime-adapters.mjs +52 -10
- package/dist/runtime/runtime-host.cjs +187 -14
- package/dist/runtime/runtime-host.mjs +187 -14
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ import { classifyRuntimePrerequisite, providerAuthenticationFailureReadiness, Ru
|
|
|
10
10
|
import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
|
|
11
11
|
import { assertAgentWorkspaceBound, managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
|
|
12
12
|
import { isPiNativeCompactionRequired, PiCompactionBreaker, PiCompactionRecoveryMachine, } from "./pi-compaction-recovery.mjs";
|
|
13
|
+
import { getDispatchedSubagent as lookupDispatchedSubagent, ledgerFilePath, noteDispatchedSubagent, noteDispatchedSubagentTerminal, noteDispatchedSubagentWakeAcknowledged, probePiSubagentRecord, readDispatchedSubagentLedger, reconcileDispatchedSubagents, taskIdsFromCompletionKey, undeliveredTerminalWakeKeys, PI_SUBAGENT_SESSION_OWNER_ENV, retirePiSubagentRecord, effectivePiStateDir, writeDispatchedSubagentLedger, writeDispatchedSubagentRecordFile, } from "./pi-subagent-ledger.mjs";
|
|
13
14
|
export class RuntimeSessionResetError extends Error {
|
|
14
15
|
code;
|
|
15
16
|
pendingCount;
|
|
@@ -32,6 +33,7 @@ export class RuntimeSessionRecoveryError extends Error {
|
|
|
32
33
|
}
|
|
33
34
|
const MAX_DELIVERIES = 2048;
|
|
34
35
|
const BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT = 5;
|
|
36
|
+
const DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS = 15_000;
|
|
35
37
|
const now = () => new Date().toISOString();
|
|
36
38
|
const isActiveDelivery = (status) => ["pending", "submitting", "accepted"].includes(status);
|
|
37
39
|
const REPLAY_FAILURE_CODES = new Set([
|
|
@@ -141,6 +143,10 @@ export function createRuntimeHost(options) {
|
|
|
141
143
|
maxAttempts: options.retryPolicy?.maxAttempts ?? 6,
|
|
142
144
|
stableWindowMs: options.retryPolicy?.stableWindowMs ?? 30_000,
|
|
143
145
|
};
|
|
146
|
+
const subagentReconcileIntervalMs = options.subagentReconcileIntervalMs ?? DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS;
|
|
147
|
+
if (!Number.isFinite(subagentReconcileIntervalMs) || subagentReconcileIntervalMs < 0) {
|
|
148
|
+
throw new Error("subagentReconcileIntervalMs must be >= 0");
|
|
149
|
+
}
|
|
144
150
|
const emit = (event) => {
|
|
145
151
|
if (event.type === "delivery")
|
|
146
152
|
telemetry?.delivery(event.agentId, event.messageId, event.status);
|
|
@@ -150,10 +156,14 @@ export function createRuntimeHost(options) {
|
|
|
150
156
|
const runtimeEnv = (config, generation) => {
|
|
151
157
|
const base = {
|
|
152
158
|
LARKIN_AGENT_ID: config.agentId,
|
|
153
|
-
...(generation ? {
|
|
159
|
+
...(generation ? {
|
|
160
|
+
LARKIN_RUNTIME_OBSERVATION_GENERATION: generation,
|
|
161
|
+
[PI_SUBAGENT_SESSION_OWNER_ENV]: generation,
|
|
162
|
+
} : {}),
|
|
154
163
|
LARKIN_CONFIG_DIR: process.env.LARKIN_CONFIG_DIR,
|
|
155
164
|
LARKIN_HOME: process.env.LARKIN_HOME,
|
|
156
165
|
...(config.piDistribution ? { LARKIN_PI_DISTRIBUTION: config.piDistribution } : {}),
|
|
166
|
+
LARKIN_STATE_DIR: effectivePiStateDir(config),
|
|
157
167
|
...(process.env.HOME ? { HOME: process.env.HOME } : {}),
|
|
158
168
|
...(process.env.SHELL ? { SHELL: process.env.SHELL } : {}),
|
|
159
169
|
...(process.env.ZDOTDIR ? { ZDOTDIR: process.env.ZDOTDIR } : {}),
|
|
@@ -639,6 +649,7 @@ export function createRuntimeHost(options) {
|
|
|
639
649
|
}
|
|
640
650
|
agent.backgroundCompletionRejectStreak = 0;
|
|
641
651
|
clearBackgroundCompletionRetryTimer(agent);
|
|
652
|
+
// Leave the durable wake pending so a later restart can requeue it.
|
|
642
653
|
};
|
|
643
654
|
const scheduleBackgroundCompletionRetry = (agent) => {
|
|
644
655
|
const streak = agent.backgroundCompletionRejectStreak;
|
|
@@ -705,6 +716,7 @@ export function createRuntimeHost(options) {
|
|
|
705
716
|
agent.backgroundCompletionWakeInputId = null;
|
|
706
717
|
agent.backgroundCompletionRejectStreak = 0;
|
|
707
718
|
clearBackgroundCompletionRetryTimer(agent);
|
|
719
|
+
acknowledgeSubagentWake(agent, completionKey);
|
|
708
720
|
};
|
|
709
721
|
const wakeOnBackgroundCompletion = async (agent) => {
|
|
710
722
|
// Caller already reserved submitting/busy so a concurrent idle drain cannot
|
|
@@ -713,7 +725,15 @@ export function createRuntimeHost(options) {
|
|
|
713
725
|
const session = await ensureSession(agent);
|
|
714
726
|
if (agent.session !== session || agent.stopped)
|
|
715
727
|
return { status: "dropped" };
|
|
716
|
-
const
|
|
728
|
+
const headKey = agent.backgroundCompletionQueue[0];
|
|
729
|
+
const tasks = taskIdsFromCompletionKey(headKey ?? "")
|
|
730
|
+
.map((taskId) => lookupDispatchedSubagent(agent.subagentLedger, taskId))
|
|
731
|
+
.filter((task) => Boolean(task));
|
|
732
|
+
const details = tasks.map((task) => `task ${task.taskId} status=${task.status}`).join("; ");
|
|
733
|
+
const wakeReason = details
|
|
734
|
+
? `background subagent completed; ${details}`
|
|
735
|
+
: "background subagent completed";
|
|
736
|
+
const input = options.promptBuilder.buildRuntimeInput("wake", crypto.randomUUID(), { wakeReason });
|
|
717
737
|
const result = await session.prompt(input);
|
|
718
738
|
if (agent.session !== session || agent.stopped)
|
|
719
739
|
return { status: "dropped" };
|
|
@@ -791,10 +811,62 @@ export function createRuntimeHost(options) {
|
|
|
791
811
|
agent.submitting = false;
|
|
792
812
|
}
|
|
793
813
|
};
|
|
814
|
+
const persistSubagentLedger = (agent) => {
|
|
815
|
+
writeDispatchedSubagentLedger(ledgerFilePath(effectivePiStateDir(agent.config)), agent.subagentLedger);
|
|
816
|
+
};
|
|
817
|
+
const dropQueuedBackgroundCompletionFallback = (agent, completionKey) => {
|
|
818
|
+
const keys = new Set([completionKey, ...taskIdsFromCompletionKey(completionKey)]);
|
|
819
|
+
for (let index = agent.backgroundCompletionQueue.length - 1; index >= 0; index -= 1) {
|
|
820
|
+
const queued = agent.backgroundCompletionQueue[index];
|
|
821
|
+
if (queued && keys.has(queued))
|
|
822
|
+
agent.backgroundCompletionQueue.splice(index, 1);
|
|
823
|
+
}
|
|
824
|
+
for (const key of keys)
|
|
825
|
+
agent.backgroundCompletionKeys.add(key);
|
|
826
|
+
};
|
|
827
|
+
const acknowledgeSubagentWake = (agent, completionKey) => {
|
|
828
|
+
const next = noteDispatchedSubagentWakeAcknowledged(agent.subagentLedger, { completionKey });
|
|
829
|
+
if (next === agent.subagentLedger)
|
|
830
|
+
return;
|
|
831
|
+
agent.subagentLedger = next;
|
|
832
|
+
persistSubagentLedger(agent);
|
|
833
|
+
for (const taskId of taskIdsFromCompletionKey(completionKey)) {
|
|
834
|
+
retirePiSubagentRecord(lookupDispatchedSubagent(agent.subagentLedger, taskId)?.recordFile);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
const enqueueUndeliveredTerminalWakes = (agent) => {
|
|
838
|
+
for (const key of undeliveredTerminalWakeKeys(agent.subagentLedger)) {
|
|
839
|
+
agent.backgroundCompletionKeys.delete(key);
|
|
840
|
+
for (const taskId of taskIdsFromCompletionKey(key))
|
|
841
|
+
agent.backgroundCompletionKeys.delete(taskId);
|
|
842
|
+
noteBackgroundCompletion(agent, key);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
const reconcileAfterSessionSwap = (agent) => {
|
|
846
|
+
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
|
|
847
|
+
enqueueUndeliveredTerminalWakes(agent);
|
|
848
|
+
if (agent.backgroundCompletionQueue.length > 0)
|
|
849
|
+
scheduleBackgroundCompletionDrain(agent);
|
|
850
|
+
};
|
|
851
|
+
const loadSubagentLedger = (config) => {
|
|
852
|
+
return readDispatchedSubagentLedger(ledgerFilePath(effectivePiStateDir(config)));
|
|
853
|
+
};
|
|
854
|
+
const clearSubagentReconcileTimer = (agent) => {
|
|
855
|
+
if (!agent.subagentReconcileTimer)
|
|
856
|
+
return;
|
|
857
|
+
clearInterval(agent.subagentReconcileTimer);
|
|
858
|
+
agent.subagentReconcileTimer = null;
|
|
859
|
+
};
|
|
860
|
+
const hasActiveDispatchedSubagent = (agent) => agent.subagentLedger.tasks.some((task) => task.status === "dispatched");
|
|
794
861
|
const noteBackgroundCompletion = (agent, completionKey) => {
|
|
862
|
+
const taskIds = taskIdsFromCompletionKey(completionKey);
|
|
795
863
|
if (agent.backgroundCompletionKeys.has(completionKey))
|
|
796
864
|
return;
|
|
865
|
+
if (taskIds.length > 0 && taskIds.every((taskId) => agent.backgroundCompletionKeys.has(taskId)))
|
|
866
|
+
return;
|
|
797
867
|
agent.backgroundCompletionKeys.add(completionKey);
|
|
868
|
+
for (const taskId of taskIds)
|
|
869
|
+
agent.backgroundCompletionKeys.add(taskId);
|
|
798
870
|
if (!agent.backgroundCompletionQueue.includes(completionKey)) {
|
|
799
871
|
agent.backgroundCompletionQueue.push(completionKey);
|
|
800
872
|
}
|
|
@@ -802,6 +874,62 @@ export function createRuntimeHost(options) {
|
|
|
802
874
|
scheduleBackgroundCompletionDrain(agent);
|
|
803
875
|
}
|
|
804
876
|
};
|
|
877
|
+
const recordDispatchedSubagent = (agent, taskId, outputFile) => {
|
|
878
|
+
const recordFile = writeDispatchedSubagentRecordFile(effectivePiStateDir(agent.config), taskId, agent.piSessionOwner);
|
|
879
|
+
agent.subagentLedger = noteDispatchedSubagent(agent.subagentLedger, { taskId, outputFile, recordFile });
|
|
880
|
+
persistSubagentLedger(agent);
|
|
881
|
+
armSubagentReconcileTimer(agent);
|
|
882
|
+
};
|
|
883
|
+
const recordTerminalSubagentNotification = (agent, completionKey, statuses) => {
|
|
884
|
+
let next = agent.subagentLedger;
|
|
885
|
+
for (const taskId of taskIdsFromCompletionKey(completionKey)) {
|
|
886
|
+
const status = statuses?.[taskId];
|
|
887
|
+
next = noteDispatchedSubagentTerminal(next, {
|
|
888
|
+
taskId,
|
|
889
|
+
status: status && status !== "dispatched" && status !== "orphaned" ? status : "completed",
|
|
890
|
+
wakeKey: completionKey,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
agent.subagentLedger = next;
|
|
894
|
+
persistSubagentLedger(agent);
|
|
895
|
+
if (!hasActiveDispatchedSubagent(agent))
|
|
896
|
+
clearSubagentReconcileTimer(agent);
|
|
897
|
+
};
|
|
898
|
+
const reconcileSubagentLedger = (agent, input = {}) => {
|
|
899
|
+
if (agent.stopped)
|
|
900
|
+
return;
|
|
901
|
+
const result = reconcileDispatchedSubagents(agent.subagentLedger, {
|
|
902
|
+
probe: options.subagentRecordProbe ?? probePiSubagentRecord,
|
|
903
|
+
forceMissing: input.forceMissing === true,
|
|
904
|
+
missingReason: input.missingReason,
|
|
905
|
+
});
|
|
906
|
+
const previousLedger = agent.subagentLedger;
|
|
907
|
+
agent.subagentLedger = result.ledger;
|
|
908
|
+
if (result.orphaned.length > 0 || result.terminals.length > 0 || result.ledger !== previousLedger) {
|
|
909
|
+
persistSubagentLedger(agent);
|
|
910
|
+
}
|
|
911
|
+
for (const record of [...result.orphaned, ...result.terminals]) {
|
|
912
|
+
noteBackgroundCompletion(agent, record.wakeKey ?? record.taskId);
|
|
913
|
+
}
|
|
914
|
+
if (!hasActiveDispatchedSubagent(agent))
|
|
915
|
+
clearSubagentReconcileTimer(agent);
|
|
916
|
+
else
|
|
917
|
+
armSubagentReconcileTimer(agent);
|
|
918
|
+
};
|
|
919
|
+
const armSubagentReconcileTimer = (agent) => {
|
|
920
|
+
if (agent.stopped || agent.subagentReconcileTimer || subagentReconcileIntervalMs === 0)
|
|
921
|
+
return;
|
|
922
|
+
if (!hasActiveDispatchedSubagent(agent))
|
|
923
|
+
return;
|
|
924
|
+
agent.subagentReconcileTimer = setInterval(() => {
|
|
925
|
+
if (agent.stopped) {
|
|
926
|
+
clearSubagentReconcileTimer(agent);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
reconcileSubagentLedger(agent);
|
|
930
|
+
}, subagentReconcileIntervalMs);
|
|
931
|
+
agent.subagentReconcileTimer.unref?.();
|
|
932
|
+
};
|
|
805
933
|
const scheduleRecreate = (agent, reason) => {
|
|
806
934
|
if (agent.stopped || agent.retryTimer || agent.starting || agent.session)
|
|
807
935
|
return;
|
|
@@ -823,6 +951,8 @@ export function createRuntimeHost(options) {
|
|
|
823
951
|
const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
|
|
824
952
|
if (proactive && await proactive === "failed")
|
|
825
953
|
return;
|
|
954
|
+
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
|
|
955
|
+
enqueueUndeliveredTerminalWakes(agent);
|
|
826
956
|
await retryPending(agent);
|
|
827
957
|
if (agent.backgroundCompletionQueue.length > 0)
|
|
828
958
|
scheduleBackgroundCompletionDrain(agent);
|
|
@@ -853,13 +983,21 @@ export function createRuntimeHost(options) {
|
|
|
853
983
|
if (agent.stabilityTimer)
|
|
854
984
|
clearTimeout(agent.stabilityTimer);
|
|
855
985
|
agent.stabilityTimer = null;
|
|
856
|
-
void session.close(`replace after ${reason}`).catch((error) => log("runtime close after replacement failed", String(error)));
|
|
857
986
|
for (const record of agent.records.values())
|
|
858
987
|
if (isActiveDelivery(record.status))
|
|
859
988
|
record.status = "pending";
|
|
860
989
|
emitConsumed(agent, persist(agent));
|
|
861
990
|
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error", error: reason });
|
|
862
|
-
|
|
991
|
+
// Close first so the shutdown sweep can bridge consumed or terminal state
|
|
992
|
+
// before force-missing reconcile treats a plain sidecar as orphaned.
|
|
993
|
+
void session.close(`replace after ${reason}`)
|
|
994
|
+
.catch((error) => log("runtime close after replacement failed", String(error)))
|
|
995
|
+
.finally(() => {
|
|
996
|
+
if (agent.stopped)
|
|
997
|
+
return;
|
|
998
|
+
reconcileAfterSessionSwap(agent);
|
|
999
|
+
scheduleRecreate(agent, reason);
|
|
1000
|
+
});
|
|
863
1001
|
};
|
|
864
1002
|
const recoverConfiguration = (agent, session, message) => {
|
|
865
1003
|
if (agent.session !== session || agent.stopped)
|
|
@@ -884,6 +1022,8 @@ export function createRuntimeHost(options) {
|
|
|
884
1022
|
const recovery = (async () => {
|
|
885
1023
|
try {
|
|
886
1024
|
await session.close("runtime configuration error");
|
|
1025
|
+
if (!agent.stopped)
|
|
1026
|
+
reconcileAfterSessionSwap(agent);
|
|
887
1027
|
const result = agent.adapter.recoverConfigurationError
|
|
888
1028
|
? await agent.adapter.recoverConfigurationError(message)
|
|
889
1029
|
: { recovered: false, reason: "runtime adapter does not support automatic configuration recovery" };
|
|
@@ -1171,6 +1311,7 @@ export function createRuntimeHost(options) {
|
|
|
1171
1311
|
if (agent.backgroundCompletionQueue.length > 0 && !agent.backgroundCompletionRetryTimer) {
|
|
1172
1312
|
scheduleBackgroundCompletionDrain(agent);
|
|
1173
1313
|
}
|
|
1314
|
+
reconcileSubagentLedger(agent);
|
|
1174
1315
|
telemetry?.runtimeEvent(agent.config.agentId, event);
|
|
1175
1316
|
if (recoveredAuthentication) {
|
|
1176
1317
|
agent.authFailureActive = false;
|
|
@@ -1191,8 +1332,18 @@ export function createRuntimeHost(options) {
|
|
|
1191
1332
|
queueMicrotask(() => { void scanAndPromoteAcceptedInboxUpdates(agent); });
|
|
1192
1333
|
}
|
|
1193
1334
|
else if (event.type === "runtime-observation") {
|
|
1194
|
-
if (event.phase === "
|
|
1195
|
-
|
|
1335
|
+
if (event.phase === "background_dispatched" && typeof event.taskId === "string" && event.taskId) {
|
|
1336
|
+
recordDispatchedSubagent(agent, event.taskId, typeof event.outputFile === "string" ? event.outputFile : undefined);
|
|
1337
|
+
}
|
|
1338
|
+
else if (event.phase === "completed" && typeof event.completionKey === "string") {
|
|
1339
|
+
recordTerminalSubagentNotification(agent, event.completionKey, event.completionStatuses);
|
|
1340
|
+
if (event.handledInTurn === true) {
|
|
1341
|
+
acknowledgeSubagentWake(agent, event.completionKey);
|
|
1342
|
+
dropQueuedBackgroundCompletionFallback(agent, event.completionKey);
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
noteBackgroundCompletion(agent, event.completionKey);
|
|
1346
|
+
}
|
|
1196
1347
|
}
|
|
1197
1348
|
}
|
|
1198
1349
|
else if (event.type === "activity") {
|
|
@@ -1310,11 +1461,12 @@ export function createRuntimeHost(options) {
|
|
|
1310
1461
|
});
|
|
1311
1462
|
const generation = ++agent.generation;
|
|
1312
1463
|
let completedSession = null;
|
|
1464
|
+
const sessionEnv = runtimeEnv(agent.config, `${agent.launchId}:${generation}`);
|
|
1313
1465
|
agent.starting = agent.adapter.createSession({
|
|
1314
1466
|
agentId: agent.config.agentId, model: agent.config.model, reasoningEffort: agent.config.effort || null,
|
|
1315
1467
|
workspaceDir: agent.config.workspaceDir, stateDir: agent.config.stateDir,
|
|
1316
1468
|
resumeSessionId: agent.config.sessionId || null, standingPrompt,
|
|
1317
|
-
env:
|
|
1469
|
+
env: sessionEnv,
|
|
1318
1470
|
}).then((session) => {
|
|
1319
1471
|
completedSession = session;
|
|
1320
1472
|
if (agent.stopped || generation !== agent.generation) {
|
|
@@ -1322,6 +1474,7 @@ export function createRuntimeHost(options) {
|
|
|
1322
1474
|
throw new Error("stale runtime session creation");
|
|
1323
1475
|
}
|
|
1324
1476
|
agent.session = session;
|
|
1477
|
+
agent.piSessionOwner = sessionEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1325
1478
|
session.subscribe((event) => observe(agent, session, event));
|
|
1326
1479
|
if (agent.session !== session)
|
|
1327
1480
|
return session;
|
|
@@ -1461,7 +1614,9 @@ export function createRuntimeHost(options) {
|
|
|
1461
1614
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}) });
|
|
1462
1615
|
}
|
|
1463
1616
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1617
|
+
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1464
1618
|
await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
|
|
1619
|
+
reconcileAfterSessionSwap(agent);
|
|
1465
1620
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1466
1621
|
void (proactive ?? Promise.resolve("noop")).then((outcome) => {
|
|
1467
1622
|
if (outcome !== "failed")
|
|
@@ -1536,11 +1691,13 @@ export function createRuntimeHost(options) {
|
|
|
1536
1691
|
clearTimeout(previous.stabilityTimer);
|
|
1537
1692
|
if (previous.backgroundCompletionRetryTimer)
|
|
1538
1693
|
clearTimeout(previous.backgroundCompletionRetryTimer);
|
|
1694
|
+
if (previous.subagentReconcileTimer)
|
|
1695
|
+
clearInterval(previous.subagentReconcileTimer);
|
|
1539
1696
|
const candidate = {
|
|
1540
1697
|
...previous, config, adapter, session, launchId: crypto.randomUUID(), busy: false, submitting: false,
|
|
1541
1698
|
starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
|
|
1542
1699
|
recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
|
|
1543
|
-
backgroundCompletionRetryTimer: null,
|
|
1700
|
+
backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
|
|
1544
1701
|
disabledReason: null, configurationRecovery: null,
|
|
1545
1702
|
readiness: previous.authFailureActive ? previous.readiness : readiness,
|
|
1546
1703
|
};
|
|
@@ -1557,13 +1714,14 @@ export function createRuntimeHost(options) {
|
|
|
1557
1714
|
if (!candidate.authFailureActive) {
|
|
1558
1715
|
emit({ type: "agent-status", agentId: config.agentId, status: "active", readiness: candidate.readiness ?? readiness });
|
|
1559
1716
|
}
|
|
1560
|
-
|
|
1561
|
-
//
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1717
|
+
candidate.piSessionOwner = stageEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1718
|
+
// Close first so the old session's shutdown sweep can bridge consumed
|
|
1719
|
+
// terminals before force-missing reconcile. Commit also clears any
|
|
1720
|
+
// inherited backoff timer; reschedule so a queued completion still
|
|
1721
|
+
// drains on the new session.
|
|
1565
1722
|
await previous.session?.close("runtime candidate committed")
|
|
1566
1723
|
.catch((error) => log("previous runtime close after candidate commit failed", String(error)));
|
|
1724
|
+
reconcileAfterSessionSwap(candidate);
|
|
1567
1725
|
},
|
|
1568
1726
|
rollback,
|
|
1569
1727
|
};
|
|
@@ -1656,8 +1814,10 @@ export function createRuntimeHost(options) {
|
|
|
1656
1814
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}) });
|
|
1657
1815
|
}
|
|
1658
1816
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1817
|
+
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1659
1818
|
await oldSession.close("fresh session reset committed")
|
|
1660
1819
|
.catch((error) => log("previous runtime close after fresh reset failed", String(error)));
|
|
1820
|
+
reconcileAfterSessionSwap(agent);
|
|
1661
1821
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
1662
1822
|
runtimeReady: true, pendingCount: 0, sessionId: fresh.sessionId };
|
|
1663
1823
|
},
|
|
@@ -1806,8 +1966,10 @@ export function createRuntimeHost(options) {
|
|
|
1806
1966
|
}
|
|
1807
1967
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1808
1968
|
durableRollback = null;
|
|
1969
|
+
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1809
1970
|
await oldSession.close("context-window recovery committed")
|
|
1810
1971
|
.catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
|
|
1972
|
+
reconcileAfterSessionSwap(agent);
|
|
1811
1973
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1812
1974
|
void (proactive ?? Promise.resolve("noop")).then((outcome) => {
|
|
1813
1975
|
if (outcome !== "failed")
|
|
@@ -1897,7 +2059,8 @@ export function createRuntimeHost(options) {
|
|
|
1897
2059
|
promotedInboxUpdateIds: new Set(),
|
|
1898
2060
|
backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
|
|
1899
2061
|
backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
|
|
1900
|
-
backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null
|
|
2062
|
+
backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
|
|
2063
|
+
subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
|
|
1901
2064
|
const startupConsumed = [];
|
|
1902
2065
|
const startupQuarantined = [];
|
|
1903
2066
|
for (const record of agent.records.values()) {
|
|
@@ -1928,6 +2091,8 @@ export function createRuntimeHost(options) {
|
|
|
1928
2091
|
emitConsumed(agent, [...startupConsumed, ...persist(agent)]);
|
|
1929
2092
|
try {
|
|
1930
2093
|
await ensureSession(agent);
|
|
2094
|
+
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
|
|
2095
|
+
enqueueUndeliveredTerminalWakes(agent);
|
|
1931
2096
|
await recoverStalePiCompaction(agent);
|
|
1932
2097
|
const startupSession = agent.session;
|
|
1933
2098
|
const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
|
|
@@ -1940,6 +2105,8 @@ export function createRuntimeHost(options) {
|
|
|
1940
2105
|
const reason = error instanceof Error ? error.message : String(error);
|
|
1941
2106
|
const transient = error instanceof RuntimePrerequisiteError && error.readiness.state === "unavailable";
|
|
1942
2107
|
agent.disabledReason = transient ? null : reason;
|
|
2108
|
+
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
|
|
2109
|
+
enqueueUndeliveredTerminalWakes(agent);
|
|
1943
2110
|
if (transient) {
|
|
1944
2111
|
recoveringCount += 1;
|
|
1945
2112
|
scheduleRecreate(agent, reason);
|
|
@@ -2050,6 +2217,8 @@ export function createRuntimeHost(options) {
|
|
|
2050
2217
|
clearTimeout(agent.stabilityTimer);
|
|
2051
2218
|
if (agent.backgroundCompletionRetryTimer)
|
|
2052
2219
|
clearTimeout(agent.backgroundCompletionRetryTimer);
|
|
2220
|
+
if (agent.subagentReconcileTimer)
|
|
2221
|
+
clearInterval(agent.subagentReconcileTimer);
|
|
2053
2222
|
if (agent.busy)
|
|
2054
2223
|
await agent.session?.cancel(reason);
|
|
2055
2224
|
await agent.session?.close(reason);
|
|
@@ -2058,5 +2227,9 @@ export function createRuntimeHost(options) {
|
|
|
2058
2227
|
},
|
|
2059
2228
|
async shutdown(reason) { await Promise.allSettled([...managed.keys()].map((id) => this.stop(id, reason))); },
|
|
2060
2229
|
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
2230
|
+
getDispatchedSubagent(agentId, taskId) {
|
|
2231
|
+
const agent = managed.get(agentId);
|
|
2232
|
+
return agent ? lookupDispatchedSubagent(agent.subagentLedger, taskId) : null;
|
|
2233
|
+
},
|
|
2061
2234
|
};
|
|
2062
2235
|
}
|