larkin 0.5.4 → 0.5.6
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/CHANGELOG.md +6 -0
- package/README.md +7 -1
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +1 -100
- package/dist/agent/context-prompt.cjs +19 -17
- package/dist/agent/context-prompt.mjs +18 -16
- package/dist/runtime/pi-state-dir.cjs +105 -0
- package/dist/runtime/pi-state-dir.mjs +94 -0
- package/dist/runtime/pi-tmux-extension.mjs +137 -0
- package/dist/runtime/pi-tmux-injection.mjs +49 -0
- package/dist/runtime/pi-tmux.bundle.js +600 -0
- package/dist/runtime/pi-tmux.mjs +408 -0
- package/dist/runtime/runtime-adapters.mjs +34 -114
- package/dist/runtime/runtime-host.cjs +3 -413
- package/dist/runtime/runtime-host.mjs +2 -412
- package/package.json +2 -8
- package/dist/runtime/pi-bash-timeout-extension.mjs +0 -60
- package/dist/runtime/pi-bash-timeout-injection.mjs +0 -54
- package/dist/runtime/pi-bash-timeout.bundle.js +0 -43
- package/dist/runtime/pi-extension-api.mjs +0 -1
- package/dist/runtime/pi-subagent-injection.mjs +0 -290
- package/dist/runtime/pi-subagent-ledger.cjs +0 -632
- package/dist/runtime/pi-subagent-ledger.mjs +0 -594
- package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +0 -43
- package/dist/runtime/pi-subagent-record-watchdog.bundle.js +0 -183
- package/dist/runtime/pi-subagent-record-watchdog.mjs +0 -46
- package/dist/runtime/pi-subagents-notification.mjs +0 -175
- package/dist/runtime/pi-subagents.bundle.js +0 -9110
- package/dist/runtime/pi-supervised-command-extension.mjs +0 -72
- package/dist/runtime/pi-supervised-command.bundle.js +0 -4735
- package/dist/runtime/pi-supervised-command.mjs +0 -420
- package/patches/@tintinweb%2Fpi-subagents@0.14.3.patch +0 -945
|
@@ -10,7 +10,7 @@ import { authFailureAppliesTo, classifyPiMissingCredentialRejection, classifyRun
|
|
|
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 {
|
|
13
|
+
import { effectivePiStateDir } from "./pi-state-dir.mjs";
|
|
14
14
|
export class RuntimeSessionResetError extends Error {
|
|
15
15
|
code;
|
|
16
16
|
pendingCount;
|
|
@@ -32,8 +32,6 @@ export class RuntimeSessionRecoveryError extends Error {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const MAX_DELIVERIES = 2048;
|
|
35
|
-
const BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT = 5;
|
|
36
|
-
const DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS = 15_000;
|
|
37
35
|
const now = () => new Date().toISOString();
|
|
38
36
|
const isActiveDelivery = (status) => ["pending", "submitting", "accepted"].includes(status);
|
|
39
37
|
const previousSessionRefFor = (session, reason) => session.sessionId
|
|
@@ -301,10 +299,6 @@ export function createRuntimeHost(options) {
|
|
|
301
299
|
maxAttempts: options.retryPolicy?.maxAttempts ?? 6,
|
|
302
300
|
stableWindowMs: options.retryPolicy?.stableWindowMs ?? 30_000,
|
|
303
301
|
};
|
|
304
|
-
const subagentReconcileIntervalMs = options.subagentReconcileIntervalMs ?? DEFAULT_SUBAGENT_RECONCILE_INTERVAL_MS;
|
|
305
|
-
if (!Number.isFinite(subagentReconcileIntervalMs) || subagentReconcileIntervalMs < 0) {
|
|
306
|
-
throw new Error("subagentReconcileIntervalMs must be >= 0");
|
|
307
|
-
}
|
|
308
302
|
const emit = (event) => {
|
|
309
303
|
if (event.type === "delivery")
|
|
310
304
|
telemetry?.delivery(event.agentId, event.messageId, event.status);
|
|
@@ -316,7 +310,6 @@ export function createRuntimeHost(options) {
|
|
|
316
310
|
LARKIN_AGENT_ID: config.agentId,
|
|
317
311
|
...(generation ? {
|
|
318
312
|
LARKIN_RUNTIME_OBSERVATION_GENERATION: generation,
|
|
319
|
-
[PI_SUBAGENT_SESSION_OWNER_ENV]: generation,
|
|
320
313
|
} : {}),
|
|
321
314
|
LARKIN_CONFIG_DIR: process.env.LARKIN_CONFIG_DIR,
|
|
322
315
|
LARKIN_HOME: process.env.LARKIN_HOME,
|
|
@@ -717,12 +710,6 @@ export function createRuntimeHost(options) {
|
|
|
717
710
|
queueMicrotask(() => { void retryPending(agent); });
|
|
718
711
|
}
|
|
719
712
|
queueMicrotask(() => { void scanAndPromoteAcceptedInboxUpdates(agent); });
|
|
720
|
-
// A completion queued during this submit has no turn-end if prompt
|
|
721
|
-
// rejected/threw before turn-start. Drain now that submitting is clear.
|
|
722
|
-
if (!agent.busy && !agent.turnInProgress && agent.backgroundCompletionQueue.length > 0
|
|
723
|
-
&& !agent.backgroundCompletionRetryTimer) {
|
|
724
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
725
|
-
}
|
|
726
713
|
}
|
|
727
714
|
};
|
|
728
715
|
const retryPending = async (agent) => {
|
|
@@ -799,326 +786,6 @@ export function createRuntimeHost(options) {
|
|
|
799
786
|
agent.retryAfterSubmit = true;
|
|
800
787
|
await retryPending(agent);
|
|
801
788
|
};
|
|
802
|
-
const clearBackgroundCompletionRetryTimer = (agent) => {
|
|
803
|
-
if (!agent.backgroundCompletionRetryTimer)
|
|
804
|
-
return;
|
|
805
|
-
clearTimeout(agent.backgroundCompletionRetryTimer);
|
|
806
|
-
agent.backgroundCompletionRetryTimer = null;
|
|
807
|
-
};
|
|
808
|
-
const scheduleBackgroundCompletionDrain = (agent, delayMs = 0) => {
|
|
809
|
-
if (agent.stopped)
|
|
810
|
-
return;
|
|
811
|
-
if (delayMs > 0) {
|
|
812
|
-
if (agent.backgroundCompletionRetryTimer)
|
|
813
|
-
return;
|
|
814
|
-
agent.backgroundCompletionRetryTimer = setTimeout(() => {
|
|
815
|
-
agent.backgroundCompletionRetryTimer = null;
|
|
816
|
-
if (agent.stopped)
|
|
817
|
-
return;
|
|
818
|
-
void drainBackgroundCompletionQueue(agent);
|
|
819
|
-
}, delayMs);
|
|
820
|
-
agent.backgroundCompletionRetryTimer.unref?.();
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
824
|
-
queueMicrotask(() => { void drainBackgroundCompletionQueue(agent); });
|
|
825
|
-
};
|
|
826
|
-
const dropBackgroundCompletion = (agent, completionKey) => {
|
|
827
|
-
if (!completionKey)
|
|
828
|
-
return;
|
|
829
|
-
if (agent.backgroundCompletionQueue[0] === completionKey)
|
|
830
|
-
agent.backgroundCompletionQueue.shift();
|
|
831
|
-
else {
|
|
832
|
-
const index = agent.backgroundCompletionQueue.indexOf(completionKey);
|
|
833
|
-
if (index >= 0)
|
|
834
|
-
agent.backgroundCompletionQueue.splice(index, 1);
|
|
835
|
-
}
|
|
836
|
-
agent.backgroundCompletionKeys.add(completionKey);
|
|
837
|
-
if (agent.backgroundCompletionInFlight === completionKey) {
|
|
838
|
-
agent.backgroundCompletionInFlight = null;
|
|
839
|
-
agent.backgroundCompletionWakeInputId = null;
|
|
840
|
-
}
|
|
841
|
-
agent.backgroundCompletionRejectStreak = 0;
|
|
842
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
843
|
-
// Leave the durable wake pending so a later restart can requeue it.
|
|
844
|
-
};
|
|
845
|
-
const scheduleBackgroundCompletionRetry = (agent) => {
|
|
846
|
-
const streak = agent.backgroundCompletionRejectStreak;
|
|
847
|
-
if (streak < BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT) {
|
|
848
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
const delayedAttempt = streak - BACKGROUND_COMPLETION_IMMEDIATE_RETRY_LIMIT;
|
|
852
|
-
if (delayedAttempt >= retryPolicy.maxAttempts) {
|
|
853
|
-
const exhausted = agent.backgroundCompletionQueue[0];
|
|
854
|
-
if (exhausted) {
|
|
855
|
-
log("background completion wake retries exhausted", exhausted);
|
|
856
|
-
dropBackgroundCompletion(agent, exhausted);
|
|
857
|
-
}
|
|
858
|
-
if (agent.backgroundCompletionQueue.length > 0)
|
|
859
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const delay = Math.min(retryPolicy.maxDelayMs, retryPolicy.baseDelayMs * 2 ** delayedAttempt);
|
|
863
|
-
scheduleBackgroundCompletionDrain(agent, delay);
|
|
864
|
-
};
|
|
865
|
-
const isPersistentBackgroundWakeFailure = (event) => {
|
|
866
|
-
if (!event)
|
|
867
|
-
return false;
|
|
868
|
-
const category = event.errorCategory;
|
|
869
|
-
if (category === "auth" || category === "billing" || category === "quota")
|
|
870
|
-
return true;
|
|
871
|
-
return category === "provider" && event.retryable === false;
|
|
872
|
-
};
|
|
873
|
-
const failBackgroundCompletionWake = (agent, event) => {
|
|
874
|
-
const completionKey = agent.backgroundCompletionInFlight ?? agent.backgroundCompletionQueue[0];
|
|
875
|
-
if (!completionKey)
|
|
876
|
-
return;
|
|
877
|
-
rearmBackgroundCompletion(agent, completionKey);
|
|
878
|
-
if (isPersistentBackgroundWakeFailure(event)) {
|
|
879
|
-
log("background completion wake dropped", event?.errorCategory ?? "persistent");
|
|
880
|
-
dropBackgroundCompletion(agent, completionKey);
|
|
881
|
-
if (agent.backgroundCompletionQueue.length > 0)
|
|
882
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
agent.backgroundCompletionRejectStreak += 1;
|
|
886
|
-
scheduleBackgroundCompletionRetry(agent);
|
|
887
|
-
};
|
|
888
|
-
const rearmBackgroundCompletion = (agent, completionKey = agent.backgroundCompletionInFlight) => {
|
|
889
|
-
if (!completionKey)
|
|
890
|
-
return;
|
|
891
|
-
agent.backgroundCompletionKeys.delete(completionKey);
|
|
892
|
-
if (agent.backgroundCompletionInFlight === completionKey) {
|
|
893
|
-
agent.backgroundCompletionInFlight = null;
|
|
894
|
-
agent.backgroundCompletionWakeInputId = null;
|
|
895
|
-
}
|
|
896
|
-
if (!agent.backgroundCompletionQueue.includes(completionKey)) {
|
|
897
|
-
agent.backgroundCompletionQueue.unshift(completionKey);
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
const commitBackgroundCompletion = (agent) => {
|
|
901
|
-
const completionKey = agent.backgroundCompletionInFlight;
|
|
902
|
-
if (!completionKey)
|
|
903
|
-
return;
|
|
904
|
-
if (agent.backgroundCompletionQueue[0] === completionKey)
|
|
905
|
-
agent.backgroundCompletionQueue.shift();
|
|
906
|
-
agent.backgroundCompletionInFlight = null;
|
|
907
|
-
agent.backgroundCompletionWakeInputId = null;
|
|
908
|
-
agent.backgroundCompletionRejectStreak = 0;
|
|
909
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
910
|
-
acknowledgeSubagentWake(agent, completionKey);
|
|
911
|
-
};
|
|
912
|
-
const wakeOnBackgroundCompletion = async (agent) => {
|
|
913
|
-
// Caller already reserved submitting/busy so a concurrent idle drain cannot
|
|
914
|
-
// dequeue another head while ensureSession/prompt yields.
|
|
915
|
-
try {
|
|
916
|
-
const session = await ensureSession(agent);
|
|
917
|
-
if (agent.session !== session || agent.stopped)
|
|
918
|
-
return { status: "dropped" };
|
|
919
|
-
const headKey = agent.backgroundCompletionQueue[0];
|
|
920
|
-
const tasks = taskIdsFromCompletionKey(headKey ?? "")
|
|
921
|
-
.map((taskId) => lookupDispatchedSubagent(agent.subagentLedger, taskId))
|
|
922
|
-
.filter((task) => Boolean(task));
|
|
923
|
-
const details = tasks.map((task) => `task ${task.taskId} status=${task.status}`).join("; ");
|
|
924
|
-
const wakeReason = details
|
|
925
|
-
? `background subagent completed; ${details}`
|
|
926
|
-
: "background subagent completed";
|
|
927
|
-
const input = options.promptBuilder.buildRuntimeInput("wake", crypto.randomUUID(), { wakeReason });
|
|
928
|
-
const result = await session.prompt(input);
|
|
929
|
-
if (agent.session !== session || agent.stopped)
|
|
930
|
-
return { status: "dropped" };
|
|
931
|
-
if (result.status === "accepted") {
|
|
932
|
-
markSessionStable(agent, session);
|
|
933
|
-
return { status: "accepted", inputId: input.inputId };
|
|
934
|
-
}
|
|
935
|
-
agent.busy = false;
|
|
936
|
-
return { status: "retry" };
|
|
937
|
-
}
|
|
938
|
-
catch (error) {
|
|
939
|
-
agent.busy = false;
|
|
940
|
-
log("background completion wake failed", error instanceof Error ? error.message : String(error));
|
|
941
|
-
return { status: "retry" };
|
|
942
|
-
}
|
|
943
|
-
};
|
|
944
|
-
const drainBackgroundCompletionQueue = async (agent) => {
|
|
945
|
-
if (agent.stopped || agent.busy || agent.turnInProgress || agent.submitting)
|
|
946
|
-
return;
|
|
947
|
-
if (agent.backgroundCompletionInFlight || !agent.backgroundCompletionQueue.length)
|
|
948
|
-
return;
|
|
949
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
950
|
-
const proactive = agent.piProactiveCompaction
|
|
951
|
-
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
952
|
-
&& agent.piProactiveCompactionSession === agent.session
|
|
953
|
-
? agent.piProactiveCompaction : null;
|
|
954
|
-
if (proactive)
|
|
955
|
-
await proactive;
|
|
956
|
-
if (agent.adapter.id === "pi" && !agent.busy && !agent.turnInProgress && agent.session) {
|
|
957
|
-
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
958
|
-
if (idleGate)
|
|
959
|
-
await idleGate;
|
|
960
|
-
}
|
|
961
|
-
if (agent.stopped || agent.busy || agent.turnInProgress || agent.submitting)
|
|
962
|
-
return;
|
|
963
|
-
if (agent.backgroundCompletionInFlight || !agent.backgroundCompletionQueue.length)
|
|
964
|
-
return;
|
|
965
|
-
agent.submitting = true;
|
|
966
|
-
agent.busy = true;
|
|
967
|
-
const completionKey = agent.backgroundCompletionQueue[0];
|
|
968
|
-
try {
|
|
969
|
-
const outcome = await wakeOnBackgroundCompletion(agent);
|
|
970
|
-
if (outcome.status === "accepted") {
|
|
971
|
-
agent.backgroundCompletionKeys.add(completionKey);
|
|
972
|
-
agent.backgroundCompletionInFlight = completionKey;
|
|
973
|
-
agent.backgroundCompletionWakeInputId = outcome.inputId;
|
|
974
|
-
// Keep the reject streak until the wake turn actually succeeds. An accepted
|
|
975
|
-
// prompt that later terminal-fails must not reset the bounded retry budget.
|
|
976
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
977
|
-
}
|
|
978
|
-
else if (outcome.status === "dropped") {
|
|
979
|
-
if (agent.stopped) {
|
|
980
|
-
if (agent.backgroundCompletionQueue[0] === completionKey)
|
|
981
|
-
agent.backgroundCompletionQueue.shift();
|
|
982
|
-
agent.backgroundCompletionKeys.delete(completionKey);
|
|
983
|
-
clearBackgroundCompletionRetryTimer(agent);
|
|
984
|
-
}
|
|
985
|
-
else {
|
|
986
|
-
rearmBackgroundCompletion(agent, completionKey);
|
|
987
|
-
agent.busy = false;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
agent.busy = false;
|
|
992
|
-
agent.backgroundCompletionRejectStreak += 1;
|
|
993
|
-
// Leave the item queued+deduped. Immediate microtask retries cover a
|
|
994
|
-
// brief reject; after that a bounded timer still drains because Pi will
|
|
995
|
-
// not re-emit the same completion.
|
|
996
|
-
scheduleBackgroundCompletionRetry(agent);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
finally {
|
|
1000
|
-
agent.submitting = false;
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
const persistSubagentLedger = (agent) => {
|
|
1004
|
-
writeDispatchedSubagentLedger(ledgerFilePath(effectivePiStateDir(agent.config)), agent.subagentLedger);
|
|
1005
|
-
};
|
|
1006
|
-
const dropQueuedBackgroundCompletionFallback = (agent, completionKey) => {
|
|
1007
|
-
const keys = new Set([completionKey, ...taskIdsFromCompletionKey(completionKey)]);
|
|
1008
|
-
for (let index = agent.backgroundCompletionQueue.length - 1; index >= 0; index -= 1) {
|
|
1009
|
-
const queued = agent.backgroundCompletionQueue[index];
|
|
1010
|
-
if (queued && keys.has(queued))
|
|
1011
|
-
agent.backgroundCompletionQueue.splice(index, 1);
|
|
1012
|
-
}
|
|
1013
|
-
for (const key of keys)
|
|
1014
|
-
agent.backgroundCompletionKeys.add(key);
|
|
1015
|
-
};
|
|
1016
|
-
const acknowledgeSubagentWake = (agent, completionKey) => {
|
|
1017
|
-
const next = noteDispatchedSubagentWakeAcknowledged(agent.subagentLedger, { completionKey });
|
|
1018
|
-
if (next === agent.subagentLedger)
|
|
1019
|
-
return;
|
|
1020
|
-
agent.subagentLedger = next;
|
|
1021
|
-
persistSubagentLedger(agent);
|
|
1022
|
-
for (const taskId of taskIdsFromCompletionKey(completionKey)) {
|
|
1023
|
-
retirePiSubagentRecord(lookupDispatchedSubagent(agent.subagentLedger, taskId)?.recordFile);
|
|
1024
|
-
}
|
|
1025
|
-
};
|
|
1026
|
-
const enqueueUndeliveredTerminalWakes = (agent) => {
|
|
1027
|
-
for (const key of undeliveredTerminalWakeKeys(agent.subagentLedger)) {
|
|
1028
|
-
agent.backgroundCompletionKeys.delete(key);
|
|
1029
|
-
for (const taskId of taskIdsFromCompletionKey(key))
|
|
1030
|
-
agent.backgroundCompletionKeys.delete(taskId);
|
|
1031
|
-
noteBackgroundCompletion(agent, key);
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1034
|
-
const reconcileAfterSessionSwap = (agent) => {
|
|
1035
|
-
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
|
|
1036
|
-
enqueueUndeliveredTerminalWakes(agent);
|
|
1037
|
-
if (agent.backgroundCompletionQueue.length > 0)
|
|
1038
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
1039
|
-
};
|
|
1040
|
-
const loadSubagentLedger = (config) => {
|
|
1041
|
-
return readDispatchedSubagentLedger(ledgerFilePath(effectivePiStateDir(config)));
|
|
1042
|
-
};
|
|
1043
|
-
const clearSubagentReconcileTimer = (agent) => {
|
|
1044
|
-
if (!agent.subagentReconcileTimer)
|
|
1045
|
-
return;
|
|
1046
|
-
clearInterval(agent.subagentReconcileTimer);
|
|
1047
|
-
agent.subagentReconcileTimer = null;
|
|
1048
|
-
};
|
|
1049
|
-
const hasActiveDispatchedSubagent = (agent) => agent.subagentLedger.tasks.some((task) => task.status === "dispatched");
|
|
1050
|
-
const noteBackgroundCompletion = (agent, completionKey) => {
|
|
1051
|
-
const taskIds = taskIdsFromCompletionKey(completionKey);
|
|
1052
|
-
if (agent.backgroundCompletionKeys.has(completionKey))
|
|
1053
|
-
return;
|
|
1054
|
-
if (taskIds.length > 0 && taskIds.every((taskId) => agent.backgroundCompletionKeys.has(taskId)))
|
|
1055
|
-
return;
|
|
1056
|
-
agent.backgroundCompletionKeys.add(completionKey);
|
|
1057
|
-
for (const taskId of taskIds)
|
|
1058
|
-
agent.backgroundCompletionKeys.add(taskId);
|
|
1059
|
-
if (!agent.backgroundCompletionQueue.includes(completionKey)) {
|
|
1060
|
-
agent.backgroundCompletionQueue.push(completionKey);
|
|
1061
|
-
}
|
|
1062
|
-
if (!agent.busy && !agent.turnInProgress && !agent.submitting && !agent.backgroundCompletionInFlight) {
|
|
1063
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
const recordDispatchedSubagent = (agent, taskId, outputFile) => {
|
|
1067
|
-
const recordFile = writeDispatchedSubagentRecordFile(effectivePiStateDir(agent.config), taskId, agent.piSessionOwner);
|
|
1068
|
-
agent.subagentLedger = noteDispatchedSubagent(agent.subagentLedger, { taskId, outputFile, recordFile });
|
|
1069
|
-
persistSubagentLedger(agent);
|
|
1070
|
-
armSubagentReconcileTimer(agent);
|
|
1071
|
-
};
|
|
1072
|
-
const recordTerminalSubagentNotification = (agent, completionKey, statuses) => {
|
|
1073
|
-
let next = agent.subagentLedger;
|
|
1074
|
-
for (const taskId of taskIdsFromCompletionKey(completionKey)) {
|
|
1075
|
-
const status = statuses?.[taskId];
|
|
1076
|
-
next = noteDispatchedSubagentTerminal(next, {
|
|
1077
|
-
taskId,
|
|
1078
|
-
status: status && status !== "dispatched" && status !== "orphaned" ? status : "completed",
|
|
1079
|
-
wakeKey: completionKey,
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
agent.subagentLedger = next;
|
|
1083
|
-
persistSubagentLedger(agent);
|
|
1084
|
-
if (!hasActiveDispatchedSubagent(agent))
|
|
1085
|
-
clearSubagentReconcileTimer(agent);
|
|
1086
|
-
};
|
|
1087
|
-
const reconcileSubagentLedger = (agent, input = {}) => {
|
|
1088
|
-
if (agent.stopped)
|
|
1089
|
-
return;
|
|
1090
|
-
const result = reconcileDispatchedSubagents(agent.subagentLedger, {
|
|
1091
|
-
probe: options.subagentRecordProbe ?? probePiSubagentRecord,
|
|
1092
|
-
forceMissing: input.forceMissing === true,
|
|
1093
|
-
missingReason: input.missingReason,
|
|
1094
|
-
});
|
|
1095
|
-
const previousLedger = agent.subagentLedger;
|
|
1096
|
-
agent.subagentLedger = result.ledger;
|
|
1097
|
-
if (result.orphaned.length > 0 || result.terminals.length > 0 || result.ledger !== previousLedger) {
|
|
1098
|
-
persistSubagentLedger(agent);
|
|
1099
|
-
}
|
|
1100
|
-
for (const record of [...result.orphaned, ...result.terminals]) {
|
|
1101
|
-
noteBackgroundCompletion(agent, record.wakeKey ?? record.taskId);
|
|
1102
|
-
}
|
|
1103
|
-
if (!hasActiveDispatchedSubagent(agent))
|
|
1104
|
-
clearSubagentReconcileTimer(agent);
|
|
1105
|
-
else
|
|
1106
|
-
armSubagentReconcileTimer(agent);
|
|
1107
|
-
};
|
|
1108
|
-
const armSubagentReconcileTimer = (agent) => {
|
|
1109
|
-
if (agent.stopped || agent.subagentReconcileTimer || subagentReconcileIntervalMs === 0)
|
|
1110
|
-
return;
|
|
1111
|
-
if (!hasActiveDispatchedSubagent(agent))
|
|
1112
|
-
return;
|
|
1113
|
-
agent.subagentReconcileTimer = setInterval(() => {
|
|
1114
|
-
if (agent.stopped) {
|
|
1115
|
-
clearSubagentReconcileTimer(agent);
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
reconcileSubagentLedger(agent);
|
|
1119
|
-
}, subagentReconcileIntervalMs);
|
|
1120
|
-
agent.subagentReconcileTimer.unref?.();
|
|
1121
|
-
};
|
|
1122
789
|
const scheduleRecreate = (agent, reason) => {
|
|
1123
790
|
if (agent.stopped || agent.retryTimer || agent.starting || agent.session)
|
|
1124
791
|
return;
|
|
@@ -1140,11 +807,7 @@ export function createRuntimeHost(options) {
|
|
|
1140
807
|
const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
|
|
1141
808
|
if (proactive)
|
|
1142
809
|
await proactive;
|
|
1143
|
-
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "pi session gone" });
|
|
1144
|
-
enqueueUndeliveredTerminalWakes(agent);
|
|
1145
810
|
await retryPending(agent);
|
|
1146
|
-
if (agent.backgroundCompletionQueue.length > 0)
|
|
1147
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
1148
811
|
}).catch((error) => {
|
|
1149
812
|
const readiness = error instanceof RuntimePrerequisiteError ? error.readiness
|
|
1150
813
|
: classifyRuntimePrerequisite(agent.adapter.id, error);
|
|
@@ -1167,7 +830,6 @@ export function createRuntimeHost(options) {
|
|
|
1167
830
|
agent.busy = false;
|
|
1168
831
|
agent.submitting = false;
|
|
1169
832
|
agent.generation += 1;
|
|
1170
|
-
rearmBackgroundCompletion(agent);
|
|
1171
833
|
agent.recreateReason = reason;
|
|
1172
834
|
if (agent.stabilityTimer)
|
|
1173
835
|
clearTimeout(agent.stabilityTimer);
|
|
@@ -1177,14 +839,11 @@ export function createRuntimeHost(options) {
|
|
|
1177
839
|
record.status = "pending";
|
|
1178
840
|
emitConsumed(agent, persist(agent));
|
|
1179
841
|
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error", error: reason });
|
|
1180
|
-
// Close first so the shutdown sweep can bridge consumed or terminal state
|
|
1181
|
-
// before force-missing reconcile treats a plain sidecar as orphaned.
|
|
1182
842
|
void session.close(`replace after ${reason}`)
|
|
1183
843
|
.catch((error) => log("runtime close after replacement failed", String(error)))
|
|
1184
844
|
.finally(() => {
|
|
1185
845
|
if (agent.stopped)
|
|
1186
846
|
return;
|
|
1187
|
-
reconcileAfterSessionSwap(agent);
|
|
1188
847
|
scheduleRecreate(agent, reason);
|
|
1189
848
|
});
|
|
1190
849
|
};
|
|
@@ -1196,7 +855,6 @@ export function createRuntimeHost(options) {
|
|
|
1196
855
|
agent.busy = false;
|
|
1197
856
|
agent.submitting = false;
|
|
1198
857
|
agent.generation += 1;
|
|
1199
|
-
rearmBackgroundCompletion(agent);
|
|
1200
858
|
if (agent.stabilityTimer)
|
|
1201
859
|
clearTimeout(agent.stabilityTimer);
|
|
1202
860
|
agent.stabilityTimer = null;
|
|
@@ -1211,8 +869,6 @@ export function createRuntimeHost(options) {
|
|
|
1211
869
|
const recovery = (async () => {
|
|
1212
870
|
try {
|
|
1213
871
|
await session.close("runtime configuration error");
|
|
1214
|
-
if (!agent.stopped)
|
|
1215
|
-
reconcileAfterSessionSwap(agent);
|
|
1216
872
|
const result = agent.adapter.recoverConfigurationError
|
|
1217
873
|
? await agent.adapter.recoverConfigurationError(message)
|
|
1218
874
|
: { recovered: false, reason: "runtime adapter does not support automatic configuration recovery" };
|
|
@@ -1489,16 +1145,6 @@ export function createRuntimeHost(options) {
|
|
|
1489
1145
|
// closed instead of inheriting a previous turn's chat or reminder.
|
|
1490
1146
|
agent.stateStore?.clearCurrentInboxSource?.();
|
|
1491
1147
|
finalizeReminderContexts(agent, "Runtime turn ended without a matching Feishu write");
|
|
1492
|
-
if (agent.backgroundCompletionInFlight) {
|
|
1493
|
-
if (agent.turnHadFailure)
|
|
1494
|
-
failBackgroundCompletionWake(agent);
|
|
1495
|
-
else
|
|
1496
|
-
commitBackgroundCompletion(agent);
|
|
1497
|
-
}
|
|
1498
|
-
if (agent.backgroundCompletionQueue.length > 0 && !agent.backgroundCompletionRetryTimer) {
|
|
1499
|
-
scheduleBackgroundCompletionDrain(agent);
|
|
1500
|
-
}
|
|
1501
|
-
reconcileSubagentLedger(agent);
|
|
1502
1148
|
telemetry?.runtimeEvent(agent.config.agentId, event);
|
|
1503
1149
|
if (recoveredAuthentication) {
|
|
1504
1150
|
clearAuthFailure(agent);
|
|
@@ -1516,34 +1162,12 @@ export function createRuntimeHost(options) {
|
|
|
1516
1162
|
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
1517
1163
|
queueMicrotask(() => { void scanAndPromoteAcceptedInboxUpdates(agent); });
|
|
1518
1164
|
}
|
|
1519
|
-
else if (event.type === "runtime-observation") {
|
|
1520
|
-
if (event.phase === "background_dispatched" && typeof event.taskId === "string" && event.taskId) {
|
|
1521
|
-
recordDispatchedSubagent(agent, event.taskId, typeof event.outputFile === "string" ? event.outputFile : undefined);
|
|
1522
|
-
}
|
|
1523
|
-
else if (event.phase === "completed" && typeof event.completionKey === "string") {
|
|
1524
|
-
recordTerminalSubagentNotification(agent, event.completionKey, event.completionStatuses);
|
|
1525
|
-
if (event.handledInTurn === true) {
|
|
1526
|
-
acknowledgeSubagentWake(agent, event.completionKey);
|
|
1527
|
-
dropQueuedBackgroundCompletionFallback(agent, event.completionKey);
|
|
1528
|
-
}
|
|
1529
|
-
else {
|
|
1530
|
-
noteBackgroundCompletion(agent, event.completionKey);
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
1165
|
else if (event.type === "activity") {
|
|
1535
1166
|
if (agent.turnInProgress && event.activity !== "internal")
|
|
1536
1167
|
agent.turnHadAuthenticatedOutput = true;
|
|
1537
1168
|
emit({ type: "activity", agentId: agent.config.agentId, activity: event.activity, activityKind: event.activity });
|
|
1538
1169
|
}
|
|
1539
1170
|
else if (event.type === "input-error") {
|
|
1540
|
-
if (agent.backgroundCompletionInFlight
|
|
1541
|
-
&& (!event.inputId || !agent.backgroundCompletionWakeInputId
|
|
1542
|
-
|| event.inputId === agent.backgroundCompletionWakeInputId)
|
|
1543
|
-
&& event.willRetry !== true) {
|
|
1544
|
-
agent.turnHadFailure = true;
|
|
1545
|
-
failBackgroundCompletionWake(agent, event);
|
|
1546
|
-
}
|
|
1547
1171
|
const record = event.inputId
|
|
1548
1172
|
? agent.records.get(event.inputId) ?? [...agent.records.values()].find((candidate) => candidate.input.inputId === event.inputId)
|
|
1549
1173
|
: undefined;
|
|
@@ -1673,7 +1297,6 @@ export function createRuntimeHost(options) {
|
|
|
1673
1297
|
throw new Error("stale runtime session creation");
|
|
1674
1298
|
}
|
|
1675
1299
|
agent.session = session;
|
|
1676
|
-
agent.piSessionOwner = sessionEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1677
1300
|
session.subscribe((event) => observe(agent, session, event));
|
|
1678
1301
|
if (agent.session !== session)
|
|
1679
1302
|
return session;
|
|
@@ -1819,9 +1442,7 @@ export function createRuntimeHost(options) {
|
|
|
1819
1442
|
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1820
1443
|
}
|
|
1821
1444
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1822
|
-
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1823
1445
|
await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
|
|
1824
|
-
reconcileAfterSessionSwap(agent);
|
|
1825
1446
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1826
1447
|
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
1827
1448
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
@@ -1891,10 +1512,6 @@ export function createRuntimeHost(options) {
|
|
|
1891
1512
|
clearTimeout(previous.retryTimer);
|
|
1892
1513
|
if (previous.stabilityTimer)
|
|
1893
1514
|
clearTimeout(previous.stabilityTimer);
|
|
1894
|
-
if (previous.backgroundCompletionRetryTimer)
|
|
1895
|
-
clearTimeout(previous.backgroundCompletionRetryTimer);
|
|
1896
|
-
if (previous.subagentReconcileTimer)
|
|
1897
|
-
clearInterval(previous.subagentReconcileTimer);
|
|
1898
1515
|
const previousFailure = currentAuthFailure(previous);
|
|
1899
1516
|
const scoped = Boolean(previousFailure
|
|
1900
1517
|
&& authFailureAppliesTo({
|
|
@@ -1906,7 +1523,6 @@ export function createRuntimeHost(options) {
|
|
|
1906
1523
|
...previous, config, adapter, session, launchId: crypto.randomUUID(), busy: false, submitting: false,
|
|
1907
1524
|
starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
|
|
1908
1525
|
recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
|
|
1909
|
-
backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
|
|
1910
1526
|
disabledReason: null, configurationRecovery: null,
|
|
1911
1527
|
authFailureActive: scoped && previous.authFailureActive,
|
|
1912
1528
|
authFailureKind: scoped ? previous.authFailureKind : null,
|
|
@@ -1928,14 +1544,8 @@ export function createRuntimeHost(options) {
|
|
|
1928
1544
|
if (!candidate.authFailureActive) {
|
|
1929
1545
|
emit({ type: "agent-status", agentId: config.agentId, status: "active", readiness: candidate.readiness ?? readiness });
|
|
1930
1546
|
}
|
|
1931
|
-
candidate.piSessionOwner = stageEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1932
|
-
// Close first so the old session's shutdown sweep can bridge consumed
|
|
1933
|
-
// terminals before force-missing reconcile. Commit also clears any
|
|
1934
|
-
// inherited backoff timer; reschedule so a queued completion still
|
|
1935
|
-
// drains on the new session.
|
|
1936
1547
|
await previous.session?.close("runtime candidate committed")
|
|
1937
1548
|
.catch((error) => log("previous runtime close after candidate commit failed", String(error)));
|
|
1938
|
-
reconcileAfterSessionSwap(candidate);
|
|
1939
1549
|
},
|
|
1940
1550
|
rollback,
|
|
1941
1551
|
};
|
|
@@ -2038,10 +1648,8 @@ export function createRuntimeHost(options) {
|
|
|
2038
1648
|
...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
|
|
2039
1649
|
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2040
1650
|
});
|
|
2041
|
-
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
2042
1651
|
await oldSession.close("fresh session reset committed")
|
|
2043
1652
|
.catch((error) => log("previous runtime close after fresh reset failed", String(error)));
|
|
2044
|
-
reconcileAfterSessionSwap(agent);
|
|
2045
1653
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
2046
1654
|
runtimeReady: true, pendingCount: 0, sessionId: fresh.sessionId };
|
|
2047
1655
|
},
|
|
@@ -2202,10 +1810,8 @@ export function createRuntimeHost(options) {
|
|
|
2202
1810
|
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2203
1811
|
});
|
|
2204
1812
|
durableRollback = null;
|
|
2205
|
-
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
2206
1813
|
await oldSession.close("context-window recovery committed")
|
|
2207
1814
|
.catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
|
|
2208
|
-
reconcileAfterSessionSwap(agent);
|
|
2209
1815
|
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
2210
1816
|
void (proactive ?? Promise.resolve("noop")).then(() => retryPending(agent));
|
|
2211
1817
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
@@ -2291,11 +1897,7 @@ export function createRuntimeHost(options) {
|
|
|
2291
1897
|
previousSession: loadPersistedPreviousSession(config, stateStore), readiness: null,
|
|
2292
1898
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false,
|
|
2293
1899
|
authFailureKind: null, authFailureProvider: null,
|
|
2294
|
-
promotedInboxUpdateIds: new Set()
|
|
2295
|
-
backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
|
|
2296
|
-
backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
|
|
2297
|
-
backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
|
|
2298
|
-
subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
|
|
1900
|
+
promotedInboxUpdateIds: new Set() };
|
|
2299
1901
|
restoreAuthFailure(agent);
|
|
2300
1902
|
const startupConsumed = [];
|
|
2301
1903
|
const startupQuarantined = [];
|
|
@@ -2329,8 +1931,6 @@ export function createRuntimeHost(options) {
|
|
|
2329
1931
|
await ensureSession(agent);
|
|
2330
1932
|
if (agent.stopped)
|
|
2331
1933
|
return;
|
|
2332
|
-
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
|
|
2333
|
-
enqueueUndeliveredTerminalWakes(agent);
|
|
2334
1934
|
await recoverStalePiCompaction(agent);
|
|
2335
1935
|
const startupSession = agent.session;
|
|
2336
1936
|
const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
|
|
@@ -2347,8 +1947,6 @@ export function createRuntimeHost(options) {
|
|
|
2347
1947
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2348
1948
|
const transient = error instanceof RuntimePrerequisiteError && error.readiness.state === "unavailable";
|
|
2349
1949
|
agent.disabledReason = transient ? null : reason;
|
|
2350
|
-
reconcileSubagentLedger(agent, { forceMissing: true, missingReason: "runtime restarted" });
|
|
2351
|
-
enqueueUndeliveredTerminalWakes(agent);
|
|
2352
1950
|
if (transient) {
|
|
2353
1951
|
recoveringCount += 1;
|
|
2354
1952
|
scheduleRecreate(agent, reason);
|
|
@@ -2460,10 +2058,6 @@ export function createRuntimeHost(options) {
|
|
|
2460
2058
|
clearTimeout(agent.retryTimer);
|
|
2461
2059
|
if (agent.stabilityTimer)
|
|
2462
2060
|
clearTimeout(agent.stabilityTimer);
|
|
2463
|
-
if (agent.backgroundCompletionRetryTimer)
|
|
2464
|
-
clearTimeout(agent.backgroundCompletionRetryTimer);
|
|
2465
|
-
if (agent.subagentReconcileTimer)
|
|
2466
|
-
clearInterval(agent.subagentReconcileTimer);
|
|
2467
2061
|
if (agent.busy)
|
|
2468
2062
|
await agent.session?.cancel(reason);
|
|
2469
2063
|
await agent.session?.close(reason);
|
|
@@ -2476,9 +2070,5 @@ export function createRuntimeHost(options) {
|
|
|
2476
2070
|
},
|
|
2477
2071
|
async shutdown(reason) { await Promise.allSettled([...managed.keys()].map((id) => this.stop(id, reason))); },
|
|
2478
2072
|
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
2479
|
-
getDispatchedSubagent(agentId, taskId) {
|
|
2480
|
-
const agent = managed.get(agentId);
|
|
2481
|
-
return agent ? lookupDispatchedSubagent(agent.subagentLedger, taskId) : null;
|
|
2482
|
-
},
|
|
2483
2073
|
};
|
|
2484
2074
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "larkin",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"larkinPackageRole": "npm-published",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist/",
|
|
8
|
-
"patches/",
|
|
9
8
|
"assets/",
|
|
10
9
|
"scripts/npm/",
|
|
11
10
|
"README.md",
|
|
@@ -70,8 +69,7 @@
|
|
|
70
69
|
"test:live:runtime-agent-interface-v2:read-only": "LARKIN_RUN_RUNTIME_AGENT_INTERFACE_V2_LIVE=1 bun test --max-concurrency 1 test/live/runtime-agent-interface-v2-live.test.mjs",
|
|
71
70
|
"test:live:runtime-agent-interface-v2:hold-host": "bun run build && bun run test/live/runtime-agent-interface-v2-hold-host.mjs app/runtime-process.mjs",
|
|
72
71
|
"test:eval:long-running-im-progress": "bun run build && bun test --max-concurrency 1 test/live/long-running-im-progress-live.test.mjs",
|
|
73
|
-
"test:eval:pi-
|
|
74
|
-
"test:eval:pi-bash-timeout": "bun run build && bun test --max-concurrency 1 test/live/pi-bash-timeout-live.test.mjs",
|
|
72
|
+
"test:eval:pi-tmux-bash": "bun run build && LARKIN_RUN_PI_TMUX_BASH_EVAL=1 bun test --max-concurrency 1 test/live/pi-tmux-bash-live.test.mjs",
|
|
75
73
|
"test:eval:runtime-agent-interface-v2": "bun test --max-concurrency 1 test/unit/evals/runtime-agent-interface-v2.test.mjs",
|
|
76
74
|
"test:eval:authoritative-freshness-gate": "bun test --max-concurrency 1 test/unit/evals/authoritative-freshness-gate.test.mjs",
|
|
77
75
|
"test:eval:protected-message-recall": "bun run build && bun test --max-concurrency 1 test/unit/evals/protected-message-recall.test.mjs test/unit/app/lark-cli-launcher.test.mjs",
|
|
@@ -95,7 +93,6 @@
|
|
|
95
93
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
96
94
|
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
|
97
95
|
"@radix-ui/react-dialog": "^1.1.21",
|
|
98
|
-
"@tintinweb/pi-subagents": "0.14.3",
|
|
99
96
|
"lucide-react": "^1.26.0",
|
|
100
97
|
"proper-lockfile": "4.1.2",
|
|
101
98
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -119,8 +116,5 @@
|
|
|
119
116
|
"typescript": "^7.0.2",
|
|
120
117
|
"vite": "^8.1.5",
|
|
121
118
|
"vitest": "^4.1.10"
|
|
122
|
-
},
|
|
123
|
-
"patchedDependencies": {
|
|
124
|
-
"@tintinweb/pi-subagents@0.14.3": "patches/@tintinweb%2Fpi-subagents@0.14.3.patch"
|
|
125
119
|
}
|
|
126
120
|
}
|