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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +5 -9
  3. package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
  4. package/dist/app/agent-cli.mjs +14 -2
  5. package/dist/app/agent-config.mjs +26 -84
  6. package/dist/app/binary-entry.mjs +0 -20
  7. package/dist/app/cli.mjs +8 -14
  8. package/dist/app/internal-command.cjs +0 -2
  9. package/dist/app/internal-command.mjs +0 -2
  10. package/dist/app/local-control.mjs +16 -3
  11. package/dist/app/runtime-model-directory.mjs +2 -16
  12. package/dist/app/runtime-process.mjs +42 -6
  13. package/dist/app/setup.mjs +52 -57
  14. package/dist/dashboard/dashboard-config-controller.mjs +83 -24
  15. package/dist/dashboard/dashboard-view-model.mjs +4 -7
  16. package/dist/dashboard/web/assets/dashboard.js +1 -1
  17. package/dist/feishu/host-shell.cjs +68 -17
  18. package/dist/feishu/host-shell.mjs +69 -18
  19. package/dist/platform/config.cjs +201 -138
  20. package/dist/platform/config.mjs +199 -138
  21. package/dist/runtime/pi-artifact-provenance.mjs +3 -3
  22. package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
  23. package/dist/runtime/pi-compaction-recovery.cjs +9 -11
  24. package/dist/runtime/pi-compaction-recovery.mjs +8 -10
  25. package/dist/runtime/pi-extension-api.mjs +1 -0
  26. package/dist/runtime/pi-model-catalog.cjs +7 -8
  27. package/dist/runtime/pi-model-catalog.mjs +7 -8
  28. package/dist/runtime/pi-subagent-injection.mjs +17 -4
  29. package/dist/runtime/runtime-adapters.mjs +41 -38
  30. package/dist/runtime/runtime-host.cjs +197 -20
  31. package/dist/runtime/runtime-host.mjs +198 -21
  32. package/dist/runtime/runtime-model-catalog.cjs +1 -1
  33. package/dist/runtime/runtime-model-catalog.mjs +1 -1
  34. package/dist/runtime/runtime-readiness.cjs +112 -49
  35. package/dist/runtime/runtime-readiness.mjs +103 -49
  36. package/dist/runtime/user-runtime.cjs +7 -24
  37. package/dist/runtime/user-runtime.mjs +7 -23
  38. package/dist/setup/bot-register.mjs +15 -123
  39. package/dist/setup/setup-agent-choice.mjs +48 -74
  40. package/dist/setup/setup-bind.mjs +51 -151
  41. package/dist/setup/setup-binding.mjs +6 -9
  42. package/package.json +3 -2
  43. package/dist/app/pi-auth-cli.mjs +0 -67
  44. package/dist/platform/process-boundary-trace.cjs +0 -108
  45. package/dist/runtime/builtin-pi-assets.cjs +0 -117
  46. package/dist/runtime/builtin-pi-assets.mjs +0 -105
  47. package/dist/runtime/pi-artifact-provenance.cjs +0 -147
  48. package/dist/runtime/pi-distribution-label.cjs +0 -6
  49. package/dist/runtime/pi-distribution-label.mjs +0 -3
  50. package/dist/runtime/pi-inline-extensions.mjs +0 -78
  51. package/dist/runtime/pi-official-auth.mjs +0 -491
  52. package/dist/runtime/pi-profile-migration.cjs +0 -764
  53. package/dist/runtime/pi-profile-migration.mjs +0 -719
  54. package/dist/runtime/pi-provider-config.cjs +0 -331
  55. package/dist/runtime/pi-provider-config.mjs +0 -314
@@ -6,7 +6,7 @@ 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, missingProviderCredentialReadiness, parsePersistedAuthFailure, providerAuthenticationFailureReadiness, readinessForPersistedAuthFailure, safeProviderId, RuntimePrerequisiteError, } from "./runtime-readiness.mjs";
10
10
  import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
11
11
  import { assertAgentWorkspaceBound, managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
12
12
  import { isPiNativeCompactionRequired, PiCompactionBreaker, PiCompactionRecoveryMachine, } from "./pi-compaction-recovery.mjs";
@@ -93,6 +93,107 @@ function persistPreviousSession(agent, previous) {
93
93
  // In-memory ref still holds for this process; restart will miss it only if this write failed.
94
94
  }
95
95
  }
