larkin 0.4.29 → 0.5.1

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 (57) hide show
  1. package/CHANGELOG.md +15 -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 -86
  6. package/dist/app/binary-entry.mjs +0 -20
  7. package/dist/app/cli.mjs +6 -22
  8. package/dist/app/internal-command.cjs +0 -2
  9. package/dist/app/internal-command.mjs +0 -2
  10. package/dist/app/runtime-model-directory.mjs +2 -16
  11. package/dist/app/runtime-process.mjs +0 -1
  12. package/dist/app/setup.mjs +52 -57
  13. package/dist/dashboard/dashboard-config-controller.mjs +43 -109
  14. package/dist/dashboard/dashboard-view-model.mjs +4 -7
  15. package/dist/dashboard/web/assets/dashboard.css +1 -1
  16. package/dist/dashboard/web/assets/dashboard.js +15 -15
  17. package/dist/feishu/host-shell.cjs +1 -2
  18. package/dist/feishu/host-shell.mjs +1 -2
  19. package/dist/platform/config.cjs +168 -133
  20. package/dist/platform/config.mjs +168 -133
  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 +45 -15
  24. package/dist/runtime/pi-compaction-recovery.mjs +43 -14
  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 +26 -43
  30. package/dist/runtime/runtime-host.cjs +21 -43
  31. package/dist/runtime/runtime-host.mjs +22 -44
  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 +62 -98
  35. package/dist/runtime/runtime-readiness.mjs +60 -97
  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 -277
  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.cjs +0 -594
  52. package/dist/runtime/pi-official-auth.mjs +0 -544
  53. package/dist/runtime/pi-profile-migration.cjs +0 -764
  54. package/dist/runtime/pi-profile-migration.mjs +0 -719
  55. package/dist/runtime/pi-provider-config.cjs +0 -354
  56. package/dist/runtime/pi-provider-config.mjs +0 -334
  57. package/dist/runtime/pi-provider-login.mjs +0 -240
@@ -1,11 +1,7 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
  import { spawnSync } from "node:child_process";
5
- import { resolveConfigDir as resolveRootConfigDir } from "../platform/root-layout.mjs";
6
- import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
7
- import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, builtinPiProviderRecoveryNextAction, ownedPiCatalogAgentDir, piAgentDirectory } from "./pi-provider-config.mjs";
8
- import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
4
+ import { MINIMUM_PI_VERSION, parsePiExecutableVersion } from "./pi-compaction-recovery.mjs";
9
5
  export function safeProviderId(value) {
10
6
  const provider = typeof value === "string" ? value.trim() : "";
11
7
  return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
@@ -13,9 +9,6 @@ export function safeProviderId(value) {
13
9
  function safeProviderLabel(value) {
14
10
  return safeProviderId(value);
15
11
  }
16
- export function isBuiltinPiDistribution(value) {
17
- return value === "builtin";
18
- }
19
12
  /** Model ids are `provider/model`; only the provider prefix is used for scope. */
20
13
  export function configuredProviderId(model) {
21
14
  const value = typeof model === "string" ? model.trim() : "";
@@ -38,53 +31,20 @@ export function parsePersistedAuthFailure(state) {
38
31
  const provider = safeProviderId(failure.provider);
39
32
  if (kind === "missing-provider" && !provider)
40
33
  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
- };
34
+ return { kind, runtime, provider };
49
35
  }
50
36
  const legacy = safeProviderId(record.authFailureProvider);
51
- return legacy
52
- ? { kind: "missing-provider", runtime: "pi", piDistribution: "builtin", provider: legacy }
53
- : null;
37
+ return legacy ? { kind: "missing-provider", runtime: "pi", provider: legacy } : null;
54
38
  }
55
39
  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");
40
+ return current.adapterId ?? current.runtime;
61
41
  }
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
42
  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
43
  if (failure.runtime !== currentAdapterId(current))
77
44
  return false;
78
- if (failure.runtime === "pi") {
79
- const persistedBuiltin = failure.piDistribution === "builtin";
80
- if (currentIsBuiltinPi(current) !== persistedBuiltin)
81
- return false;
82
- }
83
45
  if (!failure.provider)
84
46
  return true;
85
47
  const currentProvider = configuredProviderId(current.model);
86
- if (currentIsBuiltinPi(current))
87
- return currentProvider === failure.provider;
88
48
  return currentProvider ? currentProvider === failure.provider : true;
89
49
  }
