larkin 0.4.28 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +5 -9
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
- package/dist/app/agent-cli.mjs +14 -2
- package/dist/app/agent-config.mjs +26 -84
- package/dist/app/binary-entry.mjs +0 -20
- package/dist/app/cli.mjs +8 -14
- package/dist/app/internal-command.cjs +0 -2
- package/dist/app/internal-command.mjs +0 -2
- package/dist/app/local-control.mjs +16 -3
- package/dist/app/runtime-model-directory.mjs +2 -16
- package/dist/app/runtime-process.mjs +42 -6
- package/dist/app/setup.mjs +52 -57
- package/dist/dashboard/dashboard-config-controller.mjs +83 -24
- package/dist/dashboard/dashboard-view-model.mjs +4 -7
- package/dist/dashboard/web/assets/dashboard.js +1 -1
- package/dist/feishu/host-shell.cjs +68 -17
- package/dist/feishu/host-shell.mjs +69 -18
- package/dist/platform/config.cjs +201 -138
- package/dist/platform/config.mjs +199 -138
- package/dist/runtime/pi-artifact-provenance.mjs +3 -3
- package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
- package/dist/runtime/pi-compaction-recovery.cjs +9 -11
- package/dist/runtime/pi-compaction-recovery.mjs +8 -10
- package/dist/runtime/pi-extension-api.mjs +1 -0
- package/dist/runtime/pi-model-catalog.cjs +7 -8
- package/dist/runtime/pi-model-catalog.mjs +7 -8
- package/dist/runtime/pi-subagent-injection.mjs +17 -4
- package/dist/runtime/runtime-adapters.mjs +41 -38
- package/dist/runtime/runtime-host.cjs +197 -20
- package/dist/runtime/runtime-host.mjs +198 -21
- package/dist/runtime/runtime-model-catalog.cjs +1 -1
- package/dist/runtime/runtime-model-catalog.mjs +1 -1
- package/dist/runtime/runtime-readiness.cjs +112 -49
- package/dist/runtime/runtime-readiness.mjs +103 -49
- package/dist/runtime/user-runtime.cjs +7 -24
- package/dist/runtime/user-runtime.mjs +7 -23
- package/dist/setup/bot-register.mjs +15 -123
- package/dist/setup/setup-agent-choice.mjs +48 -74
- package/dist/setup/setup-bind.mjs +51 -151
- package/dist/setup/setup-binding.mjs +6 -9
- package/package.json +3 -2
- package/dist/app/pi-auth-cli.mjs +0 -67
- package/dist/platform/process-boundary-trace.cjs +0 -108
- package/dist/runtime/builtin-pi-assets.cjs +0 -117
- package/dist/runtime/builtin-pi-assets.mjs +0 -105
- package/dist/runtime/pi-artifact-provenance.cjs +0 -147
- package/dist/runtime/pi-distribution-label.cjs +0 -6
- package/dist/runtime/pi-distribution-label.mjs +0 -3
- package/dist/runtime/pi-inline-extensions.mjs +0 -78
- package/dist/runtime/pi-official-auth.mjs +0 -491
- package/dist/runtime/pi-profile-migration.cjs +0 -764
- package/dist/runtime/pi-profile-migration.mjs +0 -719
- package/dist/runtime/pi-provider-config.cjs +0 -331
- package/dist/runtime/pi-provider-config.mjs +0 -314
|
@@ -1,15 +1,81 @@
|
|
|
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
|
-
|
|
6
|
-
import { applyPiPackageDirForChild, piChildDistributionFromOverrides } from "./builtin-pi-assets.mjs";
|
|
7
|
-
import { assertBuiltinPiAgentDirectory, BUNDLED_PI_VERSION, ownedPiCatalogAgentDir, piAgentDirectory } from "./pi-provider-config.mjs";
|
|
8
|
-
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
9
|
-
function safeProviderLabel(value) {
|
|
4
|
+
export function safeProviderId(value) {
|
|
10
5
|
const provider = typeof value === "string" ? value.trim() : "";
|
|
11
6
|
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(provider) ? provider : null;
|
|
12
7
|
}
|
|
8
|
+
function safeProviderLabel(value) {
|
|
9
|
+
return safeProviderId(value);
|
|
10
|
+
}
|
|
11
|
+
/** Model ids are `provider/model`; only the provider prefix is used for scope. */
|
|
12
|
+
export function configuredProviderId(model) {
|
|
13
|
+
const value = typeof model === "string" ? model.trim() : "";
|
|
14
|
+
const slash = value.indexOf("/");
|
|
15
|
+
return slash > 0 ? safeProviderId(value.slice(0, slash)) : null;
|
|
16
|
+
}
|
|
17
|
+
function persistedRuntime(value) {
|
|
18
|
+
return value === "pi" || value === "codex" || value === "claude" ? value : null;
|
|
19
|
+
}
|
|
20
|
+
export function parsePersistedAuthFailure(state) {
|
|
21
|
+
if (!state || typeof state !== "object" || Array.isArray(state))
|
|
22
|
+
return null;
|
|
23
|
+
const record = state;
|
|
24
|
+
if (record.authFailure && typeof record.authFailure === "object" && !Array.isArray(record.authFailure)) {
|
|
25
|
+
const failure = record.authFailure;
|
|
26
|
+
const kind = failure.kind === "missing-provider" || failure.kind === "generic" ? failure.kind : null;
|
|
27
|
+
const runtime = persistedRuntime(failure.runtime);
|
|
28
|
+
if (!kind || !runtime)
|
|
29
|
+
return null;
|
|
30
|
+
const provider = safeProviderId(failure.provider);
|
|
31
|
+
if (kind === "missing-provider" && !provider)
|
|
32
|
+
return null;
|
|
33
|
+
return { kind, runtime, provider };
|
|
34
|
+
}
|
|
35
|
+
const legacy = safeProviderId(record.authFailureProvider);
|
|
36
|
+
return legacy ? { kind: "missing-provider", runtime: "pi", provider: legacy } : null;
|
|
37
|
+
}
|
|
38
|
+
function currentAdapterId(current) {
|
|
39
|
+
return current.adapterId ?? current.runtime;
|
|
40
|
+
}
|
|
41
|
+
export function authFailureAppliesTo(current, failure) {
|
|
42
|
+
if (failure.runtime !== currentAdapterId(current))
|
|
43
|
+
return false;
|
|
44
|
+
if (!failure.provider)
|
|
45
|
+
return true;
|
|
46
|
+
const currentProvider = configuredProviderId(current.model);
|
|
47
|
+
return currentProvider ? currentProvider === failure.provider : true;
|
|
48
|
+
}
|
|
49
|
+
export function readinessForPersistedAuthFailure(failure) {
|
|
50
|
+
if (failure.kind === "missing-provider") {
|
|
51
|
+
return missingProviderCredentialReadiness(failure.runtime, failure.provider);
|
|
52
|
+
}
|
|
53
|
+
return providerAuthenticationFailureReadiness(failure.runtime, failure.provider);
|
|
54
|
+
}
|
|
55
|
+
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})$/;
|
|
56
|
+
/** Narrow match for an explicit Pi absent-key / absent-login diagnostic. */
|
|
57
|
+
export function classifyPiMissingCredentialRejection(message) {
|
|
58
|
+
const text = typeof message === "string"
|
|
59
|
+
? message.replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim()
|
|
60
|
+
: "";
|
|
61
|
+
const match = MISSING_CREDENTIAL_REJECTION.exec(text);
|
|
62
|
+
const provider = match ? safeProviderLabel(match[2]) : null;
|
|
63
|
+
return provider && match ? { provider, diagnostic: `${match[1]} ${provider}` } : null;
|
|
64
|
+
}
|
|
65
|
+
export function runtimeInstallNextAction(runtime) {
|
|
66
|
+
if (runtime === "pi")
|
|
67
|
+
return "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND.";
|
|
68
|
+
if (runtime === "codex")
|
|
69
|
+
return "Install Codex and ensure `codex` is on PATH, or set LARKIN_CODEX_COMMAND.";
|
|
70
|
+
return "Install Claude Code and ensure `claude` is on PATH, or set LARKIN_CLAUDE_COMMAND.";
|
|
71
|
+
}
|
|
72
|
+
export function runtimeLoginNextAction(runtime) {
|
|
73
|
+
if (runtime === "pi")
|
|
74
|
+
return "Log in with the external `pi` CLI, then retry.";
|
|
75
|
+
if (runtime === "codex")
|
|
76
|
+
return "Run `codex login`, then retry.";
|
|
77
|
+
return "Run `claude login`, then retry.";
|
|
78
|
+
}
|
|
13
79
|
export function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
14
80
|
const label = safeProviderLabel(provider);
|
|
15
81
|
return {
|
|
@@ -18,7 +84,20 @@ export function providerAuthenticationFailureReadiness(runtime, provider) {
|
|
|
18
84
|
reason: label
|
|
19
85
|
? `Provider ${label} API-key authentication failed during a Runtime turn.`
|
|
20
86
|
: "Configured provider API-key authentication failed during a Runtime turn.",
|
|
21
|
-
nextAction:
|
|
87
|
+
nextAction: runtimeLoginNextAction(runtime),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function missingProviderCredentialReadiness(runtime, provider) {
|
|
91
|
+
const label = safeProviderLabel(provider);
|
|
92
|
+
return {
|
|
93
|
+
runtime,
|
|
94
|
+
state: "unauthenticated",
|
|
95
|
+
reason: label
|
|
96
|
+
? `Provider ${label} is not authenticated for this runtime.`
|
|
97
|
+
: "The configured provider is not authenticated for this runtime.",
|
|
98
|
+
nextAction: runtime === "pi" && label
|
|
99
|
+
? `Log in with the external \`pi\` CLI for provider ${label}, then retry.`
|
|
100
|
+
: runtimeLoginNextAction(runtime),
|
|
22
101
|
};
|
|
23
102
|
}
|
|
24
103
|
export class RuntimePrerequisiteError extends Error {
|
|
@@ -50,24 +129,29 @@ export function classifyRuntimePrerequisite(runtime, error, executable) {
|
|
|
50
129
|
const reason = (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, " ").slice(0, 500);
|
|
51
130
|
if (/ENOENT|not found|no such file|spawn .* failed/i.test(reason))
|
|
52
131
|
return {
|
|
53
|
-
runtime, state: "missing", ...(executable ? { executable } : {}),
|
|
54
|
-
|
|
132
|
+
runtime, state: "missing", ...(executable ? { executable } : {}),
|
|
133
|
+
reason: `${runtime} is not installed`,
|
|
134
|
+
nextAction: runtimeInstallNextAction(runtime),
|
|
55
135
|
};
|
|
56
136
|
if (/no authenticated available models|login|credential|unauthenticated|unauthorized|oauth/i.test(reason))
|
|
57
137
|
return {
|
|
58
138
|
runtime, state: "unauthenticated", ...(executable ? { executable } : {}), reason,
|
|
59
|
-
nextAction: runtime
|
|
139
|
+
nextAction: runtimeLoginNextAction(runtime),
|
|
60
140
|
};
|
|
61
|
-
if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason))
|
|
141
|
+
if (/protocol (?:version )?(?:mismatch|unsupported|incompatible)|unsupported (?:rpc|protocol|schema)|schema (?:mismatch|incompatible)|requires (?:a )?newer version/i.test(reason)) {
|
|
62
142
|
return {
|
|
63
143
|
runtime, state: "incompatible", ...(executable ? { executable } : {}), reason,
|
|
64
|
-
nextAction: runtime === "pi"
|
|
144
|
+
nextAction: runtime === "pi"
|
|
145
|
+
? "Upgrade local Pi to a version that supports the documented RPC protocol."
|
|
146
|
+
: `Upgrade ${runtime}, then retry.`,
|
|
65
147
|
};
|
|
66
|
-
|
|
148
|
+
}
|
|
149
|
+
if (/timeout|timed out|unexpected EOF|\bEOF\b|TLS|ECONNRESET|socket hang up|network|temporar(?:y|ily)|unavailable/i.test(reason)) {
|
|
67
150
|
return {
|
|
68
151
|
runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
|
|
69
152
|
nextAction: `Retry ${runtime}; Larkin will use its bounded Runtime recreate/backoff policy.`,
|
|
70
153
|
};
|
|
154
|
+
}
|
|
71
155
|
return {
|
|
72
156
|
runtime, state: "unavailable", ...(executable ? { executable } : {}), reason,
|
|
73
157
|
nextAction: `Retry ${runtime}; the failure is not proven to be a protocol incompatibility.`,
|
|
@@ -90,44 +174,15 @@ function executableVersion(executable, env, commandArgs = []) {
|
|
|
90
174
|
}
|
|
91
175
|
/** Resolve and handshake through each runtime's structured native control protocol. */
|
|
92
176
|
export async function probeNativeRuntimeReadiness(options) {
|
|
93
|
-
|
|
94
|
-
const piDistribution = options.runtime === "pi" ? piChildDistributionFromOverrides(options.env) : "external";
|
|
95
|
-
if (options.runtime === "pi" && piDistribution !== "builtin") {
|
|
96
|
-
if (env.LARKIN_PI_DISTRIBUTION === "builtin")
|
|
97
|
-
delete env.LARKIN_PI_DISTRIBUTION;
|
|
98
|
-
env = applyPiPackageDirForChild(env, { distribution: "external" });
|
|
99
|
-
}
|
|
100
|
-
if (options.runtime === "pi" && piDistribution === "builtin") {
|
|
101
|
-
try {
|
|
102
|
-
if (!options.agentId || !env.LARKIN_CONFIG_DIR)
|
|
103
|
-
throw new Error("内置 Pi readiness 缺少 Agent/config identity");
|
|
104
|
-
assertBuiltinPiAgentDirectory(piAgentDirectory(env.LARKIN_CONFIG_DIR, options.agentId));
|
|
105
|
-
return { runtime: "pi", state: "ready", executable: process.execPath, version: `official-pi ${BUNDLED_PI_VERSION} (bundled)` };
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
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
|
-
return { runtime: "pi", state: "unauthenticated", reason: error instanceof Error ? error.message : String(error),
|
|
110
|
-
nextAction: "重新运行 larkin setup,选择内置 Pi 并配置有效 API Key。" };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
177
|
+
const env = { ...process.env, ...options.env };
|
|
113
178
|
const command = selectedCommand(options);
|
|
114
179
|
const executable = resolveRuntimeExecutable(command, env);
|
|
115
|
-
if (!executable)
|
|
116
|
-
return {
|
|
117
|
-
runtime: options.runtime, state: "missing",
|
|
118
|
-
reason: `${command} executable was not found`,
|
|
119
|
-
nextAction: options.runtime === "pi"
|
|
120
|
-
? "Install Pi and ensure `pi` is on PATH, or set LARKIN_PI_COMMAND."
|
|
121
|
-
: `Install ${options.runtime} and ensure it is on PATH.`,
|
|
122
|
-
};
|
|
123
|
-
const piAgentId = options.agentId;
|
|
124
|
-
if (options.runtime === "pi" && !piAgentId) {
|
|
180
|
+
if (!executable) {
|
|
125
181
|
return {
|
|
126
|
-
runtime:
|
|
127
|
-
state: "
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
nextAction: "Retry setup or start so Larkin can probe the Agent-owned Pi provider directory.",
|
|
182
|
+
runtime: options.runtime,
|
|
183
|
+
state: "missing",
|
|
184
|
+
reason: `${options.runtime} is not installed`,
|
|
185
|
+
nextAction: runtimeInstallNextAction(options.runtime),
|
|
131
186
|
};
|
|
132
187
|
}
|
|
133
188
|
const version = executableVersion(executable, env, options.commandArgs);
|
|
@@ -139,7 +194,6 @@ export async function probeNativeRuntimeReadiness(options) {
|
|
|
139
194
|
command: executable,
|
|
140
195
|
commandArgs: options.commandArgs,
|
|
141
196
|
env,
|
|
142
|
-
agentDir: ownedPiCatalogAgentDir(resolveRootConfigDir(env, os.homedir()), piAgentId),
|
|
143
197
|
});
|
|
144
198
|
}
|
|
145
199
|
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.
|
|
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 === "
|
|
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
|
-
/**
|
|
19
|
+
/** Identity mapping with validation. */
|
|
21
20
|
function runtimeOptionOf(input) {
|
|
22
|
-
|
|
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
|
|
30
|
-
return runtimeOptionOf({ runtime
|
|
23
|
+
function toUserRuntime(runtime) {
|
|
24
|
+
return runtimeOptionOf({ runtime });
|
|
31
25
|
}
|
|
32
|
-
/** Persist a user-facing
|
|
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"
|
|
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 === "
|
|
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
|
-
/**
|
|
10
|
+
/** Identity mapping with validation. */
|
|
11
11
|
export function runtimeOptionOf(input) {
|
|
12
|
-
|
|
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
|
|
20
|
-
return runtimeOptionOf({ runtime
|
|
14
|
+
export function toUserRuntime(runtime) {
|
|
15
|
+
return runtimeOptionOf({ runtime });
|
|
21
16
|
}
|
|
22
|
-
/** Persist a user-facing
|
|
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,
|
|
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
|
|
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 &&
|
|
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
|
-
?
|
|
668
|
-
: await
|
|
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
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
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(
|
|
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)
|