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,92 +1,66 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { stdin, stdout } from "node:process";
|
|
3
|
-
import { toUserRuntime } from "../runtime/user-runtime.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { RUNTIME_OPTIONS, toUserRuntime } from "../runtime/user-runtime.mjs";
|
|
4
|
+
import { resolveRuntimeExecutable, runtimeInstallNextAction } from "../runtime/runtime-readiness.mjs";
|
|
5
|
+
function runtimeCommand(runtime, env) {
|
|
6
|
+
if (runtime === "pi")
|
|
7
|
+
return env.LARKIN_PI_COMMAND || "pi";
|
|
8
|
+
if (runtime === "codex")
|
|
9
|
+
return env.LARKIN_CODEX_COMMAND || "codex";
|
|
10
|
+
return env.LARKIN_CLAUDE_COMMAND || "claude";
|
|
11
|
+
}
|
|
12
|
+
export function listRuntimeInstallStatuses(env = process.env) {
|
|
13
|
+
return RUNTIME_OPTIONS.map((runtime) => {
|
|
14
|
+
if (resolveRuntimeExecutable(runtimeCommand(runtime, env), env))
|
|
15
|
+
return { runtime, installed: true };
|
|
16
|
+
return {
|
|
17
|
+
runtime,
|
|
18
|
+
installed: false,
|
|
19
|
+
reason: `${runtime} is not installed`,
|
|
20
|
+
nextAction: runtimeInstallNextAction(runtime),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function missingRuntimeInstallMessage(runtime, env = process.env) {
|
|
25
|
+
if (runtime !== "pi" && runtime !== "codex" && runtime !== "claude")
|
|
26
|
+
return null;
|
|
27
|
+
const status = listRuntimeInstallStatuses(env).find((entry) => entry.runtime === runtime);
|
|
28
|
+
if (!status || status.installed)
|
|
29
|
+
return null;
|
|
30
|
+
return `${status.reason};${status.nextAction}`;
|
|
31
|
+
}
|
|
32
|
+
const RUNTIME_LABELS = {
|
|
33
|
+
pi: "pi(本机官方 pi CLI)",
|
|
34
|
+
codex: "Codex",
|
|
35
|
+
claude: "Claude Code",
|
|
36
|
+
};
|
|
5
37
|
function number(value, fallback, max) {
|
|
6
38
|
const parsed = value.trim() ? Number(value.trim()) : fallback;
|
|
7
39
|
if (!Number.isInteger(parsed) || parsed < 1 || parsed > max)
|
|
8
40
|
throw new Error(`请选择 1-${max}`);
|
|
9
41
|
return parsed;
|
|
10
42
|
}
|
|
11
|
-
|
|
12
|
-
const lines =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const status = await services.status();
|
|
18
|
-
if (status.length === 0)
|
|
19
|
-
services.report("尚无已配置的官方 Pi credential");
|
|
20
|
-
else
|
|
21
|
-
status.forEach((entry, index) => services.report(` ${index + 1}. ${entry.providerName} (${entry.providerId}) — ${entry.credentialType}/${entry.source}${entry.stored ? ",已存储" : ",ambient"}`));
|
|
22
|
-
const answer = (await questioner.ask("输入要 logout 的序号;直接回车返回:")).trim();
|
|
23
|
-
if (answer) {
|
|
24
|
-
const selected = number(answer, 0, status.length);
|
|
25
|
-
await services.logout(status[selected - 1].providerId);
|
|
26
|
-
services.report(`已退出 ${status[selected - 1].providerName};未修改其他 provider`);
|
|
27
|
-
}
|
|
28
|
-
return collectBuiltinPiChoice(questioner, services);
|
|
29
|
-
}
|
|
30
|
-
if (providerChoice === 6) {
|
|
31
|
-
if (!services)
|
|
32
|
-
throw new Error("当前 setup 上下文无法读取官方 Pi auth registry");
|
|
33
|
-
const providers = await services.providers();
|
|
34
|
-
const methods = providers.flatMap((provider) => provider.methods.map((method) => ({ provider, method })));
|
|
35
|
-
if (methods.length === 0)
|
|
36
|
-
throw new Error("捆绑官方 Pi auth registry 没有可交互登录方式");
|
|
37
|
-
const methodLines = methods.map(({ provider, method }, index) => ` ${index + 1}. ${provider.name} — ${method.name} [${method.type}]`).join("\n");
|
|
38
|
-
const methodChoice = number(await questioner.ask(`选择官方 Pi 登录方式:\n${methodLines}\n> `), 1, methods.length);
|
|
39
|
-
const selected = methods[methodChoice - 1];
|
|
40
|
-
const defaultModel = selected.provider.models[0] || "";
|
|
41
|
-
const model = (await questioner.ask(`模型 ID${defaultModel ? `(默认 ${defaultModel})` : "(provider/model)"}:`)).trim() || defaultModel;
|
|
42
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\/[A-Za-z0-9][A-Za-z0-9._:@+\/-]{0,255}$/.test(model))
|
|
43
|
-
throw new Error("模型 ID 必须是安全的 provider/model");
|
|
44
|
-
if (!model.startsWith(`${selected.provider.id}/`))
|
|
45
|
-
throw new Error(`模型 ID 必须属于 ${selected.provider.id}`);
|
|
46
|
-
return { runtime: "pi", distribution: "builtin", preset: "official", providerId: selected.provider.id,
|
|
47
|
-
authType: selected.method.type, model };
|
|
48
|
-
}
|
|
49
|
-
const preset = providerChoice === 5 ? "custom" : PI_PROVIDER_PRESETS[providerChoice - 1].id;
|
|
50
|
-
const selected = preset === "custom" ? null : PI_PROVIDER_PRESETS.find((candidate) => candidate.id === preset);
|
|
51
|
-
const baseUrl = preset === "custom" ? await questioner.ask("Base URL(https;localhost 可用 http):") : selected.baseUrl;
|
|
52
|
-
const model = (await questioner.ask(`模型 ID${selected ? `(默认 ${selected.defaultModel})` : ""}:`)).trim() || selected?.defaultModel || "";
|
|
53
|
-
const validated = resolveBuiltinPiProviderSetupSelection({ distribution: "builtin", preset, model, ...(baseUrl ? { baseUrl } : {}) });
|
|
54
|
-
return { runtime: "pi", distribution: "builtin", preset, model: validated.model, ...(preset === "custom" ? { baseUrl: validated.baseUrl } : {}) };
|
|
43
|
+
function formatRuntimeMenu(statuses) {
|
|
44
|
+
const lines = statuses.map((status, index) => {
|
|
45
|
+
const mark = status.installed ? "installed" : "not installed";
|
|
46
|
+
return ` ${index + 1}. ${RUNTIME_LABELS[status.runtime]} [${mark}]`;
|
|
47
|
+
});
|
|
48
|
+
return `选择 Agent:\n${lines.join("\n")}\n> `;
|
|
55
49
|
}
|
|
56
|
-
export async function collectSetupAgentChoice(questioner, prior,
|
|
50
|
+
export async function collectSetupAgentChoice(questioner, prior, statuses = listRuntimeInstallStatuses()) {
|
|
57
51
|
if (prior) {
|
|
58
|
-
const keep = (await questioner.ask(`已有 Agent:${toUserRuntime(prior.runtime
|
|
52
|
+
const keep = (await questioner.ask(`已有 Agent:${toUserRuntime(prior.runtime)}/${prior.model}。直接回车保留;输入 c 才修改:`)).trim().toLowerCase();
|
|
59
53
|
if (!keep)
|
|
60
54
|
return null;
|
|
61
55
|
if (keep !== "c")
|
|
62
56
|
throw new Error("请输入 c 修改,或直接回车保留现有配置");
|
|
63
57
|
}
|
|
64
|
-
const runtimeChoice = number(await questioner.ask(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return { runtime: "claude" };
|
|
69
|
-
if (runtimeChoice === 2)
|
|
70
|
-
return { runtime: "pi", distribution: "external" };
|
|
71
|
-
return collectBuiltinPiChoice(questioner, services);
|
|
72
|
-
}
|
|
73
|
-
export async function recoverUnavailableExternalPi(initial, questioner, probe, report = () => { }, services) {
|
|
74
|
-
let choice = initial;
|
|
75
|
-
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
76
|
-
if (choice?.runtime !== "pi" || choice.distribution !== "external")
|
|
77
|
-
return choice;
|
|
78
|
-
const readiness = await probe();
|
|
79
|
-
if (readiness.state === "ready")
|
|
80
|
-
return choice;
|
|
81
|
-
report(`外置 Pi ${readiness.state}:${readiness.reason || "prerequisite unavailable"};${readiness.nextAction || "可切换到 builtin-pi"}`);
|
|
82
|
-
const action = number(await questioner.ask("外置 Pi 当前不可用:\n 1. 切换到 builtin-pi(推荐)\n 2. 重新选择 Agent\n 3. 取消 setup(保留原配置)\n> "), 1, 3);
|
|
83
|
-
if (action === 1)
|
|
84
|
-
return collectBuiltinPiChoice(questioner, services);
|
|
85
|
-
if (action === 3)
|
|
86
|
-
throw new Error("setup 已取消;未修改 Agent/config");
|
|
87
|
-
choice = await collectSetupAgentChoice(questioner, undefined, services);
|
|
58
|
+
const runtimeChoice = number(await questioner.ask(formatRuntimeMenu(statuses)), 1, statuses.length);
|
|
59
|
+
const selected = statuses[runtimeChoice - 1];
|
|
60
|
+
if (!selected.installed) {
|
|
61
|
+
throw new Error(`${selected.reason};${selected.nextAction}`);
|
|
88
62
|
}
|
|
89
|
-
|
|
63
|
+
return { runtime: selected.runtime };
|
|
90
64
|
}
|
|
91
65
|
export function terminalSetupQuestioner() {
|
|
92
66
|
let rl = null;
|
|
@@ -150,7 +124,7 @@ export function terminalSetupQuestioner() {
|
|
|
150
124
|
const onData = (chunk) => {
|
|
151
125
|
for (const byte of chunk) {
|
|
152
126
|
if (byte === 3)
|
|
153
|
-
return finish(new Error("setup
|
|
127
|
+
return finish(new Error("setup 已取消;未保存输入"));
|
|
154
128
|
if (byte === 10 || byte === 13)
|
|
155
129
|
return finish();
|
|
156
130
|
if (byte === 8 || byte === 127) {
|
|
@@ -9,12 +9,8 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import { isWindows } from "../platform/secure-metadata.mjs";
|
|
10
10
|
import { TargetRootLayout } from "../platform/root-layout.mjs";
|
|
11
11
|
import { planSingleRootBinding } from "./setup-binding.mjs";
|
|
12
|
-
import { configureBuiltinPiProviderModel, piAgentDirectory } from "../runtime/pi-provider-config.mjs";
|
|
13
|
-
import { applyPiProfileMigration, clearStalePiProfileMigrationLock, preparePiProfileMigration, releasePiProfileMigrationLock, rollbackPiProfileMigration, } from "../runtime/pi-profile-migration.mjs";
|
|
14
12
|
import { discoverPiModelCatalog } from "../runtime/pi-model-catalog.mjs";
|
|
15
13
|
import { calculatePiCompactionSettings } from "../runtime/pi-compaction-recovery.mjs";
|
|
16
|
-
import { terminalSetupQuestioner } from "./setup-agent-choice.mjs";
|
|
17
|
-
import { beginBuiltinPiCredentialTransaction, createOfficialPiAuthInteraction, createOfficialPiModelRuntime, runOfficialPiLogin, verifyOfficialPiProviderTurn, } from "../runtime/pi-official-auth.mjs";
|
|
18
14
|
import * as larkinConfigImport from "../platform/config.mjs";
|
|
19
15
|
import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
|
|
20
16
|
import { loadValidatedBotCredential } from "./run-credential-preflight.mjs";
|
|
@@ -36,6 +32,7 @@ const options = {
|
|
|
36
32
|
agent: flag("--agent"),
|
|
37
33
|
profile: flag("--profile"),
|
|
38
34
|
runtime: flag("--runtime"),
|
|
35
|
+
model: flag("--model"),
|
|
39
36
|
selectionFile: flag("--selection-file"),
|
|
40
37
|
yes: has("--yes"),
|
|
41
38
|
list: has("--list"),
|
|
@@ -69,12 +66,6 @@ function larkJson(args) {
|
|
|
69
66
|
return { ok: false, json: null, raw: stdout, err: stderr || stdout };
|
|
70
67
|
}
|
|
71
68
|
}
|
|
72
|
-
function openAuthUrl(url) {
|
|
73
|
-
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32.exe" : "xdg-open";
|
|
74
|
-
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
75
|
-
const result = spawnSync(command, args, { stdio: "ignore", timeout: 5_000 });
|
|
76
|
-
return result.status === 0;
|
|
77
|
-
}
|
|
78
69
|
function listProfilesFromEnv(env) {
|
|
79
70
|
const official = resolveOfficialLarkCli({ env });
|
|
80
71
|
const result = spawnSync(official.command, [...official.argsPrefix, "profile", "list"], { encoding: "utf8", env });
|
|
@@ -176,9 +167,10 @@ function readSetupSelection(fileArg) {
|
|
|
176
167
|
const bytes = fs.readFileSync(file, "utf8");
|
|
177
168
|
fs.unlinkSync(file);
|
|
178
169
|
const raw = JSON.parse(bytes);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
170
|
+
const runtime = raw?.runtime;
|
|
171
|
+
if (runtime === "pi" || runtime === "codex" || runtime === "claude")
|
|
172
|
+
return { runtime };
|
|
173
|
+
return die("setup Agent 选择无效");
|
|
182
174
|
}
|
|
183
175
|
function fabricateAttachment(root, serverId) {
|
|
184
176
|
const directory = path.join(root, "computer", "servers", serverId);
|
|
@@ -208,12 +200,9 @@ function formatExternalPiSetupFailure(error, options) {
|
|
|
208
200
|
const original = (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, " ").trim();
|
|
209
201
|
const alternatives = catalogModelsFromError(error);
|
|
210
202
|
const parts = [];
|
|
211
|
-
if (/external Pi (?:auth\.json|models\.json|settings\.json) is required|external Pi profile|external Pi auth\.json has an unsafe mode|external Pi models\.json has an unsafe mode|external Pi settings\.json has an unsafe mode/.test(original)) {
|
|
212
|
-
parts.push("pi requires the official file-backed Pi profile containing auth.json (0600), models.json, and settings.json, normally after `pi` login and default-model selection. Environment-variable-only credentials are unsupported.");
|
|
213
|
-
}
|
|
214
203
|
if (original)
|
|
215
204
|
parts.push(original);
|
|
216
|
-
parts.push("
|
|
205
|
+
parts.push("Run the official `pi` login flow, then retry `larkin setup --runtime pi`.");
|
|
217
206
|
if (options.agentExisted) {
|
|
218
207
|
parts.push("This Agent already exists; after a successful setup you can switch models with `larkin model <provider>/<model>`.");
|
|
219
208
|
}
|
|
@@ -222,19 +211,6 @@ function formatExternalPiSetupFailure(error, options) {
|
|
|
222
211
|
}
|
|
223
212
|
return parts.join(" ");
|
|
224
213
|
}
|
|
225
|
-
function safeProviderFailure(error) {
|
|
226
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
227
|
-
if (/\b(?:401|403)\b|unauth|invalid.*(?:key|token)|credential/i.test(message)) {
|
|
228
|
-
return new Error("内置 Pi provider 拒绝认证(401/403);请重新登录或检查 credential");
|
|
229
|
-
}
|
|
230
|
-
if (/timeout|超时|ETIMEDOUT/i.test(message))
|
|
231
|
-
return new Error("内置 Pi provider readiness 超时;请检查网络和 endpoint");
|
|
232
|
-
if (/ENOTFOUND|ECONN|fetch failed|network|TLS/i.test(message))
|
|
233
|
-
return new Error("内置 Pi provider endpoint 不可达;请检查网络、TLS 和 Base URL");
|
|
234
|
-
if (/model|模型/i.test(message))
|
|
235
|
-
return new Error("内置 Pi provider 不接受所选模型;请检查 provider/model");
|
|
236
|
-
return new Error("内置 Pi provider readiness 失败;credential/config 已回滚,请检查 provider 状态后重试");
|
|
237
|
-
}
|
|
238
214
|
function printAgents(config) {
|
|
239
215
|
const names = Object.keys(config.agents);
|
|
240
216
|
if (!names.length) {
|
|
@@ -248,9 +224,6 @@ function printAgents(config) {
|
|
|
248
224
|
}
|
|
249
225
|
}
|
|
250
226
|
export async function main() {
|
|
251
|
-
if (has("--model")) {
|
|
252
|
-
die("setup 不支持配置 model;请用 larkin model <id> 单独切换(bun run model -- <id>,带合法值校验)");
|
|
253
|
-
}
|
|
254
227
|
if (options.help) {
|
|
255
228
|
say(`larkin setup(内部绑定阶段)
|
|
256
229
|
|
|
@@ -260,13 +233,14 @@ export async function main() {
|
|
|
260
233
|
参数(都可省略;单 bot 单 agent 场景零参数即可):
|
|
261
234
|
--agent <App ID> 必须与所选 profile 的 App ID 一致;缺省自动使用该 App ID
|
|
262
235
|
--profile <name> 复用已有 lark-cli profile(可用 profile 名或 appId;缺省交互选择)
|
|
263
|
-
--runtime <runtime> pi / codex / claude;缺省沿用已有 agent 的 runtime
|
|
236
|
+
--runtime <runtime> pi / codex / claude;缺省沿用已有 agent 的 runtime
|
|
237
|
+
--model <id> 可选;按 runtime 目录校验后写入。省略则沿用已有值或 runtime 默认
|
|
264
238
|
--yes 非交互接受默认项
|
|
265
239
|
--list 查看已经配置的 Agent
|
|
266
240
|
--help 显示帮助
|
|
267
241
|
|
|
268
242
|
普通用户请直接运行 larkin setup;该内部阶段只负责绑定已取得的凭证。
|
|
269
|
-
|
|
243
|
+
省略 --model 时沿用已有值(换 runtime 时重置为该 runtime 默认);也可用 larkin model <id> 事后切换。`);
|
|
270
244
|
return;
|
|
271
245
|
}
|
|
272
246
|
const loaded = loadConfig();
|
|
@@ -287,66 +261,55 @@ export async function main() {
|
|
|
287
261
|
verifyBot(profile);
|
|
288
262
|
const requestedAgent = options.agent || profile.appId;
|
|
289
263
|
const prior = config.agents[profile.appId];
|
|
290
|
-
const runtime = selection?.runtime || options.runtime || prior?.runtime
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
: undefined;
|
|
264
|
+
const runtime = selection?.runtime || options.runtime || prior?.runtime;
|
|
265
|
+
if (!runtime)
|
|
266
|
+
die("必须指定 --runtime pi|codex|claude");
|
|
294
267
|
validateRuntime(runtime);
|
|
268
|
+
const requestedModel = options.model?.trim() || undefined;
|
|
295
269
|
const defaultModel = larkinConfig.defaultModelFor(runtime);
|
|
296
|
-
|
|
297
|
-
|
|
270
|
+
let targetModelId = requestedModel && requestedModel !== "default"
|
|
271
|
+
? requestedModel
|
|
272
|
+
: (prior?.runtime === runtime ? prior.model : defaultModel);
|
|
298
273
|
let resolvedExternalModel;
|
|
299
|
-
let pendingMigration = null;
|
|
300
|
-
let setupConfigCommitted = false;
|
|
301
|
-
let providerTransaction = null;
|
|
302
|
-
let providerPublished = false;
|
|
303
|
-
const authAbort = new AbortController();
|
|
304
|
-
const cancelOnSignal = (signal) => {
|
|
305
|
-
authAbort.abort();
|
|
306
|
-
if (!setupConfigCommitted && pendingMigration) {
|
|
307
|
-
try {
|
|
308
|
-
rollbackPiProfileMigration(pendingMigration.state);
|
|
309
|
-
}
|
|
310
|
-
catch { /* best effort */ }
|
|
311
|
-
}
|
|
312
|
-
if (!providerPublished)
|
|
313
|
-
providerTransaction?.rollback();
|
|
314
|
-
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
315
|
-
};
|
|
316
|
-
const onSigint = () => cancelOnSignal("SIGINT");
|
|
317
|
-
const onSigterm = () => cancelOnSignal("SIGTERM");
|
|
318
|
-
process.once("SIGINT", onSigint);
|
|
319
|
-
process.once("SIGTERM", onSigterm);
|
|
320
274
|
let runtimeModels = larkinConfig.loadRuntimeModels()[runtime];
|
|
321
|
-
let authQuestioner = null;
|
|
322
275
|
try {
|
|
323
|
-
if (runtime === "pi" &&
|
|
324
|
-
fs.mkdirSync(CFG_DIR, { recursive: true, mode: 0o700 });
|
|
276
|
+
if (runtime === "pi" && (targetModelId === "default" || requestedModel)) {
|
|
325
277
|
try {
|
|
326
|
-
clearStalePiProfileMigrationLock(CFG_DIR, profile.appId);
|
|
327
|
-
const plan = preparePiProfileMigration(process.env, CFG_DIR, profile.appId, "external");
|
|
328
|
-
pendingMigration = plan;
|
|
329
|
-
applyPiProfileMigration(plan);
|
|
330
278
|
const catalog = await discoverPiModelCatalog({
|
|
331
279
|
cwd: CFG_DIR,
|
|
332
|
-
|
|
333
|
-
env: { ...process.env, LARKIN_PI_DISTRIBUTION: "external" },
|
|
280
|
+
env: process.env,
|
|
334
281
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
282
|
+
if (targetModelId === "default") {
|
|
283
|
+
const effective = catalog.effectiveModel;
|
|
284
|
+
const entry = effective ? catalog.models.find((model) => model.id === effective) : undefined;
|
|
285
|
+
if (!effective || !entry) {
|
|
286
|
+
throw new Error("Pi official default resolution returned an unavailable model");
|
|
287
|
+
}
|
|
288
|
+
if (!entry.contextWindow) {
|
|
289
|
+
const alternatives = catalog.models
|
|
290
|
+
.filter((model) => Number.isFinite(model.contextWindow) && Number(model.contextWindow) > 0)
|
|
291
|
+
.map((model) => model.id);
|
|
292
|
+
throw Object.assign(new Error("Pi effective model is missing a context window"), { catalogModels: alternatives });
|
|
293
|
+
}
|
|
294
|
+
calculatePiCompactionSettings(entry.contextWindow);
|
|
295
|
+
targetModelId = effective;
|
|
296
|
+
resolvedExternalModel = effective;
|
|
297
|
+
runtimeModels = [{ id: effective, supportedReasoningEfforts: entry.supportedReasoningEfforts }];
|
|
339
298
|
}
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
299
|
+
else {
|
|
300
|
+
const entry = catalog.models.find((model) => model.id === targetModelId);
|
|
301
|
+
if (!entry)
|
|
302
|
+
throw new Error(`目标模型 pi/${targetModelId} 不在 runtime 模型目录中;未修改 Agent 配置`);
|
|
303
|
+
if (!entry.contextWindow) {
|
|
304
|
+
const alternatives = catalog.models
|
|
305
|
+
.filter((model) => Number.isFinite(model.contextWindow) && Number(model.contextWindow) > 0)
|
|
306
|
+
.map((model) => model.id);
|
|
307
|
+
throw Object.assign(new Error("Pi effective model is missing a context window"), { catalogModels: alternatives });
|
|
308
|
+
}
|
|
309
|
+
calculatePiCompactionSettings(entry.contextWindow);
|
|
310
|
+
resolvedExternalModel = targetModelId;
|
|
311
|
+
runtimeModels = [{ id: targetModelId, supportedReasoningEfforts: entry.supportedReasoningEfforts }];
|
|
345
312
|
}
|
|
346
|
-
calculatePiCompactionSettings(entry.contextWindow);
|
|
347
|
-
targetModelId = effective;
|
|
348
|
-
resolvedExternalModel = effective;
|
|
349
|
-
runtimeModels = [{ id: effective, supportedReasoningEfforts: entry.supportedReasoningEfforts }];
|
|
350
313
|
}
|
|
351
314
|
catch (error) {
|
|
352
315
|
throw new Error(formatExternalPiSetupFailure(error, { agentExisted: Boolean(prior) }));
|
|
@@ -358,6 +321,9 @@ export async function main() {
|
|
|
358
321
|
: [];
|
|
359
322
|
runtimeModels = [{ id: targetModelId, supportedReasoningEfforts: preservedEffort }];
|
|
360
323
|
}
|
|
324
|
+
else if (requestedModel && requestedModel !== "default") {
|
|
325
|
+
resolvedExternalModel = requestedModel;
|
|
326
|
+
}
|
|
361
327
|
const targetModel = runtimeModels.find((model) => model.id === targetModelId);
|
|
362
328
|
if (!targetModel)
|
|
363
329
|
throw new Error(`目标模型 ${runtime}/${targetModelId} 不在 runtime 模型目录中;未修改 Agent 配置`);
|
|
@@ -366,67 +332,14 @@ export async function main() {
|
|
|
366
332
|
profile,
|
|
367
333
|
requestedAgent,
|
|
368
334
|
runtime: selection?.runtime || options.runtime,
|
|
369
|
-
...(
|
|
370
|
-
...(selectedModel || resolvedExternalModel ? { model: selectedModel || resolvedExternalModel } : {}),
|
|
335
|
+
...(resolvedExternalModel ? { model: resolvedExternalModel } : {}),
|
|
371
336
|
defaultModel,
|
|
372
337
|
supportedReasoningEfforts: targetModel.supportedReasoningEfforts || [],
|
|
373
338
|
now: new Date().toISOString(),
|
|
374
339
|
});
|
|
375
340
|
const bound = stored.agents[profile.appId];
|
|
376
341
|
fs.mkdirSync(CFG_DIR, { recursive: true });
|
|
377
|
-
const authAlreadyCompleted = selection?.runtime === "pi" && selection.distribution === "builtin"
|
|
378
|
-
&& selection.authCompleted === true;
|
|
379
|
-
const readinessAlreadyCompleted = authAlreadyCompleted
|
|
380
|
-
&& selection.readinessCompleted === true;
|
|
381
|
-
providerTransaction = selection?.runtime === "pi" && selection.distribution === "builtin"
|
|
382
|
-
&& !authAlreadyCompleted ? beginBuiltinPiCredentialTransaction(CFG_DIR, profile.appId)
|
|
383
|
-
: null;
|
|
384
|
-
if (selection?.runtime === "pi" && selection.distribution === "builtin") {
|
|
385
|
-
const official = selection.preset === "official" ? selection : null;
|
|
386
|
-
const configured = official ? null : configureBuiltinPiProviderModel(CFG_DIR, profile.appId, selection);
|
|
387
|
-
const providerId = official?.providerId || configured.provider;
|
|
388
|
-
const authType = official?.authType || "api_key";
|
|
389
|
-
let piRuntime = null;
|
|
390
|
-
if (!authAlreadyCompleted) {
|
|
391
|
-
piRuntime = await createOfficialPiModelRuntime(CFG_DIR, profile.appId);
|
|
392
|
-
authQuestioner = terminalSetupQuestioner();
|
|
393
|
-
say(`正在通过捆绑官方 Pi 登录 ${providerId}(${authType})…`);
|
|
394
|
-
try {
|
|
395
|
-
await runOfficialPiLogin(piRuntime, providerId, authType, createOfficialPiAuthInteraction({
|
|
396
|
-
questioner: authQuestioner,
|
|
397
|
-
report: (message) => say(message),
|
|
398
|
-
openUrl: openAuthUrl,
|
|
399
|
-
signal: authAbort.signal,
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
catch {
|
|
403
|
-
throw new Error(`官方 Pi ${providerId} 登录失败或已取消;credential/config 未修改`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (!readinessAlreadyCompleted) {
|
|
407
|
-
piRuntime ??= await createOfficialPiModelRuntime(CFG_DIR, profile.appId);
|
|
408
|
-
say("正在验证内置 Pi provider(受控单轮,不发送飞书(Lark)消息)…");
|
|
409
|
-
try {
|
|
410
|
-
if (!(process.env.LARKIN_TEST_SKIP_BUILTIN_PI_PROVIDER_TURN === "1" && process.env.LARKIN_TEST_BOT_REGISTER_MODULE)) {
|
|
411
|
-
await verifyOfficialPiProviderTurn(piRuntime, selection.model, authAbort.signal);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
throw safeProviderFailure(error);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
342
|
larkinConfig.commitSetupConfig(process.env, loaded.revision, stored);
|
|
420
|
-
setupConfigCommitted = true;
|
|
421
|
-
providerTransaction?.commit();
|
|
422
|
-
providerPublished = true;
|
|
423
|
-
if (pendingMigration) {
|
|
424
|
-
try {
|
|
425
|
-
releasePiProfileMigrationLock(pendingMigration.state);
|
|
426
|
-
}
|
|
427
|
-
catch { /* post-commit lock release is best-effort; do not roll back credentials */ }
|
|
428
|
-
pendingMigration = null;
|
|
429
|
-
}
|
|
430
343
|
fabricateAttachment(layout.root, stored.serverId);
|
|
431
344
|
say(`\n✓ 已写配置: ${CFG_FILE}`);
|
|
432
345
|
say(` agent=${profile.appId} runtime=${bound.runtime} model=${bound.model}`);
|
|
@@ -438,21 +351,8 @@ export async function main() {
|
|
|
438
351
|
say(" larkin setup");
|
|
439
352
|
}
|
|
440
353
|
catch (error) {
|
|
441
|
-
if (!setupConfigCommitted && pendingMigration) {
|
|
442
|
-
try {
|
|
443
|
-
rollbackPiProfileMigration(pendingMigration.state);
|
|
444
|
-
}
|
|
445
|
-
catch { /* best effort */ }
|
|
446
|
-
pendingMigration = null;
|
|
447
|
-
}
|
|
448
|
-
providerTransaction?.rollback();
|
|
449
354
|
throw error;
|
|
450
355
|
}
|
|
451
|
-
finally {
|
|
452
|
-
authQuestioner?.close?.();
|
|
453
|
-
process.off("SIGINT", onSigint);
|
|
454
|
-
process.off("SIGTERM", onSigterm);
|
|
455
|
-
}
|
|
456
356
|
}
|
|
457
357
|
if (path.resolve(process.argv[1] || "") === path.resolve(fileURLToPath(import.meta.url))) {
|
|
458
358
|
main().catch((error) => die(error instanceof Error ? error.message : String(error)));
|
|
@@ -5,14 +5,13 @@ function cloneAgent(agent) {
|
|
|
5
5
|
return {
|
|
6
6
|
runtime: agent.runtime,
|
|
7
7
|
model: agent.model,
|
|
8
|
-
...(agent.piDistribution ? { piDistribution: agent.piDistribution } : {}),
|
|
9
8
|
...(agent.effort ? { effort: agent.effort } : {}),
|
|
10
9
|
...(agent.mentionPolicy ? { mentionPolicy: agent.mentionPolicy } : {}),
|
|
11
10
|
...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies } : {}),
|
|
12
11
|
...(agent.createdAt ? { createdAt: agent.createdAt } : {}),
|
|
13
12
|
};
|
|
14
13
|
}
|
|
15
|
-
export function planSingleRootBinding({ config, profile, requestedAgent, runtime,
|
|
14
|
+
export function planSingleRootBinding({ config, profile, requestedAgent, runtime, model, defaultModel, supportedReasoningEfforts, now, }) {
|
|
16
15
|
if (!/^cli_[A-Za-z0-9]+$/.test(profile.appId))
|
|
17
16
|
throw new Error(`profile.appId 不是合法飞书 App ID:${profile.appId}`);
|
|
18
17
|
if (typeof defaultModel !== "string" || !defaultModel)
|
|
@@ -25,22 +24,20 @@ export function planSingleRootBinding({ config, profile, requestedAgent, runtime
|
|
|
25
24
|
}
|
|
26
25
|
const agents = Object.fromEntries(Object.entries(config.agents).map(([key, agent]) => [key, cloneAgent(agent)]));
|
|
27
26
|
const prior = agents[profile.appId];
|
|
28
|
-
const nextRuntime = runtime || prior?.runtime
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
: undefined;
|
|
27
|
+
const nextRuntime = runtime || prior?.runtime;
|
|
28
|
+
if (!nextRuntime)
|
|
29
|
+
throw new Error("必须指定 --runtime pi|codex|claude");
|
|
32
30
|
if (prior) {
|
|
33
|
-
const { effort,
|
|
31
|
+
const { effort, ...withoutEffort } = prior;
|
|
34
32
|
agents[profile.appId] = {
|
|
35
33
|
...withoutEffort,
|
|
36
34
|
runtime: nextRuntime,
|
|
37
35
|
model: model || (prior.runtime === nextRuntime ? prior.model : defaultModel),
|
|
38
|
-
...(nextPiDistribution ? { piDistribution: nextPiDistribution } : {}),
|
|
39
36
|
...(effort && supportedReasoningEfforts.includes(effort) ? { effort } : {}),
|
|
40
37
|
};
|
|
41
38
|
}
|
|
42
39
|
else {
|
|
43
|
-
agents[profile.appId] = { runtime: nextRuntime, model: model || "default",
|
|
40
|
+
agents[profile.appId] = { runtime: nextRuntime, model: model || "default", createdAt: now };
|
|
44
41
|
}
|
|
45
42
|
return {
|
|
46
43
|
version: 4,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "larkin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"larkinPackageRole": "npm-published",
|
|
6
6
|
"files": [
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"assets/",
|
|
10
10
|
"scripts/npm/",
|
|
11
11
|
"README.md",
|
|
12
|
+
"CHANGELOG.md",
|
|
12
13
|
"LICENSE",
|
|
13
14
|
"SECURITY.md",
|
|
14
15
|
"CONTRIBUTING.md",
|
|
@@ -88,7 +89,6 @@
|
|
|
88
89
|
"test:live:agent-cli-routing-codex": "bun run build && LARKIN_RUN_AGENT_CLI_ROUTING_CODEX_LIVE=1 bun test --max-concurrency 1 test/live/agent-cli-routing-codex-live.test.mjs"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
|
-
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
92
92
|
"@larksuite/channel": "^0.4.0",
|
|
93
93
|
"@opentelemetry/api": "^1.9.1",
|
|
94
94
|
"@opentelemetry/resources": "^2.10.0",
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
"undici": "8.5.0"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
107
108
|
"@tailwindcss/vite": "^4.3.3",
|
|
108
109
|
"@testing-library/jest-dom": "^7.0.0",
|
|
109
110
|
"@testing-library/react": "^16.3.2",
|
package/dist/app/pi-auth-cli.mjs
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import * as larkinConfig from "../platform/config.mjs";
|
|
2
|
-
import { beginBuiltinPiCredentialTransaction, createOfficialPiCredentialRuntime, createOfficialPiLogoutRuntime, logoutOfficialPiProvider, officialPiAuthStatus, } from "../runtime/pi-official-auth.mjs";
|
|
3
|
-
const APP_ID = /^cli_[A-Za-z0-9]+$/;
|
|
4
|
-
function flag(args, name) {
|
|
5
|
-
const index = args.indexOf(name);
|
|
6
|
-
return index >= 0 ? args[index + 1] : undefined;
|
|
7
|
-
}
|
|
8
|
-
export async function main(args = process.argv.slice(2), env = process.env) {
|
|
9
|
-
const action = args[0] || "status";
|
|
10
|
-
const json = args.includes("--json");
|
|
11
|
-
const requestedAgent = flag(args, "--agent");
|
|
12
|
-
let index = action === "logout" ? 2 : 1;
|
|
13
|
-
let valid = action === "status" || action === "logout";
|
|
14
|
-
while (valid && index < args.length) {
|
|
15
|
-
const argument = args[index];
|
|
16
|
-
if (argument === "--json" && action === "status") {
|
|
17
|
-
index += 1;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (argument === "--agent" && args[index + 1]) {
|
|
21
|
-
index += 2;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
valid = false;
|
|
25
|
-
}
|
|
26
|
-
if (!valid) {
|
|
27
|
-
throw new Error("Usage: larkin pi-auth status [--agent <App ID>] [--json] | larkin pi-auth logout <provider> [--agent <App ID>]");
|
|
28
|
-
}
|
|
29
|
-
const loaded = larkinConfig.loadConfig(env);
|
|
30
|
-
const agentId = requestedAgent || loaded.config.activeAgent || undefined;
|
|
31
|
-
if (!agentId || !APP_ID.test(agentId))
|
|
32
|
-
throw new Error("请用 --agent <App ID> 选择 Agent");
|
|
33
|
-
const agent = loaded.config.agents[agentId];
|
|
34
|
-
if (!agent)
|
|
35
|
-
throw new Error(`Agent ${agentId} 不存在`);
|
|
36
|
-
if (agent.runtime !== "pi" || agent.piDistribution !== "builtin")
|
|
37
|
-
throw new Error(`Agent ${agentId} 不是内置 Pi`);
|
|
38
|
-
if (action === "status") {
|
|
39
|
-
const runtime = await createOfficialPiCredentialRuntime(loaded.configDir, agentId);
|
|
40
|
-
const statuses = await officialPiAuthStatus(runtime);
|
|
41
|
-
if (json)
|
|
42
|
-
console.log(JSON.stringify({ agentId, credentials: statuses }));
|
|
43
|
-
else if (statuses.length === 0)
|
|
44
|
-
console.log(`Agent ${agentId} 尚无已配置的官方 Pi credential`);
|
|
45
|
-
else {
|
|
46
|
-
console.log(`Agent ${agentId} 官方 Pi credential:`);
|
|
47
|
-
for (const entry of statuses)
|
|
48
|
-
console.log(` ${entry.providerName} (${entry.providerId}): ${entry.credentialType}/${entry.source}${entry.stored ? " [stored]" : " [ambient]"}`);
|
|
49
|
-
}
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const providerId = args[1];
|
|
53
|
-
if (!providerId || providerId.startsWith("--") || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(providerId)) {
|
|
54
|
-
throw new Error("logout 需要安全的 provider ID");
|
|
55
|
-
}
|
|
56
|
-
const transaction = beginBuiltinPiCredentialTransaction(loaded.configDir, agentId);
|
|
57
|
-
try {
|
|
58
|
-
const runtime = await createOfficialPiLogoutRuntime(loaded.configDir, agentId);
|
|
59
|
-
await logoutOfficialPiProvider(runtime, providerId);
|
|
60
|
-
transaction.commit();
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
transaction.rollback();
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
console.log(`Agent ${agentId} 已退出 ${providerId};其他 provider 未修改`);
|
|
67
|
-
}
|