larkin 0.4.17 → 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/builtin-pi-assets.cjs +117 -0
- package/dist/runtime/builtin-pi-assets.mjs +72 -0
- package/dist/runtime/pi-inline-extensions.mjs +5 -0
- package/dist/runtime/pi-model-catalog.cjs +16 -2
- package/dist/runtime/pi-model-catalog.mjs +16 -2
- package/dist/runtime/pi-profile-migration.cjs +113 -13
- package/dist/runtime/pi-profile-migration.mjs +113 -13
- 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 +68 -20
- package/dist/runtime/runtime-host.cjs +187 -14
- package/dist/runtime/runtime-host.mjs +187 -14
- package/dist/runtime/runtime-readiness.cjs +9 -2
- package/dist/runtime/runtime-readiness.mjs +9 -2
- package/dist/setup/setup-bind.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/runtime/pi-subagent-ledger.ts
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var PI_SUBAGENT_RECORD_DIRNAME = "pi-subagent-records";
|
|
6
|
+
var PI_SUBAGENT_SESSION_OWNER_ENV = "LARKIN_PI_SESSION_OWNER";
|
|
7
|
+
var TERMINAL_STATUSES = new Set([
|
|
8
|
+
"completed",
|
|
9
|
+
"failed",
|
|
10
|
+
"cancelled",
|
|
11
|
+
"timed_out",
|
|
12
|
+
"orphaned"
|
|
13
|
+
]);
|
|
14
|
+
function isTerminalPiSubagentRecord(record) {
|
|
15
|
+
if (!record || typeof record !== "object")
|
|
16
|
+
return false;
|
|
17
|
+
const status = record.status;
|
|
18
|
+
return typeof status === "string" && status.length > 0 && status !== "running" && status !== "queued";
|
|
19
|
+
}
|
|
20
|
+
function isConsumedTerminalPiSubagentRecord(record) {
|
|
21
|
+
if (!isTerminalPiSubagentRecord(record))
|
|
22
|
+
return false;
|
|
23
|
+
return record.resultConsumed === true;
|
|
24
|
+
}
|
|
25
|
+
function ledgerStatusFromPiSubagentRecord(record) {
|
|
26
|
+
const status = typeof record === "object" && record && "status" in record ? String(record.status || "").trim().toLowerCase() : "";
|
|
27
|
+
if (status === "error")
|
|
28
|
+
return "failed";
|
|
29
|
+
if (status === "aborted")
|
|
30
|
+
return "timed_out";
|
|
31
|
+
if (status === "stopped")
|
|
32
|
+
return "cancelled";
|
|
33
|
+
if (status === "steered")
|
|
34
|
+
return "timed_out";
|
|
35
|
+
return "completed";
|
|
36
|
+
}
|
|
37
|
+
function readPersistedPiSubagentTerminal(file) {
|
|
38
|
+
return readPiSubagentTerminalFile(file, false);
|
|
39
|
+
}
|
|
40
|
+
function writePersistedPiSubagentTerminal(file, input, options) {
|
|
41
|
+
writePiSubagentTerminalFile(file, input, options?.resultConsumed === true);
|
|
42
|
+
}
|
|
43
|
+
function dispatchedSubagentRecordDir(stateDir) {
|
|
44
|
+
return path.join(stateDir, PI_SUBAGENT_RECORD_DIRNAME);
|
|
45
|
+
}
|
|
46
|
+
function sweepAbsentPiSubagentRecordFiles(recordDir, getRecord, options) {
|
|
47
|
+
let names;
|
|
48
|
+
try {
|
|
49
|
+
names = fs.readdirSync(recordDir);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error.code === "ENOENT")
|
|
52
|
+
return [];
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
const removed = [];
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
if (!name || name.startsWith("."))
|
|
58
|
+
continue;
|
|
59
|
+
const file = path.join(recordDir, name);
|
|
60
|
+
try {
|
|
61
|
+
const stat = fs.lstatSync(file);
|
|
62
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
63
|
+
continue;
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const owner = typeof options?.owner === "string" && options.owner.trim() ? options.owner.trim() : undefined;
|
|
68
|
+
if (owner && readPiSubagentRecordOwner(file) !== owner)
|
|
69
|
+
continue;
|
|
70
|
+
const record = getRecord(name);
|
|
71
|
+
if (isTerminalPiSubagentRecord(record)) {
|
|
72
|
+
try {
|
|
73
|
+
writePersistedPiSubagentTerminal(file, {
|
|
74
|
+
taskId: name,
|
|
75
|
+
status: ledgerStatusFromPiSubagentRecord(record)
|
|
76
|
+
}, { resultConsumed: isConsumedTerminalPiSubagentRecord(record) });
|
|
77
|
+
} catch {}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (record != null)
|
|
81
|
+
continue;
|
|
82
|
+
if (readPersistedPiSubagentTerminal(file))
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
fs.unlinkSync(file);
|
|
86
|
+
removed.push(name);
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
return removed;
|
|
90
|
+
}
|
|
91
|
+
function readPiSubagentTerminalFile(file, requireConsumed) {
|
|
92
|
+
if (!file)
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
const stat = fs.lstatSync(file);
|
|
96
|
+
if (stat.isSymbolicLink() || !stat.isFile())
|
|
97
|
+
return null;
|
|
98
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
99
|
+
if (!parsed || typeof parsed !== "object")
|
|
100
|
+
return null;
|
|
101
|
+
if (requireConsumed && parsed.resultConsumed !== true && parsed.consumed !== true)
|
|
102
|
+
return null;
|
|
103
|
+
if (typeof parsed.taskId !== "string" || parsed.taskId.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
if (parsed.status !== "completed" && parsed.status !== "failed" && parsed.status !== "cancelled" && parsed.status !== "timed_out") {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return { taskId: parsed.taskId, status: parsed.status };
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function writePiSubagentTerminalFile(file, input, resultConsumed) {
|
|
114
|
+
const existing = (() => {
|
|
115
|
+
try {
|
|
116
|
+
return fs.lstatSync(file);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
if (existing?.isSymbolicLink())
|
|
122
|
+
throw new Error("subagent record file must not be a symlink");
|
|
123
|
+
const owner = readPiSubagentRecordOwner(file);
|
|
124
|
+
const payload = `${JSON.stringify({
|
|
125
|
+
taskId: input.taskId,
|
|
126
|
+
...resultConsumed ? { resultConsumed: true } : {},
|
|
127
|
+
status: input.status,
|
|
128
|
+
...owner ? { owner } : {}
|
|
129
|
+
})}
|
|
130
|
+
`;
|
|
131
|
+
if (existing?.isFile()) {
|
|
132
|
+
try {
|
|
133
|
+
if (fs.readFileSync(file, "utf8") === payload)
|
|
134
|
+
return;
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
fs.writeFileSync(file, payload, { mode: 384 });
|
|
138
|
+
fs.chmodSync(file, 384);
|
|
139
|
+
}
|
|
140
|
+
function readPiSubagentRecordOwner(file) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
143
|
+
return typeof parsed?.owner === "string" && parsed.owner.trim() ? parsed.owner.trim() : undefined;
|
|
144
|
+
} catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/runtime/pi-subagent-record-watchdog.ts
|
|
150
|
+
var PI_SUBAGENTS_MANAGER = Symbol.for("pi-subagents:manager");
|
|
151
|
+
var DEFAULT_SWEEP_INTERVAL_MS = 15000;
|
|
152
|
+
function piSubagentRecordWatchdog(pi) {
|
|
153
|
+
const stateDir = process.env.LARKIN_STATE_DIR;
|
|
154
|
+
const owner = process.env[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
155
|
+
if (!stateDir || !owner)
|
|
156
|
+
return;
|
|
157
|
+
const recordDir = dispatchedSubagentRecordDir(stateDir);
|
|
158
|
+
const intervalMs = sweepIntervalMs();
|
|
159
|
+
const tick = () => {
|
|
160
|
+
try {
|
|
161
|
+
const manager = globalThis[PI_SUBAGENTS_MANAGER];
|
|
162
|
+
if (typeof manager?.getRecord !== "function")
|
|
163
|
+
return;
|
|
164
|
+
sweepAbsentPiSubagentRecordFiles(recordDir, (taskId) => manager.getRecord(taskId), { owner });
|
|
165
|
+
} catch {}
|
|
166
|
+
};
|
|
167
|
+
const timer = setInterval(tick, intervalMs);
|
|
168
|
+
timer.unref?.();
|
|
169
|
+
const stop = () => {
|
|
170
|
+
clearInterval(timer);
|
|
171
|
+
tick();
|
|
172
|
+
};
|
|
173
|
+
pi.on("session_shutdown", stop);
|
|
174
|
+
}
|
|
175
|
+
function sweepIntervalMs() {
|
|
176
|
+
const raw = Number.parseInt(process.env.LARKIN_PI_SUBAGENT_RECORD_SWEEP_MS || "", 10);
|
|
177
|
+
if (Number.isInteger(raw) && raw > 0)
|
|
178
|
+
return raw;
|
|
179
|
+
return DEFAULT_SWEEP_INTERVAL_MS;
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
piSubagentRecordWatchdog as default
|
|
183
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { dispatchedSubagentRecordDir, PI_SUBAGENT_SESSION_OWNER_ENV, sweepAbsentPiSubagentRecordFiles, } from "./pi-subagent-ledger.mjs";
|
|
2
|
+
const PI_SUBAGENTS_MANAGER = Symbol.for("pi-subagents:manager");
|
|
3
|
+
const DEFAULT_SWEEP_INTERVAL_MS = 15_000;
|
|
4
|
+
/**
|
|
5
|
+
* Pi-process watchdog: delete Larkin record sidecars once AgentManager no longer
|
|
6
|
+
* has the in-memory task. Persist any terminal AgentRecord — including an
|
|
7
|
+
* unconsumed completion Pi may evict after ten minutes — before treating a
|
|
8
|
+
* missing getRecord as a genuine orphan. A get_subagent_result consume also
|
|
9
|
+
* suppresses the canonical notification; bridge that consumed marker before
|
|
10
|
+
* cleanup can delete the sidecar. Leftover transcripts are intentionally
|
|
11
|
+
* ignored. Sidecars are owner-tagged so a staged Pi sharing LARKIN_STATE_DIR
|
|
12
|
+
* cannot sweep another session's records. This extension registers
|
|
13
|
+
* session_shutdown first so the final sweep still sees getRecord.
|
|
14
|
+
*/
|
|
15
|
+
export default function piSubagentRecordWatchdog(pi) {
|
|
16
|
+
const stateDir = process.env.LARKIN_STATE_DIR;
|
|
17
|
+
const owner = process.env[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
18
|
+
if (!stateDir || !owner)
|
|
19
|
+
return;
|
|
20
|
+
const recordDir = dispatchedSubagentRecordDir(stateDir);
|
|
21
|
+
const intervalMs = sweepIntervalMs();
|
|
22
|
+
const tick = () => {
|
|
23
|
+
try {
|
|
24
|
+
const manager = globalThis[PI_SUBAGENTS_MANAGER];
|
|
25
|
+
if (typeof manager?.getRecord !== "function")
|
|
26
|
+
return;
|
|
27
|
+
sweepAbsentPiSubagentRecordFiles(recordDir, (taskId) => manager.getRecord(taskId), { owner });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Best-effort: a transient EACCES/EMFILE must not crash the Pi timer.
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const timer = setInterval(tick, intervalMs);
|
|
34
|
+
timer.unref?.();
|
|
35
|
+
const stop = () => {
|
|
36
|
+
clearInterval(timer);
|
|
37
|
+
tick();
|
|
38
|
+
};
|
|
39
|
+
pi.on("session_shutdown", stop);
|
|
40
|
+
}
|
|
41
|
+
function sweepIntervalMs() {
|
|
42
|
+
const raw = Number.parseInt(process.env.LARKIN_PI_SUBAGENT_RECORD_SWEEP_MS || "", 10);
|
|
43
|
+
if (Number.isInteger(raw) && raw > 0)
|
|
44
|
+
return raw;
|
|
45
|
+
return DEFAULT_SWEEP_INTERVAL_MS;
|
|
46
|
+
}
|
|
@@ -20,6 +20,20 @@ const TERMINAL_STATUS_TOKENS = new Set([
|
|
|
20
20
|
"turn-limit",
|
|
21
21
|
"turn_limit",
|
|
22
22
|
]);
|
|
23
|
+
export function ledgerStatusFromPiNotificationStatus(status) {
|
|
24
|
+
const normalized = status.trim().toLowerCase();
|
|
25
|
+
if (normalized.startsWith("error") || normalized === "error")
|
|
26
|
+
return "failed";
|
|
27
|
+
if (normalized.startsWith("stopped"))
|
|
28
|
+
return "cancelled";
|
|
29
|
+
if (normalized.startsWith("aborted")
|
|
30
|
+
|| normalized.includes("turn limit")
|
|
31
|
+
|| normalized.includes("turn-limit")
|
|
32
|
+
|| normalized.includes("turn_limit")
|
|
33
|
+
|| normalized.includes("steered"))
|
|
34
|
+
return "timed_out";
|
|
35
|
+
return "completed";
|
|
36
|
+
}
|
|
23
37
|
export function isCanonicalTerminalPiSubagentStatus(status) {
|
|
24
38
|
const normalized = status.trim().toLowerCase();
|
|
25
39
|
if (!normalized)
|
|
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
|
+
import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
|
|
6
7
|
import { isPiThinkingLevel } from "./pi-model-catalog.mjs";
|
|
7
8
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
8
9
|
import { internalCommandSpec } from "../app/internal-command.mjs";
|
|
@@ -10,7 +11,9 @@ import { BUNDLED_PI_VERSION, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
|
10
11
|
import { recordPiRuntimeArtifactProvenance } from "./pi-artifact-provenance.mjs";
|
|
11
12
|
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
12
13
|
import { resolvePiSubagentExtensionArg } from "./pi-subagent-injection.mjs";
|
|
13
|
-
import {
|
|
14
|
+
import { extractCanonicalPiSubagentNotification, ledgerStatusFromPiNotificationStatus, } from "./pi-subagents-notification.mjs";
|
|
15
|
+
import { resolvePiSubagentRecordWatchdogExtensionArg } from "./pi-subagent-record-watchdog-injection.mjs";
|
|
16
|
+
import { effectivePiStateDir, extractBackgroundPiSubagentDispatch } from "./pi-subagent-ledger.mjs";
|
|
14
17
|
import { resolvePiBashTimeoutExtensionArg } from "./pi-bash-timeout-injection.mjs";
|
|
15
18
|
import { classifyRuntimePrerequisite, probeNativeRuntimeReadiness, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
16
19
|
import { buildStrictProviderErrorInput, classifyStrictProviderError, INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON, } from "./provider-error-classifier.mjs";
|
|
@@ -454,6 +457,7 @@ class PiSession extends EventSession {
|
|
|
454
457
|
observedCompletedEpochs = new Set();
|
|
455
458
|
observedBackgroundCompletionKeys = new Set();
|
|
456
459
|
pendingUnownedCompletionKeys = new Set();
|
|
460
|
+
pendingUnownedCompletionStatuses = new Map();
|
|
457
461
|
observedAgentEndEpochs = new Set();
|
|
458
462
|
firstOutputObserved = false;
|
|
459
463
|
toolCallOpen = false;
|
|
@@ -556,6 +560,7 @@ class PiSession extends EventSession {
|
|
|
556
560
|
this.observedCompletedEpochs.clear();
|
|
557
561
|
this.observedBackgroundCompletionKeys.clear();
|
|
558
562
|
this.pendingUnownedCompletionKeys.clear();
|
|
563
|
+
this.pendingUnownedCompletionStatuses.clear();
|
|
559
564
|
this.observedAgentEndEpochs.clear();
|
|
560
565
|
this.activeEpoch = null;
|
|
561
566
|
this.settleArmedEpoch = null;
|
|
@@ -605,13 +610,34 @@ class PiSession extends EventSession {
|
|
|
605
610
|
this.observedCompletedEpochs.add(this.activeEpoch);
|
|
606
611
|
this.emitObservation("completed");
|
|
607
612
|
}
|
|
608
|
-
const
|
|
609
|
-
if (
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
613
|
+
const completionNotification = extractCanonicalPiSubagentNotification(event.messages);
|
|
614
|
+
if (completionNotification && !this.observedBackgroundCompletionKeys.has(completionNotification.key)) {
|
|
615
|
+
const completionStatuses = Object.fromEntries(completionNotification.notifications.map((notification) => [
|
|
616
|
+
notification.taskId,
|
|
617
|
+
ledgerStatusFromPiNotificationStatus(notification.status),
|
|
618
|
+
]));
|
|
619
|
+
const owningTurnFailed = event.willRetry === true
|
|
620
|
+
|| this.finalAssistantStopReason === "error"
|
|
621
|
+
|| this.finalAssistantStopReason === "aborted";
|
|
622
|
+
if (this.activeEpoch === null || owningTurnFailed) {
|
|
623
|
+
// Unowned agent_end still has an active Pi session, so prompting
|
|
624
|
+
// before settle is rejected as "Agent is already processing".
|
|
625
|
+
// A failed or retrying owned turn also did not process the
|
|
626
|
+
// notification; do not emit handledInTurn so RuntimeHost can still
|
|
627
|
+
// wake after input-error, retry, or restart.
|
|
628
|
+
this.pendingUnownedCompletionKeys.add(completionNotification.key);
|
|
629
|
+
this.pendingUnownedCompletionStatuses.set(completionNotification.key, completionStatuses);
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
// Already visible in the owned turn. Persist as acknowledged; do not
|
|
633
|
+
// schedule another wake after the parent turn settles.
|
|
634
|
+
this.observedBackgroundCompletionKeys.add(completionNotification.key);
|
|
635
|
+
this.emitObservation("completed", {
|
|
636
|
+
completionKey: completionNotification.key,
|
|
637
|
+
completionStatuses,
|
|
638
|
+
handledInTurn: true,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
615
641
|
}
|
|
616
642
|
}
|
|
617
643
|
else if (event?.type === "agent_settled") {
|
|
@@ -682,6 +708,13 @@ class PiSession extends EventSession {
|
|
|
682
708
|
this.toolCallOpen = false;
|
|
683
709
|
this.emit({ type: "runtime-observation", runtime: "pi", distribution: this.distribution, phase: "tool_result" });
|
|
684
710
|
}
|
|
711
|
+
const dispatched = extractBackgroundPiSubagentDispatch(event);
|
|
712
|
+
if (dispatched) {
|
|
713
|
+
this.emitObservation("background_dispatched", {
|
|
714
|
+
taskId: dispatched.taskId,
|
|
715
|
+
...(dispatched.outputFile ? { outputFile: dispatched.outputFile } : {}),
|
|
716
|
+
});
|
|
717
|
+
}
|
|
685
718
|
}
|
|
686
719
|
else if (event?.type === "message_update" && event.assistantMessageEvent?.delta) {
|
|
687
720
|
if (!this.firstOutputObserved) {
|
|
@@ -697,9 +730,14 @@ class PiSession extends EventSession {
|
|
|
697
730
|
if (this.observedBackgroundCompletionKeys.has(completionKey))
|
|
698
731
|
continue;
|
|
699
732
|
this.observedBackgroundCompletionKeys.add(completionKey);
|
|
700
|
-
this.
|
|
733
|
+
const completionStatuses = this.pendingUnownedCompletionStatuses.get(completionKey);
|
|
734
|
+
this.emitObservation("completed", {
|
|
735
|
+
completionKey,
|
|
736
|
+
...(completionStatuses ? { completionStatuses } : {}),
|
|
737
|
+
});
|
|
701
738
|
}
|
|
702
739
|
this.pendingUnownedCompletionKeys.clear();
|
|
740
|
+
this.pendingUnownedCompletionStatuses.clear();
|
|
703
741
|
}
|
|
704
742
|
emitObservation(phase, fields = {}) {
|
|
705
743
|
const inputId = this.oldestOwnedInput();
|
|
@@ -937,6 +975,11 @@ export function resolvePiProcessExtensionArgs(input, resolvers = {}) {
|
|
|
937
975
|
return [];
|
|
938
976
|
const resolverInput = { distribution: "external", piCommand: input.piCommand, env: input.env };
|
|
939
977
|
const args = [];
|
|
978
|
+
// Watchdog must load before the subagent extension so session_shutdown still
|
|
979
|
+
// sees AgentManager.getRecord and can bridge consumed or terminal state.
|
|
980
|
+
const recordWatchdog = (resolvers.recordWatchdog ?? resolvePiSubagentRecordWatchdogExtensionArg)(resolverInput);
|
|
981
|
+
if (recordWatchdog)
|
|
982
|
+
args.push("-e", recordWatchdog);
|
|
940
983
|
const subagents = (resolvers.subagents ?? resolvePiSubagentExtensionArg)(resolverInput);
|
|
941
984
|
if (subagents)
|
|
942
985
|
args.push("-e", subagents);
|
|
@@ -946,7 +989,7 @@ export function resolvePiProcessExtensionArgs(input, resolvers = {}) {
|
|
|
946
989
|
return args;
|
|
947
990
|
}
|
|
948
991
|
async function createPiRpcBackend(input, dependencies, spawn, productionSpawn = true) {
|
|
949
|
-
const stateRoot =
|
|
992
|
+
const stateRoot = effectivePiStateDir(input);
|
|
950
993
|
const mergedEnv = { ...globalThis.process.env, ...dependencies.env, ...input.env, NO_COLOR: "1" };
|
|
951
994
|
assertNoProjectPiCompactionOverride(input.workspaceDir);
|
|
952
995
|
const ownedPiDirectory = mergedEnv.LARKIN_CONFIG_DIR
|
|
@@ -967,23 +1010,29 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
967
1010
|
...(session.sessionFile ? ["--session", session.sessionFile] : []),
|
|
968
1011
|
...(requestedModel ? ["--model", requestedModel] : []),
|
|
969
1012
|
...(requestedEffort ? ["--thinking", requestedEffort] : [])];
|
|
970
|
-
const builtin =
|
|
1013
|
+
const builtin = piChildDistributionFromOverrides(dependencies.env, input.env) === "builtin";
|
|
1014
|
+
if (!builtin && mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin")
|
|
1015
|
+
delete mergedEnv.LARKIN_PI_DISTRIBUTION;
|
|
971
1016
|
const builtinSpec = builtin ? internalCommandSpec("pi-rpc", [], mergedEnv) : null;
|
|
972
1017
|
const command = builtinSpec?.command ?? dependencies.piCommand ?? dependencies.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
973
1018
|
const commandPrefix = builtinSpec?.args ?? dependencies.piCommandArgs ?? [];
|
|
974
1019
|
const commandArgs = [...commandPrefix, ...args];
|
|
1020
|
+
mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
|
|
1021
|
+
if (builtin)
|
|
1022
|
+
mergedEnv.PI_TELEMETRY = "0";
|
|
1023
|
+
const childEnv = applyPiPackageDirForChild(mergedEnv, {
|
|
1024
|
+
distribution: builtin ? "builtin" : "external",
|
|
1025
|
+
explicitPackageDir: input.env?.PI_PACKAGE_DIR ?? dependencies.env?.PI_PACKAGE_DIR,
|
|
1026
|
+
});
|
|
975
1027
|
const reportedVersion = productionSpawn && !builtin
|
|
976
1028
|
? parsePiExecutableVersion(String(spawnSync(command, [...commandPrefix, "--version"], {
|
|
977
|
-
cwd: input.workspaceDir, env:
|
|
1029
|
+
cwd: input.workspaceDir, env: childEnv, encoding: "utf8", timeout: 5_000,
|
|
978
1030
|
}).stdout || ""))
|
|
979
1031
|
: BUNDLED_PI_VERSION;
|
|
980
|
-
mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
|
|
981
|
-
if (builtin)
|
|
982
|
-
mergedEnv.PI_TELEMETRY = "0";
|
|
983
1032
|
const extensionArgs = (dependencies.resolvePiProcessExtensionArgs ?? resolvePiProcessExtensionArgs)({
|
|
984
1033
|
distribution: builtin ? "builtin" : "external",
|
|
985
1034
|
piCommand: command,
|
|
986
|
-
env:
|
|
1035
|
+
env: childEnv,
|
|
987
1036
|
platform: process.platform,
|
|
988
1037
|
});
|
|
989
1038
|
commandArgs.push(...extensionArgs);
|
|
@@ -993,14 +1042,14 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
993
1042
|
// requested model first, then write the matching 15% reserve before the
|
|
994
1043
|
// real session starts; no prompt, provider request, package, or extension
|
|
995
1044
|
// is loaded by the probe.
|
|
996
|
-
probedModel = await discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel,
|
|
1045
|
+
probedModel = await discoverEffectivePiContextWindow(input, command, commandPrefix, requestedModel, childEnv, spawn, dependencies.piRpcClientOptions);
|
|
997
1046
|
writeOwnedPiSettings(ownedPiDirectory, calculatePiCompactionSettings(probedModel.contextWindow));
|
|
998
1047
|
}
|
|
999
1048
|
const artifactEntriesBeforeSpawn = new Set(fs.readdirSync(ownedPiDirectory));
|
|
1000
1049
|
const artifactSpawnBoundary = Date.now();
|
|
1001
1050
|
const child = spawn(command, commandArgs, {
|
|
1002
1051
|
cwd: input.workspaceDir,
|
|
1003
|
-
env:
|
|
1052
|
+
env: childEnv,
|
|
1004
1053
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1005
1054
|
});
|
|
1006
1055
|
traceProcessBoundary(mergedEnv, "pi-rpc:child-spawned", { configDir: mergedEnv.LARKIN_CONFIG_DIR, agentId: input.agentId, targetDir: ownedPiDirectory, childPid: child.pid ?? null });
|
|
@@ -1164,8 +1213,7 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
|
|
|
1164
1213
|
throw new RuntimePrerequisiteError(readiness);
|
|
1165
1214
|
}
|
|
1166
1215
|
if (id === "pi") {
|
|
1167
|
-
const distribution = (
|
|
1168
|
-
? "builtin" : "external";
|
|
1216
|
+
const distribution = piChildDistributionFromOverrides(dependencies.env, input.env);
|
|
1169
1217
|
return new PiSession(await (dependencies.createPiSession
|
|
1170
1218
|
? dependencies.createPiSession(input)
|
|
1171
1219
|
: createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn, productionSpawn)), distribution);
|