larkin 0.4.1 → 0.4.5
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 +76 -33
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +218 -228
- package/assets/readme/larkin-github-cover.png +0 -0
- package/assets/readme/larkin-hero.svg +111 -0
- package/dist/agent/agent-state-store.cjs +193 -1
- package/dist/agent/agent-state-store.mjs +191 -0
- 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 +7 -1
- package/dist/app/local-control.mjs +108 -2
- package/dist/app/run.mjs +8 -0
- package/dist/app/runtime-process.mjs +17 -1
- package/dist/app/session-cli.mjs +55 -20
- package/dist/dashboard/dashboard-view-model.mjs +12 -3
- package/dist/feishu/host-shell.cjs +104 -9
- package/dist/feishu/host-shell.mjs +105 -10
- 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 +703 -0
- package/dist/runtime/pi-compaction-recovery.mjs +679 -0
- 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 +3 -3
- package/dist/runtime/pi-provider-config.mjs +3 -3
- package/dist/runtime/pi-rpc-client.cjs +39 -22
- package/dist/runtime/pi-rpc-client.mjs +39 -22
- package/dist/runtime/provider-error-classifier.cjs +39 -0
- package/dist/runtime/provider-error-classifier.mjs +34 -0
- package/dist/runtime/runtime-adapters.mjs +176 -27
- package/dist/runtime/runtime-host.cjs +630 -19
- package/dist/runtime/runtime-host.mjs +628 -18
- package/dist/runtime/runtime-readiness.cjs +6 -4
- package/dist/runtime/runtime-readiness.mjs +6 -4
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
1
|
+
import { spawn as nodeSpawn, spawnSync } from "node:child_process";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path from "node:path";
|
|
@@ -6,10 +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";
|
|
15
|
+
import { buildStrictProviderErrorInput, classifyStrictProviderError, INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON, } from "./provider-error-classifier.mjs";
|
|
16
|
+
import { assertEffectivePiCompactionSettings, assertNoProjectPiCompactionOverride, calculatePiCompactionSettings, parsePiExecutableVersion, prepareOwnedPiDirectory, readOwnedPiSettings, verifyPiCapabilities, writeOwnedPiSettings, } from "./pi-compaction-recovery.mjs";
|
|
13
17
|
class EventSession {
|
|
14
18
|
listeners = new Set();
|
|
15
19
|
subscribe(listener) {
|
|
@@ -447,12 +451,15 @@ class PiSession extends EventSession {
|
|
|
447
451
|
observedSubmitEpochs = new Set();
|
|
448
452
|
observedAcceptedEpochs = new Set();
|
|
449
453
|
observedCompletedEpochs = new Set();
|
|
454
|
+
observedAgentEndEpochs = new Set();
|
|
450
455
|
firstOutputObserved = false;
|
|
451
456
|
toolCallOpen = false;
|
|
457
|
+
piPolicyManaged;
|
|
452
458
|
constructor(sdk, distribution) {
|
|
453
459
|
super();
|
|
454
460
|
this.sdk = sdk;
|
|
455
461
|
this.distribution = distribution;
|
|
462
|
+
this.piPolicyManaged = sdk.policyManaged ?? typeof sdk.validatePolicy === "function";
|
|
456
463
|
const result = sdk.subscribe?.((event) => this.onEvent(event));
|
|
457
464
|
if (typeof result === "function")
|
|
458
465
|
this.unsubscribe = result;
|
|
@@ -470,6 +477,25 @@ class PiSession extends EventSession {
|
|
|
470
477
|
async prompt(input) { return this.enqueue(input, () => this.sdk.prompt(input.text)); }
|
|
471
478
|
async busyInput(input) { return this.enqueue(input, () => this.sdk.steer(input.text)); }
|
|
472
479
|
async cancel(_reason) { await this.sdk.abort(); }
|
|
480
|
+
async compact(customInstructions) {
|
|
481
|
+
if (!this.sdk.compact)
|
|
482
|
+
throw new Error("Pi compact RPC is unavailable");
|
|
483
|
+
await this.sdk.validatePolicy?.();
|
|
484
|
+
return this.sdk.compact(customInstructions);
|
|
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
|
+
}
|
|
473
499
|
async close(_reason) { this.unsubscribe?.(); await this.sdk.dispose?.(); }
|
|
474
500
|
async enqueue(input, operation) {
|
|
475
501
|
if (this.ownedInputIds.size === 0)
|
|
@@ -481,14 +507,15 @@ class PiSession extends EventSession {
|
|
|
481
507
|
const ownsRpcObservation = !this.observedSubmitEpochs.has(epoch);
|
|
482
508
|
if (ownsRpcObservation) {
|
|
483
509
|
this.observedSubmitEpochs.add(epoch);
|
|
484
|
-
this.
|
|
510
|
+
this.emitObservation("rpc_submit");
|
|
485
511
|
}
|
|
486
512
|
try {
|
|
487
|
-
|
|
513
|
+
const accepted = this.sdk.validatePolicy ? this.sdk.validatePolicy().then(operation) : operation();
|
|
514
|
+
await accepted;
|
|
488
515
|
this.awaitingAcknowledgement.delete(input.inputId);
|
|
489
516
|
if (ownsRpcObservation && !this.observedAcceptedEpochs.has(epoch)) {
|
|
490
517
|
this.observedAcceptedEpochs.add(epoch);
|
|
491
|
-
this.
|
|
518
|
+
this.emitObservation("rpc_accepted");
|
|
492
519
|
}
|
|
493
520
|
return { status: "accepted", inputId: input.inputId };
|
|
494
521
|
}
|
|
@@ -524,19 +551,23 @@ class PiSession extends EventSession {
|
|
|
524
551
|
this.observedSubmitEpochs.clear();
|
|
525
552
|
this.observedAcceptedEpochs.clear();
|
|
526
553
|
this.observedCompletedEpochs.clear();
|
|
554
|
+
this.observedAgentEndEpochs.clear();
|
|
527
555
|
this.activeEpoch = null;
|
|
528
556
|
this.settleArmedEpoch = null;
|
|
529
557
|
this.emit({ type: "error", message });
|
|
530
558
|
}
|
|
531
|
-
else if (event?.type === "compaction_start"
|
|
532
|
-
this.
|
|
559
|
+
else if (event?.type === "compaction_start") {
|
|
560
|
+
this.emitObservation("compaction_start", event.reason === "manual" || event.reason === "threshold" || event.reason === "overflow" ? { reason: event.reason } : {});
|
|
533
561
|
}
|
|
534
|
-
else if (event?.type === "compaction_end"
|
|
535
|
-
this.
|
|
562
|
+
else if (event?.type === "compaction_end") {
|
|
563
|
+
this.emitObservation("compaction_end", {
|
|
564
|
+
...(event.reason === "manual" || event.reason === "threshold" || event.reason === "overflow" ? { reason: event.reason } : {}),
|
|
565
|
+
willRetry: event.willRetry === true, success: event.aborted !== true && event.result !== undefined,
|
|
566
|
+
});
|
|
536
567
|
}
|
|
537
568
|
else if ((event?.type === "auto_retry_start" || event?.type === "auto_retry_end"
|
|
538
569
|
|| String(event?.type || "").startsWith("summarization_retry_")) && this.awaitingAcknowledgement.size > 0) {
|
|
539
|
-
this.
|
|
570
|
+
this.emitObservation("retry_progress");
|
|
540
571
|
}
|
|
541
572
|
else if (event?.type === "turn_start") {
|
|
542
573
|
const epoch = this.oldestOwnedEpoch();
|
|
@@ -546,7 +577,7 @@ class PiSession extends EventSession {
|
|
|
546
577
|
this.settleArmedEpoch = null;
|
|
547
578
|
this.firstOutputObserved = false;
|
|
548
579
|
this.toolCallOpen = false;
|
|
549
|
-
this.
|
|
580
|
+
this.emitObservation("turn_start");
|
|
550
581
|
this.emit({ type: "turn-start", ...(Number.isInteger(event.turnIndex) ? { turnId: `pi-${event.turnIndex}` } : {}) });
|
|
551
582
|
}
|
|
552
583
|
else if (event?.type === "agent_end") {
|
|
@@ -557,9 +588,17 @@ class PiSession extends EventSession {
|
|
|
557
588
|
? piAssistantProviderError(assistant)
|
|
558
589
|
: null;
|
|
559
590
|
this.settleArmedEpoch = this.activeEpoch;
|
|
591
|
+
if (this.activeEpoch !== null && !this.observedAgentEndEpochs.has(this.activeEpoch)) {
|
|
592
|
+
this.observedAgentEndEpochs.add(this.activeEpoch);
|
|
593
|
+
this.emitObservation("agent_end", { willRetry: event.willRetry === true });
|
|
594
|
+
}
|
|
595
|
+
if (event.willRetry === true && this.finalAssistantError?.upstream.message === "Your input exceeds the context window of this model. Please adjust your input and try again.") {
|
|
596
|
+
this.emit({ type: "input-error", inputId: this.oldestOwnedInput(), retryable: true, willRetry: true,
|
|
597
|
+
message: "Pi owns the context-overflow retry", errorCategory: "context_window" });
|
|
598
|
+
}
|
|
560
599
|
if (event.willRetry !== true && this.activeEpoch !== null && !this.observedCompletedEpochs.has(this.activeEpoch)) {
|
|
561
600
|
this.observedCompletedEpochs.add(this.activeEpoch);
|
|
562
|
-
this.
|
|
601
|
+
this.emitObservation("completed");
|
|
563
602
|
}
|
|
564
603
|
}
|
|
565
604
|
else if (event?.type === "agent_settled") {
|
|
@@ -576,7 +615,7 @@ class PiSession extends EventSession {
|
|
|
576
615
|
this.toolCallOpen = false;
|
|
577
616
|
this.emit({ type: "runtime-observation", runtime: "pi", distribution: this.distribution, phase: "tool_result" });
|
|
578
617
|
}
|
|
579
|
-
this.
|
|
618
|
+
this.emitObservation("settled");
|
|
580
619
|
const owned = [...this.ownedInputIds].filter((inputId) => this.inputEpochs.get(inputId) === epoch);
|
|
581
620
|
if (error) {
|
|
582
621
|
const classified = classifyPiProviderError(error);
|
|
@@ -605,6 +644,7 @@ class PiSession extends EventSession {
|
|
|
605
644
|
this.observedSubmitEpochs.delete(epoch);
|
|
606
645
|
this.observedAcceptedEpochs.delete(epoch);
|
|
607
646
|
this.observedCompletedEpochs.delete(epoch);
|
|
647
|
+
this.observedAgentEndEpochs.delete(epoch);
|
|
608
648
|
}
|
|
609
649
|
else if (event?.type === "tool_execution_start") {
|
|
610
650
|
if (!this.firstOutputObserved) {
|
|
@@ -632,6 +672,20 @@ class PiSession extends EventSession {
|
|
|
632
672
|
this.emit({ type: "activity", activity: kind, text: String(event.assistantMessageEvent.delta) });
|
|
633
673
|
}
|
|
634
674
|
}
|
|
675
|
+
emitObservation(phase, fields = {}) {
|
|
676
|
+
const inputId = this.oldestOwnedInput();
|
|
677
|
+
const observation = { type: "runtime-observation", runtime: "pi",
|
|
678
|
+
distribution: this.distribution, phase, ...fields };
|
|
679
|
+
// Correlation is host-internal metadata, not telemetry payload.
|
|
680
|
+
if (this.sessionId)
|
|
681
|
+
Object.defineProperty(observation, "sessionId", { value: this.sessionId, enumerable: false });
|
|
682
|
+
if (inputId)
|
|
683
|
+
Object.defineProperty(observation, "inputId", { value: inputId, enumerable: false });
|
|
684
|
+
this.emit(observation);
|
|
685
|
+
}
|
|
686
|
+
oldestOwnedInput() {
|
|
687
|
+
return this.ownedInputIds.values().next().value;
|
|
688
|
+
}
|
|
635
689
|
oldestOwnedEpoch() {
|
|
636
690
|
for (const inputId of this.ownedInputIds) {
|
|
637
691
|
const epoch = this.inputEpochs.get(inputId);
|
|
@@ -675,6 +729,13 @@ export function classifyPiProviderError(error) {
|
|
|
675
729
|
const errorType = "type" in error ? error.type : undefined;
|
|
676
730
|
const reason = safeProviderText(upstream.message, "Pi provider request failed");
|
|
677
731
|
const signal = new Set([String(upstream.code ?? "").trim().toLowerCase(), String(errorType ?? "").trim().toLowerCase()].filter(Boolean));
|
|
732
|
+
if (classifyStrictProviderError(buildStrictProviderErrorInput({
|
|
733
|
+
message: reason, type: errorType, upstream,
|
|
734
|
+
})) === "context_window")
|
|
735
|
+
return {
|
|
736
|
+
category: "context_window", reason: INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON,
|
|
737
|
+
nextAction: "Start a fresh Runtime session, then replay the retained Inbox deliveries.",
|
|
738
|
+
};
|
|
678
739
|
if (upstream.status === 402 || [...signal].some((value) => ["payment_required", "billing_hard_limit_reached", "insufficient_balance"].includes(value)))
|
|
679
740
|
return {
|
|
680
741
|
category: "billing", reason, nextAction: "Update the provider billing method or balance, then retry.",
|
|
@@ -746,6 +807,28 @@ export function createPiSessionManager(input) {
|
|
|
746
807
|
throw error;
|
|
747
808
|
}
|
|
748
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
|
+
}
|
|
749
832
|
function writePrivateAtomic(file, content) {
|
|
750
833
|
try {
|
|
751
834
|
if (fs.lstatSync(file).isSymbolicLink())
|
|
@@ -770,11 +853,15 @@ function writePrivateAtomic(file, content) {
|
|
|
770
853
|
}
|
|
771
854
|
class PiRpcBackend {
|
|
772
855
|
client;
|
|
856
|
+
policy;
|
|
857
|
+
policyManaged;
|
|
773
858
|
sessionId;
|
|
774
859
|
model;
|
|
775
860
|
thinkingLevel;
|
|
776
|
-
constructor(client, state) {
|
|
861
|
+
constructor(client, state, policy) {
|
|
777
862
|
this.client = client;
|
|
863
|
+
this.policy = policy;
|
|
864
|
+
this.policyManaged = Boolean(policy);
|
|
778
865
|
this.sessionId = state.sessionId ?? null;
|
|
779
866
|
if (state.model?.provider && state.model.id)
|
|
780
867
|
this.model = {
|
|
@@ -787,6 +874,24 @@ class PiRpcBackend {
|
|
|
787
874
|
prompt(text) { return this.client.request("prompt", { message: text }); }
|
|
788
875
|
steer(text) { return this.client.request("steer", { message: text }); }
|
|
789
876
|
abort() { return this.client.request("abort"); }
|
|
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
|
+
}
|
|
894
|
+
getState() { return this.client.request("get_state"); }
|
|
790
895
|
dispose() { return this.client.close(); }
|
|
791
896
|
subscribe(listener) {
|
|
792
897
|
const offEvent = this.client.subscribe(listener);
|
|
@@ -809,8 +914,15 @@ export function resolvePiProcessExtensionArgs(input, resolvers = {}) {
|
|
|
809
914
|
args.push("-e", bashTimeout);
|
|
810
915
|
return args;
|
|
811
916
|
}
|
|
812
|
-
async function createPiRpcBackend(input, dependencies, spawn) {
|
|
917
|
+
async function createPiRpcBackend(input, dependencies, spawn, productionSpawn = true) {
|
|
813
918
|
const stateRoot = input.stateDir ?? path.join(input.workspaceDir, ".larkin");
|
|
919
|
+
const mergedEnv = { ...globalThis.process.env, ...dependencies.env, ...input.env, NO_COLOR: "1" };
|
|
920
|
+
assertNoProjectPiCompactionOverride(input.workspaceDir);
|
|
921
|
+
const ownedPiDirectory = mergedEnv.LARKIN_CONFIG_DIR
|
|
922
|
+
? piAgentDirectory(mergedEnv.LARKIN_CONFIG_DIR, input.agentId)
|
|
923
|
+
: path.join(stateRoot, "pi-agent");
|
|
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 });
|
|
814
926
|
const runtimeDir = path.join(stateRoot, "runtime");
|
|
815
927
|
fs.mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
|
|
816
928
|
const promptFile = path.join(runtimeDir, "pi-standing-prompt.md");
|
|
@@ -824,28 +936,43 @@ async function createPiRpcBackend(input, dependencies, spawn) {
|
|
|
824
936
|
...(session.sessionFile ? ["--session", session.sessionFile] : []),
|
|
825
937
|
...(requestedModel ? ["--model", requestedModel] : []),
|
|
826
938
|
...(requestedEffort ? ["--thinking", requestedEffort] : [])];
|
|
827
|
-
const mergedEnv = { ...globalThis.process.env, ...dependencies.env, ...input.env, NO_COLOR: "1" };
|
|
828
939
|
const builtin = mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin";
|
|
829
940
|
const builtinSpec = builtin ? internalCommandSpec("pi-rpc", [], mergedEnv) : null;
|
|
830
941
|
const command = builtinSpec?.command ?? dependencies.piCommand ?? dependencies.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
942
|
+
const commandPrefix = builtinSpec?.args ?? dependencies.piCommandArgs ?? [];
|
|
943
|
+
const commandArgs = [...commandPrefix, ...args];
|
|
944
|
+
const reportedVersion = productionSpawn && !builtin
|
|
945
|
+
? parsePiExecutableVersion(String(spawnSync(command, [...commandPrefix, "--version"], {
|
|
946
|
+
cwd: input.workspaceDir, env: mergedEnv, encoding: "utf8", timeout: 5_000,
|
|
947
|
+
}).stdout || ""))
|
|
948
|
+
: BUNDLED_PI_VERSION;
|
|
949
|
+
mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
|
|
950
|
+
if (builtin)
|
|
836
951
|
mergedEnv.PI_TELEMETRY = "0";
|
|
837
|
-
|
|
838
|
-
commandArgs.push(...(dependencies.resolvePiProcessExtensionArgs ?? resolvePiProcessExtensionArgs)({
|
|
952
|
+
const extensionArgs = (dependencies.resolvePiProcessExtensionArgs ?? resolvePiProcessExtensionArgs)({
|
|
839
953
|
distribution: builtin ? "builtin" : "external",
|
|
840
954
|
piCommand: command,
|
|
841
955
|
env: mergedEnv,
|
|
842
956
|
platform: process.platform,
|
|
843
|
-
})
|
|
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();
|
|
844
970
|
const child = spawn(command, commandArgs, {
|
|
845
971
|
cwd: input.workspaceDir,
|
|
846
972
|
env: mergedEnv,
|
|
847
973
|
stdio: ["pipe", "pipe", "pipe"],
|
|
848
974
|
});
|
|
975
|
+
traceProcessBoundary(mergedEnv, "pi-rpc:child-spawned", { configDir: mergedEnv.LARKIN_CONFIG_DIR, agentId: input.agentId, targetDir: ownedPiDirectory, childPid: child.pid ?? null });
|
|
849
976
|
const client = new PiRpcClient(child, dependencies.piRpcClientOptions);
|
|
850
977
|
try {
|
|
851
978
|
const [state, available] = await Promise.all([
|
|
@@ -855,6 +982,24 @@ async function createPiRpcBackend(input, dependencies, spawn) {
|
|
|
855
982
|
if (!available?.models?.length)
|
|
856
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.");
|
|
857
984
|
const effectiveModel = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
|
|
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
|
+
}
|
|
990
|
+
const ownedSettings = readOwnedPiSettings(ownedPiDirectory);
|
|
991
|
+
assertEffectivePiCompactionSettings({ contextWindow: state.model?.contextWindow, compaction: {
|
|
992
|
+
enabled: state.autoCompactionEnabled,
|
|
993
|
+
reserveTokens: ownedSettings.compaction?.reserveTokens,
|
|
994
|
+
keepRecentTokens: ownedSettings.compaction?.keepRecentTokens,
|
|
995
|
+
} });
|
|
996
|
+
const handshake = state.compactionCapabilities;
|
|
997
|
+
verifyPiCapabilities({ distribution: builtin ? "builtin" : "external", version: reportedVersion,
|
|
998
|
+
contextWindow: state.model?.contextWindow, autoCompactionEnabled: state.autoCompactionEnabled,
|
|
999
|
+
reserveTokens: handshake?.reserveTokens, keepRecentTokens: handshake?.keepRecentTokens,
|
|
1000
|
+
compactRpc: true, events: handshake?.events,
|
|
1001
|
+
trustedProtocol: builtin });
|
|
1002
|
+
}
|
|
858
1003
|
// 严格对账(Owner 决策):模型必须来自 pi 的权威可用列表,不做后缀猜测——
|
|
859
1004
|
// 配置的模型与 pi 可用列表不符即报错,并在错误里列出可用模型。
|
|
860
1005
|
const availableEntries = (available.models ?? []).map((entry) => ({
|
|
@@ -867,7 +1012,10 @@ async function createPiRpcBackend(input, dependencies, spawn) {
|
|
|
867
1012
|
}
|
|
868
1013
|
if (requestedEffort && state.thinkingLevel !== requestedEffort)
|
|
869
1014
|
throw new Error(`Pi thinking level ${requestedEffort} was not accepted by effective model ${effectiveModel || "unknown"}`);
|
|
870
|
-
|
|
1015
|
+
recordPiRuntimeArtifactProvenance(ownedPiDirectory, artifactEntriesBeforeSpawn, artifactSpawnBoundary);
|
|
1016
|
+
return new PiRpcBackend(client, state, productionSpawn ? {
|
|
1017
|
+
model: effectiveModel, contextWindow: state.model?.contextWindow, ownedPiDirectory,
|
|
1018
|
+
} : undefined);
|
|
871
1019
|
}
|
|
872
1020
|
catch (error) {
|
|
873
1021
|
await client.close();
|
|
@@ -972,7 +1120,8 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
|
|
|
972
1120
|
capabilities: CAPABILITIES[id],
|
|
973
1121
|
async probe(input) {
|
|
974
1122
|
const readiness = await probeNativeRuntimeReadiness({ runtime: id, agentId: input.agentId, cwd: input.workspaceDir,
|
|
975
|
-
env: { ...dependencies.env, ...input.env }, command: configuredCommand
|
|
1123
|
+
env: { ...dependencies.env, ...input.env }, command: configuredCommand,
|
|
1124
|
+
...(id === "pi" && dependencies.piCommandArgs ? { commandArgs: dependencies.piCommandArgs } : {}) });
|
|
976
1125
|
resolvedExecutable = readiness.state === "ready" ? readiness.executable ?? null : null;
|
|
977
1126
|
return readiness;
|
|
978
1127
|
},
|
|
@@ -988,7 +1137,7 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
|
|
|
988
1137
|
? "builtin" : "external";
|
|
989
1138
|
return new PiSession(await (dependencies.createPiSession
|
|
990
1139
|
? dependencies.createPiSession(input)
|
|
991
|
-
: createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn)), distribution);
|
|
1140
|
+
: createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn, productionSpawn)), distribution);
|
|
992
1141
|
}
|
|
993
1142
|
if (id === "codex") {
|
|
994
1143
|
const codexInput = dependencies.codexModelOverride?.trim()
|