90
50
  export function readinessForPersistedAuthFailure(failure) {
@@ -103,6 +63,20 @@ export function classifyPiMissingCredentialRejection(message) {
103
63
  const provider = match ? safeProviderLabel(match[2]) : null;
104
64
  return provider && match ? { provider, diagnostic: `${match[1]} ${provider}` } : null;
105
65
  }
66
+ export function runtimeInstallNextAction(runtime) {
67
+ if (runtime === "pi")
68
+ return "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND.";
69
+ if (runtime === "codex")
70
+ return "Install Codex and ensure `codex` is on PATH, or set LARKIN_CODEX_COMMAND.";
71
+ return "Install Claude Code and ensure `claude` is on PATH, or set LARKIN_CLAUDE_COMMAND.";
72
+ }
73
+ export function runtimeLoginNextAction(runtime) {
74
+ if (runtime === "pi")
75
+ return "Log in with the external `pi` CLI, then retry.";
76
+ if (runtime === "codex")
77
+ return "Run `codex login`, then retry.";
78
+ return "Run `claude login`, then retry.";
79
+ }
106
80
  export function providerAuthenticationFailureReadiness(runtime, provider) {
107
81
  const label = safeProviderLabel(provider);
108
82
  return {
@@ -111,23 +85,20 @@ export function providerAuthenticationFailureReadiness(runtime, provider) {
111
85
  reason: label
112
86
  ? `Provider ${label} API-key authentication failed during a Runtime turn.`
113
87
  : "Configured provider API-key authentication failed during a Runtime turn.",
114
- nextAction: "Check the provider login or API-key resolver command, then retry the Agent turn.",
88
+ nextAction: runtimeLoginNextAction(runtime),
115
89
  };
116
90
  }
117
- /** P0 分类;Pi nextAction 共用 UX preset 恢复文案,不写官方 provider ID。 */
118
91
  export function missingProviderCredentialReadiness(runtime, provider) {
119
92
  const label = safeProviderLabel(provider);
120
93
  return {
121
94
  runtime,
122
95
  state: "unauthenticated",
123
96
  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.",
97
+ ? `Provider ${label} is not authenticated for this runtime.`
98
+ : "The configured provider is not authenticated for this runtime.",
99
+ nextAction: runtime === "pi" && label
100
+ ? `Log in with the external \`pi\` CLI for provider ${label}, then retry.`
101
+ : runtimeLoginNextAction(runtime),
131
102
  };
132
103
  }
133
104
  export class RuntimePrerequisiteError extends Error {
@@ -159,27 +130,37 @@ export function classifyRuntimePrerequisite(runtime, error, executable) {
159
130
  const reason = (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, " ").slice(0, 500);
160
131
  if (/ENOENT|not found|no such file|spawn .* failed/i.test(reason))
161
132
  return {
162
- runtime, state: "missing", ...(executable ? { executable } : {}), reason,
163
- nextAction: runtime === "pi" ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND." : `Install ${runtime} and ensure it is on PATH.`,
133
+ runtime, state: "missing", ...(executable ? { executable } : {}),
134
+ reason: `${runtime} is not installed`,
135
+ nextAction: runtimeInstallNextAction(runtime),
164
136
  };
165
137
  if (/no authenticated available models|login|credential|unauthenticated|unauthorized|oauth/i.test(reason))
166
138
  return {
167
139
  runtime, state: "unauthenticated", ...(executable ? { executable } : {}), reason,
168
- // 此分类没有 distribution:外部 Pi 不能指向 Dashboard / pi-auth login。
140
+ nextAction: runtimeLoginNextAction(runtime),
141
+ };
142
+ if (/older than the minimum|not a supported version(?: line)?|version output must contain exactly one/i.test(reason)) {
143
+ return {
144
+ runtime, state: "incompatible", ...(executable ? { executable } : {}), reason,
169
145
  nextAction: runtime === "pi"
170
- ? "Run the official `pi` login flow, then retry."
171
- : `Authenticate ${runtime}, then retry.`,
146
+ ? `Upgrade pi to ${MINIMUM_PI_VERSION} or newer`
147
+ : `Upgrade ${runtime}, then retry.`,
172
148
  };
173
- if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason))
149
+ }
150
+ if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason)) {
174
151
  return {
175
152
  runtime, state: "incompatible", ...(executable ? { executable } : {}), reason,
176
- nextAction: runtime === "pi" ? "Upgrade local Pi to a version that supports the documented RPC protocol." : `Upgrade ${runtime}, then retry.`,
153
+ nextAction: runtime === "pi"
154
+ ? "Upgrade local Pi to a version that supports the documented RPC protocol."
155
+ : `Upgrade ${runtime}, then retry.`,
177
156
  };
178
- if (/timeout|timed out|unexpected EOF|\bEOF\b|TLS|ECONNRESET|socket hang up|network|temporar(?:y|ily)|unavailable/i.test(reason))
157
+ }
158
+ if (/timeout|timed out|unexpected EOF|\bEOF\b|TLS|ECONNRESET|socket hang up|network|temporar(?:y|ily)|unavailable/i.test(reason)) {
179
159
  return {
180
160
  runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
181
161
  nextAction: `Retry ${runtime}; Larkin will use its bounded Runtime recreate/backoff policy.`,
182
162
  };
163
+ }
183
164
  return {
184
165
  runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
185
166
  nextAction: `Retry ${runtime}; the failure is not proven to be a protocol incompatibility.`,
@@ -202,47 +183,30 @@ function executableVersion(executable, env, commandArgs = []) {
202
183
  }
203
184
  /** Resolve and handshake through each runtime's structured native control protocol. */
204
185
  export async function probeNativeRuntimeReadiness(options) {
205
- let env = { ...process.env, ...options.env };
206
- const piDistribution = options.runtime === "pi" ? piChildDistributionFromOverrides(options.env) : "external";
207
- if (options.runtime === "pi" && piDistribution !== "builtin") {
208
- if (env.LARKIN_PI_DISTRIBUTION === "builtin")
209
- delete env.LARKIN_PI_DISTRIBUTION;
210
- env = applyPiPackageDirForChild(env, { distribution: "external" });
186
+ const env = { ...process.env, ...options.env };
187
+ const command = selectedCommand(options);
188
+ const executable = resolveRuntimeExecutable(command, env);
189
+ if (!executable) {
190
+ return {
191
+ runtime: options.runtime,
192
+ state: "missing",
193
+ reason: `${options.runtime} is not installed`,
194
+ nextAction: runtimeInstallNextAction(options.runtime),
195
+ };
211
196
  }
212
- if (options.runtime === "pi" && piDistribution === "builtin") {
197
+ let version = executableVersion(executable, env, options.commandArgs);
198
+ if (options.runtime === "pi") {
213
199
  try {
214
- if (!options.agentId || !env.LARKIN_CONFIG_DIR)
215
- throw new Error("内置 Pi readiness 缺少 Agent/config identity");
216
- assertBuiltinPiAgentDirectory(piAgentDirectory(env.LARKIN_CONFIG_DIR, options.agentId));
217
- return { runtime: "pi", state: "ready", executable: process.execPath, version: `official-pi ${BUNDLED_PI_VERSION} (bundled)` };
200
+ const probe = spawnSync(executable, [...options.commandArgs ?? [], "--version"], {
201
+ env, encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024,
202
+ });
203
+ version = parsePiExecutableVersion(String(probe.stdout || ""));
218
204
  }
219
205
  catch (error) {
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 });
221
- return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
222
- nextAction: builtinPiProviderRecoveryNextAction({ agentId: options.agentId }) };
206
+ const classified = classifyRuntimePrerequisite("pi", error, executable);
207
+ return { ...classified, executable, ...(version ? { version } : {}) };
223
208
  }
224
209
  }
225
- const command = selectedCommand(options);
226
- const executable = resolveRuntimeExecutable(command, env);
227
- if (!executable)
228
- return {
229
- runtime: options.runtime, state: "missing",
230
- reason: `${command} executable was not found`,
231
- nextAction: options.runtime === "pi"
232
- ? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND."
233
- : `Install ${options.runtime} and ensure it is on PATH.`,
234
- };
235
- const piAgentId = options.agentId;
236
- if (options.runtime === "pi" && !piAgentId) {
237
- return {
238
- runtime: "pi",
239
- state: "unavailable",
240
- executable,
241
- reason: "Pi catalog readiness requires Agent ID",
242
- nextAction: "Retry setup or start so Larkin can probe the Agent-owned Pi provider directory.",
243
- };
244
- }
245
- const version = executableVersion(executable, env, options.commandArgs);
246
210
  try {
247
211
  if (options.runtime === "pi") {
248
212
  const { discoverPiModelCatalog } = await import("./pi-model-catalog.mjs");
@@ -251,7 +215,6 @@ export async function probeNativeRuntimeReadiness(options) {
251
215
  command: executable,
252
216
  commandArgs: options.commandArgs,
253
217
  env,
254
- agentDir: ownedPiCatalogAgentDir(resolveRootConfigDir(env, os.homedir()), piAgentId),
255
218
  });
256
219
  }
257
220
  else if (options.runtime === "codex") {
@@ -7,45 +7,28 @@ exports.runtimeOptionOf = runtimeOptionOf;
7
7
  exports.toUserRuntime = toUserRuntime;
8
8
  exports.runtimeOptionTarget = runtimeOptionTarget;
9
9
  exports.fromUserRuntime = fromUserRuntime;
10
- exports.piCatalogDistributionForUserRuntime = piCatalogDistributionForUserRuntime;
11
- exports.RUNTIME_OPTIONS = ["codex", "claude", "pi", "builtin-pi"];
10
+ exports.RUNTIME_OPTIONS = ["codex", "claude", "pi"];
12
11
  exports.USER_RUNTIMES = exports.RUNTIME_OPTIONS;
13
12
  exports.ADAPTER_RUNTIMES = ["pi", "codex", "claude"];
14
13
  function isUserRuntime(value) {
15
- return value === "pi" || value === "builtin-pi" || value === "codex" || value === "claude";
14
+ return value === "pi" || value === "codex" || value === "claude";
16
15
  }
17
16
  function isAdapterRuntime(value) {
18
17
  return value === "pi" || value === "codex" || value === "claude";
19
18
  }
20
- /** Project stored adapter + distribution to the user-facing sibling id. Legacy `runtime=pi` without distribution is `pi`, never builtin. */
19
+ /** Identity mapping with validation. */
21
20
  function runtimeOptionOf(input) {
22
- const { runtime, piDistribution } = input;
23
- if (runtime === "codex" || runtime === "claude" || runtime === "builtin-pi")
24
- return runtime;
25
- if (runtime === "pi")
26
- return piDistribution === "builtin" ? "builtin-pi" : "pi";
27
- return runtime;
21
+ return input.runtime;
28
22
  }
29
- function toUserRuntime(runtime, piDistribution) {
30
- return runtimeOptionOf({ runtime, piDistribution });
23
+ function toUserRuntime(runtime) {
24
+ return runtimeOptionOf({ runtime });
31
25
  }
32
- /** Persist a user-facing sibling as adapter id + distribution. Adapter id stays `pi` for both Pi siblings. */
26
+ /** Persist a user-facing runtime as the stored adapter id. */
33
27
  function runtimeOptionTarget(option) {
34
28
  if (!isUserRuntime(option))
35
29
  throw new Error(`未知 runtime:${option}`);
36
- if (option === "builtin-pi")
37
- return { runtime: "pi", piDistribution: "builtin" };
38
- if (option === "pi")
39
- return { runtime: "pi", piDistribution: "external" };
40
30
  return { runtime: option };
41
31
  }
42
32
  function fromUserRuntime(userRuntime) {
43
33
  return runtimeOptionTarget(userRuntime);
44
34
  }
45
- function piCatalogDistributionForUserRuntime(userRuntime) {
46
- if (userRuntime === "builtin-pi")
47
- return "builtin";
48
- if (userRuntime === "pi")
49
- return "external";
50
- throw new Error(`非 Pi sibling runtime:${userRuntime}`);
51
- }
@@ -1,41 +1,25 @@
1
- export const RUNTIME_OPTIONS = ["codex", "claude", "pi", "builtin-pi"];
1
+ export const RUNTIME_OPTIONS = ["codex", "claude", "pi"];
2
2
  export const USER_RUNTIMES = RUNTIME_OPTIONS;
3
3
  export const ADAPTER_RUNTIMES = ["pi", "codex", "claude"];
4
4
  export function isUserRuntime(value) {
5
- return value === "pi" || value === "builtin-pi" || value === "codex" || value === "claude";
5
+ return value === "pi" || value === "codex" || value === "claude";
6
6
  }
7
7
  export function isAdapterRuntime(value) {
8
8
  return value === "pi" || value === "codex" || value === "claude";
9
9
  }
10
- /** Project stored adapter + distribution to the user-facing sibling id. Legacy `runtime=pi` without distribution is `pi`, never builtin. */
10
+ /** Identity mapping with validation. */
11
11
  export function runtimeOptionOf(input) {
12
- const { runtime, piDistribution } = input;
13
- if (runtime === "codex" || runtime === "claude" || runtime === "builtin-pi")
14
- return runtime;
15
- if (runtime === "pi")
16
- return piDistribution === "builtin" ? "builtin-pi" : "pi";
17
- return runtime;
12
+ return input.runtime;
18
13
  }
19
- export function toUserRuntime(runtime, piDistribution) {
20
- return runtimeOptionOf({ runtime, piDistribution });
14
+ export function toUserRuntime(runtime) {
15
+ return runtimeOptionOf({ runtime });
21
16
  }
22
- /** Persist a user-facing sibling as adapter id + distribution. Adapter id stays `pi` for both Pi siblings. */
17
+ /** Persist a user-facing runtime as the stored adapter id. */
23
18
  export function runtimeOptionTarget(option) {
24
19
  if (!isUserRuntime(option))
25
20
  throw new Error(`未知 runtime:${option}`);
26
- if (option === "builtin-pi")
27
- return { runtime: "pi", piDistribution: "builtin" };
28
- if (option === "pi")
29
- return { runtime: "pi", piDistribution: "external" };
30
21
  return { runtime: option };
31
22
  }
32
23
  export function fromUserRuntime(userRuntime) {
33
24
  return runtimeOptionTarget(userRuntime);
34
25
  }
35
- export function piCatalogDistributionForUserRuntime(userRuntime) {
36
- if (userRuntime === "builtin-pi")
37
- return "builtin";
38
- if (userRuntime === "pi")
39
- return "external";
40
- throw new Error(`非 Pi sibling runtime:${userRuntime}`);
41
- }
@@ -12,10 +12,7 @@ import { createAgentStateStore } from "../agent/agent-state-store.mjs";
12
12
  import { hydrateRuntimeAgent, syncAgentProfile, syncAgentProfileAsync } from "../app/runtime-agent-config.mjs";
13
13
  import { managedLarkCliEnv } from "../app/agent-lark-cli-workspace.mjs";
14
14
  import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
15
- import { collectSetupAgentChoice, recoverUnavailableExternalPi, terminalSetupQuestioner } from "./setup-agent-choice.mjs";
16
- import { probeNativeRuntimeReadiness } from "../runtime/runtime-readiness.mjs";
17
- import { configureBuiltinPiProviderModel, stageBuiltinPiProvider, validatePiBaseUrl, validateBuiltinPiProviderSelection, listProviderModels, PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
18
- import { beginBuiltinPiCredentialTransaction, createOfficialPiCredentialRuntime, createOfficialPiAuthInteraction, createOfficialPiModelRuntime, createOfficialPiLogoutRuntime, createOfficialPiRegistryRuntime, listOfficialPiAuthProviders, logoutOfficialPiProvider, officialPiAuthStatus, runOfficialPiLogin, verifyOfficialPiProviderTurn, } from "../runtime/pi-official-auth.mjs";
15
+ import { collectSetupAgentChoice, missingRuntimeInstallMessage, terminalSetupQuestioner } from "./setup-agent-choice.mjs";
19
16
  import { documentCommentSubscriptionCapability, markDocumentCommentSubscriptionVerified, } from "../platform/callback-capability.mjs";
20
17
  import { openPlatformHost, parseLarkinTenant, registerAppAccountsHost, } from "../feishu/platform-hosts.mjs";
21
18
  import { authorizationUrlFailureMessage, presentAuthorizationUrl } from "./setup-authorization-url.mjs";
@@ -35,7 +32,6 @@ const resolveOfficialCli = testFixture?.resolveOfficialLarkCli ?? resolveOfficia
35
32
  const wait = testFixture?.wait ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
36
33
  const CFG_DIR = larkinConfig.resolveConfigDir(process.env);
37
34
  let temporaryAgentChoiceFile = null;
38
- let pendingPiAuthTransaction = null;
39
35
  let pendingBindChild = null;
40
36
  let pendingChildSettled = null;
41
37
  let settlePendingChild = null;
@@ -54,7 +50,6 @@ function trackPendingChild(child) {
54
50
  pendingChildSettled = null;
55
51
  }
56
52
  process.on("exit", () => {
57
- pendingPiAuthTransaction?.rollback();
58
53
  if (!temporaryAgentChoiceFile)
59
54
  return;
60
55
  try {
@@ -70,8 +65,6 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
70
65
  const settling = pendingChildSettled;
71
66
  shutdownController.abort(new Error(`setup interrupted by ${signal}`));
72
67
  void (settling ?? Promise.resolve()).finally(() => {
73
- pendingPiAuthTransaction?.rollback();
74
- pendingPiAuthTransaction = null;
75
68
  process.exit(signal === "SIGINT" ? 130 : 143);
76
69
  });
77
70
  });
@@ -99,8 +92,6 @@ const CHILD_MAX_OUTPUT_BYTES = Number.isFinite(testChildMaxOutput) && testChildM
99
92
  function officialCliForProfile(env) {
100
93
  if (resolvedSetupOfficialCli)
101
94
  return resolvedSetupOfficialCli;
102
- if (pendingPiAuthTransaction)
103
- throw new Error("official lark-cli must be resolved before the Pi credential transaction starts");
104
95
  resolvedSetupOfficialCli = resolveOfficialCli({ env });
105
96
  return resolvedSetupOfficialCli;
106
97
  }
@@ -304,7 +295,7 @@ function openBrowser(url) {
304
295
  }
305
296
  }
306
297
  async function runBindProcess(command, args) {
307
- if (testFixture?.spawnSync && !pendingPiAuthTransaction) {
298
+ if (testFixture?.spawnSync) {
308
299
  return spawnSync(command, [...args], { env: process.env, stdio: "inherit" }).status;
309
300
  }
310
301
  return await new Promise((resolve, reject) => {
@@ -336,7 +327,7 @@ async function runBindProcess(command, args) {
336
327
  }).finally(() => { trackPendingChild(null); });
337
328
  }
338
329
  async function runBoundedCliProcess(command, args, env, label) {
339
- if (testFixture?.spawnSync && !pendingPiAuthTransaction && process.env.LARKIN_TEST_ASYNC_IDENTITY !== "1") {
330
+ if (testFixture?.spawnSync && process.env.LARKIN_TEST_ASYNC_IDENTITY !== "1") {
340
331
  const result = spawnSync(command, [...args], { encoding: "utf8", env });
341
332
  return { status: result.status, stdout: result.stdout || "", stderr: result.stderr || "" };
342
333
  }
@@ -643,128 +634,30 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
643
634
  : commentSubscription === "none" || !priorSubscription
644
635
  ? { mode: "none", status: "safe-default", source: "setup-default", updatedAt: requestedAt }
645
636
  : priorSubscription;
637
+ if (flag("--provider") || flag("--api-key") || flag("--base-url") || flag("--pi-distribution")) {
638
+ die("setup 不再接受 --provider/--api-key/--base-url;请安装并登录外部 pi、codex 或 claude");
639
+ }
646
640
  const injectedAgentChoice = testFixture?.collectSetupAgentChoice;
647
641
  if (!flag("--runtime") && (!testFixture || injectedAgentChoice || process.env.LARKIN_TEST_ENABLE_AGENT_CHOICE === "1")) {
648
642
  const existing = larkinConfig.loadConfig(process.env).config.agents[id];
649
- // The official resolver performs a synchronous login-shell probe. Resolve it
650
- // exactly once before the credential heartbeat lock becomes active.
651
643
  resolvedSetupOfficialCli = resolveOfficialCli({ env: process.env });
652
644
  const questioner = injectedAgentChoice ? { ask: async () => "", secret: async () => "" } : terminalSetupQuestioner();
653
- // One setup-owned transaction starts before status/logout and remains active
654
- // through selection, login, bind, readiness, and the final setup commit.
655
- pendingPiAuthTransaction = beginBuiltinPiCredentialTransaction(CFG_DIR, id);
656
- const authServices = {
657
- providers: async () => listOfficialPiAuthProviders(await createOfficialPiRegistryRuntime()),
658
- status: async () => officialPiAuthStatus(await createOfficialPiCredentialRuntime(CFG_DIR, id)),
659
- logout: async (providerId) => logoutOfficialPiProvider(await createOfficialPiLogoutRuntime(CFG_DIR, id), providerId),
660
- report: (message) => say(message),
661
- };
662
645
  try {
663
- const requested = injectedAgentChoice
664
- ? await injectedAgentChoice()
665
- : await collectSetupAgentChoice(questioner, existing, authServices);
666
646
  const choice = injectedAgentChoice
667
- ? requested
668
- : await recoverUnavailableExternalPi(requested, questioner, () => probeNativeRuntimeReadiness({
669
- runtime: "pi", agentId: id, cwd: path.join(CFG_DIR, "agents", id),
670
- env: { ...process.env, LARKIN_CONFIG_DIR: CFG_DIR, LARKIN_PI_DISTRIBUTION: "external" },
671
- }), (message) => say(`! ${message}`), authServices);
647
+ ? await injectedAgentChoice()
648
+ : await collectSetupAgentChoice(questioner, existing);
672
649
  if (choice) {
673
- let serializedChoice = choice;
674
- if (choice.runtime === "pi" && choice.distribution === "builtin") {
675
- if (!injectedAgentChoice) {
676
- const official = choice.preset === "official" ? choice : null;
677
- const configured = official ? null : configureBuiltinPiProviderModel(CFG_DIR, id, choice);
678
- const providerId = official?.providerId || configured.provider;
679
- const authType = official?.authType || "api_key";
680
- let piRuntime;
681
- try {
682
- say(`正在通过捆绑官方 Pi 登录 ${providerId}(${authType})…`);
683
- piRuntime = await createOfficialPiModelRuntime(CFG_DIR, id);
684
- await runOfficialPiLogin(piRuntime, providerId, authType, createOfficialPiAuthInteraction({ questioner, report: (message) => say(message), openUrl: openBrowser }));
685
- }
686
- catch {
687
- pendingPiAuthTransaction.rollback();
688
- pendingPiAuthTransaction = null;
689
- throw new Error(`官方 Pi ${providerId} 登录失败或已取消;credential/config 未修改`);
690
- }
691
- if (process.env.LARKIN_TEST_SKIP_BUILTIN_PI_PROVIDER_TURN !== "1") {
692
- say("正在验证内置 Pi provider(受控单轮,不发送飞书(Lark)消息)…");
693
- try {
694
- await verifyOfficialPiProviderTurn(piRuntime, choice.model);
695
- }
696
- catch {
697
- pendingPiAuthTransaction.rollback();
698
- pendingPiAuthTransaction = null;
699
- throw new Error(`官方 Pi ${providerId} readiness 失败;credential/config 未修改`);
700
- }
701
- }
702
- }
703
- serializedChoice = { ...choice, authCompleted: true, readinessCompleted: true };
704
- }
650
+ const missing = missingRuntimeInstallMessage(choice.runtime, process.env);
651
+ if (missing)
652
+ die(missing);
705
653
  temporaryAgentChoiceFile = path.join(CFG_DIR, `.setup-agent-choice-${process.pid}-${Date.now()}.json`);
706
- fs.writeFileSync(temporaryAgentChoiceFile, `${JSON.stringify(serializedChoice)}\n`, { mode: 0o600, flag: "wx" });
654
+ fs.writeFileSync(temporaryAgentChoiceFile, `${JSON.stringify({ runtime: choice.runtime })}\n`, { mode: 0o600, flag: "wx" });
707
655
  }
708
656
  }
709
657
  finally {
710
658
  questioner.close?.();
711
659
  }
712
660
  }
713
- // ── 非交互参数化内置 Pi(builtin-pi 是默认 runtime 类型)──
714
- // --pi-distribution builtin:需 --provider <id> + --api-key <key>(或 --base-url 自定义端点)。
715
- const piDistributionFlag = flag("--pi-distribution");
716
- const setupProvider = flag("--provider");
717
- const setupApiKey = flag("--api-key");
718
- const setupBaseUrl = flag("--base-url");
719
- const setupModel = flag("--model");
720
- if (piDistributionFlag === "builtin") {
721
- if (!setupProvider && !setupBaseUrl) {
722
- throw new Error(`builtin-pi 需要 --provider <id>(${PI_PROVIDER_PRESETS.map((p) => p.id).join("|")}|custom)或 --base-url;或改用 --runtime pi 使用已有 pi 环境`);
723
- }
724
- if (!setupApiKey)
725
- throw new Error("builtin-pi 需要 --api-key;或改用 --runtime pi 使用已有 pi 登录");
726
- // 自定义网关(--base-url)走 custom 预设:pi 会把 baseUrl 作为 provider 端点写入配置,
727
- // 而不是用预设厂商目录(否则会打到预设默认地址,如 api.openai.com)。
728
- const presetId = setupBaseUrl ? "custom" : (setupProvider ?? "custom");
729
- if (presetId === "custom" && !setupModel)
730
- throw new Error("custom base-url 需要 --model <模型名>");
731
- const presetDef = PI_PROVIDER_PRESETS.find((p) => p.id === presetId);
732
- if (!presetDef && !setupBaseUrl)
733
- throw new Error(`未知 provider \`${presetId}\`;可选:${PI_PROVIDER_PRESETS.map((p) => p.id).join(" | ")} | custom`);
734
- const raw = {
735
- distribution: "builtin",
736
- preset: presetId,
737
- model: setupModel ?? presetDef?.defaultModel ?? "",
738
- // 显式 --base-url 优先于预设默认值(自定义网关场景,如 opencode)。
739
- ...(setupBaseUrl ? { baseUrl: validatePiBaseUrl(setupBaseUrl) } : presetDef ? { baseUrl: presetDef.baseUrl } : {}),
740
- };
741
- if (setupModel && raw.baseUrl) {
742
- // Owner 决策:模型名必须来自 provider 的权威可用列表,输错即报错,不做运行时猜测。
743
- // 网络超时/不可达时不阻塞 setup(null),运行时对账会严格兜底。
744
- const availableIds = await listProviderModels(raw.baseUrl, setupApiKey);
745
- if (availableIds !== null) {
746
- const matched = availableIds.some((id) => id === setupModel || id.endsWith(`/${setupModel}`));
747
- if (!matched) {
748
- const preview = availableIds.slice(0, 12).join(", ") + (availableIds.length > 12 ? ", …" : "");
749
- throw new Error(`未知模型 ${setupModel};provider 可用模型:[${preview || "无"}]`);
750
- }
751
- }
752
- else {
753
- say("[setup] 网络暂不可达,跳过 /models 预校验;运行时将对模型严格对账");
754
- }
755
- }
756
- stageBuiltinPiProvider(CFG_DIR, id, { ...raw, apiKey: setupApiKey });
757
- // 选择文件写入解析后的全称模型(custom 预设为 larkin-custom/<model>):
758
- // pi 运行时按 provider 前缀解析凭证,裸模型名会回落到默认 provider(deepseek)
759
- // 导致「No API key found for deepseek」。
760
- const validated = validateBuiltinPiProviderSelection({ ...raw, apiKey: setupApiKey });
761
- temporaryAgentChoiceFile = path.join(CFG_DIR, `.setup-agent-choice-${process.pid}-${Date.now()}.json`);
762
- fs.writeFileSync(temporaryAgentChoiceFile, `${JSON.stringify({ ...raw, runtime: "pi", model: validated.model, authCompleted: true, readinessCompleted: true })}\n`, { mode: 0o600, flag: "wx" });
763
- say(`[setup 2/5] ✓ 内置 Pi provider 已配置(${presetId}${setupBaseUrl ? " / custom" : ""})`);
764
- }
765
- else if (piDistributionFlag === "external" && (setupProvider || setupApiKey || setupBaseUrl)) {
766
- throw new Error("pi 使用已有 pi 环境与登录,不接受 --provider/--api-key/--base-url");
767
- }
768
661
  const stagedBotFile = path.join(botsDir, `.${id}.${process.pid}.tmp`);
769
662
  try {
770
663
  fs.writeFileSync(stagedBotFile, `${JSON.stringify({
@@ -811,13 +704,14 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
811
704
  const runtime = flag("--runtime");
812
705
  if (runtime)
813
706
  bindArgs.push("--runtime", runtime);
707
+ const model = flag("--model");
708
+ if (model)
709
+ bindArgs.push("--model", model);
814
710
  if (temporaryAgentChoiceFile)
815
711
  bindArgs.push("--selection-file", temporaryAgentChoiceFile);
816
712
  const bindSpec = internalCommandSpec("setup-bind", bindArgs, process.env);
817
713
  const bindStatus = await runBindProcess(bindSpec.command, bindSpec.args);
818
714
  if (bindStatus !== 0) {
819
- pendingPiAuthTransaction?.rollback();
820
- pendingPiAuthTransaction = null;
821
715
  die("新 bot 凭证已发布但 Agent 绑定失败;权威凭证状态已保留,请重跑 larkin setup 并在网页中重新选择机器人");
822
716
  }
823
717
  try {
@@ -943,8 +837,6 @@ Lark 必须用 --tenant lark:授权二维码是官方 /page/cli(begin 仍走
943
837
  die("Agent 绑定与新 bot 凭证已保留,但 setup 结果写入失败;请重跑 larkin setup 并在网页中重新选择机器人");
944
838
  }
945
839
  }
946
- pendingPiAuthTransaction?.commit();
947
- pendingPiAuthTransaction = null;
948
840
  }
949
841
  if (path.resolve(process.argv[1] || "") === path.resolve(fileURLToPath(import.meta.url))) {
950
842
  main().catch((error) => { if (!requestedShutdown)