larkin 0.4.29 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +5 -9
  3. package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
  4. package/dist/app/agent-cli.mjs +14 -2
  5. package/dist/app/agent-config.mjs +26 -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 +9 -11
  24. package/dist/runtime/pi-compaction-recovery.mjs +8 -10
  25. package/dist/runtime/pi-extension-api.mjs +1 -0
  26. package/dist/runtime/pi-model-catalog.cjs +7 -8
  27. package/dist/runtime/pi-model-catalog.mjs +7 -8
  28. package/dist/runtime/pi-subagent-injection.mjs +17 -4
  29. package/dist/runtime/runtime-adapters.mjs +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 +42 -99
  35. package/dist/runtime/runtime-readiness.mjs +40 -98
  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
@@ -12,6 +12,8 @@ import { createReminderRoutes } from "../agent/reminder-routes.mjs";
12
12
  import { InteractionStateMachine } from "../agent/interaction-state-machine.mjs";
13
13
  import { issueCallbackProbe, readCallbackCapability } from "../platform/callback-capability.mjs";
14
14
  import { requestAgentUpsert } from "./local-control.mjs";
15
+ import { isUserRuntime } from "../runtime/user-runtime.mjs";
16
+ import { resolveRuntimeExecutable, runtimeInstallNextAction } from "../runtime/runtime-readiness.mjs";
15
17
  export { AGENT_CLI_CAPABILITIES } from "../agent/agent-cli-capabilities.mjs";
16
18
  import { AGENT_CLI_CAPABILITIES } from "../agent/agent-cli-capabilities.mjs";
17
19
  import { CONFIG_CLI_USAGE, CONFIG_CLI_VALUES } from "../agent/config-cli-contract.mjs";
