larkin 0.4.28 → 0.5.0
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 +11 -0
- package/README.md +5 -9
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
- package/dist/app/agent-cli.mjs +14 -2
- package/dist/app/agent-config.mjs +26 -84
- package/dist/app/binary-entry.mjs +0 -20
- package/dist/app/cli.mjs +8 -14
- package/dist/app/internal-command.cjs +0 -2
- package/dist/app/internal-command.mjs +0 -2
- package/dist/app/local-control.mjs +16 -3
- package/dist/app/runtime-model-directory.mjs +2 -16
- package/dist/app/runtime-process.mjs +42 -6
- package/dist/app/setup.mjs +52 -57
- package/dist/dashboard/dashboard-config-controller.mjs +83 -24
- package/dist/dashboard/dashboard-view-model.mjs +4 -7
- package/dist/dashboard/web/assets/dashboard.js +1 -1
- package/dist/feishu/host-shell.cjs +68 -17
- package/dist/feishu/host-shell.mjs +69 -18
- package/dist/platform/config.cjs +201 -138
- package/dist/platform/config.mjs +199 -138
- package/dist/runtime/pi-artifact-provenance.mjs +3 -3
- package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
- package/dist/runtime/pi-compaction-recovery.cjs +9 -11
- package/dist/runtime/pi-compaction-recovery.mjs +8 -10
- package/dist/runtime/pi-extension-api.mjs +1 -0
- package/dist/runtime/pi-model-catalog.cjs +7 -8
- package/dist/runtime/pi-model-catalog.mjs +7 -8
- package/dist/runtime/pi-subagent-injection.mjs +17 -4
- package/dist/runtime/runtime-adapters.mjs +41 -38
- package/dist/runtime/runtime-host.cjs +197 -20
- package/dist/runtime/runtime-host.mjs +198 -21
- package/dist/runtime/runtime-model-catalog.cjs +1 -1
- package/dist/runtime/runtime-model-catalog.mjs +1 -1
- package/dist/runtime/runtime-readiness.cjs +112 -49
- package/dist/runtime/runtime-readiness.mjs +103 -49
- package/dist/runtime/user-runtime.cjs +7 -24
- package/dist/runtime/user-runtime.mjs +7 -23
- package/dist/setup/bot-register.mjs +15 -123
- package/dist/setup/setup-agent-choice.mjs +48 -74
- package/dist/setup/setup-bind.mjs +51 -151
- package/dist/setup/setup-binding.mjs +6 -9
- package/package.json +3 -2
- package/dist/app/pi-auth-cli.mjs +0 -67
- package/dist/platform/process-boundary-trace.cjs +0 -108
- package/dist/runtime/builtin-pi-assets.cjs +0 -117
- package/dist/runtime/builtin-pi-assets.mjs +0 -105
- package/dist/runtime/pi-artifact-provenance.cjs +0 -147
- package/dist/runtime/pi-distribution-label.cjs +0 -6
- package/dist/runtime/pi-distribution-label.mjs +0 -3
- package/dist/runtime/pi-inline-extensions.mjs +0 -78
- package/dist/runtime/pi-official-auth.mjs +0 -491
- package/dist/runtime/pi-profile-migration.cjs +0 -764
- package/dist/runtime/pi-profile-migration.mjs +0 -719
- package/dist/runtime/pi-provider-config.cjs +0 -331
- package/dist/runtime/pi-provider-config.mjs +0 -314
|
@@ -41,7 +41,6 @@ exports.findExactPiModel = findExactPiModel;
|
|
|
41
41
|
exports.discoverPiModelCatalog = discoverPiModelCatalog;
|
|
42
42
|
const node_child_process_1 = require("node:child_process");
|
|
43
43
|
const path = __importStar(require("node:path"));
|
|
44
|
-
const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
|
|
45
44
|
const pi_rpc_client_js_1 = require("./pi-rpc-client.cjs");
|
|
46
45
|
const runtime_readiness_js_1 = require("./runtime-readiness.cjs");
|
|
47
46
|
exports.PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -73,16 +72,16 @@ function findExactPiModel(reference, models) {
|
|
|
73
72
|
const discoveryCache = new Map();
|
|
74
73
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
75
74
|
function piCatalogChildEnv(options) {
|
|
76
|
-
const
|
|
75
|
+
const env = {
|
|
77
76
|
...process.env,
|
|
78
77
|
...options.env,
|
|
79
|
-
...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}),
|
|
80
78
|
NO_COLOR: "1",
|
|
81
79
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
if (options.agentDir)
|
|
81
|
+
env.PI_CODING_AGENT_DIR = path.resolve(options.agentDir);
|
|
82
|
+
else
|
|
83
|
+
delete env.PI_CODING_AGENT_DIR;
|
|
84
|
+
return env;
|
|
86
85
|
}
|
|
87
86
|
async function discoverPiModelCatalog(options) {
|
|
88
87
|
if (!options.spawn) {
|
|
@@ -118,7 +117,7 @@ async function discoverPiModelCatalogUncached(options) {
|
|
|
118
117
|
]);
|
|
119
118
|
const available = [...(availableResponse?.models ?? [])].sort((left, right) => piModelId(left).localeCompare(piModelId(right)));
|
|
120
119
|
if (!available.length)
|
|
121
|
-
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow
|
|
120
|
+
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow; Larkin will not create a fallback session.");
|
|
122
121
|
const effectiveModel = state.model ? piModelId(state.model) : null;
|
|
123
122
|
if (!effectiveModel || !available.some((model) => piModelId(model) === effectiveModel)) {
|
|
124
123
|
throw new Error(`Pi official default resolution returned an unavailable model (${effectiveModel || "none"}); refusing implicit fallback`);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { applyPiPackageDirForChild, catalogPiChildDistribution } from "./builtin-pi-assets.mjs";
|
|
4
3
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
5
4
|
import { classifyRuntimePrerequisite, RuntimePrerequisiteError } from "./runtime-readiness.mjs";
|
|
6
5
|
export const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -32,16 +31,16 @@ export function findExactPiModel(reference, models) {
|
|
|
32
31
|
const discoveryCache = new Map();
|
|
33
32
|
/** Discover only through Pi's structured RPC protocol; no table parsing or static fallback. */
|
|
34
33
|
function piCatalogChildEnv(options) {
|
|
35
|
-
const
|
|
34
|
+
const env = {
|
|
36
35
|
...process.env,
|
|
37
36
|
...options.env,
|
|
38
|
-
...(options.agentDir ? { PI_CODING_AGENT_DIR: path.resolve(options.agentDir) } : {}),
|
|
39
37
|
NO_COLOR: "1",
|
|
40
38
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
if (options.agentDir)
|
|
40
|
+
env.PI_CODING_AGENT_DIR = path.resolve(options.agentDir);
|
|
41
|
+
else
|
|
42
|
+
delete env.PI_CODING_AGENT_DIR;
|
|
43
|
+
return env;
|
|
45
44
|
}
|
|
46
45
|
export async function discoverPiModelCatalog(options) {
|
|
47
46
|
if (!options.spawn) {
|
|
@@ -77,7 +76,7 @@ async function discoverPiModelCatalogUncached(options) {
|
|
|
77
76
|
]);
|
|
78
77
|
const available = [...(availableResponse?.models ?? [])].sort((left, right) => piModelId(left).localeCompare(piModelId(right)));
|
|
79
78
|
if (!available.length)
|
|
80
|
-
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow
|
|
79
|
+
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow; Larkin will not create a fallback session.");
|
|
81
80
|
const effectiveModel = state.model ? piModelId(state.model) : null;
|
|
82
81
|
if (!effectiveModel || !available.some((model) => piModelId(model) === effectiveModel)) {
|
|
83
82
|
throw new Error(`Pi official default resolution returned an unavailable model (${effectiveModel || "none"}); refusing implicit fallback`);
|
|
@@ -160,12 +160,25 @@ export function piVersionSupportsSubagents(version) {
|
|
|
160
160
|
return false;
|
|
161
161
|
return version.major > 0 || (version.major === 0 && version.minor >= 80);
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
const externalPiVersionProbeCache = new Map();
|
|
164
|
+
function externalPiVersionProbeKey(piCommand, env) {
|
|
165
|
+
return JSON.stringify([piCommand, env.PATH ?? ""]);
|
|
166
|
+
}
|
|
167
|
+
/** 测试用:清空进程内 `pi --version` 探测缓存。 */
|
|
168
|
+
export function resetExternalPiVersionProbeCache() {
|
|
169
|
+
externalPiVersionProbeCache.clear();
|
|
170
|
+
}
|
|
171
|
+
/** 解析 external `pi --version`;无法确认版本时视为不支持,不注入。按 piCommand+PATH 缓存。 */
|
|
164
172
|
export function probeExternalPiVersion(piCommand, env) {
|
|
173
|
+
const key = externalPiVersionProbeKey(piCommand, env);
|
|
174
|
+
if (externalPiVersionProbeCache.has(key))
|
|
175
|
+
return externalPiVersionProbeCache.get(key) ?? null;
|
|
165
176
|
const result = spawnSync(piCommand, ["--version"], { env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024 });
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
177
|
+
const version = result.error || result.status !== 0
|
|
178
|
+
? null
|
|
179
|
+
: parsePiVersion(String(result.stdout || result.stderr || ""));
|
|
180
|
+
externalPiVersionProbeCache.set(key, version);
|
|
181
|
+
return version;
|
|
169
182
|
}
|
|
170
183
|
/**
|
|
171
184
|
* 用户 pi 是否已自行安装 pi-subagents(settings.json packages 或包目录)。
|
|
@@ -3,11 +3,8 @@ 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";
|
|
7
6
|
import { isPiThinkingLevel } from "./pi-model-catalog.mjs";
|
|
8
7
|
import { PiRpcClient } from "./pi-rpc-client.mjs";
|
|
9
|
-
import { internalCommandSpec } from "../app/internal-command.mjs";
|
|
10
|
-
import { BUNDLED_PI_VERSION, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
11
8
|
import { recordPiRuntimeArtifactProvenance } from "./pi-artifact-provenance.mjs";
|
|
12
9
|
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
13
10
|
import { resolvePiSubagentExtensionArg } from "./pi-subagent-injection.mjs";
|
|
@@ -15,9 +12,12 @@ import { extractCanonicalPiSubagentNotification, ledgerStatusFromPiNotificationS
|
|
|
15
12
|
import { resolvePiSubagentRecordWatchdogExtensionArg } from "./pi-subagent-record-watchdog-injection.mjs";
|
|
16
13
|
import { effectivePiStateDir, extractBackgroundPiSubagentDispatch } from "./pi-subagent-ledger.mjs";
|
|
17
14
|
import { resolvePiBashTimeoutExtensionArg } from "./pi-bash-timeout-injection.mjs";
|
|
18
|
-
import { classifyRuntimePrerequisite, probeNativeRuntimeReadiness, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
15
|
+
import { classifyPiMissingCredentialRejection, classifyRuntimePrerequisite, missingProviderCredentialReadiness, probeNativeRuntimeReadiness, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
19
16
|
import { buildStrictProviderErrorInput, classifyStrictProviderError, INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON, } from "./provider-error-classifier.mjs";
|
|
20
|
-
import { assertEffectivePiCompactionSettings, assertNoProjectPiCompactionOverride, calculatePiCompactionSettings, parsePiExecutableVersion, prepareOwnedPiDirectory, readOwnedPiSettings, verifyPiCapabilities, writeOwnedPiSettings, } from "./pi-compaction-recovery.mjs";
|
|
17
|
+
import { assertEffectivePiCompactionSettings, assertNoProjectPiCompactionOverride, PINNED_PI_VERSION, calculatePiCompactionSettings, parsePiExecutableVersion, prepareOwnedPiDirectory, readOwnedPiSettings, verifyPiCapabilities, writeOwnedPiSettings, } from "./pi-compaction-recovery.mjs";
|
|
18
|
+
function piAgentDirectory(configDir, agentId) {
|
|
19
|
+
return path.join(path.resolve(configDir), "providers", "pi", agentId);
|
|
20
|
+
}
|
|
21
21
|
class EventSession {
|
|
22
22
|
listeners = new Set();
|
|
23
23
|
subscribe(listener) {
|
|
@@ -462,7 +462,7 @@ class PiSession extends EventSession {
|
|
|
462
462
|
firstOutputObserved = false;
|
|
463
463
|
toolCallOpen = false;
|
|
464
464
|
piPolicyManaged;
|
|
465
|
-
constructor(sdk, distribution) {
|
|
465
|
+
constructor(sdk, distribution = "external") {
|
|
466
466
|
super();
|
|
467
467
|
this.sdk = sdk;
|
|
468
468
|
this.distribution = distribution;
|
|
@@ -537,7 +537,19 @@ class PiSession extends EventSession {
|
|
|
537
537
|
this.awaitingAcknowledgement.delete(input.inputId);
|
|
538
538
|
this.ownedInputIds.delete(input.inputId);
|
|
539
539
|
this.inputEpochs.delete(input.inputId);
|
|
540
|
-
|
|
540
|
+
const rawReason = error instanceof Error ? error.message : String(error);
|
|
541
|
+
const missing = classifyPiMissingCredentialRejection(rawReason);
|
|
542
|
+
if (missing) {
|
|
543
|
+
const classified = classifyPiProviderError({ provider: missing.provider, message: missing.diagnostic });
|
|
544
|
+
this.emit({
|
|
545
|
+
type: "input-error", inputId: input.inputId, retryable: false, willRetry: false,
|
|
546
|
+
message: classified.reason, errorCategory: classified.category, nextAction: classified.nextAction,
|
|
547
|
+
upstream: { provider: missing.provider, message: missing.diagnostic },
|
|
548
|
+
});
|
|
549
|
+
// 拒绝原因保留 Pi 原文,方便 RuntimeHost 独立分类,不依赖 input-error 事件顺序。
|
|
550
|
+
return { status: "rejected", inputId: input.inputId, retryable: false, reason: rawReason };
|
|
551
|
+
}
|
|
552
|
+
return { status: "rejected", inputId: input.inputId, retryable: true, reason: rawReason };
|
|
541
553
|
}
|
|
542
554
|
}
|
|
543
555
|
onEvent(raw) {
|
|
@@ -664,7 +676,7 @@ class PiSession extends EventSession {
|
|
|
664
676
|
this.emitObservation("settled");
|
|
665
677
|
const owned = [...this.ownedInputIds].filter((inputId) => this.inputEpochs.get(inputId) === epoch);
|
|
666
678
|
if (error) {
|
|
667
|
-
const classified = classifyPiProviderError(error);
|
|
679
|
+
const classified = classifyPiProviderError(error, { distribution: this.distribution });
|
|
668
680
|
const retryable = classified.category === "rate_limit" || isRetryablePiProviderError(error.upstream.message);
|
|
669
681
|
for (const inputId of owned)
|
|
670
682
|
this.emit({
|
|
@@ -794,7 +806,7 @@ function piAssistantProviderError(message) {
|
|
|
794
806
|
...(typeof bodyError.type === "string" ? { type: bodyError.type } : {}),
|
|
795
807
|
};
|
|
796
808
|
}
|
|
797
|
-
export function classifyPiProviderError(error) {
|
|
809
|
+
export function classifyPiProviderError(error, scope) {
|
|
798
810
|
const upstream = "upstream" in error ? error.upstream : error;
|
|
799
811
|
const errorType = "type" in error ? error.type : undefined;
|
|
800
812
|
const reason = safeProviderText(upstream.message, "Pi provider request failed");
|
|
@@ -818,6 +830,11 @@ export function classifyPiProviderError(error) {
|
|
|
818
830
|
return {
|
|
819
831
|
category: "rate_limit", reason, nextAction: "Wait for the provider rate-limit window, then retry.",
|
|
820
832
|
};
|
|
833
|
+
const missing = classifyPiMissingCredentialRejection(reason);
|
|
834
|
+
if (missing) {
|
|
835
|
+
const readiness = missingProviderCredentialReadiness("pi", missing.provider);
|
|
836
|
+
return { category: "auth", reason: readiness.reason, nextAction: readiness.nextAction };
|
|
837
|
+
}
|
|
821
838
|
if (upstream.status === 401
|
|
822
839
|
|| [...signal].some((value) => ["authentication_error", "invalid_api_key", "invalid_token", "token_expired", "unauthorized", "provider_auth_error"].includes(value))
|
|
823
840
|
|| /\bAPI key auth failed\b|\bFailed to resolve API key\b/i.test(reason)) {
|
|
@@ -983,10 +1000,6 @@ class PiRpcBackend {
|
|
|
983
1000
|
}
|
|
984
1001
|
}
|
|
985
1002
|
export function resolvePiProcessExtensionArgs(input, resolvers = {}) {
|
|
986
|
-
// Builtin factories are passed directly to Pi main on every platform. The platform
|
|
987
|
-
// field makes that invariant explicit and testable without changing process.platform.
|
|
988
|
-
if (input.distribution === "builtin")
|
|
989
|
-
return [];
|
|
990
1003
|
const resolverInput = { distribution: "external", piCommand: input.piCommand, env: input.env };
|
|
991
1004
|
const args = [];
|
|
992
1005
|
// Watchdog must load before the subagent extension so session_shutdown still
|
|
@@ -1024,31 +1037,23 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
1024
1037
|
...(session.sessionFile ? ["--session", session.sessionFile] : []),
|
|
1025
1038
|
...(requestedModel ? ["--model", requestedModel] : []),
|
|
1026
1039
|
...(requestedEffort ? ["--thinking", requestedEffort] : [])];
|
|
1027
|
-
|
|
1028
|
-
if (!builtin && mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin")
|
|
1040
|
+
if (mergedEnv.LARKIN_PI_DISTRIBUTION === "builtin")
|
|
1029
1041
|
delete mergedEnv.LARKIN_PI_DISTRIBUTION;
|
|
1030
|
-
const
|
|
1031
|
-
const
|
|
1032
|
-
const commandPrefix = builtinSpec?.args ?? dependencies.piCommandArgs ?? [];
|
|
1042
|
+
const command = dependencies.piCommand ?? dependencies.env?.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi";
|
|
1043
|
+
const commandPrefix = dependencies.piCommandArgs ?? [];
|
|
1033
1044
|
const commandArgs = [...commandPrefix, ...args];
|
|
1034
1045
|
mergedEnv.PI_CODING_AGENT_DIR = ownedPiDirectory;
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const childEnv = applyPiPackageDirForChild(mergedEnv, {
|
|
1038
|
-
distribution: builtin ? "builtin" : "external",
|
|
1039
|
-
explicitPackageDir: input.env?.PI_PACKAGE_DIR ?? dependencies.env?.PI_PACKAGE_DIR,
|
|
1040
|
-
});
|
|
1041
|
-
const reportedVersion = productionSpawn && !builtin
|
|
1046
|
+
const childEnv = { ...mergedEnv };
|
|
1047
|
+
const reportedVersion = productionSpawn
|
|
1042
1048
|
? parsePiExecutableVersion(String(spawnSync(command, [...commandPrefix, "--version"], {
|
|
1043
1049
|
cwd: input.workspaceDir, env: childEnv, encoding: "utf8", timeout: 5_000,
|
|
1044
1050
|
}).stdout || ""))
|
|
1045
|
-
:
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
});
|
|
1051
|
+
: PINNED_PI_VERSION;
|
|
1052
|
+
const extensionInput = { piCommand: command, env: childEnv, platform: process.platform };
|
|
1053
|
+
// 假 spawn 不需要真实 extension 路径;默认 resolver 会 spawnSync(`pi --version`)。
|
|
1054
|
+
const extensionArgs = dependencies.resolvePiProcessExtensionArgs
|
|
1055
|
+
? dependencies.resolvePiProcessExtensionArgs(extensionInput)
|
|
1056
|
+
: productionSpawn ? resolvePiProcessExtensionArgs(extensionInput) : [];
|
|
1052
1057
|
commandArgs.push(...extensionArgs);
|
|
1053
1058
|
let probedModel = null;
|
|
1054
1059
|
if (productionSpawn) {
|
|
@@ -1074,7 +1079,7 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
1074
1079
|
client.request("get_available_models"),
|
|
1075
1080
|
]);
|
|
1076
1081
|
if (!available?.models?.length)
|
|
1077
|
-
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow
|
|
1082
|
+
throw new Error("Pi has no authenticated available models. Run the official `pi` login flow; Larkin will not create a fallback session.");
|
|
1078
1083
|
const effectiveModel = state.model?.provider && state.model.id ? `${state.model.provider}/${state.model.id}` : null;
|
|
1079
1084
|
if (productionSpawn) {
|
|
1080
1085
|
if (!effectiveModel || !probedModel || effectiveModel !== probedModel.model
|
|
@@ -1088,11 +1093,10 @@ async function createPiRpcBackend(input, dependencies, spawn, productionSpawn =
|
|
|
1088
1093
|
keepRecentTokens: ownedSettings.compaction?.keepRecentTokens,
|
|
1089
1094
|
} });
|
|
1090
1095
|
const handshake = state.compactionCapabilities;
|
|
1091
|
-
verifyPiCapabilities({ distribution:
|
|
1096
|
+
verifyPiCapabilities({ distribution: "external", version: reportedVersion,
|
|
1092
1097
|
contextWindow: state.model?.contextWindow, autoCompactionEnabled: state.autoCompactionEnabled,
|
|
1093
1098
|
reserveTokens: handshake?.reserveTokens, keepRecentTokens: handshake?.keepRecentTokens,
|
|
1094
|
-
compactRpc: true, events: handshake?.events
|
|
1095
|
-
trustedProtocol: builtin });
|
|
1099
|
+
compactRpc: true, events: handshake?.events });
|
|
1096
1100
|
}
|
|
1097
1101
|
// 严格对账(Owner 决策):模型必须来自 pi 的权威可用列表,不做后缀猜测——
|
|
1098
1102
|
// 配置的模型与 pi 可用列表不符即报错,并在错误里列出可用模型。
|
|
@@ -1227,10 +1231,9 @@ export function createNativeRuntimeAdapter(id, dependencies = {}) {
|
|
|
1227
1231
|
throw new RuntimePrerequisiteError(readiness);
|
|
1228
1232
|
}
|
|
1229
1233
|
if (id === "pi") {
|
|
1230
|
-
const distribution = piChildDistributionFromOverrides(dependencies.env, input.env);
|
|
1231
1234
|
return new PiSession(await (dependencies.createPiSession
|
|
1232
1235
|
? dependencies.createPiSession(input)
|
|
1233
|
-
: createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn, productionSpawn))
|
|
1236
|
+
: createPiRpcBackend(input, { ...dependencies, piCommand: resolvedExecutable }, spawn, productionSpawn)));
|
|
1234
1237
|
}
|
|
1235
1238
|
if (id === "codex") {
|
|
1236
1239
|
const codexInput = dependencies.codexModelOverride?.trim()
|
|
@@ -102,6 +102,107 @@ function persistPreviousSession(agent, previous) {
|
|
|
102
102
|
// In-memory ref still holds for this process; restart will miss it only if this write failed.
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
function authFailureScopeOf(agent) {
|
|
106
|
+
return {
|
|
107
|
+
runtime: agent.config.runtime,
|
|
108
|
+
model: agent.config.model,
|
|
109
|
+
adapterId: agent.adapter.id,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function currentAuthFailure(agent) {
|
|
113
|
+
if (!agent.authFailureActive)
|
|
114
|
+
return null;
|
|
115
|
+
const runtime = agent.adapter.id === "codex" || agent.adapter.id === "claude" || agent.adapter.id === "pi"
|
|
116
|
+
? agent.adapter.id : null;
|
|
117
|
+
if (!runtime)
|
|
118
|
+
return null;
|
|
119
|
+
return {
|
|
120
|
+
kind: agent.authFailureKind === "missing-provider" ? "missing-provider" : "generic",
|
|
121
|
+
runtime,
|
|
122
|
+
provider: agent.authFailureProvider,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function genericAuthProvider(agent, upstream) {
|
|
126
|
+
return (0, runtime_readiness_js_1.safeProviderId)(upstream?.provider) ?? (0, runtime_readiness_js_1.configuredProviderId)(agent.config.model);
|
|
127
|
+
}
|
|
128
|
+
function persistAuthFailure(agent) {
|
|
129
|
+
const store = agent.stateStore;
|
|
130
|
+
if (!store)
|
|
131
|
+
return;
|
|
132
|
+
try {
|
|
133
|
+
const write = () => {
|
|
134
|
+
const current = store.readJson("agentState", { sessions: {} });
|
|
135
|
+
if (Array.isArray(current.records) && !("sessions" in current))
|
|
136
|
+
return;
|
|
137
|
+
const { authFailureProvider: _legacy, authFailure: _scoped, ...rest } = current;
|
|
138
|
+
const failure = currentAuthFailure(agent);
|
|
139
|
+
if (!failure) {
|
|
140
|
+
if (!("authFailureProvider" in current) && !("authFailure" in current))
|
|
141
|
+
return;
|
|
142
|
+
store.writeJson("agentState", rest);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
store.writeJson("agentState", {
|
|
146
|
+
...rest,
|
|
147
|
+
authFailure: failure,
|
|
148
|
+
...(failure.kind === "missing-provider" && failure.provider
|
|
149
|
+
? { authFailureProvider: failure.provider } : {}),
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
if (store.withInboxTransaction)
|
|
153
|
+
store.withInboxTransaction(write);
|
|
154
|
+
else
|
|
155
|
+
write();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// In-memory flags and the delivery ledger still hold for this process.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function loadPersistedAuthFailure(store) {
|
|
162
|
+
if (!store)
|
|
163
|
+
return null;
|
|
164
|
+
try {
|
|
165
|
+
return (0, runtime_readiness_js_1.parsePersistedAuthFailure)(store.readJson("agentState", {}));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function applyAuthFailure(agent, failure) {
|
|
172
|
+
agent.authFailureActive = true;
|
|
173
|
+
agent.authFailureKind = failure.kind;
|
|
174
|
+
agent.authFailureProvider = failure.provider ?? null;
|
|
175
|
+
agent.readiness = (0, runtime_readiness_js_1.readinessForPersistedAuthFailure)(failure);
|
|
176
|
+
}
|
|
177
|
+
function clearAuthFailure(agent, persist = true) {
|
|
178
|
+
agent.authFailureActive = false;
|
|
179
|
+
agent.authFailureKind = null;
|
|
180
|
+
agent.authFailureProvider = null;
|
|
181
|
+
if (persist)
|
|
182
|
+
persistAuthFailure(agent);
|
|
183
|
+
}
|
|
184
|
+
function restoreAuthFailure(agent) {
|
|
185
|
+
const persisted = loadPersistedAuthFailure(agent.stateStore);
|
|
186
|
+
if (!persisted)
|
|
187
|
+
return;
|
|
188
|
+
// 只恢复仍绑定当前 candidate 的当前作用域 auth;不从历史 delivery 回灌。
|
|
189
|
+
if (!(0, runtime_readiness_js_1.authFailureAppliesTo)(authFailureScopeOf(agent), persisted)) {
|
|
190
|
+
clearAuthFailure(agent);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
applyAuthFailure(agent, persisted);
|
|
194
|
+
}
|
|
195
|
+
function clearLedgerAuthProviders(agent) {
|
|
196
|
+
let changed = false;
|
|
197
|
+
for (const record of agent.records.values()) {
|
|
198
|
+
if (!record.authProvider)
|
|
199
|
+
continue;
|
|
200
|
+
delete record.authProvider;
|
|
201
|
+
changed = true;
|
|
202
|
+
}
|
|
203
|
+
if (changed)
|
|
204
|
+
persist(agent);
|
|
205
|
+
}
|
|
105
206
|
const REPLAY_FAILURE_CODES = new Set([
|
|
106
207
|
"canonical_inbox_row_missing", "canonical_inbox_malformed", "duplicate_message_id",
|
|
107
208
|
"inbox_state_conflict", "delivery_target_conflict", "structured_target_invalid",
|
|
@@ -228,7 +329,6 @@ function createRuntimeHost(options) {
|
|
|
228
329
|
} : {}),
|
|
229
330
|
LARKIN_CONFIG_DIR: process.env.LARKIN_CONFIG_DIR,
|
|
230
331
|
LARKIN_HOME: process.env.LARKIN_HOME,
|
|
231
|
-
...(config.piDistribution ? { LARKIN_PI_DISTRIBUTION: config.piDistribution } : {}),
|
|
232
332
|
LARKIN_STATE_DIR: (0, pi_subagent_ledger_js_1.effectivePiStateDir)(config),
|
|
233
333
|
...(process.env.HOME ? { HOME: process.env.HOME } : {}),
|
|
234
334
|
...(process.env.SHELL ? { SHELL: process.env.SHELL } : {}),
|
|
@@ -490,6 +590,25 @@ function createRuntimeHost(options) {
|
|
|
490
590
|
messageId: record.messageId, status: "consumed" });
|
|
491
591
|
}
|
|
492
592
|
};
|
|
593
|
+
const projectAuthFailure = (agent, readiness, failure) => {
|
|
594
|
+
agent.authFailureActive = true;
|
|
595
|
+
agent.authFailureKind = failure.kind;
|
|
596
|
+
agent.authFailureProvider = failure.provider;
|
|
597
|
+
agent.readiness = {
|
|
598
|
+
...readiness,
|
|
599
|
+
...(agent.readiness?.executable ? { executable: agent.readiness.executable } : {}),
|
|
600
|
+
...(agent.readiness?.version ? { version: agent.readiness.version } : {}),
|
|
601
|
+
};
|
|
602
|
+
persistAuthFailure(agent);
|
|
603
|
+
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
604
|
+
error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
|
|
605
|
+
};
|
|
606
|
+
const namedAuthProviderPresent = (agent) => {
|
|
607
|
+
if (agent.authFailureKind !== "missing-provider" || !agent.authFailureProvider)
|
|
608
|
+
return true;
|
|
609
|
+
const failure = currentAuthFailure(agent);
|
|
610
|
+
return Boolean(failure && (0, runtime_readiness_js_1.authFailureAppliesTo)(authFailureScopeOf(agent), failure));
|
|
611
|
+
};
|
|
493
612
|
const resultFor = (agent, record, result) => {
|
|
494
613
|
if (result.status === "accepted") {
|
|
495
614
|
if (record.errorCategory !== "context_window") {
|
|
@@ -503,6 +622,28 @@ function createRuntimeHost(options) {
|
|
|
503
622
|
deliveryId: record.deliveryId, messageId: record.messageId, status: "accepted" });
|
|
504
623
|
return { status: "accepted", deliveryId: record.deliveryId };
|
|
505
624
|
}
|
|
625
|
+
const missing = result.status === "rejected" && agent.adapter.id === "pi"
|
|
626
|
+
? (0, runtime_readiness_js_1.classifyPiMissingCredentialRejection)(result.reason)
|
|
627
|
+
: null;
|
|
628
|
+
if (missing) {
|
|
629
|
+
const readiness = (0, runtime_readiness_js_1.missingProviderCredentialReadiness)(agent.adapter.id, missing.provider);
|
|
630
|
+
const reason = readiness.reason ?? result.reason;
|
|
631
|
+
if (record.status !== "error") {
|
|
632
|
+
record.reason = reason;
|
|
633
|
+
record.retryable = false;
|
|
634
|
+
record.errorCategory = "auth";
|
|
635
|
+
record.authProvider = missing.provider;
|
|
636
|
+
const finalRecord = setRecord(agent, record, "error");
|
|
637
|
+
if (finalRecord.status !== "consumed")
|
|
638
|
+
emit({ type: "delivery", agentId: agent.config.agentId,
|
|
639
|
+
deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason });
|
|
640
|
+
projectAuthFailure(agent, readiness, { kind: "missing-provider", provider: missing.provider });
|
|
641
|
+
}
|
|
642
|
+
return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? reason, retryable: false };
|
|
643
|
+
}
|
|
644
|
+
if (record.status === "error") {
|
|
645
|
+
return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? result.reason, retryable: false };
|
|
646
|
+
}
|
|
506
647
|
if (record.errorCategory !== "context_window")
|
|
507
648
|
delete record.errorCategory;
|
|
508
649
|
const retryable = result.status === "deferred" || result.retryable;
|
|
@@ -1347,7 +1488,7 @@ function createRuntimeHost(options) {
|
|
|
1347
1488
|
agent.compactionMachines.delete(inputId);
|
|
1348
1489
|
}
|
|
1349
1490
|
const recoveredAuthentication = agent.authFailureActive && agent.turnInProgress
|
|
1350
|
-
&& agent.turnHadAuthenticatedOutput && !agent.turnHadFailure;
|
|
1491
|
+
&& agent.turnHadAuthenticatedOutput && !agent.turnHadFailure && namedAuthProviderPresent(agent);
|
|
1351
1492
|
agent.turnInProgress = false;
|
|
1352
1493
|
agent.busy = false;
|
|
1353
1494
|
emit({ type: "activity", agentId: agent.config.agentId, activity: "idle", activityKind: "idle", detailKind: "turn_ended" });
|
|
@@ -1369,7 +1510,8 @@ function createRuntimeHost(options) {
|
|
|
1369
1510
|
reconcileSubagentLedger(agent);
|
|
1370
1511
|
telemetry?.runtimeEvent(agent.config.agentId, event);
|
|
1371
1512
|
if (recoveredAuthentication) {
|
|
1372
|
-
agent
|
|
1513
|
+
clearAuthFailure(agent);
|
|
1514
|
+
clearLedgerAuthProviders(agent);
|
|
1373
1515
|
const prior = agent.readiness;
|
|
1374
1516
|
agent.readiness = {
|
|
1375
1517
|
runtime: agent.adapter.id,
|
|
@@ -1462,23 +1604,30 @@ function createRuntimeHost(options) {
|
|
|
1462
1604
|
record.errorCategory = classifiedCategory;
|
|
1463
1605
|
else
|
|
1464
1606
|
delete record.errorCategory;
|
|
1465
|
-
const
|
|
1607
|
+
const missing = agent.adapter.id === "pi"
|
|
1608
|
+
? (0, runtime_readiness_js_1.classifyPiMissingCredentialRejection)(event.upstream?.message)
|
|
1609
|
+
?? (0, runtime_readiness_js_1.classifyPiMissingCredentialRejection)(event.message)
|
|
1610
|
+
: null;
|
|
1611
|
+
if (missing) {
|
|
1612
|
+
record.retryable = false;
|
|
1613
|
+
record.errorCategory = "auth";
|
|
1614
|
+
record.authProvider = missing.provider;
|
|
1615
|
+
}
|
|
1616
|
+
const terminal = Boolean(missing) || !event.retryable;
|
|
1617
|
+
const finalRecord = setRecord(agent, record, terminal ? "error" : "pending");
|
|
1466
1618
|
if (finalRecord.status !== "consumed")
|
|
1467
1619
|
emit({ type: "delivery", agentId: agent.config.agentId,
|
|
1468
1620
|
deliveryId: record.deliveryId, messageId: record.messageId,
|
|
1469
|
-
status:
|
|
1470
|
-
if (
|
|
1621
|
+
status: terminal ? "error" : "deferred", reason: event.message });
|
|
1622
|
+
if (missing) {
|
|
1623
|
+
const readiness = (0, runtime_readiness_js_1.missingProviderCredentialReadiness)(agent.adapter.id, missing.provider);
|
|
1624
|
+
projectAuthFailure(agent, readiness, { kind: "missing-provider", provider: missing.provider });
|
|
1625
|
+
}
|
|
1626
|
+
else if (event.errorCategory === "auth" && !event.retryable) {
|
|
1471
1627
|
const readiness = (0, runtime_readiness_js_1.providerAuthenticationFailureReadiness)(agent.adapter.id, event.upstream?.provider);
|
|
1472
|
-
agent.
|
|
1473
|
-
agent.readiness = {
|
|
1474
|
-
...readiness,
|
|
1475
|
-
...(agent.readiness?.executable ? { executable: agent.readiness.executable } : {}),
|
|
1476
|
-
...(agent.readiness?.version ? { version: agent.readiness.version } : {}),
|
|
1477
|
-
};
|
|
1478
|
-
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
1479
|
-
error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
|
|
1628
|
+
projectAuthFailure(agent, readiness, { kind: "generic", provider: genericAuthProvider(agent, event.upstream) });
|
|
1480
1629
|
}
|
|
1481
|
-
if (event.retryable && !agent.busy && !agent.turnInProgress)
|
|
1630
|
+
if (!terminal && event.retryable && !agent.busy && !agent.turnInProgress)
|
|
1482
1631
|
void retryPending(agent);
|
|
1483
1632
|
}
|
|
1484
1633
|
else if (event.type === "configuration-error") {
|
|
@@ -1748,14 +1897,26 @@ function createRuntimeHost(options) {
|
|
|
1748
1897
|
clearTimeout(previous.backgroundCompletionRetryTimer);
|
|
1749
1898
|
if (previous.subagentReconcileTimer)
|
|
1750
1899
|
clearInterval(previous.subagentReconcileTimer);
|
|
1900
|
+
const previousFailure = currentAuthFailure(previous);
|
|
1901
|
+
const scoped = Boolean(previousFailure
|
|
1902
|
+
&& (0, runtime_readiness_js_1.authFailureAppliesTo)({
|
|
1903
|
+
runtime: config.runtime,
|
|
1904
|
+
model: config.model,
|
|
1905
|
+
adapterId: adapter.id,
|
|
1906
|
+
}, previousFailure));
|
|
1751
1907
|
const candidate = {
|
|
1752
1908
|
...previous, config, adapter, session, launchId: node_crypto_1.default.randomUUID(), busy: false, submitting: false,
|
|
1753
1909
|
starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
|
|
1754
1910
|
recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
|
|
1755
1911
|
backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
|
|
1756
1912
|
disabledReason: null, configurationRecovery: null,
|
|
1757
|
-
|
|
1913
|
+
authFailureActive: scoped && previous.authFailureActive,
|
|
1914
|
+
authFailureKind: scoped ? previous.authFailureKind : null,
|
|
1915
|
+
authFailureProvider: scoped ? previous.authFailureProvider : null,
|
|
1916
|
+
readiness: scoped && previous.authFailureActive ? previous.readiness : readiness,
|
|
1758
1917
|
};
|
|
1918
|
+
if (previous.authFailureActive && !candidate.authFailureActive)
|
|
1919
|
+
clearAuthFailure(candidate);
|
|
1759
1920
|
managed.set(config.agentId, candidate);
|
|
1760
1921
|
session.subscribe((event) => observe(candidate, session, event));
|
|
1761
1922
|
if (session.sessionId) {
|
|
@@ -1850,7 +2011,8 @@ function createRuntimeHost(options) {
|
|
|
1850
2011
|
agent.config.sessionId = null;
|
|
1851
2012
|
agent.session = fresh;
|
|
1852
2013
|
persistPreviousSession(agent, previousRef);
|
|
1853
|
-
agent.
|
|
2014
|
+
if (!agent.authFailureActive)
|
|
2015
|
+
agent.readiness = readiness;
|
|
1854
2016
|
agent.disabledReason = null;
|
|
1855
2017
|
};
|
|
1856
2018
|
try {
|
|
@@ -1871,7 +2033,13 @@ function createRuntimeHost(options) {
|
|
|
1871
2033
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
1872
2034
|
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1873
2035
|
}
|
|
1874
|
-
emit({
|
|
2036
|
+
emit({
|
|
2037
|
+
type: "agent-status",
|
|
2038
|
+
agentId,
|
|
2039
|
+
status: agent.authFailureActive ? "error" : "active",
|
|
2040
|
+
...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
|
|
2041
|
+
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2042
|
+
});
|
|
1875
2043
|
agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1876
2044
|
await oldSession.close("fresh session reset committed")
|
|
1877
2045
|
.catch((error) => log("previous runtime close after fresh reset failed", String(error)));
|
|
@@ -2009,7 +2177,8 @@ function createRuntimeHost(options) {
|
|
|
2009
2177
|
agent.config.sessionId = null;
|
|
2010
2178
|
agent.session = fresh;
|
|
2011
2179
|
persistPreviousSession(agent, previousRef);
|
|
2012
|
-
agent.
|
|
2180
|
+
if (!agent.authFailureActive)
|
|
2181
|
+
agent.readiness = readiness;
|
|
2013
2182
|
agent.disabledReason = null;
|
|
2014
2183
|
for (const record of agent.records.values()) {
|
|
2015
2184
|
if (!messageIds.includes(record.messageId))
|
|
@@ -2027,7 +2196,13 @@ function createRuntimeHost(options) {
|
|
|
2027
2196
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
2028
2197
|
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
2029
2198
|
}
|
|
2030
|
-
emit({
|
|
2199
|
+
emit({
|
|
2200
|
+
type: "agent-status",
|
|
2201
|
+
agentId,
|
|
2202
|
+
status: agent.authFailureActive ? "error" : "active",
|
|
2203
|
+
...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
|
|
2204
|
+
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2205
|
+
});
|
|
2031
2206
|
durableRollback = null;
|
|
2032
2207
|
agent.piSessionOwner = probeEnv[pi_subagent_ledger_js_1.PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
2033
2208
|
await oldSession.close("context-window recovery committed")
|
|
@@ -2117,11 +2292,13 @@ function createRuntimeHost(options) {
|
|
|
2117
2292
|
piProactiveCompactionFailedGeneration: null,
|
|
2118
2293
|
previousSession: loadPersistedPreviousSession(config, stateStore), readiness: null,
|
|
2119
2294
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false,
|
|
2295
|
+
authFailureKind: null, authFailureProvider: null,
|
|
2120
2296
|
promotedInboxUpdateIds: new Set(),
|
|
2121
2297
|
backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
|
|
2122
2298
|
backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
|
|
2123
2299
|
backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
|
|
2124
2300
|
subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
|
|
2301
|
+
restoreAuthFailure(agent);
|
|
2125
2302
|
const startupConsumed = [];
|
|
2126
2303
|
const startupQuarantined = [];
|
|
2127
2304
|
for (const record of agent.records.values()) {
|