larkin 0.4.2 → 0.4.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/README.md +5 -1
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +218 -228
- package/assets/readme/larkin-github-cover.png +0 -0
- package/dist/agent/agent-state-store.cjs +1 -2
- package/dist/agent/agent-state-store.mjs +1 -2
- package/dist/app/agent-config.mjs +73 -6
- package/dist/app/agent-readiness.cjs +22 -2
- package/dist/app/agent-readiness.mjs +20 -2
- package/dist/app/binary-entry.mjs +6 -0
- package/dist/app/cli.mjs +4 -0
- package/dist/app/run.mjs +8 -0
- package/dist/app/runtime-process.mjs +13 -1
- package/dist/dashboard/dashboard-view-model.mjs +12 -3
- package/dist/feishu/host-shell.cjs +33 -10
- package/dist/feishu/host-shell.mjs +34 -11
- package/dist/platform/config.cjs +406 -33
- package/dist/platform/config.mjs +405 -33
- package/dist/platform/process-boundary-trace.cjs +108 -0
- package/dist/platform/process-boundary-trace.mjs +102 -0
- package/dist/runtime/pi-artifact-provenance.cjs +147 -0
- package/dist/runtime/pi-artifact-provenance.mjs +139 -0
- package/dist/runtime/pi-compaction-recovery.cjs +120 -40
- package/dist/runtime/pi-compaction-recovery.mjs +117 -40
- package/dist/runtime/pi-model-catalog.cjs +2 -2
- package/dist/runtime/pi-model-catalog.mjs +2 -2
- package/dist/runtime/pi-profile-migration.cjs +556 -0
- package/dist/runtime/pi-profile-migration.mjs +515 -0
- package/dist/runtime/pi-provider-config.cjs +1 -1
- package/dist/runtime/pi-provider-config.mjs +1 -1
- package/dist/runtime/runtime-adapters.mjs +97 -11
- package/dist/runtime/runtime-host.cjs +162 -39
- package/dist/runtime/runtime-host.mjs +163 -40
- package/dist/runtime/runtime-readiness.cjs +6 -4
- package/dist/runtime/runtime-readiness.mjs +6 -4
- package/package.json +3 -3
|
@@ -6,12 +6,14 @@ import { createInterface } from "node:readline";
|
|
|
6
6
|
import { isPiThinkingLevel } from "./pi-model-catalog.mjs";
|
|
7
7
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
8
8
|
import { internalCommandSpec } from "../app/internal-command.mjs";
|
|
9
|
-
import { piAgentDirectory } from "./pi-provider-config.mjs";
|
|
9
|
+
import { BUNDLED_PI_VERSION, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
10
|
+
import { recordPiRuntimeArtifactProvenance } from "./pi-artifact-provenance.mjs";
|
|
11
|
+
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
10
12
|
import { resolvePiSubagentExtensionArg } from "./pi-subagent-injection.mjs";
|
|
11
13
|
import { resolvePiBashTimeoutExtensionArg } from "./pi-bash-timeout-injection.mjs";
|
|
12
14
|
import { classifyRuntimePrerequisite, probeNativeRuntimeReadiness, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
13
15
|
import { buildStrictProviderErrorInput, classifyStrictProviderError, INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON, } from "./provider-error-classifier.mjs";
|
|
14
|
-
import { assertEffectivePiCompactionSettings, assertNoProjectPiCompactionOverride, prepareOwnedPiDirectory, readOwnedPiSettings, verifyPiCapabilities, } from "./pi-compaction-recovery.mjs";
|
|
16
|
+
import { assertEffectivePiCompactionSettings, assertNoProjectPiCompactionOverride, calculatePiCompactionSettings, parsePiExecutableVersion, prepareOwnedPiDirectory, readOwnedPiSettings, verifyPiCapabilities, writeOwnedPiSettings, } from "./pi-compaction-recovery.mjs";
|
|
15
17
|
class EventSession {
|
|
16
18
|
listeners = new Set();
|
|
17
19
|
subscribe(listener) {
|
|
@@ -452,10 +454,12 @@ class PiSession extends EventSession {
|
|
|
452
454
|
observedAgentEndEpochs = new Set();
|
|
453
455
|
firstOutputObserved = false;
|
|
454
456
|
toolCallOpen = false;
|
|
457
|
+
piPolicyManaged;
|
|
455
458
|
constructor(sdk, distribution) {
|
|
456
459
|
super();
|
|
457
460
|
this.sdk = sdk;
|
|
458
461
|
this.distribution = distribution;
|
|
462
|
+
this.piPolicyManaged = sdk.policyManaged ?? typeof sdk.validatePolicy === "function";
|
|
459
463
|
const result = sdk.subscribe?.((event) => this.onEvent(event));
|
|
460
464
|
if (typeof result === "function")
|
|
461
465
|
this.unsubscribe = result;
|
|
@@ -476,8 +480,22 @@ class PiSession extends EventSession {
|
|
|
476
480
|
async compact(customInstructions) {
|
|
477
481
|
if (!this.sdk.compact)
|
|
478
482
|
throw new Error("Pi compact RPC is unavailable");
|
|
483
|
+
await this.sdk.validatePolicy?.();
|
|
479
484
|
return this.sdk.compact(customInstructions);
|
|
480
485
|
}
|
|
486
|
+
async getContextUsage() {
|
|
487
|
+
if (!this.sdk.getSessionStats)
|
|
488
|
+
return null;
|
|
489
|
+
await this.sdk.validatePolicy?.();
|
|
490
|
+
const stats = await this.sdk.getSessionStats();
|
|
491
|
+
const usage = stats?.contextUsage;
|
|
492
|
+
const tokens = Number(usage?.tokens);
|
|
493
|
+
const contextWindow = Number(usage?.contextWindow);
|
|
494
|
+
if (!Number.isFinite(tokens) || tokens < 0 || !Number.isSafeInteger(contextWindow) || contextWindow <= 0) {
|
|
495
|
+
throw new Error("Pi get_session_stats returned invalid contextUsage");
|
|
496
|
+
}
|
|
497
|
+
return { tokens, contextWindow };
|
|
498
|
+
}
|
|
481
499
|
async close(_reason) { this.unsubscribe?.(); await this.sdk.dispose?.(); }
|
|
482
500
|
async enqueue(input, operation) {
|
|
483
501
|
if (this.ownedInputIds.size === 0)
|
|
@@ -492,7 +510,8 @@ class PiSession extends EventSession {
|
|
|
492
510
|
this.emitObservation("rpc_submit");
|
|
493
511
|
}
|
|
494
512
|
try {
|
|
495
|
-
|
|
513
|
+
const accepted = this.sdk.validatePolicy ? this.sdk.validatePolicy().then(operation) : operation();
|
|
514
|
+
await accepted;
|
|
496
515
|
this.awaitingAcknowledgement.delete(input.inputId);
|
|
497
516
|
if (ownsRpcObservation && !this.observedAcceptedEpochs.has(epoch)) {
|
|
498
517
|
this.observedAcceptedEpochs.add(epoch);
|
|
@@ -788,6 +807,28 @@ export function createPiSessionManager(input) {
|
|
|
788
807
|
throw error;
|
|
789
808
|
}
|
|
790
809
|
}
|
|
810
|
+
async function discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel, env, spawn, rpcOptions) {
|
|
811
|
+
const probeArgs = [...commandPrefix, "--mode", "rpc", "--no-session", "--no-extensions",
|
|
812
|
+
...(requestedModel ? ["--model", requestedModel] : [])];
|
|
813
|
+
const probe = spawn(command, probeArgs, {
|
|
814
|
+
cwd: input.workspaceDir,
|
|
815
|
+
env,
|
|
816
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
817
|
+
});
|
|
818
|
+
const client = new PiRpcClient(probe, rpcOptions);
|
|
819
|
+
try {
|
|
820
|
+
const state = await client.request("get_state");
|
|
821
|
+
const contextWindow = state.model?.contextWindow;
|
|
822
|
+
calculatePiCompactionSettings(contextWindow);
|
|
823
|
+
const model = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
|
|
824
|
+
if (!model)
|
|
825
|
+
throw new Error("Pi isolated probe did not report an effective model");
|
|
826
|
+
return { model, contextWindow: contextWindow };
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
await client.close();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
791
832
|
function writePrivateAtomic(file, content) {
|
|
792
833
|
try {
|
|
793
834
|
if (fs.lstatSync(file).isSymbolicLink())
|
|
@@ -812,11 +853,15 @@ function writePrivateAtomic(file, content) {
|
|
|
812
853
|
}
|
|
813
854
|
class PiRpcBackend {
|
|
814
855
|
client;
|
|
856
|
+
policy;
|
|
857
|
+
policyManaged;
|
|
815
858
|
sessionId;
|
|
816
859
|
model;
|
|
817
860
|
thinkingLevel;
|
|
818
|
-
constructor(client, state) {
|
|
861
|
+
constructor(client, state, policy) {
|
|
819
862
|
this.client = client;
|
|
863
|
+
this.policy = policy;
|
|
864
|
+
this.policyManaged = Boolean(policy);
|
|
820
865
|
this.sessionId = state.sessionId ?? null;
|
|
821
866
|
if (state.model?.provider && state.model.id)
|
|
822
867
|
this.model = {
|
|
@@ -830,6 +875,22 @@ class PiRpcBackend {
|
|
|
830
875
|
steer(text) { return this.client.request("steer", { message: text }); }
|
|
831
876
|
abort() { return this.client.request("abort"); }
|
|
832
877
|
compact(customInstructions) { return this.client.requestCompact(customInstructions); }
|
|
878
|
+
getSessionStats() { return this.client.request("get_session_stats"); }
|
|
879
|
+
async validatePolicy() {
|
|
880
|
+
if (!this.policy)
|
|
881
|
+
return;
|
|
882
|
+
const state = await this.client.request("get_state");
|
|
883
|
+
const effectiveModel = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
|
|
884
|
+
if (effectiveModel !== this.policy.model || state.model?.contextWindow !== this.policy.contextWindow) {
|
|
885
|
+
throw new Error("Pi model or context window changed after startup; compaction policy is no longer safe");
|
|
886
|
+
}
|
|
887
|
+
const ownedSettings = readOwnedPiSettings(this.policy.ownedPiDirectory);
|
|
888
|
+
assertEffectivePiCompactionSettings({ contextWindow: state.model?.contextWindow, compaction: {
|
|
889
|
+
enabled: state.autoCompactionEnabled,
|
|
890
|
+
reserveTokens: ownedSettings.compaction?.reserveTokens,
|
|
891
|
+
keepRecentTokens: ownedSettings.compaction?.keepRecentTokens,
|
|
892
|
+
} });
|
|
893
|
+
}
|
|
833
894
|
getState() { return this.client.request("get_state"); }
|
|
834
895
|
dispose() { return this.client.close(); }
|
|
835
896
|
subscribe(listener) {
|
|
@@ -861,6 +922,7 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
861
922
|
? piAgentDirectory(mergedEnv.LARKIN_CONFIG_DIR, input.agentId)
|
|
862
923
|
: path.join(stateRoot, "pi-agent");
|
|
863
924
|
prepareOwnedPiDirectory(ownedPiDirectory);
|
|
925
|
+
traceProcessBoundary(mergedEnv, "pi-rpc:child-env", { configDir: mergedEnv.LARKIN_CONFIG_DIR, agentId: input.agentId, targetDir: ownedPiDirectory, childEnvConfigDir: mergedEnv.LARKIN_CONFIG_DIR || null, childEnvHome: mergedEnv.HOME || null });
|
|
864
926
|
const runtimeDir = path.join(stateRoot, "runtime");
|
|
865
927
|
fs.mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
|
|
866
928
|
const promptFile = path.join(runtimeDir, "pi-standing-prompt.md");
|
|
@@ -877,24 +939,40 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
877
939
|
const builtin = mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin";
|
|
878
940
|
const builtinSpec = builtin ? internalCommandSpec("pi-rpc", [], mergedEnv) : null;
|
|
879
941
|
const command = builtinSpec?.command ?? dependencies.piCommand ?? dependencies.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
880
|
-
const
|
|
942
|
+
const commandPrefix = builtinSpec?.args ?? dependencies.piCommandArgs ?? [];
|
|
943
|
+
const commandArgs = [...commandPrefix, ...args];
|
|
881
944
|
const reportedVersion = productionSpawn && !builtin
|
|
882
|
-
? String(spawnSync(command, ["--version"], {
|
|
883
|
-
|
|
945
|
+
? parsePiExecutableVersion(String(spawnSync(command, [...commandPrefix, "--version"], {
|
|
946
|
+
cwd: input.workspaceDir, env: mergedEnv, encoding: "utf8", timeout: 5_000,
|
|
947
|
+
}).stdout || ""))
|
|
948
|
+
: BUNDLED_PI_VERSION;
|
|
884
949
|
mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
|
|
885
950
|
if (builtin)
|
|
886
951
|
mergedEnv.PI_TELEMETRY = "0";
|
|
887
|
-
|
|
952
|
+
const extensionArgs = (dependencies.resolvePiProcessExtensionArgs ?? resolvePiProcessExtensionArgs)({
|
|
888
953
|
distribution: builtin ? "builtin" : "external",
|
|
889
954
|
piCommand: command,
|
|
890
955
|
env: mergedEnv,
|
|
891
956
|
platform: process.platform,
|
|
892
|
-
})
|
|
957
|
+
});
|
|
958
|
+
commandArgs.push(...extensionArgs);
|
|
959
|
+
let probedModel = null;
|
|
960
|
+
if (productionSpawn) {
|
|
961
|
+
// Pi reads compaction settings when the process starts. Probe the exact
|
|
962
|
+
// requested model first, then write the matching 15% reserve before the
|
|
963
|
+
// real session starts; no prompt, provider request, package, or extension
|
|
964
|
+
// is loaded by the probe.
|
|
965
|
+
probedModel = await discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel, mergedEnv, spawn, dependencies.piRpcClientOptions);
|
|
966
|
+
writeOwnedPiSettings(ownedPiDirectory, calculatePiCompactionSettings(probedModel.contextWindow));
|
|
967
|
+
}
|
|
968
|
+
const artifactEntriesBeforeSpawn = new Set(fs.readdirSync(ownedPiDirectory));
|
|
969
|
+
const artifactSpawnBoundary = Date.now();
|
|
893
970
|
const child = spawn(command, commandArgs, {
|
|
894
971
|
cwd: input.workspaceDir,
|
|
895
972
|
env: mergedEnv,
|
|
896
973
|
stdio: ["pipe", "pipe", "pipe"],
|
|
897
974
|
});
|
|
975
|
+
traceProcessBoundary(mergedEnv, "pi-rpc:child-spawned", { configDir: mergedEnv.LARKIN_CONFIG_DIR, agentId: input.agentId, targetDir: ownedPiDirectory, childPid: child.pid ?? null });
|
|
898
976
|
const client = new PiRpcClient(child, dependencies.piRpcClientOptions);
|
|
899
977
|
try {
|
|
900
978
|
const [state, available] = await Promise.all([
|
|
@@ -905,6 +983,10 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
905
983
|
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow or configure provider credentials; Larkin will not create a fallback session.");
|
|
906
984
|
const effectiveModel = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
|
|
907
985
|
if (productionSpawn) {
|
|
986
|
+
if (!effectiveModel || !probedModel || effectiveModel !== probedModel.model
|
|
987
|
+
|| state.model?.contextWindow !== probedModel.contextWindow) {
|
|
988
|
+
throw new Error("Pi model or context window changed between the isolated probe and runtime startup");
|
|
989
|
+
}
|
|
908
990
|
const ownedSettings = readOwnedPiSettings(ownedPiDirectory);
|
|
909
991
|
assertEffectivePiCompactionSettings({ contextWindow: state.model?.contextWindow, compaction: {
|
|
910
992
|
enabled: state.autoCompactionEnabled,
|
|
@@ -930,7 +1012,10 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
930
1012
|
}
|
|
931
1013
|
if (requestedEffort && state.thinkingLevel !== requestedEffort)
|
|
932
1014
|
throw new Error(`Pi thinking level ${requestedEffort} was not accepted by effective model ${effectiveModel || "unknown"}`);
|
|
933
|
-
|
|
1015
|
+
recordPiRuntimeArtifactProvenance(ownedPiDirectory, artifactEntriesBeforeSpawn, artifactSpawnBoundary);
|
|
1016
|
+
return new PiRpcBackend(client, state, productionSpawn ? {
|
|
1017
|
+
model: effectiveModel, contextWindow: state.model?.contextWindow, ownedPiDirectory,
|
|
1018
|
+
} : undefined);
|
|
934
1019
|
}
|
|
935
1020
|
catch (error) {
|
|
936
1021
|
await client.close();
|
|
@@ -1035,7 +1120,8 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
|
|
|
1035
1120
|
capabilities: CAPABILITIES[id],
|
|
1036
1121
|
async probe(input) {
|
|
1037
1122
|
const readiness = await probeNativeRuntimeReadiness({ runtime: id, agentId: input.agentId, cwd: input.workspaceDir,
|
|
1038
|
-
env: { ...dependencies.env, ...input.env }, command: configuredCommand
|
|
1123
|
+
env: { ...dependencies.env, ...input.env }, command: configuredCommand,
|
|
1124
|
+
...(id === "pi" && dependencies.piCommandArgs ? { commandArgs: dependencies.piCommandArgs } : {}) });
|
|
1039
1125
|
resolvedExecutable = readiness.state === "ready" ? readiness.executable ?? null : null;
|
|
1040
1126
|
return readiness;
|
|
1041
1127
|
},
|
|
@@ -397,18 +397,22 @@ function createRuntimeHost(options) {
|
|
|
397
397
|
};
|
|
398
398
|
const resultFor = (agent, record, result) => {
|
|
399
399
|
if (result.status === "accepted") {
|
|
400
|
-
|
|
400
|
+
if (record.errorCategory !== "context_window") {
|
|
401
|
+
delete record.reason;
|
|
402
|
+
delete record.errorCategory;
|
|
403
|
+
}
|
|
401
404
|
delete record.retryable;
|
|
402
|
-
delete record.errorCategory;
|
|
403
405
|
const finalRecord = setRecord(agent, record, "accepted");
|
|
404
406
|
if (finalRecord.status !== "consumed")
|
|
405
407
|
emit({ type: "delivery", agentId: agent.config.agentId,
|
|
406
408
|
deliveryId: record.deliveryId, messageId: record.messageId, status: "accepted" });
|
|
407
409
|
return { status: "accepted", deliveryId: record.deliveryId };
|
|
408
410
|
}
|
|
409
|
-
|
|
411
|
+
if (record.errorCategory !== "context_window")
|
|
412
|
+
delete record.errorCategory;
|
|
410
413
|
const retryable = result.status === "deferred" || result.retryable;
|
|
411
|
-
record.
|
|
414
|
+
if (record.errorCategory !== "context_window")
|
|
415
|
+
record.reason = result.reason;
|
|
412
416
|
record.retryable = retryable;
|
|
413
417
|
const finalRecord = setRecord(agent, record, retryable ? "pending" : "error");
|
|
414
418
|
if (finalRecord.status === "consumed")
|
|
@@ -420,6 +424,22 @@ function createRuntimeHost(options) {
|
|
|
420
424
|
: { status: "error", deliveryId: record.deliveryId, reason: result.reason, retryable: false };
|
|
421
425
|
};
|
|
422
426
|
const submit = async (agent, record, busy) => {
|
|
427
|
+
const proactive = agent.piProactiveCompaction
|
|
428
|
+
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
429
|
+
&& agent.piProactiveCompactionSession === agent.session
|
|
430
|
+
? agent.piProactiveCompaction : null;
|
|
431
|
+
if (proactive && await proactive === "failed") {
|
|
432
|
+
return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
|
|
433
|
+
}
|
|
434
|
+
if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation) {
|
|
435
|
+
return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
|
|
436
|
+
}
|
|
437
|
+
if (agent.adapter.id === "pi" && !busy && !agent.busy && !agent.turnInProgress && agent.session) {
|
|
438
|
+
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
439
|
+
if (idleGate && await idleGate === "failed") {
|
|
440
|
+
return { status: "deferred", deliveryId: record.deliveryId, reason: "Pi proactive compaction failed for the current session generation" };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
423
443
|
agent.submitting = true;
|
|
424
444
|
if (!busy)
|
|
425
445
|
agent.busy = true; // Reserve the turn before prompt() can yield.
|
|
@@ -459,9 +479,11 @@ function createRuntimeHost(options) {
|
|
|
459
479
|
if (!busy)
|
|
460
480
|
agent.busy = false;
|
|
461
481
|
const reason = error instanceof Error ? error.message : String(error);
|
|
462
|
-
record.
|
|
482
|
+
if (record.errorCategory !== "context_window") {
|
|
483
|
+
record.reason = reason;
|
|
484
|
+
delete record.errorCategory;
|
|
485
|
+
}
|
|
463
486
|
record.retryable = true;
|
|
464
|
-
delete record.errorCategory;
|
|
465
487
|
setRecord(agent, record, "pending");
|
|
466
488
|
emit({ type: "delivery", agentId: agent.config.agentId, deliveryId: record.deliveryId, messageId: record.messageId, status: "deferred", reason });
|
|
467
489
|
return { status: "deferred", deliveryId: record.deliveryId, reason };
|
|
@@ -475,16 +497,41 @@ function createRuntimeHost(options) {
|
|
|
475
497
|
}
|
|
476
498
|
};
|
|
477
499
|
const retryPending = async (agent) => {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
500
|
+
if (agent.retryPendingInFlight)
|
|
501
|
+
return agent.retryPendingInFlight;
|
|
502
|
+
const run = (async () => {
|
|
503
|
+
reconcileExternalConsumption(agent);
|
|
504
|
+
const proactive = agent.piProactiveCompaction
|
|
505
|
+
&& agent.piProactiveCompactionGeneration === agent.generation
|
|
506
|
+
&& agent.piProactiveCompactionSession === agent.session
|
|
507
|
+
? agent.piProactiveCompaction : null;
|
|
508
|
+
if (proactive && await proactive === "failed")
|
|
509
|
+
return;
|
|
510
|
+
if (agent.submitting || agent.starting) {
|
|
511
|
+
agent.retryAfterSubmit = true;
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (agent.adapter.id === "pi" && agent.piProactiveCompactionFailedGeneration === agent.generation)
|
|
515
|
+
return;
|
|
516
|
+
if (agent.busy || agent.turnInProgress || !agent.session)
|
|
517
|
+
return;
|
|
518
|
+
if (agent.adapter.id === "pi") {
|
|
519
|
+
const idleGate = proactivelyCompactPiAtIdle(agent, agent.session);
|
|
520
|
+
if (idleGate && await idleGate === "failed")
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
|
|
524
|
+
if (record)
|
|
525
|
+
await submit(agent, record, false);
|
|
526
|
+
})();
|
|
527
|
+
agent.retryPendingInFlight = run;
|
|
528
|
+
try {
|
|
529
|
+
await run;
|
|
530
|
+
}
|
|
531
|
+
finally {
|
|
532
|
+
if (agent.retryPendingInFlight === run)
|
|
533
|
+
agent.retryPendingInFlight = null;
|
|
482
534
|
}
|
|
483
|
-
if (agent.busy || agent.turnInProgress || !agent.session)
|
|
484
|
-
return;
|
|
485
|
-
const record = [...agent.records.values()].find((candidate) => candidate.status === "pending" || candidate.status === "submitting");
|
|
486
|
-
if (record)
|
|
487
|
-
await submit(agent, record, false);
|
|
488
535
|
};
|
|
489
536
|
const scheduleRecreate = (agent, reason) => {
|
|
490
537
|
if (agent.stopped || agent.retryTimer || agent.starting || agent.session)
|
|
@@ -502,8 +549,12 @@ function createRuntimeHost(options) {
|
|
|
502
549
|
agent.retryTimer = null;
|
|
503
550
|
if (agent.stopped)
|
|
504
551
|
return;
|
|
505
|
-
void ensureSession(agent).then(() => {
|
|
506
|
-
|
|
552
|
+
void ensureSession(agent).then(async () => {
|
|
553
|
+
const session = agent.session;
|
|
554
|
+
const proactive = session ? proactivelyCompactPiAtIdle(agent, session) : null;
|
|
555
|
+
if (proactive && await proactive === "failed")
|
|
556
|
+
return;
|
|
557
|
+
await retryPending(agent);
|
|
507
558
|
}).catch((error) => {
|
|
508
559
|
const readiness = error instanceof runtime_readiness_js_1.RuntimePrerequisiteError ? error.readiness
|
|
509
560
|
: (0, runtime_readiness_js_1.classifyRuntimePrerequisite)(agent.adapter.id, error);
|
|
@@ -614,7 +665,6 @@ function createRuntimeHost(options) {
|
|
|
614
665
|
machine.retrySubmitted();
|
|
615
666
|
record.status = "pending";
|
|
616
667
|
record.retryable = true;
|
|
617
|
-
record.reason = "Pi manual compaction succeeded; retrying the same delivery input";
|
|
618
668
|
record.updatedAt = now();
|
|
619
669
|
persist(agent);
|
|
620
670
|
queueMicrotask(() => { if (!agent.stopped)
|
|
@@ -623,14 +673,19 @@ function createRuntimeHost(options) {
|
|
|
623
673
|
else if (action === "fresh_session_fallback") {
|
|
624
674
|
agent.busy = false;
|
|
625
675
|
agent.turnInProgress = false;
|
|
626
|
-
|
|
627
|
-
|
|
676
|
+
const recoveryReason = machine.recordSnapshot.fallbackReason || "Pi compaction recovery fallback";
|
|
677
|
+
// Delivery proof must remain the exact provider-classified context error.
|
|
678
|
+
// Generic Pi recovery text belongs only in the breaker and logs.
|
|
679
|
+
if (record.errorCategory === "context_window" && typeof record.reason === "string") {
|
|
628
680
|
record.retryable = false;
|
|
629
|
-
record.errorCategory = "context_window";
|
|
630
681
|
setRecord(agent, record, "error");
|
|
682
|
+
if (!agent.compactionRecoveryInFlight.has(key)) {
|
|
683
|
+
agent.compactionRecoveryInFlight.add(key);
|
|
684
|
+
void recoverContextOverflowInternal(agent.config.agentId, key, recoveryReason, key)
|
|
685
|
+
.catch((error) => log("Pi context fallback failed", String(error)))
|
|
686
|
+
.finally(() => agent.compactionRecoveryInFlight.delete(key));
|
|
687
|
+
}
|
|
631
688
|
}
|
|
632
|
-
void recoverContextOverflowInternal(agent.config.agentId, key, machine.recordSnapshot.fallbackReason || "Pi compaction recovery fallback", key)
|
|
633
|
-
.catch((error) => log("Pi context fallback failed", String(error)));
|
|
634
689
|
}
|
|
635
690
|
},
|
|
636
691
|
});
|
|
@@ -665,6 +720,8 @@ function createRuntimeHost(options) {
|
|
|
665
720
|
});
|
|
666
721
|
};
|
|
667
722
|
const recoverStalePiCompaction = async (agent) => {
|
|
723
|
+
if (!agent.stateStore)
|
|
724
|
+
return;
|
|
668
725
|
const breaker = agent.compactionBreaker;
|
|
669
726
|
if (!breaker)
|
|
670
727
|
return;
|
|
@@ -679,14 +736,59 @@ function createRuntimeHost(options) {
|
|
|
679
736
|
|| candidate.input.inputId === stale.inputId);
|
|
680
737
|
if (!record)
|
|
681
738
|
return;
|
|
682
|
-
|
|
739
|
+
const recoveryReason = stale.fallbackReason || "Pi compaction recovery resumed after restart";
|
|
740
|
+
if (record.errorCategory !== "context_window" || typeof record.reason !== "string")
|
|
741
|
+
return;
|
|
683
742
|
record.retryable = false;
|
|
684
|
-
record.errorCategory = "context_window";
|
|
685
743
|
setRecord(agent, record, "error");
|
|
686
|
-
breaker.forceFallback(stale.key,
|
|
744
|
+
breaker.forceFallback(stale.key, recoveryReason);
|
|
687
745
|
agent.busy = false;
|
|
688
746
|
agent.turnInProgress = false;
|
|
689
|
-
await recoverContextOverflowInternal(agent.config.agentId, stale.key,
|
|
747
|
+
await recoverContextOverflowInternal(agent.config.agentId, stale.key, recoveryReason, stale.key);
|
|
748
|
+
};
|
|
749
|
+
const proactivelyCompactPiAtIdle = (agent, session) => {
|
|
750
|
+
if (agent.adapter.id !== "pi" || session.piPolicyManaged === false
|
|
751
|
+
|| !session.getContextUsage || !session.compact)
|
|
752
|
+
return null;
|
|
753
|
+
if (agent.session !== session || agent.stopped || agent.busy || agent.turnInProgress || agent.submitting || agent.starting
|
|
754
|
+
|| agent.compactionRecoveryInFlight.size > 0
|
|
755
|
+
|| agent.compactionMachines.size > 0 || agent.piOverflowCompactionFailed.size > 0)
|
|
756
|
+
return null;
|
|
757
|
+
if (agent.piProactiveCompactionFailedGeneration === agent.generation)
|
|
758
|
+
return null;
|
|
759
|
+
const generation = agent.generation;
|
|
760
|
+
if (agent.piProactiveCompaction && agent.piProactiveCompactionGeneration === generation
|
|
761
|
+
&& agent.piProactiveCompactionSession === session)
|
|
762
|
+
return agent.piProactiveCompaction;
|
|
763
|
+
const task = (async () => {
|
|
764
|
+
const before = await session.getContextUsage();
|
|
765
|
+
if (!before || agent.session !== session || agent.stopped || agent.busy || agent.turnInProgress || agent.submitting
|
|
766
|
+
|| agent.compactionRecoveryInFlight.size > 0 || agent.compactionMachines.size > 0
|
|
767
|
+
|| agent.piOverflowCompactionFailed.size > 0)
|
|
768
|
+
return "noop";
|
|
769
|
+
if (!(0, pi_compaction_recovery_js_1.isPiNativeCompactionRequired)(before.tokens, before.contextWindow))
|
|
770
|
+
return "noop";
|
|
771
|
+
await session.compact();
|
|
772
|
+
const after = await session.getContextUsage();
|
|
773
|
+
if (!after || after.contextWindow !== before.contextWindow || after.tokens >= before.tokens) {
|
|
774
|
+
throw new Error("Pi proactive compaction did not reduce the verified context usage");
|
|
775
|
+
}
|
|
776
|
+
return "succeeded";
|
|
777
|
+
})();
|
|
778
|
+
const tracked = task.catch((error) => {
|
|
779
|
+
agent.piProactiveCompactionFailedGeneration = generation;
|
|
780
|
+
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
781
|
+
error: "Pi proactive compaction failed; the current session generation is degraded" });
|
|
782
|
+
log("Pi proactive compaction failed", String(error));
|
|
783
|
+
return "failed";
|
|
784
|
+
}).finally(() => {
|
|
785
|
+
if (agent.piProactiveCompaction === tracked)
|
|
786
|
+
agent.piProactiveCompaction = null;
|
|
787
|
+
});
|
|
788
|
+
agent.piProactiveCompactionGeneration = generation;
|
|
789
|
+
agent.piProactiveCompactionSession = session;
|
|
790
|
+
agent.piProactiveCompaction = tracked;
|
|
791
|
+
return tracked;
|
|
690
792
|
};
|
|
691
793
|
const observe = (agent, session, event) => {
|
|
692
794
|
if (agent.session !== session)
|
|
@@ -728,7 +830,6 @@ function createRuntimeHost(options) {
|
|
|
728
830
|
agent.config.sessionId = event.sessionId;
|
|
729
831
|
emit({ type: "session", agentId: agent.config.agentId, runtime: agent.adapter.id, sessionId: event.sessionId, launchId: agent.launchId,
|
|
730
832
|
...(event.model ? { model: event.model } : {}), ...(event.reasoningEffort ? { reasoningEffort: event.reasoningEffort } : {}) });
|
|
731
|
-
void retryPending(agent);
|
|
732
833
|
}
|
|
733
834
|
else if (event.type === "turn-start") {
|
|
734
835
|
agent.busy = true;
|
|
@@ -764,7 +865,11 @@ function createRuntimeHost(options) {
|
|
|
764
865
|
};
|
|
765
866
|
emit({ type: "agent-status", agentId: agent.config.agentId, status: "active", readiness: agent.readiness });
|
|
766
867
|
}
|
|
767
|
-
|
|
868
|
+
const proactive = proactivelyCompactPiAtIdle(agent, session);
|
|
869
|
+
void (proactive ?? Promise.resolve("noop")).then((outcome) => {
|
|
870
|
+
if (outcome !== "failed")
|
|
871
|
+
return retryPending(agent);
|
|
872
|
+
});
|
|
768
873
|
}
|
|
769
874
|
else if (event.type === "activity") {
|
|
770
875
|
if (agent.turnInProgress && event.activity !== "internal")
|
|
@@ -790,8 +895,13 @@ function createRuntimeHost(options) {
|
|
|
790
895
|
if (agent.piOverflowCompactionFailed.has(record.input.inputId)) {
|
|
791
896
|
agent.busy = false;
|
|
792
897
|
agent.turnInProgress = false;
|
|
793
|
-
|
|
794
|
-
|
|
898
|
+
const recoveryKey = `${record.deliveryId}:${record.input.inputId}`;
|
|
899
|
+
if (!agent.compactionRecoveryInFlight.has(recoveryKey)) {
|
|
900
|
+
agent.compactionRecoveryInFlight.add(recoveryKey);
|
|
901
|
+
void recoverContextOverflowInternal(agent.config.agentId, recoveryKey, "native Pi overflow compaction failed", recoveryKey)
|
|
902
|
+
.catch((error) => log("Pi native compaction fallback failed", String(error)))
|
|
903
|
+
.finally(() => agent.compactionRecoveryInFlight.delete(recoveryKey));
|
|
904
|
+
}
|
|
795
905
|
return;
|
|
796
906
|
}
|
|
797
907
|
// Pi emits this terminal error only after agent_settled; release the
|
|
@@ -976,7 +1086,6 @@ function createRuntimeHost(options) {
|
|
|
976
1086
|
continue;
|
|
977
1087
|
candidate.status = "pending";
|
|
978
1088
|
candidate.retryable = true;
|
|
979
|
-
candidate.reason = "context-window recovery rearmed the retained Inbox delivery";
|
|
980
1089
|
candidate.updatedAt = now();
|
|
981
1090
|
}
|
|
982
1091
|
};
|
|
@@ -1022,7 +1131,11 @@ function createRuntimeHost(options) {
|
|
|
1022
1131
|
}
|
|
1023
1132
|
emit({ type: "agent-status", agentId, status: "active", readiness });
|
|
1024
1133
|
await oldSession.close("Pi context fallback committed").catch((error) => log("previous Pi session close after fallback failed", String(error)));
|
|
1025
|
-
|
|
1134
|
+
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1135
|
+
void (proactive ?? Promise.resolve("noop")).then((outcome) => {
|
|
1136
|
+
if (outcome !== "failed")
|
|
1137
|
+
return retryPending(agent);
|
|
1138
|
+
});
|
|
1026
1139
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
1027
1140
|
runtimeReady: true, pendingCount: remainingPendingCount, rearmedCount,
|
|
1028
1141
|
replayStatus: remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
|
|
@@ -1334,7 +1447,6 @@ function createRuntimeHost(options) {
|
|
|
1334
1447
|
continue;
|
|
1335
1448
|
record.status = "pending";
|
|
1336
1449
|
record.retryable = true;
|
|
1337
|
-
record.reason = "context-window recovery rearmed the retained Inbox delivery";
|
|
1338
1450
|
record.updatedAt = now();
|
|
1339
1451
|
}
|
|
1340
1452
|
});
|
|
@@ -1349,7 +1461,11 @@ function createRuntimeHost(options) {
|
|
|
1349
1461
|
durableRollback = null;
|
|
1350
1462
|
await oldSession.close("context-window recovery committed")
|
|
1351
1463
|
.catch((error) => log("previous runtime close after context-window recovery failed", String(error)));
|
|
1352
|
-
|
|
1464
|
+
const proactive = proactivelyCompactPiAtIdle(agent, fresh);
|
|
1465
|
+
void (proactive ?? Promise.resolve("noop")).then((outcome) => {
|
|
1466
|
+
if (outcome !== "failed")
|
|
1467
|
+
return retryPending(agent);
|
|
1468
|
+
});
|
|
1353
1469
|
return { generationChanged: true, sessionChanged: oldSessionId !== fresh.sessionId, turns: 0,
|
|
1354
1470
|
runtimeReady: true, pendingCount: rearmed.remainingPendingCount, rearmedCount: rearmed.rearmedCount,
|
|
1355
1471
|
replayStatus: rearmed.remainingPendingCount > 0 ? "pending" : "consumed", sessionId: fresh.sessionId };
|
|
@@ -1417,16 +1533,19 @@ function createRuntimeHost(options) {
|
|
|
1417
1533
|
: readDeliveryFile(config.stateDir ? node_path_1.default.join(config.stateDir, "runtime-deliveries.json") : null);
|
|
1418
1534
|
const agent = { config, adapter: options.adapterFor(config.runtime), session: null,
|
|
1419
1535
|
launchId: node_crypto_1.default.randomUUID(), busy: false, submitting: false, starting: null, retryAfterSubmit: false,
|
|
1536
|
+
retryPendingInFlight: null,
|
|
1420
1537
|
records: new Map(persisted.records.map((record) => [record.deliveryId, record])),
|
|
1421
1538
|
byMessage: new Map(persisted.records.map((record) => [record.messageId, record.deliveryId])),
|
|
1422
1539
|
generation: 0, poller: null, retryTimer: null, recreateAttempts: 0,
|
|
1423
1540
|
stabilityTimer: null, recreateReason: null, stopped: false, disabledReason: null,
|
|
1424
1541
|
configurationRecovery: null, stateStore,
|
|
1425
|
-
compactionBreaker: config.runtime === "pi"
|
|
1542
|
+
compactionBreaker: config.runtime === "pi" && stateStore
|
|
1426
1543
|
? new pi_compaction_recovery_js_1.PiCompactionBreaker(config.stateDir ?? node_path_1.default.join(config.workspaceDir, ".larkin", "agents", config.agentId), {
|
|
1427
|
-
withLock:
|
|
1544
|
+
withLock: (operation) => stateStore.withInboxTransaction(operation),
|
|
1428
1545
|
}) : undefined,
|
|
1429
|
-
compactionMachines: new Map(),
|
|
1546
|
+
compactionMachines: new Map(), compactionRecoveryInFlight: new Set(), piOverflowCompactionFailed: new Set(),
|
|
1547
|
+
piProactiveCompaction: null, piProactiveCompactionGeneration: null, piProactiveCompactionSession: null,
|
|
1548
|
+
piProactiveCompactionFailedGeneration: null, readiness: null,
|
|
1430
1549
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false };
|
|
1431
1550
|
const startupConsumed = [];
|
|
1432
1551
|
const startupQuarantined = [];
|
|
@@ -1459,8 +1578,12 @@ function createRuntimeHost(options) {
|
|
|
1459
1578
|
try {
|
|
1460
1579
|
await ensureSession(agent);
|
|
1461
1580
|
await recoverStalePiCompaction(agent);
|
|
1581
|
+
const startupSession = agent.session;
|
|
1582
|
+
const proactive = startupSession ? proactivelyCompactPiAtIdle(agent, startupSession) : null;
|
|
1583
|
+
const proactiveOutcome = proactive ? await proactive : "noop";
|
|
1462
1584
|
activeCount += 1;
|
|
1463
|
-
|
|
1585
|
+
if (proactiveOutcome !== "failed")
|
|
1586
|
+
await retryPending(agent);
|
|
1464
1587
|
}
|
|
1465
1588
|
catch (error) {
|
|
1466
1589
|
const reason = error instanceof Error ? error.message : String(error);
|