@@ -157,7 +159,7 @@ function agentConfigRequest(argv, env, config, agent, dependencies) {
157
159
  const target = config.agents[targetId];
158
160
  if (!target)
159
161
  throw new Error(`Agent 不存在:${targetId};运行 larkin config show --json 查看可用 Agent`);
160
- const currentDirectory = (runtime = larkinConfig.toUserRuntime(target.runtime, target.piDistribution)) => (dependencies.modelDirectory ?? ((input) => discoverRuntimeModelDirectorySync(input, env)))({
162
+ const currentDirectory = (runtime = larkinConfig.toUserRuntime(target.runtime)) => (dependencies.modelDirectory ?? ((input) => discoverRuntimeModelDirectorySync(input, env)))({
161
163
  agentId: target.agentId, cwd: target.workspaceDir, runtime,
162
164
  });
163
165
  if (operation === "show") {
@@ -173,6 +175,16 @@ function agentConfigRequest(argv, env, config, agent, dependencies) {
173
175
  const runtime = options.positionals[0];
174
176
  if (!runtime || options.positionals.length > 1)
175
177
  throw new Error("用法: larkin config runtime <runtime> [--model <model>] [--agent <App ID>]");
178
+ if (!isUserRuntime(runtime))
179
+ throw new Error(`未知 runtime:${runtime}`);
180
+ const command = runtime === "pi"
181
+ ? env.LARKIN_PI_COMMAND || "pi"
182
+ : runtime === "codex"
183
+ ? env.LARKIN_CODEX_COMMAND || "codex"
184
+ : env.LARKIN_CLAUDE_COMMAND || "claude";
185
+ if (!resolveRuntimeExecutable(command, env)) {
186
+ throw new Error(`${runtime} is not installed;${runtimeInstallNextAction(runtime)}`);
187
+ }
176
188
  const model = options.values.get("--model");
177
189
  if (model && !currentDirectory(runtime).some((item) => item.id === model))
178
190
  throw new Error(`model 不在 ${runtime} 当前目录中:${model}`);
@@ -594,7 +606,7 @@ export function runAgentCli(argv, env = process.env, dependencies = {}) {
594
606
  openId: identity?.open_id ?? identity?.openId ?? null,
595
607
  avatarUrl: identity?.avatar_url ?? identity?.avatarUrl ?? null,
596
608
  runtime: agent.runtime,
597
- runtimeOption: larkinConfig.toUserRuntime(agent.runtime, agent.piDistribution),
609
+ runtimeOption: larkinConfig.toUserRuntime(agent.runtime),
598
610
  model: agent.model, reasoningEffort: agent.effort ?? null,
599
611
  createdAt: agent.createdAt ?? "1970-01-01T00:00:00.000Z",
600
612
  });
@@ -11,9 +11,8 @@ import { isChannelReconnecting, projectAgentReadiness } from "./agent-readiness.
11
11
  import { requestAgentUpsert } from "./local-control.mjs";
12
12
  import * as larkinConfig from "../platform/config.mjs";
13
13
  import { managedOfficialLarkCli } from "./agent-lark-cli-workspace.mjs";
14
- import { assertBuiltinPiAgentDirectory, ownedPiCatalogAgentDir, piAgentDirectory, piCatalogCommandSpec } from "../runtime/pi-provider-config.mjs";
15
- import { RUNTIME_OPTIONS, fromUserRuntime, isUserRuntime, piCatalogDistributionForUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
16
- import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
14
+ import { RUNTIME_OPTIONS, fromUserRuntime, isUserRuntime, toUserRuntime } from "../runtime/user-runtime.mjs";
15
+ import { resolveRuntimeExecutable, runtimeInstallNextAction } from "../runtime/runtime-readiness.mjs";
17
16
  function safeDocumentCommentCapabilities(configDir, profile) {
18
17
  try {
19
18
  return {
@@ -52,15 +51,15 @@ const die = (message) => {
52
51
  console.error(" 下一步: larkin config --help");
53
52
  process.exit(1);
54
53
  };
55
- if (!kind || !["agents", "model", "runtime", "effort", "pi-distribution", "chats", "config"].includes(kind)) {
56
- die("用法: larkin agents | larkin config <show|mention|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin pi-distribution [show|builtin|external] | larkin effort [<level>|clear] | larkin chats [free|strict <oc_id>]");
54
+ if (!kind || !["agents", "model", "runtime", "effort", "chats", "config"].includes(kind)) {
55
+ die("用法: larkin agents | larkin config <show|mention|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin effort [<level>|clear] | larkin chats [free|strict <oc_id>]");
57
56
  }
58
57
  const allowedValueFlags = {
59
58
  agents: new Set(), model: new Set(["--agent"]), runtime: new Set(["--agent", "--model"]),
60
- effort: new Set(["--agent"]), "pi-distribution": new Set(["--agent", "--snapshot"]), chats: new Set(["--agent"]), config: new Set(["--agent", "--chat"]),
59
+ effort: new Set(["--agent"]), chats: new Set(["--agent"]), config: new Set(["--agent", "--chat"]),
61
60
  };
62
61
  const allowedBooleanFlags = {
63
- agents: new Set(["--json"]), model: new Set(), runtime: new Set(), effort: new Set(), "pi-distribution": new Set(["--import-external-profile"]), chats: new Set(), config: new Set(["--json"]),
62
+ agents: new Set(["--json"]), model: new Set(), runtime: new Set(), effort: new Set(), chats: new Set(), config: new Set(["--json"]),
64
63
  };
65
64
  const parsedValues = new Map();
66
65
  const parsedBooleans = new Set();
@@ -94,7 +93,6 @@ const flagAgent = parsedValues.get("--agent");
94
93
  const flagModel = parsedValues.get("--model");
95
94
  const flagChat = parsedValues.get("--chat");
96
95
  const flagJson = parsedBooleans.has("--json");
97
- const importExternalProfile = parsedBooleans.has("--import-external-profile");
98
96
  const value = positionals[0];
99
97
  function assertOnlyFlags(valueFlags, booleanFlags = []) {
100
98
  const allowedValues = new Set(valueFlags);
@@ -126,11 +124,6 @@ else if (kind === "effort") {
126
124
  if (positionals.length > 1)
127
125
  die("用法: larkin effort [<level>|clear|default] [--agent <App ID>]");
128
126
  }
129
- else if (kind === "pi-distribution") {
130
- assertOnlyFlags(["--agent", "--snapshot"], ["--import-external-profile"]);
131
- if (positionals.length > 1)
132
- die("用法: larkin pi-distribution [show|builtin|external] [--agent <App ID>] [--snapshot <private-file>]");
133
- }
134
127
  else if (kind === "chats") {
135
128
  assertOnlyFlags(["--agent"]);
136
129
  if (![0, 2].includes(positionals.length))
@@ -178,21 +171,6 @@ const loaded = (() => {
178
171
  const { configDir, file, config } = loaded;
179
172
  if (!fs.existsSync(file))
180
173
  die("未找到 Larkin 配置,请运行 larkin setup");
181
- const snapshotFile = parsedValues.get("--snapshot");
182
- if (kind === "pi-distribution" && value === "rollback") {
183
- if (importExternalProfile)
184
- die("--import-external-profile 只允许与 builtin 一起使用");
185
- if (!snapshotFile || flagAgent || positionals.length !== 1)
186
- die("用法: larkin pi-distribution rollback --snapshot <private-file>");
187
- try {
188
- const result = larkinConfig.rollbackConfig(process.env, snapshotFile);
189
- say(JSON.stringify({ ok: true, rolledBackAgent: result.agentId, revision: result.revision }));
190
- }
191
- catch (error) {
192
- die(`回滚失败:${error instanceof Error ? error.message : String(error)}`);
193
- }
194
- process.exit(0);
195
- }
196
174
  if (kind === "agents") {
197
175
  const list = Object.values(config.agents || {});
198
176
  const daemon = readProcessState(config.larkinHome).daemon;
@@ -224,7 +202,7 @@ if (kind === "agents") {
224
202
  agent_id: agent.agentId,
225
203
  name: agent.name,
226
204
  runtime: agent.runtime,
227
- runtimeOption: toUserRuntime(agent.runtime, agent.piDistribution),
205
+ runtimeOption: toUserRuntime(agent.runtime),
228
206
  model: effectiveModel,
229
207
  document_comment: {
230
208
  event: "drive.notice.comment_add_v1",
@@ -295,7 +273,7 @@ if (kind === "agents") {
295
273
  say(` ${agent.name}${agent.name === config.activeAgent ? " [active]" : ""}`);
296
274
  const effectiveModel = status?.session?.runtime === agent.runtime && status.session.model ? status.session.model : agent.model;
297
275
  const effectiveEffort = status?.session?.runtime === agent.runtime && status.session.reasoningEffort ? status.session.reasoningEffort : agent.effort;
298
- say(` runtime=${toUserRuntime(agent.runtime, agent.piDistribution)} model=${effectiveModel}${effectiveModel !== agent.model ? ` stored=${agent.model}` : ""}${effectiveEffort ? ` effort=${effectiveEffort}` : ""}`);
276
+ say(` runtime=${toUserRuntime(agent.runtime)} model=${effectiveModel}${effectiveModel !== agent.model ? ` stored=${agent.model}` : ""}${effectiveEffort ? ` effort=${effectiveEffort}` : ""}`);
299
277
  if (status?.runtimeReadiness) {
300
278
  const current = projectedReadiness.readiness.runtime_ready;
301
279
  const state = status.runtimeReadiness.state === "ready" && !current ? "unavailable" : status.runtimeReadiness.state || "incompatible";
@@ -429,7 +407,7 @@ if (!key) {
429
407
  say(`共 ${keys.length} 个 agent(用 --agent <appId> 查看/修改指定一个):`);
430
408
  for (const candidate of keys) {
431
409
  const item = config.agents[candidate];
432
- say(` ${candidate}${candidate === config.activeAgent ? " [active]" : ""} runtime=${toUserRuntime(item.runtime, item.piDistribution)} model=${item.model}`);
410
+ say(` ${candidate}${candidate === config.activeAgent ? " [active]" : ""} runtime=${toUserRuntime(item.runtime)} model=${item.model}`);
433
411
  }
434
412
  process.exit(0);
435
413
  }
@@ -437,49 +415,21 @@ if (!key) {
437
415
  }
438
416
  const selectedKey = key;
439
417
  const agent = config.agents[selectedKey];
440
- if (kind === "pi-distribution") {
441
- traceProcessBoundary(process.env, "agent-config:pi-distribution-loaded", { configDir, agentId: selectedKey, targetDir: piAgentDirectory(configDir, selectedKey), requested: value || "show", importExternalProfile });
442
- const requested = value || "show";
443
- if (importExternalProfile && requested !== "builtin")
444
- die("--import-external-profile 只允许与 builtin 一起使用");
445
- if (requested === "show") {
446
- say(JSON.stringify({
447
- agentId: selectedKey,
448
- runtime: agent.runtime,
449
- runtimeOption: toUserRuntime(agent.runtime, agent.piDistribution),
450
- piDistribution: agent.piDistribution ?? "external",
451
- }));
452
- process.exit(0);
453
- }
454
- if (requested !== "builtin" && requested !== "external")
455
- die("Pi distribution 只允许 show/builtin/external");
456
- if (agent.runtime !== "pi")
457
- die(`Agent ${selectedKey} 不是 Pi runtime`);
458
- if (!snapshotFile)
459
- die("修改 Pi distribution 必须提供 --snapshot <private-file>");
460
- if (requested === "builtin" && !importExternalProfile) {
461
- try {
462
- assertBuiltinPiAgentDirectory(piAgentDirectory(configDir, selectedKey));
463
- }
464
- catch {
465
- die(agent.piDistribution === "builtin"
466
- ? "内置 Pi provider 尚未为该 Agent 配置。请使用 Dashboard Provider Credentials 或 larkin pi-auth login。"
467
- : "内置 Pi provider 尚未为该 Agent 配置。");
468
- }
469
- }
470
- try {
471
- const result = larkinConfig.mutateConfig(process.env, { kind: "set-agent-pi-distribution", agentId: selectedKey, distribution: requested }, { kind: "user" }, {
472
- snapshotFile: snapshotFile, ...(importExternalProfile ? { importExternalProfile: true } : {}),
473
- });
474
- traceProcessBoundary(process.env, "agent-config:pi-distribution-persisted", { configDir, agentId: selectedKey, targetDir: piAgentDirectory(configDir, selectedKey), requested, applyState: result.applyState });
475
- say(JSON.stringify({ ok: true, agentId: selectedKey, piDistribution: requested, revision: result.revision, applyState: result.applyState }));
418
+ function runtimeExecutableName(runtime) {
419
+ if (runtime === "pi")
420
+ return process.env.LARKIN_PI_COMMAND || "pi";
421
+ if (runtime === "codex")
422
+ return process.env.LARKIN_CODEX_COMMAND || "codex";
423
+ return process.env.LARKIN_CLAUDE_COMMAND || "claude";
424
+ }
425
+ if (kind === "runtime" && value) {
426
+ if (!isUserRuntime(value)) {
427
+ console.error(`✗ 未知 runtime:${value}。合法值: ${RUNTIME_OPTIONS.join(", ")}`);
428
+ process.exit(1);
476
429
  }
477
- catch (error) {
478
- if (importExternalProfile)
479
- die("Pi external profile import failed; no secret or private path was disclosed");
480
- die(`Pi distribution 修改失败:${error instanceof Error ? error.message : String(error)}`);
430
+ if (!resolveRuntimeExecutable(runtimeExecutableName(value), process.env)) {
431
+ die(`${value} is not installed;${runtimeInstallNextAction(value)}`);
481
432
  }
482
- process.exit(0);
483
433
  }
484
434
  const catalog = larkinConfig.loadRuntimeModels();
485
435
  if ((["model", "effort"].includes(kind || "") && agent.runtime === "codex") || (kind === "runtime" && value === "codex")) {
@@ -501,28 +451,22 @@ if ((["model", "effort"].includes(kind || "") && agent.runtime === "claude") ||
501
451
  }
502
452
  }
503
453
  let piCatalog = null;
504
- const userRuntime = toUserRuntime(agent.runtime, agent.piDistribution);
505
- const catalogUserRuntime = kind === "runtime" && (value === "pi" || value === "builtin-pi")
506
- ? value
507
- : userRuntime;
454
+ const userRuntime = toUserRuntime(agent.runtime);
508
455
  if ((kind === "model" || kind === "effort" || kind === "runtime") && !value) {
509
456
  say(`agent=${selectedKey} runtime=${userRuntime} model=${agent.model}`);
510
457
  }
511
458
  const needsLivePiCatalog = (["model", "effort"].includes(kind) && agent.runtime === "pi")
512
- || (kind === "runtime" && (value === "pi" || value === "builtin-pi") && Boolean(flagModel));
459
+ || (kind === "runtime" && value === "pi" && Boolean(flagModel));
513
460
  if (needsLivePiCatalog) {
514
461
  try {
515
- const catalogCommand = piCatalogCommandSpec(piCatalogDistributionForUserRuntime(catalogUserRuntime), process.env);
516
462
  piCatalog = await discoverPiModelCatalog({
517
463
  cwd: String(agent.workspaceDir),
518
- agentDir: ownedPiCatalogAgentDir(configDir, selectedKey),
519
- command: catalogCommand.command,
520
- commandArgs: catalogCommand.commandArgs,
464
+ command: process.env.LARKIN_PI_COMMAND || "pi",
521
465
  env: process.env,
522
466
  });
523
467
  }
524
468
  catch (error) {
525
- die(`Pi 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请先用官方 pi 登录或检查该 Agent 的隔离 provider 目录。`);
469
+ die(`Pi 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请先运行官方 pi 登录流程。`);
526
470
  }
527
471
  if (!piCatalog)
528
472
  die("Pi 模型目录加载失败");
@@ -695,10 +639,6 @@ else {
695
639
  say("\n切换: larkin runtime <id> [--model <id>]");
696
640
  process.exit(0);
697
641
  }
698
- if (!isUserRuntime(value)) {
699
- console.error(`✗ "${value}" 不是合法 runtime。合法值: ${RUNTIME_OPTIONS.join(", ")}`);
700
- process.exit(1);
701
- }
702
642
  let model = flagModel;
703
643
  if (model) {
704
644
  const items = catalog[catalogRuntimeKey(value)];
@@ -49,26 +49,6 @@ async function dispatchInternal(mode, rest) {
49
49
  case "lark-channel-secret":
50
50
  await (await import("./lark-channel-secret.mjs")).main(process.env);
51
51
  return;
52
- case "pi-rpc": {
53
- const { prepareBuiltinPiPackageAssets } = await import("../runtime/builtin-pi-assets.mjs");
54
- prepareBuiltinPiPackageAssets();
55
- process.title = "pi-rpc";
56
- process.env.PI_CODING_AGENT = "true";
57
- process.emitWarning = (() => { });
58
- // The Pi package's normal Bun CLI registers OAuth loaders before starting.
59
- // Larkin invokes its RPC entrypoint directly, so do the same here; otherwise
60
- // an OAuth-backed compact falls back to a dynamic module import unavailable in
61
- // a compiled standalone binary.
62
- const { registerBunOAuthFlows } = await import("@earendil-works/pi-ai/bun-oauth");
63
- registerBunOAuthFlows();
64
- const { main: piMain } = await import("@earendil-works/pi-coding-agent");
65
- const { invokeBuiltinPiRpc } = await import("../runtime/pi-inline-extensions.mjs");
66
- await invokeBuiltinPiRpc(piMain, rest);
67
- return;
68
- }
69
- case "pi-auth":
70
- await (await import("./pi-auth-cli.mjs")).main(rest, process.env);
71
- return;
72
52
  case "telemetry":
73
53
  await (await import("./telemetry.mjs")).main(rest, process.env);
74
54
  return;
package/dist/app/cli.mjs CHANGED
@@ -29,11 +29,9 @@ const routes = {
29
29
  model: ["agent-config", "model"],
30
30
  runtime: ["agent-config", "runtime"],
31
31
  effort: ["agent-config", "effort"],
32
- "pi-distribution": ["agent-config", "pi-distribution"],
33
32
  chats: ["agent-config", "chats"],
34
33
  config: ["agent-config", "config"],
35
34
  session: ["session-cli"],
36
- "pi-auth": ["pi-auth"],
37
35
  telemetry: ["telemetry"],
38
36
  };
39
37
  const runtimeAgentAuthority = typeof process.env.LARKIN_AGENT_ID === "string"
@@ -49,8 +47,8 @@ if (runtimeAgentAuthority && command === "comment")
49
47
  const commandHelp = {
50
48
  start: `Usage: larkin start [--agent <App ID> | --agents <App ID,...>]
51
49
  Start one foreground supervisor for the daemon and local dashboard, or reuse it.`,
52
- setup: `Usage: larkin setup [--tenant feishu|lark] [--runtime <builtin-pi|pi|codex|claude>] [--provider <id> --api-key <key>]
53
- Run setup to create or connect a bot, configure its Agent, and attach it. Choose Feishu or Lark before the authorization QR (--tenant; default feishu). Interactive terminals keep the guided flow; Agent-driven (non-TTY) runs default to builtin-pi and need --provider/--api-key (or --runtime pi).`,
50
+ setup: `Usage: larkin setup [--tenant feishu|lark] [--runtime <pi|codex|claude>]
51
+ Run setup to create or connect a bot, configure its Agent, and attach it. Choose Feishu or Lark before the authorization QR (--tenant; default feishu). Interactive terminals list pi, Codex, and Claude Code with installed / not installed status. Non-TTY runs require --runtime and fail if that executable is missing.`,
54
52
  status: `Usage: larkin status [--json]
55
53
  Show Agent configuration, bot identity, credentials, and connection status. Use --json for readiness automation.`,
56
54
  agents: `Usage: larkin agents [--json]
@@ -58,10 +56,7 @@ List every configured Agent and its current local status. Use --json for daemon/
58
56
  model: `Usage: larkin model [<model>] [--agent <App ID>]
59
57
  Show or change an Agent model.`,
60
58
  runtime: `Usage: larkin runtime [<runtime>] [--agent <App ID>] [--model <model>]
61
- Show or change an Agent runtime. User-facing ids: pi | builtin-pi | codex | claude.`,
62
- "pi-distribution": `Usage: larkin pi-distribution [show|builtin|external] [--agent <App ID>] [--snapshot <private-file>] [--import-external-profile]
63
- larkin pi-distribution rollback --snapshot <private-file>
64
- Show or change one Pi Agent distribution. builtin requires configured provider state, or explicitly imports the external Pi 0.84.2 profile with --import-external-profile; all changes support config-lock CAS rollback.`,
59
+ Show or change an Agent runtime. User-facing ids: pi | codex | claude. A missing executable is rejected and does not write config.`,
65
60
  effort: `Usage: larkin effort [<level>|clear|default] [--agent <App ID>]
66
61
  Show or change an Agent reasoning effort; clear/default restores the Runtime default.`,
67
62
  chats: `Usage: larkin chats [--agent <App ID>]
@@ -83,11 +78,6 @@ Credentials, internal paths, serverId, activeAgent, and raw config are never exp
83
78
  session: `Usage: larkin session reset --agent <App ID> --json [--wait-ready <seconds>]
84
79
  larkin session recover --agent <App ID> --reason context-overflow --json [--wait-ready <seconds>]
85
80
  Reset replaces one idle, zero-backlog Runtime session. Recover is an explicit operator-only context-window recovery that preserves and replays canonical Inbox deliveries.`,
86
- "pi-auth": `Usage: larkin pi-auth status [--agent <App ID>] [--json]
87
- larkin pi-auth providers [--json]
88
- larkin pi-auth login <preset|custom> --agent <App ID> [--model <model>] [--base-url <url>] [--api-key-stdin] [--json]
89
- larkin pi-auth logout <provider> [--agent <App ID>]
90
- Show a redacted builtin Pi provider catalog, configure one existing Agent with a known preset or custom OpenAI-compatible endpoint, or remove one target provider credential. API keys are accepted only from a TTY prompt or --api-key-stdin.`,
91
81
  comment: `Usage: larkin comment reply --message-id <doc_comment_message_id> --text '<reply>' --json
92
82
  Reply as the Runtime-bound Bot to the exact cloud-document comment locator supplied by canonical Inbox. Retrying the same body is idempotent; a different body appends a follow-up reply to the same comment.`,
93
83
  telemetry: `Usage: larkin telemetry <status|export|import|flush>
@@ -112,9 +102,10 @@ if (!runtimeAgentCommand && command === "config" && ["runtime", "model", "effort
112
102
  // At a user terminal, unregistered non-flag commands keep the legacy lark-cli passthrough.
113
103
  // Inside an Agent Runtime, unknown Feishu command groups enter the same Larkin-owned
114
104
  // identity and freshness AOP before delegation to the verified global official CLI.
105
+ const retiredPublicCommands = new Set(["pi-auth", "pi-distribution"]);
115
106
  const wantsHelp = command === "help" || command === "--help" || command === "-h"
116
107
  || (!runtimeAgentAuthority && command.startsWith("-"));
117
- if (!routes[command] && !wantsHelp) {
108
+ if (!routes[command] && !wantsHelp && !retiredPublicCommands.has(command)) {
118
109
  routes[command] = runtimeAgentAuthority ? ["lark-cli", command] : ["lark", command];
119
110
  }
120
111
  if (!routes[command]) {
@@ -132,7 +123,6 @@ Usage: larkin <command>
132
123
  config Inspect effective config/source, edit mention inheritance, or explicitly apply runtime changes
133
124
  session reset Replace one idle, zero-backlog Agent Runtime session for a fresh scenario
134
125
  session recover Explicitly recover a context-overflowed session and replay retained Inbox deliveries
135
- pi-auth Show, configure, or logout one built-in Pi Agent provider credential
136
126
  comment reply Reply to the exact cloud-document comment bound by a polled Inbox message
137
127
  telemetry Inspect, export, import, or flush the durable OpenTelemetry trace queue
138
128
  <lark-cli 命令组> im/docs/wiki/drive 等 lark-cli 命令原样转发,机器人身份已锁定(如 larkin im +chat-list)
@@ -146,15 +136,9 @@ Process persistence is managed externally. larkin start stays in the foreground
146
136
  }
147
137
  const [mode, ...presetArguments] = routes[command];
148
138
  const childSpec = internalCommandSpec(mode, [...presetArguments, ...rest]);
149
- // 管道 stdin 必须显式转发:Linux 上 inherit 会在父进程排空后再交给子进程,导致 --api-key-stdin 读到空。
150
- const pipeStdin = command === "pi-auth" && rest.includes("--api-key-stdin") && !process.stdin.isTTY;
151
- if (pipeStdin && typeof process.stdin.pause === "function")
152
- process.stdin.pause();
153
139
  const child = spawn(childSpec.command, childSpec.args, {
154
- stdio: [pipeStdin ? "pipe" : "inherit", "inherit", "inherit"],
140
+ stdio: ["inherit", "inherit", "inherit"],
155
141
  });
156
- if (pipeStdin && child.stdin)
157
- process.stdin.pipe(child.stdin);
158
142
  // Package-manager bin shims add a wrapper process. Forward terminal signals so the actual command
159
143
  // does not become an orphan that keeps the machine lock or Feishu connection.
160
144
  const signalForwarders = new Map();
@@ -24,8 +24,6 @@ exports.INTERNAL_MODES = [
24
24
  "setup-bind",
25
25
  "grant-scopes",
26
26
  "lark-channel-secret",
27
- "pi-rpc",
28
- "pi-auth",
29
27
  "telemetry",
30
28
  ];
31
29
  exports.PROCESS_COMMAND_TOKENS = Object.freeze({
@@ -15,8 +15,6 @@ export const INTERNAL_MODES = [
15
15
  "setup-bind",
16
16
  "grant-scopes",
17
17
  "lark-channel-secret",
18
- "pi-rpc",
19
- "pi-auth",
20
18
  "telemetry",
21
19
  ];
22
20
  export const PROCESS_COMMAND_TOKENS = Object.freeze({
@@ -2,12 +2,9 @@
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { loadConfig, resolveConfigDir } from "../platform/config.mjs";
6
5
  import { discoverClaudeModelCatalog } from "../runtime/claude-model-catalog.mjs";
7
6
  import { discoverPiModelCatalog } from "../runtime/pi-model-catalog.mjs";
8
7
  import { discoverCodexModelCatalog } from "../runtime/codex-model-catalog.mjs";
9
- import { ownedPiCatalogAgentDir, piCatalogCommandSpec } from "../runtime/pi-provider-config.mjs";
10
- import { piCatalogDistributionForUserRuntime } from "../runtime/user-runtime.mjs";
11
8
  function readTestModelDirectory(env) {
12
9
  const fixture = env?.LARKIN_TEST_RUNTIME_MODEL_DIRECTORY_FILE;
13
10
  if (!fixture)
@@ -37,22 +34,11 @@ export async function discoverRuntimeModelDirectory(input) {
37
34
  ...catalog.models.map(({ id, label, supportedReasoningEfforts }) => ({ id, label, supportedReasoningEfforts })),
38
35
  ];
39
36
  }
40
- if (input.runtime === "pi" || input.runtime === "builtin-pi") {
37
+ if (input.runtime === "pi") {
41
38
  const env = input.env ?? process.env;
42
- const agentId = input.agentId || "";
43
- if (!agentId)
44
- throw new Error("runtime model directory requires agentId for Pi");
45
- const configDir = resolveConfigDir(env);
46
- const { config } = loadConfig(env);
47
- const agent = config.agents[agentId];
48
- if (!agent)
49
- throw new Error(`unknown agent: ${agentId}`);
50
- const catalogCommand = piCatalogCommandSpec(piCatalogDistributionForUserRuntime(input.runtime), env);
51
39
  const catalog = await discoverPiModelCatalog({
52
40
  cwd: input.cwd,
53
- agentDir: ownedPiCatalogAgentDir(configDir, agentId),
54
- command: catalogCommand.command,
55
- commandArgs: catalogCommand.commandArgs,
41
+ command: env.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi",
56
42
  env,
57
43
  });
58
44
  if (!catalog.effectiveModel)
@@ -65,7 +65,6 @@ export async function markConfigAppliedAfterRuntimeReady(env, runningAgents, run
65
65
  for (const running of runningAgents) {
66
66
  const current = loaded.config.agents[running.agentId];
67
67
  if (!current || current.runtime !== running.runtime || current.model !== running.model
68
- || (current.piDistribution || "external") !== (running.piDistribution || "external")
69
68
  || (current.effort || null) !== (running.effort || null))
70
69
  continue;
71
70
  markConfigApplied(env, running.agentId, runtimeConfigSignature(loaded.config, running.agentId));
@@ -7,13 +7,11 @@ import path from "node:path";
7
7
  import readline from "node:readline/promises";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import * as larkinConfig from "../platform/config.mjs";
10
- import { PI_PROVIDER_PRESETS } from "../runtime/pi-provider-config.mjs";
11
- import { fromUserRuntime, isUserRuntime } from "../runtime/user-runtime.mjs";
12
- const PI_PROVIDER_IDS = PI_PROVIDER_PRESETS.map((p) => p.id).join(" | ") + " | custom";
10
+ import { isUserRuntime } from "../runtime/user-runtime.mjs";
13
11
  import { acquireProcessLock, readProcessState } from "../platform/process-state.mjs";
14
12
  import { requestAgentUpsert } from "./local-control.mjs";
15
13
  import { openOwnedDashboardWhenReady } from "./setup-dashboard.mjs";
16
- import { probeNativeRuntimeReadiness } from "../runtime/runtime-readiness.mjs";
14
+ import { probeNativeRuntimeReadiness, resolveRuntimeExecutable, runtimeInstallNextAction } from "../runtime/runtime-readiness.mjs";
17
15
  import { internalCommandSpec, processCommandToken } from "./internal-command.mjs";
18
16
  import { ensureOfficialLarkCliForSetup, formatOfficialLarkCliConsent, } from "./official-lark-cli.mjs";
19
17
  const CFG_DIR = process.env.LARKIN_CONFIG_DIR || path.join(os.homedir(), ".larkin");
@@ -33,60 +31,54 @@ if (has("--start") && has("--no-start"))
33
31
  if (has("--comment-subscription")) {
34
32
  die("--comment-subscription 已移除:评论订阅默认 application(平台验证的应用级订阅),不需要该 flag");
35
33
  }
34
+ const REMOVED_SETUP_FLAGS = ["--provider", "--api-key", "--base-url", "--pi-distribution", "--api-key-stdin", "--model-from-preset"];
35
+ if (REMOVED_SETUP_FLAGS.some((name) => has(name) || argv.some((arg) => arg.startsWith(`${name}=`)))) {
36
+ die("setup 不再接受 --provider/--api-key/--base-url;请安装并登录外部 pi、codex 或 claude");
37
+ }
36
38
  // 默认应用级评论订阅:平台验证的应用订阅让该应用可见文档的全部评论进入 Agent Inbox。
37
39
  const commentSubscription = "application";
38
- const OPT = { runtime: flag("--runtime") || null, commentSubscription, start: true, help: has("--help") || has("-h"), nonInteractive: !Boolean(process.stdin.isTTY) };
39
- // runtime 枚举归一:builtin-pi(默认)/ pi / codex / claude → 内部 (runtime, distribution)
40
- const RUNTIME_ENUM = ["builtin-pi", "pi", "codex", "claude"];
40
+ const OPT = { runtime: flag("--runtime") || null, commentSubscription, start: !has("--no-start"), help: has("--help") || has("-h"), nonInteractive: !Boolean(process.stdin.isTTY) };
41
+ const RUNTIME_ENUM = ["pi", "codex", "claude"];
42
+ function runtimeCommand(runtime, env = process.env) {
43
+ if (runtime === "pi")
44
+ return env.LARKIN_PI_COMMAND || "pi";
45
+ if (runtime === "codex")
46
+ return env.LARKIN_CODEX_COMMAND || "codex";
47
+ return env.LARKIN_CLAUDE_COMMAND || "claude";
48
+ }
41
49
  function normalizeRuntime(value) {
42
- const raw = value || "builtin-pi";
43
- if (raw === "external-pi") {
50
+ if (!value)
51
+ return null;
52
+ if (value === "external-pi") {
44
53
  console.error("external-pi 已改名为 pi");
45
- return fromUserRuntime("pi");
54
+ return "pi";
46
55
  }
47
- if (!isUserRuntime(raw) || !RUNTIME_ENUM.includes(raw)) {
48
- die(`未知 runtime \`${value}\`;可选:${RUNTIME_ENUM.join(" | ")}`);
49
- }
50
- return fromUserRuntime(raw);
51
- }
52
- const normalizedRuntime = normalizeRuntime(OPT.runtime);
53
- // 参数化路径:显式 --runtime、无 TTY(Agent 驱动)、或任何 provider 参数。
54
- // 交互终端且未指定任何参数时,不传 --runtime 给 bot-register,由其交互选择流程接管。
55
- const parameterized = Boolean(OPT.runtime) || OPT.nonInteractive
56
- || ["--provider", "--api-key", "--base-url"].some((name) => has(name));
57
- // builtin-pi 前置校验:授权前就 fast-fail(避免用户白授权后才发现缺 key);--help 永远跳过
58
- if (!OPT.help && parameterized && normalizedRuntime.distribution === "builtin") {
59
- const p = flag("--provider");
60
- const b = flag("--base-url");
61
- const k = flag("--api-key");
62
- if (!p && !b)
63
- die(`builtin-pi 需要 --provider <id>(${PI_PROVIDER_IDS})或 --base-url;或 --runtime pi 用已有 pi`);
64
- if (!k)
65
- die("builtin-pi 需要 --api-key;或 --runtime pi 用已有登录");
56
+ if (isUserRuntime(value))
57
+ return value;
58
+ return die(`未知 runtime \`${value}\`;可选:${RUNTIME_ENUM.join(" | ")}`);
66
59
  }
67
- if (!OPT.help && normalizedRuntime.distribution === "external" && (flag("--provider") || flag("--api-key") || flag("--base-url"))) {
68
- die("pi 使用已有 pi 环境,不接受 --provider/--api-key/--base-url");
60
+ function assertRuntimeInstalled(runtime) {
61
+ if (resolveRuntimeExecutable(runtimeCommand(runtime), process.env))
62
+ return;
63
+ die(`${runtime} is not installed;${runtimeInstallNextAction(runtime)}`);
69
64
  }
70
65
  if (OPT.help) {
71
66
  say(`larkin setup — Create or connect a Feishu (Lark) bot, then configure and attach its Agent
72
67
 
73
68
  Usage:
74
- larkin setup Select a bot in the browser and run setup(默认:内置 Pi + 配置完成即热挂载)
75
- larkin setup --provider <id> --api-key <key> 指定内置 Pi provider(无需交互)
69
+ larkin setup Select a bot in the browser and choose an installed runtime
70
+ larkin setup --runtime pi Non-interactive setup for an externally installed runtime
76
71
 
77
72
  Options:
78
- --runtime <runtime> Agent runtime 枚举:builtin-pi(默认,内置 Pi)| pi | codex | claude
79
- --provider <id> 内置 Pi provider:deepseek | kimi | minimax | zhipu | openai |
80
- anthropic | gemini | groq | cerebras | xai | fireworks |
81
- together | mistral | openrouter | kimi-coding | qwen-cn | custom
82
- --api-key <key> API key(内置 Pi 必填)
83
- --base-url <url> custom 端点(provider=custom 时必填;也可覆盖 preset 默认端点)
84
- --model <id> 模型 ID;默认取 provider 预设。不同 runtime 的模型可用
85
- \`larkin model\` 查看 / 切换
86
- --from-cli-profile <name> 可选:复用已有官方 lark-cli profile(需 LARKIN_SETUP_APP_SECRET)
87
- --tenant feishu|lark 授权二维码之前选择品牌。默认 feishu(扫码 /page/launcher)。
88
- Lark 用 --tenant lark:扫码 /page/cli,完成后凭证回传,
89
- 不要打开 /page/launcher。交互式未指定时会先询问。
73
+ --runtime <runtime> Required without a TTY. Agent runtime: pi | codex | claude
74
+ --model <id> Optional model ID; otherwise the runtime default is used.
75
+ Use \`larkin model\` after setup to inspect or switch.
76
+ --from-cli-profile <name> Optional: reuse an existing official lark-cli profile
77
+ (requires LARKIN_SETUP_APP_SECRET)
78
+ --tenant feishu|lark Choose the brand before the authorization QR. Default feishu
79
+ (scan /page/launcher). Lark uses --tenant lark: scan /page/cli,
80
+ then credentials return; do not open /page/launcher.
81
+ Interactive runs ask when omitted.
90
82
 
91
83
  setup handles browser authorization, permission grants, credential storage, Agent configuration,
92
84
  and target-only hot attach. Each Agent is identified by its bot App ID: selecting the same bot reuses
@@ -95,11 +87,18 @@ its existing Agent, memory, and state; creating a new bot creates a new Agent.
95
87
  If larkin is running, only the selected Agent is added or updated. Otherwise setup starts the same
96
88
  daemon + dashboard supervisor used by larkin start.
97
89
 
98
- Interactive terminals without flags keep the guided flow (browser bot selection + runtime choice).
99
- Non-interactive (Agent-driven) runs default to builtin-pi and require --provider/--api-key (or
100
- --runtime pi to reuse an existing pi login).`);
90
+ Interactive terminals list pi, Codex, and Claude Code with installed / not installed status and
91
+ refuse a runtime that is not installed. Non-interactive (Agent-driven) runs require --runtime
92
+ and exit non-zero with the readiness message when that executable is missing.`);
101
93
  process.exit(0);
102
94
  }
95
+ if (OPT.nonInteractive && !OPT.runtime) {
96
+ die("非交互 setup 必须指定 --runtime pi|codex|claude");
97
+ }
98
+ const selectedRuntime = normalizeRuntime(OPT.runtime);
99
+ if (selectedRuntime)
100
+ assertRuntimeInstalled(selectedRuntime);
101
+ const parameterized = Boolean(OPT.runtime);
103
102
  function runForeground(mode, args) {
104
103
  return new Promise((resolve, reject) => {
105
104
  const childSpec = internalCommandSpec(mode, args);
@@ -151,11 +150,8 @@ export async function main() {
151
150
  fs.mkdirSync(CFG_DIR, { recursive: true, mode: 0o700 });
152
151
  const resultFile = path.join(CFG_DIR, `.setup-result-${process.pid}.json`);
153
152
  const registerArgs = ["--auto", "--result-file", resultFile];
154
- if (parameterized) {
155
- registerArgs.push("--runtime", normalizedRuntime.runtime);
156
- if (normalizedRuntime.distribution)
157
- registerArgs.push("--pi-distribution", normalizedRuntime.distribution);
158
- }
153
+ if (parameterized && selectedRuntime)
154
+ registerArgs.push("--runtime", selectedRuntime);
159
155
  registerArgs.push("--comment-subscription", OPT.commentSubscription);
160
156
  if (has("--tenant")) {
161
157
  const tenant = flag("--tenant");
@@ -163,7 +159,7 @@ export async function main() {
163
159
  die("--tenant 只支持 feishu 或 lark");
164
160
  registerArgs.push("--tenant", tenant === "lark" ? "lark" : "feishu");
165
161
  }
166
- for (const name of ["--provider", "--api-key", "--base-url", "--model", "--from-cli-profile"]) {
162
+ for (const name of ["--model", "--from-cli-profile"]) {
167
163
  const value = flag(name);
168
164
  if (value)
169
165
  registerArgs.push(name, value);
@@ -193,12 +189,11 @@ export async function main() {
193
189
  die(`Runtime ${configuredAgent.runtime} 不受支持`);
194
190
  // 非交互(Agent 驱动)时仍跳过 probeNativeRuntimeReadiness:该探测保持 interactive-only。
195
191
  // external-pi 绑定已在 setup-bind 内做非交互 Pi RPC catalog discovery(RPC over pipes 不需要 TTY);
196
- // daemon attach 探测仍是权威校验。其余真实阻塞(缺 runtime 可执行文件 / 缺 key / 缺 lark-cli)一律 fast-fail。
192
+ // daemon attach 探测仍是权威校验。其余真实阻塞(缺 runtime 可执行文件 / 缺登录 / 缺 lark-cli)一律 fast-fail。
197
193
  if (!OPT.nonInteractive) {
198
194
  const runtimeReadiness = await probeNativeRuntimeReadiness({ runtime: configuredAgent.runtime,
199
195
  agentId: selectedAgentId, cwd: configuredAgent.workspaceDir,
200
- env: { ...process.env, LARKIN_CONFIG_DIR: CFG_DIR,
201
- ...(configuredAgent.piDistribution ? { LARKIN_PI_DISTRIBUTION: configuredAgent.piDistribution } : {}) } });
196
+ env: { ...process.env, LARKIN_CONFIG_DIR: CFG_DIR } });
202
197
  if (runtimeReadiness.state !== "ready") {
203
198
  die(`Runtime ${configuredAgent.runtime} ${runtimeReadiness.state}:${runtimeReadiness.reason || "prerequisite unavailable"};${runtimeReadiness.nextAction || "修复后重试"}`);
204
199
  }