larkin 0.4.28 → 0.4.29
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/app/agent-config.mjs +3 -1
- package/dist/app/cli.mjs +13 -3
- package/dist/app/local-control.mjs +16 -3
- package/dist/app/pi-auth-cli.mjs +227 -17
- package/dist/app/runtime-process.mjs +42 -5
- package/dist/dashboard/dashboard-config-controller.mjs +130 -5
- package/dist/dashboard/web/assets/dashboard.css +1 -1
- package/dist/dashboard/web/assets/dashboard.js +15 -15
- package/dist/feishu/host-shell.cjs +68 -16
- package/dist/feishu/host-shell.mjs +69 -17
- package/dist/platform/config.cjs +38 -10
- package/dist/platform/config.mjs +36 -10
- package/dist/runtime/pi-official-auth.cjs +594 -0
- package/dist/runtime/pi-official-auth.mjs +53 -0
- package/dist/runtime/pi-provider-config.cjs +26 -3
- package/dist/runtime/pi-provider-config.mjs +22 -2
- package/dist/runtime/pi-provider-login.mjs +240 -0
- package/dist/runtime/runtime-adapters.mjs +24 -4
- package/dist/runtime/runtime-host.cjs +215 -16
- package/dist/runtime/runtime-host.mjs +216 -17
- package/dist/runtime/runtime-readiness.cjs +123 -3
- package/dist/runtime/runtime-readiness.mjs +116 -4
- package/package.json +1 -1
|
@@ -6,7 +6,8 @@ import { auditReminderDelivery } from "../agent/reminder-delivery-audit.mjs";
|
|
|
6
6
|
import { isCanonicalInboxTarget, targetKeyOfInboxEnvelope } from "../agent/inbox-projection.mjs";
|
|
7
7
|
import { SpanKind } from "@opentelemetry/api";
|
|
8
8
|
import { buildStrictProviderErrorInput, classifyStrictProviderError } from "./provider-error-classifier.mjs";
|
|
9
|
-
import { classifyRuntimePrerequisite, providerAuthenticationFailureReadiness, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
9
|
+
import { authFailureAppliesTo, classifyPiMissingCredentialRejection, classifyRuntimePrerequisite, configuredProviderId, isBuiltinPiDistribution, missingProviderCredentialReadiness, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, safeProviderId, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
|
|
10
|
+
import { officialPiHasStoredProvider } from "./pi-official-auth.mjs";
|
|
10
11
|
import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
|
|
11
12
|
import { assertAgentWorkspaceBound, managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
|
|
12
13
|
import { isPiNativeCompactionRequired, PiCompactionBreaker, PiCompactionRecoveryMachine, } from "./pi-compaction-recovery.mjs";
|
|
@@ -93,6 +94,121 @@ function persistPreviousSession(agent, previous) {
|
|
|
93
94
|
// In-memory ref still holds for this process; restart will miss it only if this write failed.
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
function isBuiltinPiAgent(agent) {
|
|
98
|
+
return agent.adapter.id === "pi"
|
|
99
|
+
&& (isBuiltinPiDistribution(agent.config.piDistribution) || agent.config.runtime === "builtin-pi");
|
|
100
|
+
}
|
|
101
|
+
function authFailureScopeOf(agent) {
|
|
102
|
+
return {
|
|
103
|
+
runtime: agent.config.runtime,
|
|
104
|
+
model: agent.config.model,
|
|
105
|
+
piDistribution: agent.config.piDistribution,
|
|
106
|
+
adapterId: agent.adapter.id,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function currentAuthFailure(agent) {
|
|
110
|
+
if (!agent.authFailureActive)
|
|
111
|
+
return null;
|
|
112
|
+
const kind = agent.authFailureKind
|
|
113
|
+
?? (agent.authFailureProvider ? "missing-provider" : "generic");
|
|
114
|
+
if (kind === "missing-provider") {
|
|
115
|
+
if (!agent.authFailureProvider)
|
|
116
|
+
return null;
|
|
117
|
+
return { kind: "missing-provider", runtime: "pi", piDistribution: "builtin", provider: agent.authFailureProvider };
|
|
118
|
+
}
|
|
119
|
+
const runtime = agent.adapter.id === "codex" || agent.adapter.id === "claude" || agent.adapter.id === "pi"
|
|
120
|
+
? agent.adapter.id : null;
|
|
121
|
+
if (!runtime)
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
kind: "generic",
|
|
125
|
+
runtime,
|
|
126
|
+
...(runtime === "pi" && (agent.config.piDistribution === "builtin" || agent.config.piDistribution === "external")
|
|
127
|
+
? { piDistribution: agent.config.piDistribution } : {}),
|
|
128
|
+
provider: agent.authFailureProvider,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function genericAuthProvider(agent, upstream) {
|
|
132
|
+
return safeProviderId(upstream?.provider) ?? configuredProviderId(agent.config.model);
|
|
133
|
+
}
|
|
134
|
+
function persistAuthFailure(agent) {
|
|
135
|
+
const store = agent.stateStore;
|
|
136
|
+
if (!store)
|
|
137
|
+
return;
|
|
138
|
+
try {
|
|
139
|
+
const write = () => {
|
|
140
|
+
const current = store.readJson("agentState", { sessions: {} });
|
|
141
|
+
if (Array.isArray(current.records) && !("sessions" in current))
|
|
142
|
+
return;
|
|
143
|
+
const { authFailureProvider: _legacy, authFailure: _scoped, ...rest } = current;
|
|
144
|
+
const failure = currentAuthFailure(agent);
|
|
145
|
+
if (!failure) {
|
|
146
|
+
if (!("authFailureProvider" in current) && !("authFailure" in current))
|
|
147
|
+
return;
|
|
148
|
+
store.writeJson("agentState", rest);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
store.writeJson("agentState", {
|
|
152
|
+
...rest,
|
|
153
|
+
authFailure: failure,
|
|
154
|
+
...(failure.kind === "missing-provider" && failure.provider
|
|
155
|
+
? { authFailureProvider: failure.provider } : {}),
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
if (store.withInboxTransaction)
|
|
159
|
+
store.withInboxTransaction(write);
|
|
160
|
+
else
|
|
161
|
+
write();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// In-memory flags and the delivery ledger still hold for this process.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function loadPersistedAuthFailure(store) {
|
|
168
|
+
if (!store)
|
|
169
|
+
return null;
|
|
170
|
+
try {
|
|
171
|
+
return parsePersistedAuthFailure(store.readJson("agentState", {}));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function applyAuthFailure(agent, failure) {
|
|
178
|
+
agent.authFailureActive = true;
|
|
179
|
+
agent.authFailureKind = failure.kind;
|
|
180
|
+
agent.authFailureProvider = failure.provider ?? null;
|
|
181
|
+
agent.readiness = readinessForPersistedAuthFailure(failure);
|
|
182
|
+
}
|
|
183
|
+
function clearAuthFailure(agent, persist = true) {
|
|
184
|
+
agent.authFailureActive = false;
|
|
185
|
+
agent.authFailureKind = null;
|
|
186
|
+
agent.authFailureProvider = null;
|
|
187
|
+
if (persist)
|
|
188
|
+
persistAuthFailure(agent);
|
|
189
|
+
}
|
|
190
|
+
function restoreAuthFailure(agent) {
|
|
191
|
+
const persisted = loadPersistedAuthFailure(agent.stateStore);
|
|
192
|
+
if (!persisted)
|
|
193
|
+
return;
|
|
194
|
+
// 只恢复仍绑定当前 candidate 的当前作用域 auth;不从历史 delivery 回灌。
|
|
195
|
+
if (!authFailureAppliesTo(authFailureScopeOf(agent), persisted)) {
|
|
196
|
+
clearAuthFailure(agent);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
applyAuthFailure(agent, persisted);
|
|
200
|
+
}
|
|
201
|
+
function clearLedgerAuthProviders(agent) {
|
|
202
|
+
let changed = false;
|
|
203
|
+
for (const record of agent.records.values()) {
|
|
204
|
+
if (!record.authProvider)
|
|
205
|
+
continue;
|
|
206
|
+
delete record.authProvider;
|
|
207
|
+
changed = true;
|
|
208
|
+
}
|
|
209
|
+
if (changed)
|
|
210
|
+
persist(agent);
|
|
211
|
+
}
|
|
96
212
|
const REPLAY_FAILURE_CODES = new Set([
|
|
97
213
|
"canonical_inbox_row_missing", "canonical_inbox_malformed", "duplicate_message_id",
|
|
98
214
|
"inbox_state_conflict", "delivery_target_conflict", "structured_target_invalid",
|
|
@@ -481,6 +597,34 @@ export function createRuntimeHost(options) {
|
|
|
481
597
|
messageId: record.messageId, status: "consumed" });
|
|
482
598
|
}
|
|
483
599
|
};
|
|
600
|
+
const ownedConfigDir = () => {
|
|
601
|
+
const value = process.env.LARKIN_CONFIG_DIR?.trim();
|
|
602
|
+
return value || null;
|
|
603
|
+
};
|
|
604
|
+
const projectAuthFailure = (agent, readiness, failure) => {
|
|
605
|
+
agent.authFailureActive = true;
|
|
606
|
+
agent.authFailureKind = failure.kind;
|
|
607
|
+
agent.authFailureProvider = failure.provider;
|
|
608
|
+
agent.readiness = {
|
|
609
|
+
...readiness,
|
|
610
|
+
...(agent.readiness?.executable ? { executable: agent.readiness.executable } : {}),
|
|
611
|
+
...(agent.readiness?.version ? { version: agent.readiness.version } : {}),
|
|
612
|
+
};
|
|
613
|
+
persistAuthFailure(agent);
|
|
614
|
+
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
615
|
+
error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
|
|
616
|
+
};
|
|
617
|
+
const namedAuthProviderPresent = (agent) => {
|
|
618
|
+
if (agent.authFailureKind !== "missing-provider" || !agent.authFailureProvider)
|
|
619
|
+
return true;
|
|
620
|
+
const failure = currentAuthFailure(agent);
|
|
621
|
+
if (!failure || !authFailureAppliesTo(authFailureScopeOf(agent), failure))
|
|
622
|
+
return false;
|
|
623
|
+
const configDir = ownedConfigDir();
|
|
624
|
+
if (!configDir)
|
|
625
|
+
return false;
|
|
626
|
+
return officialPiHasStoredProvider(configDir, agent.config.agentId, agent.authFailureProvider);
|
|
627
|
+
};
|
|
484
628
|
const resultFor = (agent, record, result) => {
|
|
485
629
|
if (result.status === "accepted") {
|
|
486
630
|
if (record.errorCategory !== "context_window") {
|
|
@@ -494,6 +638,28 @@ export function createRuntimeHost(options) {
|
|
|
494
638
|
deliveryId: record.deliveryId, messageId: record.messageId, status: "accepted" });
|
|
495
639
|
return { status: "accepted", deliveryId: record.deliveryId };
|
|
496
640
|
}
|
|
641
|
+
const missing = result.status === "rejected" && isBuiltinPiAgent(agent)
|
|
642
|
+
? classifyPiMissingCredentialRejection(result.reason)
|
|
643
|
+
: null;
|
|
644
|
+
if (missing) {
|
|
645
|
+
const readiness = missingProviderCredentialReadiness(agent.adapter.id, missing.provider);
|
|
646
|
+
const reason = readiness.reason ?? `Provider ${missing.provider} is missing from this Agent's official credential store.`;
|
|
647
|
+
if (record.status !== "error") {
|
|
648
|
+
record.reason = reason;
|
|
649
|
+
record.retryable = false;
|
|
650
|
+
record.errorCategory = "auth";
|
|
651
|
+
record.authProvider = missing.provider;
|
|
652
|
+
const finalRecord = setRecord(agent, record, "error");
|
|
653
|
+
if (finalRecord.status !== "consumed")
|
|
654
|
+
emit({ type: "delivery", agentId: agent.config.agentId,
|
|
655
|
+
deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason });
|
|
656
|
+
projectAuthFailure(agent, readiness, { kind: "missing-provider", provider: missing.provider });
|
|
657
|
+
}
|
|
658
|
+
return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? reason, retryable: false };
|
|
659
|
+
}
|
|
660
|
+
if (record.status === "error") {
|
|
661
|
+
return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? result.reason, retryable: false };
|
|
662
|
+
}
|
|
497
663
|
if (record.errorCategory !== "context_window")
|
|
498
664
|
delete record.errorCategory;
|
|
499
665
|
const retryable = result.status === "deferred" || result.retryable;
|
|
@@ -1338,7 +1504,7 @@ export function createRuntimeHost(options) {
|
|
|
1338
1504
|
agent.compactionMachines.delete(inputId);
|
|
1339
1505
|
}
|
|
1340
1506
|
const recoveredAuthentication = agent.authFailureActive && agent.turnInProgress
|
|
1341
|
-
&& agent.turnHadAuthenticatedOutput && !agent.turnHadFailure;
|
|
1507
|
+
&& agent.turnHadAuthenticatedOutput && !agent.turnHadFailure && namedAuthProviderPresent(agent);
|
|
1342
1508
|
agent.turnInProgress = false;
|
|
1343
1509
|
agent.busy = false;
|
|
1344
1510
|
emit({ type: "activity", agentId: agent.config.agentId, activity: "idle", activityKind: "idle", detailKind: "turn_ended" });
|
|
@@ -1360,7 +1526,8 @@ export function createRuntimeHost(options) {
|
|
|
1360
1526
|
reconcileSubagentLedger(agent);
|
|
1361
1527
|
telemetry?.runtimeEvent(agent.config.agentId, event);
|
|
1362
1528
|
if (recoveredAuthentication) {
|
|
1363
|
-
agent
|
|
1529
|
+
clearAuthFailure(agent);
|
|
1530
|
+
clearLedgerAuthProviders(agent);
|
|
1364
1531
|
const prior = agent.readiness;
|
|
1365
1532
|
agent.readiness = {
|
|
1366
1533
|
runtime: agent.adapter.id,
|
|
@@ -1453,21 +1620,24 @@ export function createRuntimeHost(options) {
|
|
|
1453
1620
|
record.errorCategory = classifiedCategory;
|
|
1454
1621
|
else
|
|
1455
1622
|
delete record.errorCategory;
|
|
1623
|
+
const missing = event.errorCategory === "auth" && !event.retryable && isBuiltinPiAgent(agent)
|
|
1624
|
+
? classifyPiMissingCredentialRejection(event.upstream?.message)
|
|
1625
|
+
?? classifyPiMissingCredentialRejection(event.message)
|
|
1626
|
+
: null;
|
|
1627
|
+
if (missing)
|
|
1628
|
+
record.authProvider = missing.provider;
|
|
1456
1629
|
const finalRecord = setRecord(agent, record, event.retryable ? "pending" : "error");
|
|
1457
1630
|
if (finalRecord.status !== "consumed")
|
|
1458
1631
|
emit({ type: "delivery", agentId: agent.config.agentId,
|
|
1459
1632
|
deliveryId: record.deliveryId, messageId: record.messageId,
|
|
1460
1633
|
status: event.retryable ? "deferred" : "error", reason: event.message });
|
|
1461
1634
|
if (event.errorCategory === "auth" && !event.retryable) {
|
|
1462
|
-
const readiness =
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
};
|
|
1469
|
-
emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
|
|
1470
|
-
error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
|
|
1635
|
+
const readiness = missing
|
|
1636
|
+
? missingProviderCredentialReadiness(agent.adapter.id, missing.provider)
|
|
1637
|
+
: providerAuthenticationFailureReadiness(agent.adapter.id, event.upstream?.provider);
|
|
1638
|
+
projectAuthFailure(agent, readiness, missing
|
|
1639
|
+
? { kind: "missing-provider", provider: missing.provider }
|
|
1640
|
+
: { kind: "generic", provider: genericAuthProvider(agent, event.upstream) });
|
|
1471
1641
|
}
|
|
1472
1642
|
if (event.retryable && !agent.busy && !agent.turnInProgress)
|
|
1473
1643
|
void retryPending(agent);
|
|
@@ -1739,14 +1909,27 @@ export function createRuntimeHost(options) {
|
|
|
1739
1909
|
clearTimeout(previous.backgroundCompletionRetryTimer);
|
|
1740
1910
|
if (previous.subagentReconcileTimer)
|
|
1741
1911
|
clearInterval(previous.subagentReconcileTimer);
|
|
1912
|
+
const previousFailure = currentAuthFailure(previous);
|
|
1913
|
+
const scoped = Boolean(previousFailure
|
|
1914
|
+
&& authFailureAppliesTo({
|
|
1915
|
+
runtime: config.runtime,
|
|
1916
|
+
model: config.model,
|
|
1917
|
+
piDistribution: config.piDistribution,
|
|
1918
|
+
adapterId: adapter.id,
|
|
1919
|
+
}, previousFailure));
|
|
1742
1920
|
const candidate = {
|
|
1743
1921
|
...previous, config, adapter, session, launchId: crypto.randomUUID(), busy: false, submitting: false,
|
|
1744
1922
|
starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
|
|
1745
1923
|
recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
|
|
1746
1924
|
backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
|
|
1747
1925
|
disabledReason: null, configurationRecovery: null,
|
|
1748
|
-
|
|
1926
|
+
authFailureActive: scoped && previous.authFailureActive,
|
|
1927
|
+
authFailureKind: scoped ? previous.authFailureKind : null,
|
|
1928
|
+
authFailureProvider: scoped ? previous.authFailureProvider : null,
|
|
1929
|
+
readiness: scoped && previous.authFailureActive ? previous.readiness : readiness,
|
|
1749
1930
|
};
|
|
1931
|
+
if (previous.authFailureActive && !candidate.authFailureActive)
|
|
1932
|
+
clearAuthFailure(candidate);
|
|
1750
1933
|
managed.set(config.agentId, candidate);
|
|
1751
1934
|
session.subscribe((event) => observe(candidate, session, event));
|
|
1752
1935
|
if (session.sessionId) {
|
|
@@ -1841,7 +2024,8 @@ export function createRuntimeHost(options) {
|
|
|
1841
2024
|
agent.config.sessionId = null;
|
|
1842
2025
|
agent.session = fresh;
|
|
1843
2026
|
persistPreviousSession(agent, previousRef);
|
|
1844
|
-
agent.
|
|
2027
|
+
if (!agent.authFailureActive)
|
|
2028
|
+
agent.readiness = readiness;
|
|
1845
2029
|
agent.disabledReason = null;
|
|
1846
2030
|
};
|
|
1847
2031
|
try {
|
|
@@ -1862,7 +2046,13 @@ export function createRuntimeHost(options) {
|
|
|
1862
2046
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
1863
2047
|
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
1864
2048
|
}
|
|
1865
|
-
emit({
|
|
2049
|
+
emit({
|
|
2050
|
+
type: "agent-status",
|
|
2051
|
+
agentId,
|
|
2052
|
+
status: agent.authFailureActive ? "error" : "active",
|
|
2053
|
+
...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
|
|
2054
|
+
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2055
|
+
});
|
|
1866
2056
|
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
1867
2057
|
await oldSession.close("fresh session reset committed")
|
|
1868
2058
|
.catch((error) => log("previous runtime close after fresh reset failed", String(error)));
|
|
@@ -2000,7 +2190,8 @@ export function createRuntimeHost(options) {
|
|
|
2000
2190
|
agent.config.sessionId = null;
|
|
2001
2191
|
agent.session = fresh;
|
|
2002
2192
|
persistPreviousSession(agent, previousRef);
|
|
2003
|
-
agent.
|
|
2193
|
+
if (!agent.authFailureActive)
|
|
2194
|
+
agent.readiness = readiness;
|
|
2004
2195
|
agent.disabledReason = null;
|
|
2005
2196
|
for (const record of agent.records.values()) {
|
|
2006
2197
|
if (!messageIds.includes(record.messageId))
|
|
@@ -2018,7 +2209,13 @@ export function createRuntimeHost(options) {
|
|
|
2018
2209
|
...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
|
|
2019
2210
|
...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
|
|
2020
2211
|
}
|
|
2021
|
-
emit({
|
|
2212
|
+
emit({
|
|
2213
|
+
type: "agent-status",
|
|
2214
|
+
agentId,
|
|
2215
|
+
status: agent.authFailureActive ? "error" : "active",
|
|
2216
|
+
...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
|
|
2217
|
+
readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
|
|
2218
|
+
});
|
|
2022
2219
|
durableRollback = null;
|
|
2023
2220
|
agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
|
|
2024
2221
|
await oldSession.close("context-window recovery committed")
|
|
@@ -2108,11 +2305,13 @@ export function createRuntimeHost(options) {
|
|
|
2108
2305
|
piProactiveCompactionFailedGeneration: null,
|
|
2109
2306
|
previousSession: loadPersistedPreviousSession(config, stateStore), readiness: null,
|
|
2110
2307
|
turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false,
|
|
2308
|
+
authFailureKind: null, authFailureProvider: null,
|
|
2111
2309
|
promotedInboxUpdateIds: new Set(),
|
|
2112
2310
|
backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
|
|
2113
2311
|
backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
|
|
2114
2312
|
backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
|
|
2115
2313
|
subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
|
|
2314
|
+
restoreAuthFailure(agent);
|
|
2116
2315
|
const startupConsumed = [];
|
|
2117
2316
|
const startupQuarantined = [];
|
|
2118
2317
|
for (const record of agent.records.values()) {
|
|
@@ -37,7 +37,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.RuntimePrerequisiteError = void 0;
|
|
40
|
+
exports.safeProviderId = safeProviderId;
|
|
41
|
+
exports.isBuiltinPiDistribution = isBuiltinPiDistribution;
|
|
42
|
+
exports.configuredProviderId = configuredProviderId;
|
|
43
|
+
exports.parsePersistedAuthFailure = parsePersistedAuthFailure;
|
|
44
|
+
exports.authFailureAppliesTo = authFailureAppliesTo;
|
|
45
|
+
exports.readinessForPersistedAuthFailure = readinessForPersistedAuthFailure;
|
|
46
|
+
exports.classifyPiMissingCredentialRejection = classifyPiMissingCredentialRejection;
|
|
40
47
|
exports.providerAuthenticationFailureReadiness = providerAuthenticationFailureReadiness;
|
|
48
|
+
exports.missingProviderCredentialReadiness = missingProviderCredentialReadiness;
|
|
41
49
|
exports.resolveRuntimeExecutable = resolveRuntimeExecutable;
|
|
42
50
|
exports.classifyRuntimePrerequisite = classifyRuntimePrerequisite;
|
|
43
51
|
exports.probeNativeRuntimeReadiness = probeNativeRuntimeReadiness;
|
|
@@ -49,10 +57,103 @@ const root_layout_js_1 = require("../platform/root-layout.cjs");
|
|
|
49
57
|
const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
|
|
50
58
|
const pi_provider_config_js_1 = require("./pi-provider-config.cjs");
|
|
51
59
|
const process_boundary_trace_js_1 = require("../platform/process-boundary-trace.cjs");
|
|
52
|
-
function
|
|
60
|
+
function safeProviderId(value) {
|
|
53
61
|
const provider = typeof value === "string" ? value.trim() : "";
|
|
54
62
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
|
|
55
63
|
}
|
|
64
|
+
function safeProviderLabel(value) {
|
|
65
|
+
return safeProviderId(value);
|
|
66
|
+
}
|
|
67
|
+
function isBuiltinPiDistribution(value) {
|
|
68
|
+
return value === "builtin";
|
|
69
|
+
}
|
|
70
|
+
/** Model ids are `provider/model`; only the provider prefix is used for scope. */
|
|
71
|
+
function configuredProviderId(model) {
|
|
72
|
+
const value = typeof model === "string" ? model.trim() : "";
|
|
73
|
+
const slash = value.indexOf("/");
|
|
74
|
+
return slash > 0 ? safeProviderId(value.slice(0, slash)) : null;
|
|
75
|
+
}
|
|
76
|
+
function persistedRuntime(value) {
|
|
77
|
+
return value === "pi" || value === "codex" || value === "claude" ? value : null;
|
|
78
|
+
}
|
|
79
|
+
function parsePersistedAuthFailure(state) {
|
|
80
|
+
if (!state || typeof state !== "object" || Array.isArray(state))
|
|
81
|
+
return null;
|
|
82
|
+
const record = state;
|
|
83
|
+
if (record.authFailure && typeof record.authFailure === "object" && !Array.isArray(record.authFailure)) {
|
|
84
|
+
const failure = record.authFailure;
|
|
85
|
+
const kind = failure.kind === "missing-provider" || failure.kind === "generic" ? failure.kind : null;
|
|
86
|
+
const runtime = persistedRuntime(failure.runtime);
|
|
87
|
+
if (!kind || !runtime)
|
|
88
|
+
return null;
|
|
89
|
+
const provider = safeProviderId(failure.provider);
|
|
90
|
+
if (kind === "missing-provider" && !provider)
|
|
91
|
+
return null;
|
|
92
|
+
const piDistribution = failure.piDistribution === "builtin" || failure.piDistribution === "external"
|
|
93
|
+
? failure.piDistribution : undefined;
|
|
94
|
+
return {
|
|
95
|
+
kind,
|
|
96
|
+
runtime,
|
|
97
|
+
...(piDistribution ? { piDistribution } : {}),
|
|
98
|
+
provider,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const legacy = safeProviderId(record.authFailureProvider);
|
|
102
|
+
return legacy
|
|
103
|
+
? { kind: "missing-provider", runtime: "pi", piDistribution: "builtin", provider: legacy }
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
function currentAdapterId(current) {
|
|
107
|
+
return current.adapterId ?? (current.runtime === "builtin-pi" ? "pi" : current.runtime);
|
|
108
|
+
}
|
|
109
|
+
function currentIsBuiltinPi(current) {
|
|
110
|
+
return currentAdapterId(current) === "pi"
|
|
111
|
+
&& (isBuiltinPiDistribution(current.piDistribution) || current.runtime === "builtin-pi");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Missing-provider and known-provider generic markers apply only to that provider.
|
|
115
|
+
* Generic without a provider is conservative fallback: it still binds runtime /
|
|
116
|
+
* builtin distribution, but cannot reject an A→B model switch because the
|
|
117
|
+
* upstream provider was genuinely unavailable.
|
|
118
|
+
*/
|
|
119
|
+
function authFailureAppliesTo(current, failure) {
|
|
120
|
+
if (failure.kind === "missing-provider") {
|
|
121
|
+
return currentIsBuiltinPi(current)
|
|
122
|
+
&& failure.runtime === "pi"
|
|
123
|
+
&& failure.piDistribution === "builtin"
|
|
124
|
+
&& Boolean(failure.provider)
|
|
125
|
+
&& configuredProviderId(current.model) === failure.provider;
|
|
126
|
+
}
|
|
127
|
+
if (failure.runtime !== currentAdapterId(current))
|
|
128
|
+
return false;
|
|
129
|
+
if (failure.runtime === "pi") {
|
|
130
|
+
const persistedBuiltin = failure.piDistribution === "builtin";
|
|
131
|
+
if (currentIsBuiltinPi(current) !== persistedBuiltin)
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
if (!failure.provider)
|
|
135
|
+
return true;
|
|
136
|
+
const currentProvider = configuredProviderId(current.model);
|
|
137
|
+
if (currentIsBuiltinPi(current))
|
|
138
|
+
return currentProvider === failure.provider;
|
|
139
|
+
return currentProvider ? currentProvider === failure.provider : true;
|
|
140
|
+
}
|
|
141
|
+
function readinessForPersistedAuthFailure(failure) {
|
|
142
|
+
if (failure.kind === "missing-provider") {
|
|
143
|
+
return missingProviderCredentialReadiness(failure.runtime, failure.provider);
|
|
144
|
+
}
|
|
145
|
+
return providerAuthenticationFailureReadiness(failure.runtime, failure.provider);
|
|
146
|
+
}
|
|
147
|
+
const MISSING_CREDENTIAL_REJECTION = /^(?:Pi RPC (?:prompt|steer) failed: )?(No API key found for|No login found for) ([A-Za-z0-9][A-Za-z0-9._-]{0,79})$/;
|
|
148
|
+
/** Narrow match for an explicit Pi absent-key / absent-login diagnostic. */
|
|
149
|
+
function classifyPiMissingCredentialRejection(message) {
|
|
150
|
+
const text = typeof message === "string"
|
|
151
|
+
? message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim()
|
|
152
|
+
: "";
|
|
153
|
+
const match = MISSING_CREDENTIAL_REJECTION.exec(text);
|
|
154
|
+
const provider = match ? safeProviderLabel(match[2]) : null;
|
|
155
|
+
return provider && match ? { provider, diagnostic: `${match[1]} ${provider}` } : null;
|
|
156
|
+
}
|
|
56
157
|
function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
57
158
|
const label = safeProviderLabel(provider);
|
|
58
159
|
return {
|
|
@@ -64,6 +165,22 @@ function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
|
64
165
|
nextAction: "Check the provider login or API-key resolver command, then retry the Agent turn.",
|
|
65
166
|
};
|
|
66
167
|
}
|
|
168
|
+
/** P0 分类;Pi nextAction 共用 UX preset 恢复文案,不写官方 provider ID。 */
|
|
169
|
+
function missingProviderCredentialReadiness(runtime, provider) {
|
|
170
|
+
const label = safeProviderLabel(provider);
|
|
171
|
+
return {
|
|
172
|
+
runtime,
|
|
173
|
+
state: "unauthenticated",
|
|
174
|
+
reason: label
|
|
175
|
+
? `Provider ${label} is missing from this Agent's official credential store.`
|
|
176
|
+
: "The configured provider is missing from this Agent's official credential store.",
|
|
177
|
+
nextAction: runtime === "pi"
|
|
178
|
+
? (0, pi_provider_config_js_1.builtinPiProviderRecoveryNextAction)({ providerId: label ?? undefined })
|
|
179
|
+
: label
|
|
180
|
+
? `Add the missing ${label} credential to this Agent's official store, then retry the Agent turn.`
|
|
181
|
+
: "Add the missing provider credential to this Agent's official store, then retry the Agent turn.",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
67
184
|
class RuntimePrerequisiteError extends Error {
|
|
68
185
|
readiness;
|
|
69
186
|
constructor(readiness) {
|
|
@@ -100,7 +217,10 @@ function classifyRuntimePrerequisite(runtime, error, executable) {
|
|
|
100
217
|
if (/no authenticated available models|login|credential|unauthenticated|unauthorized|oauth/i.test(reason))
|
|
101
218
|
return {
|
|
102
219
|
runtime, state: "unauthenticated", ...(executable ? { executable } : {}), reason,
|
|
103
|
-
|
|
220
|
+
// 此分类没有 distribution:外部 Pi 不能指向 Dashboard / pi-auth login。
|
|
221
|
+
nextAction: runtime === "pi"
|
|
222
|
+
? "Run the official `pi` login flow, then retry."
|
|
223
|
+
: `Authenticate ${runtime}, then retry.`,
|
|
104
224
|
};
|
|
105
225
|
if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason))
|
|
106
226
|
return {
|
|
@@ -151,7 +271,7 @@ async function probeNativeRuntimeReadiness(options) {
|
|
|
151
271
|
catch (error) {
|
|
152
272
|
(0, process_boundary_trace_js_1.traceProcessBoundary)(env, "readiness:builtin-pi-failure", { configDir: env.LARKIN_CONFIG_DIR, agentId: options.agentId, targetDir: options.agentId && env.LARKIN_CONFIG_DIR ? (0, pi_provider_config_js_1.piAgentDirectory)(env.LARKIN_CONFIG_DIR, options.agentId) : undefined, error });
|
|
153
273
|
return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
|
|
154
|
-
nextAction:
|
|
274
|
+
nextAction: (0, pi_provider_config_js_1.builtinPiProviderRecoveryNextAction)({ agentId: options.agentId }) };
|
|
155
275
|
}
|
|
156
276
|
}
|
|
157
277
|
const command = selectedCommand(options);
|
|
@@ -4,12 +4,105 @@ import path from "node:path";
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { resolveConfigDir as resolveRootConfigDir } from "../platform/root-layout.mjs";
|
|
6
6
|
import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
|
|
7
|
-
import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, ownedPiCatalogAgentDir, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
7
|
+
import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, builtinPiProviderRecoveryNextAction, ownedPiCatalogAgentDir, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
8
8
|
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
9
|
-
function
|
|
9
|
+
export function safeProviderId(value) {
|
|
10
10
|
const provider = typeof value === "string" ? value.trim() : "";
|
|
11
11
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
|
|
12
12
|
}
|
|
13
|
+
function safeProviderLabel(value) {
|
|
14
|
+
return safeProviderId(value);
|
|
15
|
+
}
|
|
16
|
+
export function isBuiltinPiDistribution(value) {
|
|
17
|
+
return value === "builtin";
|
|
18
|
+
}
|
|
19
|
+
/** Model ids are `provider/model`; only the provider prefix is used for scope. */
|
|
20
|
+
export function configuredProviderId(model) {
|
|
21
|
+
const value = typeof model === "string" ? model.trim() : "";
|
|
22
|
+
const slash = value.indexOf("/");
|
|
23
|
+
return slash > 0 ? safeProviderId(value.slice(0, slash)) : null;
|
|
24
|
+
}
|
|
25
|
+
function persistedRuntime(value) {
|
|
26
|
+
return value === "pi" || value === "codex" || value === "claude" ? value : null;
|
|
27
|
+
}
|
|
28
|
+
export function parsePersistedAuthFailure(state) {
|
|
29
|
+
if (!state || typeof state !== "object" || Array.isArray(state))
|
|
30
|
+
return null;
|
|
31
|
+
const record = state;
|
|
32
|
+
if (record.authFailure && typeof record.authFailure === "object" && !Array.isArray(record.authFailure)) {
|
|
33
|
+
const failure = record.authFailure;
|
|
34
|
+
const kind = failure.kind === "missing-provider" || failure.kind === "generic" ? failure.kind : null;
|
|
35
|
+
const runtime = persistedRuntime(failure.runtime);
|
|
36
|
+
if (!kind || !runtime)
|
|
37
|
+
return null;
|
|
38
|
+
const provider = safeProviderId(failure.provider);
|
|
39
|
+
if (kind === "missing-provider" && !provider)
|
|
40
|
+
return null;
|
|
41
|
+
const piDistribution = failure.piDistribution === "builtin" || failure.piDistribution === "external"
|
|
42
|
+
? failure.piDistribution : undefined;
|
|
43
|
+
return {
|
|
44
|
+
kind,
|
|
45
|
+
runtime,
|
|
46
|
+
...(piDistribution ? { piDistribution } : {}),
|
|
47
|
+
provider,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const legacy = safeProviderId(record.authFailureProvider);
|
|
51
|
+
return legacy
|
|
52
|
+
? { kind: "missing-provider", runtime: "pi", piDistribution: "builtin", provider: legacy }
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
function currentAdapterId(current) {
|
|
56
|
+
return current.adapterId ?? (current.runtime === "builtin-pi" ? "pi" : current.runtime);
|
|
57
|
+
}
|
|
58
|
+
function currentIsBuiltinPi(current) {
|
|
59
|
+
return currentAdapterId(current) === "pi"
|
|
60
|
+
&& (isBuiltinPiDistribution(current.piDistribution) || current.runtime === "builtin-pi");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Missing-provider and known-provider generic markers apply only to that provider.
|
|
64
|
+
* Generic without a provider is conservative fallback: it still binds runtime /
|
|
65
|
+
* builtin distribution, but cannot reject an A→B model switch because the
|
|
66
|
+
* upstream provider was genuinely unavailable.
|
|
67
|
+
*/
|
|
68
|
+
export function authFailureAppliesTo(current, failure) {
|
|
69
|
+
if (failure.kind === "missing-provider") {
|
|
70
|
+
return currentIsBuiltinPi(current)
|
|
71
|
+
&& failure.runtime === "pi"
|
|
72
|
+
&& failure.piDistribution === "builtin"
|
|
73
|
+
&& Boolean(failure.provider)
|
|
74
|
+
&& configuredProviderId(current.model) === failure.provider;
|
|
75
|
+
}
|
|
76
|
+
if (failure.runtime !== currentAdapterId(current))
|
|
77
|
+
return false;
|
|
78
|
+
if (failure.runtime === "pi") {
|
|
79
|
+
const persistedBuiltin = failure.piDistribution === "builtin";
|
|
80
|
+
if (currentIsBuiltinPi(current) !== persistedBuiltin)
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (!failure.provider)
|
|
84
|
+
return true;
|
|
85
|
+
const currentProvider = configuredProviderId(current.model);
|
|
86
|
+
if (currentIsBuiltinPi(current))
|
|
87
|
+
return currentProvider === failure.provider;
|
|
88
|
+
return currentProvider ? currentProvider === failure.provider : true;
|
|
89
|
+
}
|
|
90
|
+
export function readinessForPersistedAuthFailure(failure) {
|
|
91
|
+
if (failure.kind === "missing-provider") {
|
|
92
|
+
return missingProviderCredentialReadiness(failure.runtime, failure.provider);
|
|
93
|
+
}
|
|
94
|
+
return providerAuthenticationFailureReadiness(failure.runtime, failure.provider);
|
|
95
|
+
}
|
|
96
|
+
const MISSING_CREDENTIAL_REJECTION = /^(?:Pi RPC (?:prompt|steer) failed: )?(No API key found for|No login found for) ([A-Za-z0-9][A-Za-z0-9._-]{0,79})$/;
|
|
97
|
+
/** Narrow match for an explicit Pi absent-key / absent-login diagnostic. */
|
|
98
|
+
export function classifyPiMissingCredentialRejection(message) {
|
|
99
|
+
const text = typeof message === "string"
|
|
100
|
+
? message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim()
|
|
101
|
+
: "";
|
|
102
|
+
const match = MISSING_CREDENTIAL_REJECTION.exec(text);
|
|
103
|
+
const provider = match ? safeProviderLabel(match[2]) : null;
|
|
104
|
+
return provider && match ? { provider, diagnostic: `${match[1]} ${provider}` } : null;
|
|
105
|
+
}
|
|
13
106
|
export function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
14
107
|
const label = safeProviderLabel(provider);
|
|
15
108
|
return {
|
|
@@ -21,6 +114,22 @@ export function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
|
21
114
|
nextAction: "Check the provider login or API-key resolver command, then retry the Agent turn.",
|
|
22
115
|
};
|
|
23
116
|
}
|
|
117
|
+
/** P0 分类;Pi nextAction 共用 UX preset 恢复文案,不写官方 provider ID。 */
|
|
118
|
+
export function missingProviderCredentialReadiness(runtime, provider) {
|
|
119
|
+
const label = safeProviderLabel(provider);
|
|
120
|
+
return {
|
|
121
|
+
runtime,
|
|
122
|
+
state: "unauthenticated",
|
|
123
|
+
reason: label
|
|
124
|
+
? `Provider ${label} is missing from this Agent's official credential store.`
|
|
125
|
+
: "The configured provider is missing from this Agent's official credential store.",
|
|
126
|
+
nextAction: runtime === "pi"
|
|
127
|
+
? builtinPiProviderRecoveryNextAction({ providerId: label ?? undefined })
|
|
128
|
+
: label
|
|
129
|
+
? `Add the missing ${label} credential to this Agent's official store, then retry the Agent turn.`
|
|
130
|
+
: "Add the missing provider credential to this Agent's official store, then retry the Agent turn.",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
24
133
|
export class RuntimePrerequisiteError extends Error {
|
|
25
134
|
readiness;
|
|
26
135
|
constructor(readiness) {
|
|
@@ -56,7 +165,10 @@ export function classifyRuntimePrerequisite(runtime, error, executable) {
|
|
|
56
165
|
if (/no authenticated available models|login|credential|unauthenticated|unauthorized|oauth/i.test(reason))
|
|
57
166
|
return {
|
|
58
167
|
runtime, state: "unauthenticated", ...(executable ? { executable } : {}), reason,
|
|
59
|
-
|
|
168
|
+
// 此分类没有 distribution:外部 Pi 不能指向 Dashboard / pi-auth login。
|
|
169
|
+
nextAction: runtime === "pi"
|
|
170
|
+
? "Run the official `pi` login flow, then retry."
|
|
171
|
+
: `Authenticate ${runtime}, then retry.`,
|
|
60
172
|
};
|
|
61
173
|
if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason))
|
|
62
174
|
return {
|
|
@@ -107,7 +219,7 @@ export async function probeNativeRuntimeReadiness(options) {
|
|
|
107
219
|
catch (error) {
|
|
108
220
|
traceProcessBoundary(env, "readiness:builtin-pi-failure", { configDir: env.LARKIN_CONFIG_DIR, agentId: options.agentId, targetDir: options.agentId && env.LARKIN_CONFIG_DIR ? piAgentDirectory(env.LARKIN_CONFIG_DIR, options.agentId) : undefined, error });
|
|
109
221
|
return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
|
|
110
|
-
nextAction:
|
|
222
|
+
nextAction: builtinPiProviderRecoveryNextAction({ agentId: options.agentId }) };
|
|
111
223
|
}
|
|
112
224
|
}
|
|
113
225
|
const command = selectedCommand(options);
|