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
package/dist/app/agent-cli.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
15
|
-
import {
|
|
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", "
|
|
56
|
-
die("用法: larkin agents | larkin config <show|mention|apply> | larkin model [<id>] | larkin runtime [<id>] [--model <id>] | larkin
|
|
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"]),
|
|
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(),
|
|
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
|
|
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
|
|
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
|
|
410
|
+
say(` ${candidate}${candidate === config.activeAgent ? " [active]" : ""} runtime=${toUserRuntime(item.runtime)} model=${item.model}`);
|
|
433
411
|
}
|
|
434
412
|
process.exit(0);
|
|
435
413
|
}
|
|
@@ -437,47 +415,21 @@ if (!key) {
|
|
|
437
415
|
}
|
|
438
416
|
const selectedKey = key;
|
|
439
417
|
const agent = config.agents[selectedKey];
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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("内置 Pi provider 尚未为该 Agent 配置;请先运行 larkin setup 完成内置 Pi provider 配置,或使用 --import-external-profile");
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
try {
|
|
469
|
-
const result = larkinConfig.mutateConfig(process.env, { kind: "set-agent-pi-distribution", agentId: selectedKey, distribution: requested }, { kind: "user" }, {
|
|
470
|
-
snapshotFile: snapshotFile, ...(importExternalProfile ? { importExternalProfile: true } : {}),
|
|
471
|
-
});
|
|
472
|
-
traceProcessBoundary(process.env, "agent-config:pi-distribution-persisted", { configDir, agentId: selectedKey, targetDir: piAgentDirectory(configDir, selectedKey), requested, applyState: result.applyState });
|
|
473
|
-
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);
|
|
474
429
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
die("Pi external profile import failed; no secret or private path was disclosed");
|
|
478
|
-
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)}`);
|
|
479
432
|
}
|
|
480
|
-
process.exit(0);
|
|
481
433
|
}
|
|
482
434
|
const catalog = larkinConfig.loadRuntimeModels();
|
|
483
435
|
if ((["model", "effort"].includes(kind || "") && agent.runtime === "codex") || (kind === "runtime" && value === "codex")) {
|
|
@@ -499,28 +451,22 @@ if ((["model", "effort"].includes(kind || "") && agent.runtime === "claude") ||
|
|
|
499
451
|
}
|
|
500
452
|
}
|
|
501
453
|
let piCatalog = null;
|
|
502
|
-
const userRuntime = toUserRuntime(agent.runtime
|
|
503
|
-
const catalogUserRuntime = kind === "runtime" && (value === "pi" || value === "builtin-pi")
|
|
504
|
-
? value
|
|
505
|
-
: userRuntime;
|
|
454
|
+
const userRuntime = toUserRuntime(agent.runtime);
|
|
506
455
|
if ((kind === "model" || kind === "effort" || kind === "runtime") && !value) {
|
|
507
456
|
say(`agent=${selectedKey} runtime=${userRuntime} model=${agent.model}`);
|
|
508
457
|
}
|
|
509
458
|
const needsLivePiCatalog = (["model", "effort"].includes(kind) && agent.runtime === "pi")
|
|
510
|
-
|| (kind === "runtime" &&
|
|
459
|
+
|| (kind === "runtime" && value === "pi" && Boolean(flagModel));
|
|
511
460
|
if (needsLivePiCatalog) {
|
|
512
461
|
try {
|
|
513
|
-
const catalogCommand = piCatalogCommandSpec(piCatalogDistributionForUserRuntime(catalogUserRuntime), process.env);
|
|
514
462
|
piCatalog = await discoverPiModelCatalog({
|
|
515
463
|
cwd: String(agent.workspaceDir),
|
|
516
|
-
|
|
517
|
-
command: catalogCommand.command,
|
|
518
|
-
commandArgs: catalogCommand.commandArgs,
|
|
464
|
+
command: process.env.LARKIN_PI_COMMAND || "pi",
|
|
519
465
|
env: process.env,
|
|
520
466
|
});
|
|
521
467
|
}
|
|
522
468
|
catch (error) {
|
|
523
|
-
die(`Pi 模型目录加载失败:${error instanceof Error ? error.message : String(error)}
|
|
469
|
+
die(`Pi 模型目录加载失败:${error instanceof Error ? error.message : String(error)}。请先运行官方 pi 登录流程。`);
|
|
524
470
|
}
|
|
525
471
|
if (!piCatalog)
|
|
526
472
|
die("Pi 模型目录加载失败");
|
|
@@ -693,10 +639,6 @@ else {
|
|
|
693
639
|
say("\n切换: larkin runtime <id> [--model <id>]");
|
|
694
640
|
process.exit(0);
|
|
695
641
|
}
|
|
696
|
-
if (!isUserRuntime(value)) {
|
|
697
|
-
console.error(`✗ "${value}" 不是合法 runtime。合法值: ${RUNTIME_OPTIONS.join(", ")}`);
|
|
698
|
-
process.exit(1);
|
|
699
|
-
}
|
|
700
642
|
let model = flagModel;
|
|
701
643
|
if (model) {
|
|
702
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 <
|
|
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
|
|
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 |
|
|
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,9 +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 logout <provider> [--agent <App ID>]
|
|
88
|
-
Show non-sensitive official Pi credential metadata or remove one target provider credential.`,
|
|
89
81
|
comment: `Usage: larkin comment reply --message-id <doc_comment_message_id> --text '<reply>' --json
|
|
90
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.`,
|
|
91
83
|
telemetry: `Usage: larkin telemetry <status|export|import|flush>
|
|
@@ -110,9 +102,10 @@ if (!runtimeAgentCommand && command === "config" && ["runtime", "model", "effort
|
|
|
110
102
|
// At a user terminal, unregistered non-flag commands keep the legacy lark-cli passthrough.
|
|
111
103
|
// Inside an Agent Runtime, unknown Feishu command groups enter the same Larkin-owned
|
|
112
104
|
// identity and freshness AOP before delegation to the verified global official CLI.
|
|
105
|
+
const retiredPublicCommands = new Set(["pi-auth", "pi-distribution"]);
|
|
113
106
|
const wantsHelp = command === "help" || command === "--help" || command === "-h"
|
|
114
107
|
|| (!runtimeAgentAuthority && command.startsWith("-"));
|
|
115
|
-
if (!routes[command] && !wantsHelp) {
|
|
108
|
+
if (!routes[command] && !wantsHelp && !retiredPublicCommands.has(command)) {
|
|
116
109
|
routes[command] = runtimeAgentAuthority ? ["lark-cli", command] : ["lark", command];
|
|
117
110
|
}
|
|
118
111
|
if (!routes[command]) {
|
|
@@ -130,7 +123,6 @@ Usage: larkin <command>
|
|
|
130
123
|
config Inspect effective config/source, edit mention inheritance, or explicitly apply runtime changes
|
|
131
124
|
session reset Replace one idle, zero-backlog Agent Runtime session for a fresh scenario
|
|
132
125
|
session recover Explicitly recover a context-overflowed session and replay retained Inbox deliveries
|
|
133
|
-
pi-auth Show non-sensitive built-in Pi auth status or logout one provider
|
|
134
126
|
comment reply Reply to the exact cloud-document comment bound by a polled Inbox message
|
|
135
127
|
telemetry Inspect, export, import, or flush the durable OpenTelemetry trace queue
|
|
136
128
|
<lark-cli 命令组> im/docs/wiki/drive 等 lark-cli 命令原样转发,机器人身份已锁定(如 larkin im +chat-list)
|
|
@@ -144,7 +136,9 @@ Process persistence is managed externally. larkin start stays in the foreground
|
|
|
144
136
|
}
|
|
145
137
|
const [mode, ...presetArguments] = routes[command];
|
|
146
138
|
const childSpec = internalCommandSpec(mode, [...presetArguments, ...rest]);
|
|
147
|
-
const child = spawn(childSpec.command, childSpec.args, {
|
|
139
|
+
const child = spawn(childSpec.command, childSpec.args, {
|
|
140
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
141
|
+
});
|
|
148
142
|
// Package-manager bin shims add a wrapper process. Forward terminal signals so the actual command
|
|
149
143
|
// does not become an orphan that keeps the machine lock or Feishu connection.
|
|
150
144
|
const signalForwarders = new Map();
|
|
@@ -14,6 +14,7 @@ const WINDOWS_SOCKET_BINDING = { device: "0", inode: "0", owner: "0", changeTime
|
|
|
14
14
|
const AGENT_ID = /^cli_[A-Za-z0-9]+$/;
|
|
15
15
|
const OPERATION_ID = /^[A-Za-z0-9_-]{8,128}$/;
|
|
16
16
|
const AUTHORIZATION = /^[A-Za-z0-9_-]{43,128}$/;
|
|
17
|
+
const CONFIG_SIGNATURE = /^sha256:[a-f0-9]{64}$/;
|
|
17
18
|
const UNIX_SOCKET_PATH_MAX_BYTES = process.platform === "darwin" ? 103 : 107;
|
|
18
19
|
function recoveryErrorText(code) {
|
|
19
20
|
switch (code) {
|
|
@@ -159,9 +160,13 @@ function parseRequest(line) {
|
|
|
159
160
|
if (value.waitReadyMs !== undefined) {
|
|
160
161
|
throw new Error("invalid session reset waitReadyMs");
|
|
161
162
|
}
|
|
162
|
-
if (Object.keys(value).some((key) => !["operationId", "agentId", "authorization"].includes(key))) {
|
|
163
|
+
if (Object.keys(value).some((key) => !["operationId", "agentId", "authorization", "expectedSignature"].includes(key))) {
|
|
163
164
|
throw new Error("agent control request 包含未知字段");
|
|
164
165
|
}
|
|
166
|
+
if (value.expectedSignature !== undefined
|
|
167
|
+
&& (typeof value.expectedSignature !== "string" || !CONFIG_SIGNATURE.test(value.expectedSignature))) {
|
|
168
|
+
throw new Error("invalid agent upsert expectedSignature");
|
|
169
|
+
}
|
|
165
170
|
return value;
|
|
166
171
|
}
|
|
167
172
|
function atomicWritePrivateJson(file, value) {
|
|
@@ -741,7 +746,11 @@ export function createAgentControlServer({ larkinHome, authorityToken, upsert, r
|
|
|
741
746
|
}
|
|
742
747
|
const execute = async () => {
|
|
743
748
|
try {
|
|
744
|
-
await upsert({
|
|
749
|
+
await upsert({
|
|
750
|
+
operationId: upsertRequest.operationId,
|
|
751
|
+
agentId: upsertRequest.agentId,
|
|
752
|
+
...(upsertRequest.expectedSignature ? { expectedSignature: upsertRequest.expectedSignature } : {}),
|
|
753
|
+
});
|
|
745
754
|
return { ok: true, operationId: upsertRequest.operationId, agentId: upsertRequest.agentId };
|
|
746
755
|
}
|
|
747
756
|
catch (error) {
|
|
@@ -926,7 +935,11 @@ async function requestAgentControl({ larkinHome, timeoutMs = 30_000, request, })
|
|
|
926
935
|
}
|
|
927
936
|
export async function requestAgentUpsert(input) {
|
|
928
937
|
return requestAgentControl({ larkinHome: input.larkinHome, timeoutMs: input.timeoutMs,
|
|
929
|
-
request: {
|
|
938
|
+
request: {
|
|
939
|
+
operationId: input.operationId ?? crypto.randomUUID(),
|
|
940
|
+
agentId: input.agentId,
|
|
941
|
+
...(input.expectedSignature ? { expectedSignature: input.expectedSignature } : {}),
|
|
942
|
+
} });
|
|
930
943
|
}
|
|
931
944
|
export async function requestSessionReset({ larkinHome, agentId, waitReadyMs = 30_000, }) {
|
|
932
945
|
return requestAgentControl({
|
|
@@ -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"
|
|
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
|
-
|
|
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)
|
|
@@ -6,22 +6,58 @@ import { ContextPromptBuilder } from "../agent/context-prompt.mjs";
|
|
|
6
6
|
import { createNativeRuntimeAdapter } from "../runtime/runtime-adapters.mjs";
|
|
7
7
|
import { createRuntimeHost } from "../runtime/runtime-host.mjs";
|
|
8
8
|
import { createAgentStateStore } from "../agent/agent-state-store.mjs";
|
|
9
|
-
import { loadConfig, markConfigApplied, runtimeConfigSignature } from "../platform/config.mjs";
|
|
9
|
+
import { loadConfig, markConfigApplied, runtimeConfigSignature, withConfigMutationLock, withConfigMutationLockAsync, } from "../platform/config.mjs";
|
|
10
10
|
import { traceProcessBoundary } from "../platform/process-boundary-trace.mjs";
|
|
11
11
|
import { createAgentControlServer, requestSupervisorAgentUpsert } from "./local-control.mjs";
|
|
12
|
-
import { hydrateRuntimeAgent, syncAgentProfile } from "./runtime-agent-config.mjs";
|
|
12
|
+
import { hydrateRuntimeAgent, syncAgentProfile, } from "./runtime-agent-config.mjs";
|
|
13
13
|
import { loadTelemetryConfig } from "../platform/telemetry-config.mjs";
|
|
14
14
|
import { telemetrySingleton } from "../platform/telemetry-tracing.mjs";
|
|
15
15
|
import { packageVersion } from "../platform/build-info.mjs";
|
|
16
|
-
|
|
16
|
+
function readRuntimeAgent(env, agentId, expectedSignature) {
|
|
17
17
|
const loaded = loadConfig(env);
|
|
18
18
|
const stored = loaded.config.agents[agentId];
|
|
19
19
|
if (!stored)
|
|
20
20
|
throw new Error(`Agent ${agentId} 不存在于 canonical config`);
|
|
21
|
+
if (expectedSignature) {
|
|
22
|
+
const current = runtimeConfigSignature(loaded.config, agentId);
|
|
23
|
+
if (current !== expectedSignature) {
|
|
24
|
+
throw new Error("配置在 apply 期间发生变化;未热加载已切换的 Agent");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { loaded, stored };
|
|
28
|
+
}
|
|
29
|
+
function hydrateAndSyncRuntimeAgent(env, loaded, stored, dependencies) {
|
|
21
30
|
const agent = hydrateRuntimeAgent(loaded.configDir, stored);
|
|
22
31
|
syncAgentProfile(agent, { ...env, LARKIN_CONFIG_DIR: loaded.configDir }, dependencies);
|
|
23
32
|
return agent;
|
|
24
33
|
}
|
|
34
|
+
export function loadAndSyncRuntimeAgent(env, agentId, dependencies = {}, options = {}) {
|
|
35
|
+
const snapshot = readRuntimeAgent(env, agentId, options.expectedSignature);
|
|
36
|
+
options.afterCanonicalValidate?.();
|
|
37
|
+
if (!options.expectedSignature) {
|
|
38
|
+
return hydrateAndSyncRuntimeAgent(env, snapshot.loaded, snapshot.stored, dependencies);
|
|
39
|
+
}
|
|
40
|
+
return withConfigMutationLock(env, () => {
|
|
41
|
+
const locked = readRuntimeAgent(env, agentId, options.expectedSignature);
|
|
42
|
+
return hydrateAndSyncRuntimeAgent(env, locked.loaded, locked.stored, dependencies);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** 签名校验贯穿实际 Host upsert;并发 config mutation 不能热挂载已失效的 snapshot。 */
|
|
46
|
+
export async function applyRuntimeAgentUpsert(env, agentId, dependencies = {}, options = {}, upsert = () => undefined) {
|
|
47
|
+
const snapshot = readRuntimeAgent(env, agentId, options.expectedSignature);
|
|
48
|
+
options.afterCanonicalValidate?.();
|
|
49
|
+
if (!options.expectedSignature) {
|
|
50
|
+
const agent = hydrateAndSyncRuntimeAgent(env, snapshot.loaded, snapshot.stored, dependencies);
|
|
51
|
+
await upsert(agent);
|
|
52
|
+
return agent;
|
|
53
|
+
}
|
|
54
|
+
return withConfigMutationLockAsync(env, async () => {
|
|
55
|
+
const locked = readRuntimeAgent(env, agentId, options.expectedSignature);
|
|
56
|
+
const agent = hydrateAndSyncRuntimeAgent(env, locked.loaded, locked.stored, dependencies);
|
|
57
|
+
await upsert(agent);
|
|
58
|
+
return agent;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
25
61
|
export async function markConfigAppliedAfterRuntimeReady(env, runningAgents, runtimeReady) {
|
|
26
62
|
await runtimeReady;
|
|
27
63
|
try {
|
|
@@ -29,7 +65,6 @@ export async function markConfigAppliedAfterRuntimeReady(env, runningAgents, run
|
|
|
29
65
|
for (const running of runningAgents) {
|
|
30
66
|
const current = loaded.config.agents[running.agentId];
|
|
31
67
|
if (!current || current.runtime !== running.runtime || current.model !== running.model
|
|
32
|
-
|| (current.piDistribution || "external") !== (running.piDistribution || "external")
|
|
33
68
|
|| (current.effort || null) !== (running.effort || null))
|
|
34
69
|
continue;
|
|
35
70
|
markConfigApplied(env, running.agentId, runtimeConfigSignature(loaded.config, running.agentId));
|
|
@@ -100,10 +135,11 @@ export async function main(env = process.env, overrides = {}) {
|
|
|
100
135
|
larkinHome: env.LARKIN_HOME,
|
|
101
136
|
authorityToken: env.LARKIN_CONTROL_AUTHORIZATION,
|
|
102
137
|
async upsert(request) {
|
|
103
|
-
const agent =
|
|
138
|
+
const agent = await applyRuntimeAgentUpsert(env, request.agentId, {}, {
|
|
139
|
+
...(request.expectedSignature ? { expectedSignature: request.expectedSignature } : {}),
|
|
140
|
+
}, (candidate) => hostShell.upsertAgent(candidate));
|
|
104
141
|
// Only the selected profile is synchronized; active profiles and their directory
|
|
105
142
|
// are never quarantined or rebuilt during hot attach.
|
|
106
|
-
await hostShell.upsertAgent(agent);
|
|
107
143
|
const tracked = await requestSupervisorAgentUpsert({
|
|
108
144
|
larkinHome: env.LARKIN_HOME, agentId: agent.agentId, operationId: request.operationId,
|
|
109
145
|
});
|