96
+ function authFailureScopeOf(agent) {
97
+ return {
98
+ runtime: agent.config.runtime,
99
+ model: agent.config.model,
100
+ adapterId: agent.adapter.id,
101
+ };
102
+ }
103
+ function currentAuthFailure(agent) {
104
+ if (!agent.authFailureActive)
105
+ return null;
106
+ const runtime = agent.adapter.id === "codex" || agent.adapter.id === "claude" || agent.adapter.id === "pi"
107
+ ? agent.adapter.id : null;
108
+ if (!runtime)
109
+ return null;
110
+ return {
111
+ kind: agent.authFailureKind === "missing-provider" ? "missing-provider" : "generic",
112
+ runtime,
113
+ provider: agent.authFailureProvider,
114
+ };
115
+ }
116
+ function genericAuthProvider(agent, upstream) {
117
+ return safeProviderId(upstream?.provider) ?? configuredProviderId(agent.config.model);
118
+ }
119
+ function persistAuthFailure(agent) {
120
+ const store = agent.stateStore;
121
+ if (!store)
122
+ return;
123
+ try {
124
+ const write = () => {
125
+ const current = store.readJson("agentState", { sessions: {} });
126
+ if (Array.isArray(current.records) && !("sessions" in current))
127
+ return;
128
+ const { authFailureProvider: _legacy, authFailure: _scoped, ...rest } = current;
129
+ const failure = currentAuthFailure(agent);
130
+ if (!failure) {
131
+ if (!("authFailureProvider" in current) && !("authFailure" in current))
132
+ return;
133
+ store.writeJson("agentState", rest);
134
+ return;
135
+ }
136
+ store.writeJson("agentState", {
137
+ ...rest,
138
+ authFailure: failure,
139
+ ...(failure.kind === "missing-provider" && failure.provider
140
+ ? { authFailureProvider: failure.provider } : {}),
141
+ });
142
+ };
143
+ if (store.withInboxTransaction)
144
+ store.withInboxTransaction(write);
145
+ else
146
+ write();
147
+ }
148
+ catch {
149
+ // In-memory flags and the delivery ledger still hold for this process.
150
+ }
151
+ }
152
+ function loadPersistedAuthFailure(store) {
153
+ if (!store)
154
+ return null;
155
+ try {
156
+ return parsePersistedAuthFailure(store.readJson("agentState", {}));
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ function applyAuthFailure(agent, failure) {
163
+ agent.authFailureActive = true;
164
+ agent.authFailureKind = failure.kind;
165
+ agent.authFailureProvider = failure.provider ?? null;
166
+ agent.readiness = readinessForPersistedAuthFailure(failure);
167
+ }
168
+ function clearAuthFailure(agent, persist = true) {
169
+ agent.authFailureActive = false;
170
+ agent.authFailureKind = null;
171
+ agent.authFailureProvider = null;
172
+ if (persist)
173
+ persistAuthFailure(agent);
174
+ }
175
+ function restoreAuthFailure(agent) {
176
+ const persisted = loadPersistedAuthFailure(agent.stateStore);
177
+ if (!persisted)
178
+ return;
179
+ // 只恢复仍绑定当前 candidate 的当前作用域 auth;不从历史 delivery 回灌。
180
+ if (!authFailureAppliesTo(authFailureScopeOf(agent), persisted)) {
181
+ clearAuthFailure(agent);
182
+ return;
183
+ }
184
+ applyAuthFailure(agent, persisted);
185
+ }
186
+ function clearLedgerAuthProviders(agent) {
187
+ let changed = false;
188
+ for (const record of agent.records.values()) {
189
+ if (!record.authProvider)
190
+ continue;
191
+ delete record.authProvider;
192
+ changed = true;
193
+ }
194
+ if (changed)
195
+ persist(agent);
196
+ }
96
197
  const REPLAY_FAILURE_CODES = new Set([
97
198
  "canonical_inbox_row_missing", "canonical_inbox_malformed", "duplicate_message_id",
98
199
  "inbox_state_conflict", "delivery_target_conflict", "structured_target_invalid",
@@ -219,7 +320,6 @@ export function createRuntimeHost(options) {
219
320
  } : {}),
220
321
  LARKIN_CONFIG_DIR: process.env.LARKIN_CONFIG_DIR,
221
322
  LARKIN_HOME: process.env.LARKIN_HOME,
222
- ...(config.piDistribution ? { LARKIN_PI_DISTRIBUTION: config.piDistribution } : {}),
223
323
  LARKIN_STATE_DIR: effectivePiStateDir(config),
224
324
  ...(process.env.HOME ? { HOME: process.env.HOME } : {}),
225
325
  ...(process.env.SHELL ? { SHELL: process.env.SHELL } : {}),
@@ -481,6 +581,25 @@ export function createRuntimeHost(options) {
481
581
  messageId: record.messageId, status: "consumed" });
482
582
  }
483
583
  };
584
+ const projectAuthFailure = (agent, readiness, failure) => {
585
+ agent.authFailureActive = true;
586
+ agent.authFailureKind = failure.kind;
587
+ agent.authFailureProvider = failure.provider;
588
+ agent.readiness = {
589
+ ...readiness,
590
+ ...(agent.readiness?.executable ? { executable: agent.readiness.executable } : {}),
591
+ ...(agent.readiness?.version ? { version: agent.readiness.version } : {}),
592
+ };
593
+ persistAuthFailure(agent);
594
+ emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
595
+ error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
596
+ };
597
+ const namedAuthProviderPresent = (agent) => {
598
+ if (agent.authFailureKind !== "missing-provider" || !agent.authFailureProvider)
599
+ return true;
600
+ const failure = currentAuthFailure(agent);
601
+ return Boolean(failure && authFailureAppliesTo(authFailureScopeOf(agent), failure));
602
+ };
484
603
  const resultFor = (agent, record, result) => {
485
604
  if (result.status === "accepted") {
486
605
  if (record.errorCategory !== "context_window") {
@@ -494,6 +613,28 @@ export function createRuntimeHost(options) {
494
613
  deliveryId: record.deliveryId, messageId: record.messageId, status: "accepted" });
495
614
  return { status: "accepted", deliveryId: record.deliveryId };
496
615
  }
616
+ const missing = result.status === "rejected" && agent.adapter.id === "pi"
617
+ ? classifyPiMissingCredentialRejection(result.reason)
618
+ : null;
619
+ if (missing) {
620
+ const readiness = missingProviderCredentialReadiness(agent.adapter.id, missing.provider);
621
+ const reason = readiness.reason ?? result.reason;
622
+ if (record.status !== "error") {
623
+ record.reason = reason;
624
+ record.retryable = false;
625
+ record.errorCategory = "auth";
626
+ record.authProvider = missing.provider;
627
+ const finalRecord = setRecord(agent, record, "error");
628
+ if (finalRecord.status !== "consumed")
629
+ emit({ type: "delivery", agentId: agent.config.agentId,
630
+ deliveryId: record.deliveryId, messageId: record.messageId, status: "error", reason });
631
+ projectAuthFailure(agent, readiness, { kind: "missing-provider", provider: missing.provider });
632
+ }
633
+ return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? reason, retryable: false };
634
+ }
635
+ if (record.status === "error") {
636
+ return { status: "error", deliveryId: record.deliveryId, reason: record.reason ?? result.reason, retryable: false };
637
+ }
497
638
  if (record.errorCategory !== "context_window")
498
639
  delete record.errorCategory;
499
640
  const retryable = result.status === "deferred" || result.retryable;
@@ -1338,7 +1479,7 @@ export function createRuntimeHost(options) {
1338
1479
  agent.compactionMachines.delete(inputId);
1339
1480
  }
1340
1481
  const recoveredAuthentication = agent.authFailureActive && agent.turnInProgress
1341
- && agent.turnHadAuthenticatedOutput && !agent.turnHadFailure;
1482
+ && agent.turnHadAuthenticatedOutput && !agent.turnHadFailure && namedAuthProviderPresent(agent);
1342
1483
  agent.turnInProgress = false;
1343
1484
  agent.busy = false;
1344
1485
  emit({ type: "activity", agentId: agent.config.agentId, activity: "idle", activityKind: "idle", detailKind: "turn_ended" });
@@ -1360,7 +1501,8 @@ export function createRuntimeHost(options) {
1360
1501
  reconcileSubagentLedger(agent);
1361
1502
  telemetry?.runtimeEvent(agent.config.agentId, event);
1362
1503
  if (recoveredAuthentication) {
1363
- agent.authFailureActive = false;
1504
+ clearAuthFailure(agent);
1505
+ clearLedgerAuthProviders(agent);
1364
1506
  const prior = agent.readiness;
1365
1507
  agent.readiness = {
1366
1508
  runtime: agent.adapter.id,
@@ -1453,23 +1595,30 @@ export function createRuntimeHost(options) {
1453
1595
  record.errorCategory = classifiedCategory;
1454
1596
  else
1455
1597
  delete record.errorCategory;
1456
- const finalRecord = setRecord(agent, record, event.retryable ? "pending" : "error");
1598
+ const missing = agent.adapter.id === "pi"
1599
+ ? classifyPiMissingCredentialRejection(event.upstream?.message)
1600
+ ?? classifyPiMissingCredentialRejection(event.message)
1601
+ : null;
1602
+ if (missing) {
1603
+ record.retryable = false;
1604
+ record.errorCategory = "auth";
1605
+ record.authProvider = missing.provider;
1606
+ }
1607
+ const terminal = Boolean(missing) || !event.retryable;
1608
+ const finalRecord = setRecord(agent, record, terminal ? "error" : "pending");
1457
1609
  if (finalRecord.status !== "consumed")
1458
1610
  emit({ type: "delivery", agentId: agent.config.agentId,
1459
1611
  deliveryId: record.deliveryId, messageId: record.messageId,
1460
- status: event.retryable ? "deferred" : "error", reason: event.message });
1461
- if (event.errorCategory === "auth" && !event.retryable) {
1612
+ status: terminal ? "error" : "deferred", reason: event.message });
1613
+ if (missing) {
1614
+ const readiness = missingProviderCredentialReadiness(agent.adapter.id, missing.provider);
1615
+ projectAuthFailure(agent, readiness, { kind: "missing-provider", provider: missing.provider });
1616
+ }
1617
+ else if (event.errorCategory === "auth" && !event.retryable) {
1462
1618
  const readiness = providerAuthenticationFailureReadiness(agent.adapter.id, event.upstream?.provider);
1463
- agent.authFailureActive = true;
1464
- agent.readiness = {
1465
- ...readiness,
1466
- ...(agent.readiness?.executable ? { executable: agent.readiness.executable } : {}),
1467
- ...(agent.readiness?.version ? { version: agent.readiness.version } : {}),
1468
- };
1469
- emit({ type: "agent-status", agentId: agent.config.agentId, status: "error",
1470
- error: `${agent.readiness.reason} ${agent.readiness.nextAction}`, readiness: agent.readiness });
1619
+ projectAuthFailure(agent, readiness, { kind: "generic", provider: genericAuthProvider(agent, event.upstream) });
1471
1620
  }
1472
- if (event.retryable && !agent.busy && !agent.turnInProgress)
1621
+ if (!terminal && event.retryable && !agent.busy && !agent.turnInProgress)
1473
1622
  void retryPending(agent);
1474
1623
  }
1475
1624
  else if (event.type === "configuration-error") {
@@ -1739,14 +1888,26 @@ export function createRuntimeHost(options) {
1739
1888
  clearTimeout(previous.backgroundCompletionRetryTimer);
1740
1889
  if (previous.subagentReconcileTimer)
1741
1890
  clearInterval(previous.subagentReconcileTimer);
1891
+ const previousFailure = currentAuthFailure(previous);
1892
+ const scoped = Boolean(previousFailure
1893
+ && authFailureAppliesTo({
1894
+ runtime: config.runtime,
1895
+ model: config.model,
1896
+ adapterId: adapter.id,
1897
+ }, previousFailure));
1742
1898
  const candidate = {
1743
1899
  ...previous, config, adapter, session, launchId: crypto.randomUUID(), busy: false, submitting: false,
1744
1900
  starting: null, retryAfterSubmit: false, generation: 0, poller: null, retryTimer: null,
1745
1901
  recreateAttempts: 0, stabilityTimer: null, recreateReason: null, stopped: false,
1746
1902
  backgroundCompletionRetryTimer: null, subagentReconcileTimer: null,
1747
1903
  disabledReason: null, configurationRecovery: null,
1748
- readiness: previous.authFailureActive ? previous.readiness : readiness,
1904
+ authFailureActive: scoped && previous.authFailureActive,
1905
+ authFailureKind: scoped ? previous.authFailureKind : null,
1906
+ authFailureProvider: scoped ? previous.authFailureProvider : null,
1907
+ readiness: scoped && previous.authFailureActive ? previous.readiness : readiness,
1749
1908
  };
1909
+ if (previous.authFailureActive && !candidate.authFailureActive)
1910
+ clearAuthFailure(candidate);
1750
1911
  managed.set(config.agentId, candidate);
1751
1912
  session.subscribe((event) => observe(candidate, session, event));
1752
1913
  if (session.sessionId) {
@@ -1841,7 +2002,8 @@ export function createRuntimeHost(options) {
1841
2002
  agent.config.sessionId = null;
1842
2003
  agent.session = fresh;
1843
2004
  persistPreviousSession(agent, previousRef);
1844
- agent.readiness = readiness;
2005
+ if (!agent.authFailureActive)
2006
+ agent.readiness = readiness;
1845
2007
  agent.disabledReason = null;
1846
2008
  };
1847
2009
  try {
@@ -1862,7 +2024,13 @@ export function createRuntimeHost(options) {
1862
2024
  ...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
1863
2025
  ...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
1864
2026
  }
1865
- emit({ type: "agent-status", agentId, status: "active", readiness });
2027
+ emit({
2028
+ type: "agent-status",
2029
+ agentId,
2030
+ status: agent.authFailureActive ? "error" : "active",
2031
+ ...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
2032
+ readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
2033
+ });
1866
2034
  agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
1867
2035
  await oldSession.close("fresh session reset committed")
1868
2036
  .catch((error) => log("previous runtime close after fresh reset failed", String(error)));
@@ -2000,7 +2168,8 @@ export function createRuntimeHost(options) {
2000
2168
  agent.config.sessionId = null;
2001
2169
  agent.session = fresh;
2002
2170
  persistPreviousSession(agent, previousRef);
2003
- agent.readiness = readiness;
2171
+ if (!agent.authFailureActive)
2172
+ agent.readiness = readiness;
2004
2173
  agent.disabledReason = null;
2005
2174
  for (const record of agent.records.values()) {
2006
2175
  if (!messageIds.includes(record.messageId))
@@ -2018,7 +2187,13 @@ export function createRuntimeHost(options) {
2018
2187
  ...(fresh.effectiveReasoningEffort ? { reasoningEffort: fresh.effectiveReasoningEffort } : {}),
2019
2188
  ...(agent.previousSession ? { previousSession: agent.previousSession } : {}) });
2020
2189
  }
2021
- emit({ type: "agent-status", agentId, status: "active", readiness });
2190
+ emit({
2191
+ type: "agent-status",
2192
+ agentId,
2193
+ status: agent.authFailureActive ? "error" : "active",
2194
+ ...(agent.authFailureActive ? { error: `${agent.readiness?.reason} ${agent.readiness?.nextAction}` } : {}),
2195
+ readiness: agent.authFailureActive ? agent.readiness ?? readiness : readiness,
2196
+ });
2022
2197
  durableRollback = null;
2023
2198
  agent.piSessionOwner = probeEnv[PI_SUBAGENT_SESSION_OWNER_ENV];
2024
2199
  await oldSession.close("context-window recovery committed")
@@ -2108,11 +2283,13 @@ export function createRuntimeHost(options) {
2108
2283
  piProactiveCompactionFailedGeneration: null,
2109
2284
  previousSession: loadPersistedPreviousSession(config, stateStore), readiness: null,
2110
2285
  turnInProgress: false, turnHadFailure: false, turnHadAuthenticatedOutput: false, authFailureActive: false,
2286
+ authFailureKind: null, authFailureProvider: null,
2111
2287
  promotedInboxUpdateIds: new Set(),
2112
2288
  backgroundCompletionQueue: [], backgroundCompletionKeys: new Set(),
2113
2289
  backgroundCompletionInFlight: null, backgroundCompletionWakeInputId: null,
2114
2290
  backgroundCompletionRejectStreak: 0, backgroundCompletionRetryTimer: null,
2115
2291
  subagentLedger: loadSubagentLedger(config), subagentReconcileTimer: null };
2292
+ restoreAuthFailure(agent);
2116
2293
  const startupConsumed = [];
2117
2294
  const startupQuarantined = [];
2118
2295
  for (const record of agent.records.values()) {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CURRENT_RUNTIME_MODELS = void 0;
4
- // Authored catalog keys are adapter ids only. builtin-pi is a user-facing sibling, not a stored runtime.
4
+ // Authored catalog keys are adapter ids only.
5
5
  // Pi, Codex and Claude replace these placeholders/fallbacks at runtime with their machine-readable catalogs.
6
6
  // Claude's supported stdin control-channel list is the live authority; its entries below
7
7
  // remain explicit compatibility candidates for config loading and discovery failures.
@@ -1,4 +1,4 @@
1
- // Authored catalog keys are adapter ids only. builtin-pi is a user-facing sibling, not a stored runtime.
1
+ // Authored catalog keys are adapter ids only.
2
2
  // Pi, Codex and Claude replace these placeholders/fallbacks at runtime with their machine-readable catalogs.
3
3
  // Claude's supported stdin control-channel list is the live authority; its entries below
4
4
  // remain explicit compatibility candidates for config loading and discovery failures.
@@ -37,22 +37,97 @@ 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.configuredProviderId = configuredProviderId;
42
+ exports.parsePersistedAuthFailure = parsePersistedAuthFailure;
43
+ exports.authFailureAppliesTo = authFailureAppliesTo;
44
+ exports.readinessForPersistedAuthFailure = readinessForPersistedAuthFailure;
45
+ exports.classifyPiMissingCredentialRejection = classifyPiMissingCredentialRejection;
46
+ exports.runtimeInstallNextAction = runtimeInstallNextAction;
47
+ exports.runtimeLoginNextAction = runtimeLoginNextAction;
40
48
  exports.providerAuthenticationFailureReadiness = providerAuthenticationFailureReadiness;
49
+ exports.missingProviderCredentialReadiness = missingProviderCredentialReadiness;
41
50
  exports.resolveRuntimeExecutable = resolveRuntimeExecutable;
42
51
  exports.classifyRuntimePrerequisite = classifyRuntimePrerequisite;
43
52
  exports.probeNativeRuntimeReadiness = probeNativeRuntimeReadiness;
44
53
  const node_fs_1 = __importDefault(require("node:fs"));
45
- const node_os_1 = __importDefault(require("node:os"));
46
54
  const node_path_1 = __importDefault(require("node:path"));
47
55
  const node_child_process_1 = require("node:child_process");
48
- const root_layout_js_1 = require("../platform/root-layout.cjs");
49
- const builtin_pi_assets_js_1 = require("./builtin-pi-assets.cjs");
50
- const pi_provider_config_js_1 = require("./pi-provider-config.cjs");
51
- const process_boundary_trace_js_1 = require("../platform/process-boundary-trace.cjs");
52
- function safeProviderLabel(value) {
56
+ function safeProviderId(value) {
53
57
  const provider = typeof value === "string" ? value.trim() : "";
54
58
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
55
59
  }
60
+ function safeProviderLabel(value) {
61
+ return safeProviderId(value);
62
+ }
63
+ /** Model ids are `provider/model`; only the provider prefix is used for scope. */
64
+ function configuredProviderId(model) {
65
+ const value = typeof model === "string" ? model.trim() : "";
66
+ const slash = value.indexOf("/");
67
+ return slash > 0 ? safeProviderId(value.slice(0, slash)) : null;
68
+ }
69
+ function persistedRuntime(value) {
70
+ return value === "pi" || value === "codex" || value === "claude" ? value : null;
71
+ }
72
+ function parsePersistedAuthFailure(state) {
73
+ if (!state || typeof state !== "object" || Array.isArray(state))
74
+ return null;
75
+ const record = state;
76
+ if (record.authFailure && typeof record.authFailure === "object" && !Array.isArray(record.authFailure)) {
77
+ const failure = record.authFailure;
78
+ const kind = failure.kind === "missing-provider" || failure.kind === "generic" ? failure.kind : null;
79
+ const runtime = persistedRuntime(failure.runtime);
80
+ if (!kind || !runtime)
81
+ return null;
82
+ const provider = safeProviderId(failure.provider);
83
+ if (kind === "missing-provider" && !provider)
84
+ return null;
85
+ return { kind, runtime, provider };
86
+ }
87
+ const legacy = safeProviderId(record.authFailureProvider);
88
+ return legacy ? { kind: "missing-provider", runtime: "pi", provider: legacy } : null;
89
+ }
90
+ function currentAdapterId(current) {
91
+ return current.adapterId ?? current.runtime;
92
+ }
93
+ function authFailureAppliesTo(current, failure) {
94
+ if (failure.runtime !== currentAdapterId(current))
95
+ return false;
96
+ if (!failure.provider)
97
+ return true;
98
+ const currentProvider = configuredProviderId(current.model);
99
+ return currentProvider ? currentProvider === failure.provider : true;
100
+ }
101
+ function readinessForPersistedAuthFailure(failure) {
102
+ if (failure.kind === "missing-provider") {
103
+ return missingProviderCredentialReadiness(failure.runtime, failure.provider);
104
+ }
105
+ return providerAuthenticationFailureReadiness(failure.runtime, failure.provider);
106
+ }
107
+ 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})$/;
108
+ /** Narrow match for an explicit Pi absent-key / absent-login diagnostic. */
109
+ function classifyPiMissingCredentialRejection(message) {
110
+ const text = typeof message === "string"
111
+ ? message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim()
112
+ : "";
113
+ const match = MISSING_CREDENTIAL_REJECTION.exec(text);
114
+ const provider = match ? safeProviderLabel(match[2]) : null;
115
+ return provider && match ? { provider, diagnostic: `${match[1]} ${provider}` } : null;
116
+ }
117
+ function runtimeInstallNextAction(runtime) {
118
+ if (runtime === "pi")
119
+ return "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND.";
120
+ if (runtime === "codex")
121
+ return "Install Codex and ensure `codex` is on PATH, or set LARKIN_CODEX_COMMAND.";
122
+ return "Install Claude Code and ensure `claude` is on PATH, or set LARKIN_CLAUDE_COMMAND.";
123
+ }
124
+ function runtimeLoginNextAction(runtime) {
125
+ if (runtime === "pi")
126
+ return "Log in with the external `pi` CLI, then retry.";
127
+ if (runtime === "codex")
128
+ return "Run `codex login`, then retry.";
129
+ return "Run `claude login`, then retry.";
130
+ }
56
131
  function providerAuthenticationFailureReadiness(runtime, provider) {
57
132
  const label = safeProviderLabel(provider);
58
133
  return {
@@ -61,7 +136,20 @@ function providerAuthenticationFailureReadiness(runtime, provider) {
61
136
  reason: label
62
137
  ? `Provider ${label} API-key authentication failed during a Runtime turn.`
63
138
  : "Configured provider API-key authentication failed during a Runtime turn.",
64
- nextAction: "Check the provider login or API-key resolver command, then retry the Agent turn.",
139
+ nextAction: runtimeLoginNextAction(runtime),
140
+ };
141
+ }
142
+ function missingProviderCredentialReadiness(runtime, provider) {
143
+ const label = safeProviderLabel(provider);
144
+ return {
145
+ runtime,
146
+ state: "unauthenticated",
147
+ reason: label
148
+ ? `Provider ${label} is not authenticated for this runtime.`
149
+ : "The configured provider is not authenticated for this runtime.",
150
+ nextAction: runtime === "pi" && label
151
+ ? `Log in with the external \`pi\` CLI for provider ${label}, then retry.`
152
+ : runtimeLoginNextAction(runtime),
65
153
  };
66
154
  }
67
155
  class RuntimePrerequisiteError extends Error {
@@ -94,24 +182,29 @@ function classifyRuntimePrerequisite(runtime, error, executable) {
94
182
  const reason = (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, " ").slice(0, 500);
95
183
  if (/ENOENT|not found|no such file|spawn .* failed/i.test(reason))
96
184
  return {
97
- runtime, state: "missing", ...(executable ? { executable } : {}), reason,
98
- nextAction: runtime === "pi" ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND." : `Install ${runtime} and ensure it is on PATH.`,
185
+ runtime, state: "missing", ...(executable ? { executable } : {}),
186
+ reason: `${runtime} is not installed`,
187
+ nextAction: runtimeInstallNextAction(runtime),
99
188
  };
100
189
  if (/no authenticated available models|login|credential|unauthenticated|unauthorized|oauth/i.test(reason))
101
190
  return {
102
191
  runtime, state: "unauthenticated", ...(executable ? { executable } : {}), reason,
103
- nextAction: runtime === "pi" ? "Run the official `pi` login flow, then retry." : `Authenticate ${runtime}, then retry.`,
192
+ nextAction: runtimeLoginNextAction(runtime),
104
193
  };
105
- if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason))
194
+ if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason)) {
106
195
  return {
107
196
  runtime, state: "incompatible", ...(executable ? { executable } : {}), reason,
108
- nextAction: runtime === "pi" ? "Upgrade local Pi to a version that supports the documented RPC protocol." : `Upgrade ${runtime}, then retry.`,
197
+ nextAction: runtime === "pi"
198
+ ? "Upgrade local Pi to a version that supports the documented RPC protocol."
199
+ : `Upgrade ${runtime}, then retry.`,
109
200
  };
110
- if (/timeout|timed out|unexpected EOF|\bEOF\b|TLS|ECONNRESET|socket hang up|network|temporar(?:y|ily)|unavailable/i.test(reason))
201
+ }
202
+ if (/timeout|timed out|unexpected EOF|\bEOF\b|TLS|ECONNRESET|socket hang up|network|temporar(?:y|ily)|unavailable/i.test(reason)) {
111
203
  return {
112
204
  runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
113
205
  nextAction: `Retry ${runtime}; Larkin will use its bounded Runtime recreate/backoff policy.`,
114
206
  };
207
+ }
115
208
  return {
116
209
  runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
117
210
  nextAction: `Retry ${runtime}; the failure is not proven to be a protocol incompatibility.`,
@@ -134,44 +227,15 @@ function executableVersion(executable, env, commandArgs = []) {
134
227
  }
135
228
  /** Resolve and handshake through each runtime's structured native control protocol. */
136
229
  async function probeNativeRuntimeReadiness(options) {
137
- let env = { ...process.env, ...options.env };
138
- const piDistribution = options.runtime === "pi" ? (0, builtin_pi_assets_js_1.piChildDistributionFromOverrides)(options.env) : "external";
139
- if (options.runtime === "pi" && piDistribution !== "builtin") {
140
- if (env.LARKIN_PI_DISTRIBUTION === "builtin")
141
- delete env.LARKIN_PI_DISTRIBUTION;
142
- env = (0, builtin_pi_assets_js_1.applyPiPackageDirForChild)(env, { distribution: "external" });
143
- }
144
- if (options.runtime === "pi" && piDistribution === "builtin") {
145
- try {
146
- if (!options.agentId || !env.LARKIN_CONFIG_DIR)
147
- throw new Error("内置 Pi readiness 缺少 Agent/config identity");
148
- (0, pi_provider_config_js_1.assertBuiltinPiAgentDirectory)((0, pi_provider_config_js_1.piAgentDirectory)(env.LARKIN_CONFIG_DIR, options.agentId));
149
- return { runtime: "pi", state: "ready", executable: process.execPath, version: `official-pi ${pi_provider_config_js_1.BUNDLED_PI_VERSION} (bundled)` };
150
- }
151
- catch (error) {
152
- (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
- return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
154
- nextAction: "重新运行 larkin setup,选择内置 Pi 并配置有效 API Key。" };
155
- }
156
- }
230
+ const env = { ...process.env, ...options.env };
157
231
  const command = selectedCommand(options);
158
232
  const executable = resolveRuntimeExecutable(command, env);
159
- if (!executable)
160
- return {
161
- runtime: options.runtime, state: "missing",
162
- reason: `${command} executable was not found`,
163
- nextAction: options.runtime === "pi"
164
- ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND."
165
- : `Install ${options.runtime} and ensure it is on PATH.`,
166
- };
167
- const piAgentId = options.agentId;
168
- if (options.runtime === "pi" && !piAgentId) {
233
+ if (!executable) {
169
234
  return {
170
- runtime: "pi",
171
- state: "unavailable",
172
- executable,
173
- reason: "Pi catalog readiness requires Agent ID",
174
- nextAction: "Retry setup or start so Larkin can probe the Agent-owned Pi provider directory.",
235
+ runtime: options.runtime,
236
+ state: "missing",
237
+ reason: `${options.runtime} is not installed`,
238
+ nextAction: runtimeInstallNextAction(options.runtime),
175
239
  };
176
240
  }
177
241
  const version = executableVersion(executable, env, options.commandArgs);
@@ -183,7 +247,6 @@ async function probeNativeRuntimeReadiness(options) {
183
247
  command: executable,
184
248
  commandArgs: options.commandArgs,
185
249
  env,
186
- agentDir: (0, pi_provider_config_js_1.ownedPiCatalogAgentDir)((0, root_layout_js_1.resolveConfigDir)(env, node_os_1.default.homedir()), piAgentId),
187
250
  });
188
251
  }
189
252
  else if (options.runtime === "codex") {