larkin 0.2.70

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 (144) hide show
  1. package/CONTRIBUTING.md +48 -0
  2. package/LICENSE +201 -0
  3. package/README.md +122 -0
  4. package/SECURITY.md +14 -0
  5. package/artifacts/release/THIRD_PARTY_NOTICES.txt +5488 -0
  6. package/dist/agent/agent-cli-capabilities.cjs +43 -0
  7. package/dist/agent/agent-cli-capabilities.mjs +35 -0
  8. package/dist/agent/agent-state-store.cjs +961 -0
  9. package/dist/agent/agent-state-store.mjs +923 -0
  10. package/dist/agent/agent-transport.cjs +201 -0
  11. package/dist/agent/agent-transport.mjs +194 -0
  12. package/dist/agent/config-cli-contract.cjs +19 -0
  13. package/dist/agent/config-cli-contract.mjs +16 -0
  14. package/dist/agent/context-prompt.cjs +159 -0
  15. package/dist/agent/context-prompt.mjs +155 -0
  16. package/dist/agent/freshness-gate.mjs +24 -0
  17. package/dist/agent/history-projection.cjs +72 -0
  18. package/dist/agent/history-projection.mjs +65 -0
  19. package/dist/agent/host-reminder-orchestrator.cjs +289 -0
  20. package/dist/agent/host-reminder-orchestrator.mjs +252 -0
  21. package/dist/agent/inbox-projection.cjs +92 -0
  22. package/dist/agent/inbox-projection.mjs +85 -0
  23. package/dist/agent/interaction-state-machine.cjs +710 -0
  24. package/dist/agent/interaction-state-machine.mjs +705 -0
  25. package/dist/agent/reminder-routes.cjs +192 -0
  26. package/dist/agent/reminder-routes.mjs +156 -0
  27. package/dist/agent/reminder-store.cjs +220 -0
  28. package/dist/agent/reminder-store.mjs +205 -0
  29. package/dist/agent/transport-business-context.cjs +486 -0
  30. package/dist/agent/transport-business-context.mjs +446 -0
  31. package/dist/agent/transport-shell.cjs +45 -0
  32. package/dist/agent/transport-shell.mjs +39 -0
  33. package/dist/app/agent-cli.mjs +596 -0
  34. package/dist/app/agent-config.mjs +633 -0
  35. package/dist/app/agent-lark-cli-workspace.cjs +95 -0
  36. package/dist/app/agent-lark-cli-workspace.mjs +85 -0
  37. package/dist/app/agent-readiness.cjs +42 -0
  38. package/dist/app/agent-readiness.mjs +38 -0
  39. package/dist/app/binary-entry.mjs +86 -0
  40. package/dist/app/cli.mjs +176 -0
  41. package/dist/app/dashboard.mjs +205 -0
  42. package/dist/app/internal-command.cjs +57 -0
  43. package/dist/app/internal-command.mjs +48 -0
  44. package/dist/app/lark-channel-secret.mjs +22 -0
  45. package/dist/app/lark-cli.mjs +887 -0
  46. package/dist/app/lark.mjs +45 -0
  47. package/dist/app/local-control.mjs +728 -0
  48. package/dist/app/official-lark-cli.cjs +138 -0
  49. package/dist/app/official-lark-cli.mjs +128 -0
  50. package/dist/app/pi-auth-cli.mjs +67 -0
  51. package/dist/app/run.mjs +364 -0
  52. package/dist/app/runtime-agent-config.mjs +416 -0
  53. package/dist/app/runtime-model-directory.mjs +78 -0
  54. package/dist/app/runtime-process.mjs +113 -0
  55. package/dist/app/session-cli.mjs +83 -0
  56. package/dist/app/setup-dashboard.mjs +26 -0
  57. package/dist/app/setup.mjs +166 -0
  58. package/dist/app/telemetry.mjs +83 -0
  59. package/dist/dashboard/dashboard-assets.mjs +21 -0
  60. package/dist/dashboard/dashboard-avatar.mjs +128 -0
  61. package/dist/dashboard/dashboard-config-controller.mjs +525 -0
  62. package/dist/dashboard/dashboard-lifecycle.cjs +97 -0
  63. package/dist/dashboard/dashboard-lifecycle.mjs +88 -0
  64. package/dist/dashboard/dashboard-template.mjs +29 -0
  65. package/dist/dashboard/dashboard-view-model.mjs +556 -0
  66. package/dist/dashboard/dashboard-workspace.mjs +104 -0
  67. package/dist/dashboard/web/assets/dashboard.css +3 -0
  68. package/dist/dashboard/web/assets/dashboard.js +49 -0
  69. package/dist/feishu/channel-transport.cjs +151 -0
  70. package/dist/feishu/channel-transport.mjs +148 -0
  71. package/dist/feishu/document-comment.cjs +120 -0
  72. package/dist/feishu/document-comment.mjs +109 -0
  73. package/dist/feishu/host-business-state.cjs +477 -0
  74. package/dist/feishu/host-business-state.mjs +462 -0
  75. package/dist/feishu/host-channel-business.cjs +161 -0
  76. package/dist/feishu/host-channel-business.mjs +157 -0
  77. package/dist/feishu/host-processing-eye.cjs +185 -0
  78. package/dist/feishu/host-processing-eye.mjs +181 -0
  79. package/dist/feishu/host-shell.cjs +1425 -0
  80. package/dist/feishu/host-shell.mjs +1385 -0
  81. package/dist/feishu/im-freshness-adapter.mjs +89 -0
  82. package/dist/feishu/interaction-orchestrator.cjs +279 -0
  83. package/dist/feishu/interaction-orchestrator.mjs +242 -0
  84. package/dist/feishu/lark-passthrough.mjs +70 -0
  85. package/dist/feishu/message-policy.cjs +111 -0
  86. package/dist/feishu/message-policy.mjs +98 -0
  87. package/dist/feishu/outbound-transport.cjs +221 -0
  88. package/dist/feishu/outbound-transport.mjs +215 -0
  89. package/dist/platform/build-info.cjs +25 -0
  90. package/dist/platform/build-info.mjs +18 -0
  91. package/dist/platform/callback-capability.cjs +301 -0
  92. package/dist/platform/callback-capability.mjs +252 -0
  93. package/dist/platform/check-bun-version.cjs +12 -0
  94. package/dist/platform/config.cjs +751 -0
  95. package/dist/platform/config.mjs +694 -0
  96. package/dist/platform/process-inspect.cjs +89 -0
  97. package/dist/platform/process-state.cjs +237 -0
  98. package/dist/platform/process-state.mjs +223 -0
  99. package/dist/platform/release-artifacts.mjs +61 -0
  100. package/dist/platform/root-layout.cjs +111 -0
  101. package/dist/platform/root-layout.mjs +72 -0
  102. package/dist/platform/telemetry-config.cjs +64 -0
  103. package/dist/platform/telemetry-config.mjs +56 -0
  104. package/dist/platform/telemetry-spool.cjs +661 -0
  105. package/dist/platform/telemetry-spool.mjs +653 -0
  106. package/dist/platform/telemetry-tracing.cjs +602 -0
  107. package/dist/platform/telemetry-tracing.mjs +596 -0
  108. package/dist/platform/telemetry-uploader.cjs +150 -0
  109. package/dist/platform/telemetry-uploader.mjs +146 -0
  110. package/dist/platform/workspace-service.cjs +469 -0
  111. package/dist/platform/workspace-service.mjs +430 -0
  112. package/dist/runtime/builtin-pi-assets.mjs +33 -0
  113. package/dist/runtime/claude-model-catalog.cjs +102 -0
  114. package/dist/runtime/claude-model-catalog.mjs +99 -0
  115. package/dist/runtime/codex-model-catalog.cjs +108 -0
  116. package/dist/runtime/codex-model-catalog.mjs +105 -0
  117. package/dist/runtime/pi-bash-timeout-extension.mjs +60 -0
  118. package/dist/runtime/pi-bash-timeout-injection.mjs +60 -0
  119. package/dist/runtime/pi-bash-timeout.bundle.js +43 -0
  120. package/dist/runtime/pi-model-catalog.cjs +116 -0
  121. package/dist/runtime/pi-model-catalog.mjs +75 -0
  122. package/dist/runtime/pi-official-auth.mjs +480 -0
  123. package/dist/runtime/pi-provider-config.cjs +252 -0
  124. package/dist/runtime/pi-provider-config.mjs +239 -0
  125. package/dist/runtime/pi-rpc-client.cjs +255 -0
  126. package/dist/runtime/pi-rpc-client.mjs +251 -0
  127. package/dist/runtime/pi-subagent-injection.mjs +129 -0
  128. package/dist/runtime/pi-subagents.bundle.js +8990 -0
  129. package/dist/runtime/runtime-adapters.mjs +1019 -0
  130. package/dist/runtime/runtime-contracts.cjs +2 -0
  131. package/dist/runtime/runtime-contracts.mjs +1 -0
  132. package/dist/runtime/runtime-host.cjs +930 -0
  133. package/dist/runtime/runtime-host.mjs +922 -0
  134. package/dist/runtime/runtime-model-catalog.cjs +43 -0
  135. package/dist/runtime/runtime-model-catalog.mjs +40 -0
  136. package/dist/runtime/runtime-readiness.cjs +177 -0
  137. package/dist/runtime/runtime-readiness.mjs +133 -0
  138. package/dist/setup/bot-register.mjs +655 -0
  139. package/dist/setup/grant-scopes.mjs +133 -0
  140. package/dist/setup/run-credential-preflight.mjs +89 -0
  141. package/dist/setup/setup-agent-choice.mjs +172 -0
  142. package/dist/setup/setup-bind.mjs +337 -0
  143. package/dist/setup/setup-binding.mjs +52 -0
  144. package/package.json +112 -0
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env bun
2
+ // Internal setup stage: bind an existing lark-cli bot profile to its App-ID Agent.
3
+ import { spawnSync } from "node:child_process";
4
+ import crypto from "node:crypto";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import readline from "node:readline";
8
+ import { fileURLToPath } from "node:url";
9
+ import { TargetRootLayout } from "../platform/root-layout.mjs";
10
+ import { planSingleRootBinding } from "./setup-binding.mjs";
11
+ import { discoverPiModelCatalog } from "../runtime/pi-model-catalog.mjs";
12
+ import { configureBuiltinPiProviderModel } from "../runtime/pi-provider-config.mjs";
13
+ import { terminalSetupQuestioner } from "./setup-agent-choice.mjs";
14
+ import { beginBuiltinPiCredentialTransaction, createOfficialPiAuthInteraction, createOfficialPiModelRuntime, runOfficialPiLogin, verifyOfficialPiProviderTurn, } from "../runtime/pi-official-auth.mjs";
15
+ import * as larkinConfigImport from "../platform/config.mjs";
16
+ import { resolveOfficialLarkCli } from "../app/official-lark-cli.mjs";
17
+ const larkinConfig = larkinConfigImport;
18
+ const layout = TargetRootLayout.fromConfigDir(larkinConfig.resolveConfigDir(process.env));
19
+ const CFG_DIR = layout.root;
20
+ const CFG_FILE = layout.configFile;
21
+ const APP_ID = /^cli_[A-Za-z0-9]+$/;
22
+ const LARK_CFG_DIR = larkinConfig.resolveLarkConfigDir(process.env, CFG_DIR);
23
+ fs.mkdirSync(LARK_CFG_DIR, { recursive: true, mode: 0o700 });
24
+ const larkEnv = () => ({ ...process.env, LARKSUITE_CLI_CONFIG_DIR: LARK_CFG_DIR });
25
+ const argv = process.argv.slice(2);
26
+ const flag = (name) => {
27
+ const index = argv.indexOf(name);
28
+ return index >= 0 ? argv[index + 1] : undefined;
29
+ };
30
+ const has = (name) => argv.includes(name);
31
+ const options = {
32
+ agent: flag("--agent"),
33
+ profile: flag("--profile"),
34
+ runtime: flag("--runtime"),
35
+ selectionFile: flag("--selection-file"),
36
+ yes: has("--yes"),
37
+ list: has("--list"),
38
+ help: has("--help") || has("-h"),
39
+ };
40
+ const nonInteractive = options.yes || Boolean(options.profile);
41
+ const say = (...args) => console.log(...args);
42
+ const die = (message) => {
43
+ console.error(`✗ ${message}`);
44
+ process.exit(1);
45
+ };
46
+ async function ask(question) {
47
+ if (nonInteractive)
48
+ return "";
49
+ const input = readline.createInterface({ input: process.stdin, output: process.stdout });
50
+ return new Promise((resolve) => input.question(question, (answer) => {
51
+ input.close();
52
+ resolve(answer.trim());
53
+ }));
54
+ }
55
+ function larkJson(args) {
56
+ const env = larkEnv();
57
+ const official = resolveOfficialLarkCli({ env });
58
+ const result = spawnSync(official.command, [...official.argsPrefix, ...args], { encoding: "utf8", env });
59
+ const stdout = result.stdout || "";
60
+ const stderr = result.stderr || "";
61
+ try {
62
+ return { ok: result.status === 0, json: JSON.parse(stdout), raw: stdout, err: stderr };
63
+ }
64
+ catch {
65
+ return { ok: false, json: null, raw: stdout, err: stderr || stdout };
66
+ }
67
+ }
68
+ function openAuthUrl(url) {
69
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32.exe" : "xdg-open";
70
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
71
+ const result = spawnSync(command, args, { stdio: "ignore", timeout: 5_000 });
72
+ return result.status === 0;
73
+ }
74
+ function listProfiles() {
75
+ const result = larkJson(["profile", "list"]);
76
+ if (!result.ok || !Array.isArray(result.json)) {
77
+ die("读取 lark-cli profile 失败;未修改 Agent 配置,请检查隔离 profile 后重试 setup");
78
+ }
79
+ return result.json;
80
+ }
81
+ async function pickProfile() {
82
+ if (options.profile) {
83
+ if (!APP_ID.test(options.profile))
84
+ die(`profile ${options.profile} 不是合法 App ID`);
85
+ return { name: options.profile, appId: options.profile, active: true };
86
+ }
87
+ const profiles = listProfiles();
88
+ if (profiles.length === 0)
89
+ die("lark-cli 里还没有 bot profile。请运行 larkin setup 创建或连接机器人");
90
+ say("\n可复用的飞书机器人(lark-cli profiles):");
91
+ profiles.forEach((profile, index) => say(` [${index + 1}] ${profile.name} appId=${profile.appId}${profile.active ? " (当前活跃,默认)" : ""}`));
92
+ say(" (创建新机器人请退出后运行 larkin setup)");
93
+ const activeIndex = Math.max(0, profiles.findIndex((profile) => profile.active));
94
+ const answer = await ask(`选择 profile(回车=${activeIndex + 1}): `);
95
+ const selected = answer === "" ? activeIndex : Number(answer) - 1;
96
+ if (!(selected >= 0 && selected < profiles.length))
97
+ die("无效的 profile 选择");
98
+ return profiles[selected];
99
+ }
100
+ function verifyBot(profile) {
101
+ const result = larkJson(["--profile", profile.name, "im", "+chat-list", "--as", "bot", "--json"]);
102
+ const response = result.json;
103
+ if (!result.ok || !response || !response.ok)
104
+ die("bot 校验失败;未修改 Agent 配置,请检查 profile 凭证后重试 setup");
105
+ const verified = response;
106
+ if (verified.identity !== "bot")
107
+ die(`profile ${profile.name} 未返回 bot identity;未修改 Agent 配置,请重试 setup`);
108
+ say(`\n✓ profile 校验:${profile.name} appId=${profile.appId} identity=bot`);
109
+ return verified.data?.chats || [];
110
+ }
111
+ function validateRuntime(runtime) {
112
+ const catalog = larkinConfig.loadRuntimeModels();
113
+ if (!catalog[runtime])
114
+ die(`"${runtime}" 不是合法 runtime。合法值: ${Object.keys(catalog).join(", ")}`);
115
+ }
116
+ function loadConfig() {
117
+ return larkinConfig.loadConfig(process.env, { mint: () => crypto.randomUUID() });
118
+ }
119
+ function readSetupSelection(fileArg) {
120
+ if (!fileArg)
121
+ return null;
122
+ const file = path.resolve(fileArg);
123
+ if (path.dirname(file) !== path.resolve(CFG_DIR) || !/^\.setup-agent-choice-\d+-[A-Za-z0-9-]+\.json$/.test(path.basename(file))) {
124
+ die("setup Agent 选择文件路径无效");
125
+ }
126
+ const stat = fs.lstatSync(file);
127
+ if (!stat.isFile() || stat.isSymbolicLink()
128
+ || (typeof process.getuid === "function" && stat.uid !== process.getuid())
129
+ || (stat.mode & 0o777) !== 0o600)
130
+ die("setup Agent 选择文件必须是当前用户拥有的 0600 普通文件");
131
+ const bytes = fs.readFileSync(file, "utf8");
132
+ fs.unlinkSync(file);
133
+ const raw = JSON.parse(bytes);
134
+ if (!raw || !["pi", "codex", "claude"].includes(raw.runtime))
135
+ die("setup Agent 选择无效");
136
+ return raw;
137
+ }
138
+ function fabricateAttachment(root, serverId) {
139
+ const directory = path.join(root, "computer", "servers", serverId);
140
+ fs.mkdirSync(directory, { recursive: true });
141
+ const file = path.join(directory, "runner.state.json");
142
+ if (fs.existsSync(file))
143
+ return;
144
+ const attachment = {
145
+ kind: "computer-attachment",
146
+ serverId,
147
+ serverSlug: "feishu",
148
+ serverMachineId: crypto.randomUUID(),
149
+ machineId: crypto.randomUUID(),
150
+ apiKey: `sk_computer_local_${crypto.randomBytes(16).toString("hex")}`,
151
+ serverUrl: "http://127.0.0.1:8787",
152
+ attachedAt: new Date().toISOString(),
153
+ };
154
+ fs.writeFileSync(file, JSON.stringify(attachment, null, 2), { mode: 0o600 });
155
+ }
156
+ function safeProviderFailure(error) {
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ if (/\b(?:401|403)\b|unauth|invalid.*(?:key|token)|credential/i.test(message)) {
159
+ return new Error("内置 Pi provider 拒绝认证(401/403);请重新登录或检查 credential");
160
+ }
161
+ if (/timeout|超时|ETIMEDOUT/i.test(message))
162
+ return new Error("内置 Pi provider readiness 超时;请检查网络和 endpoint");
163
+ if (/ENOTFOUND|ECONN|fetch failed|network|TLS/i.test(message))
164
+ return new Error("内置 Pi provider endpoint 不可达;请检查网络、TLS 和 Base URL");
165
+ if (/model|模型/i.test(message))
166
+ return new Error("内置 Pi provider 不接受所选模型;请检查 provider/model");
167
+ return new Error("内置 Pi provider readiness 失败;credential/config 已回滚,请检查 provider 状态后重试");
168
+ }
169
+ function printAgents(config) {
170
+ const names = Object.keys(config.agents);
171
+ if (!names.length) {
172
+ say("尚未配置 Agent。");
173
+ return;
174
+ }
175
+ say("已配置 Agents:");
176
+ for (const name of names) {
177
+ const agent = config.agents[name];
178
+ say(` ${name === config.activeAgent ? "*" : " "} ${name}: profile=${agent.feishuProfile} runtime=${agent.runtime} model=${agent.model}`);
179
+ }
180
+ }
181
+ export async function main() {
182
+ if (has("--model")) {
183
+ die("setup 不支持配置 model;请用 larkin model <id> 单独切换(bun run model -- <id>,带合法值校验)");
184
+ }
185
+ if (options.help) {
186
+ say(`larkin setup(内部绑定阶段)
187
+
188
+ 用法:
189
+ larkin setup --profile <已有profile> [参数]
190
+
191
+ 参数(都可省略;单 bot 单 agent 场景零参数即可):
192
+ --agent <App ID> 必须与所选 profile 的 App ID 一致;缺省自动使用该 App ID
193
+ --profile <name> 复用已有 lark-cli profile(可用 profile 名或 appId;缺省交互选择)
194
+ --runtime <runtime> pi / codex / claude;缺省沿用已有 agent 的 runtime,全新 agent 默认 pi
195
+ --yes 非交互接受默认项
196
+ --list 查看已经配置的 Agent
197
+ --help 显示帮助
198
+
199
+ 普通用户请直接运行 larkin setup;该内部阶段只负责绑定已取得的凭证。
200
+ 模型不在 setup 配置:沿用已有值(换 runtime 时重置为该 runtime 默认),切换用 larkin model <id>。`);
201
+ return;
202
+ }
203
+ const loaded = loadConfig();
204
+ const config = loaded.config;
205
+ const selection = readSetupSelection(options.selectionFile);
206
+ if (options.list) {
207
+ printAgents(config);
208
+ return;
209
+ }
210
+ say("=== larkin 多 Agent onboarding ===");
211
+ const profile = await pickProfile();
212
+ if (typeof profile?.appId !== "string" || !APP_ID.test(profile.appId) || profile.name !== profile.appId) {
213
+ die("profile name 与合法 App ID 必须完全一致;未修改 Agent 配置,请重新运行 larkin setup 刷新 profile");
214
+ }
215
+ if (!options.profile)
216
+ verifyBot(profile);
217
+ const requestedAgent = options.agent || profile.appId;
218
+ const prior = config.agents[profile.appId];
219
+ const runtime = selection?.runtime || options.runtime || prior?.runtime || "pi";
220
+ const piDistribution = runtime === "pi"
221
+ ? (selection?.runtime === "pi" ? selection.distribution : prior?.piDistribution || "external")
222
+ : undefined;
223
+ validateRuntime(runtime);
224
+ const defaultModel = larkinConfig.defaultModelFor(runtime);
225
+ const selectedModel = selection?.runtime === "pi" && selection.distribution === "builtin" ? selection.model : undefined;
226
+ const targetModelId = selectedModel || (prior?.runtime === runtime ? prior.model : defaultModel);
227
+ let runtimeModels = larkinConfig.loadRuntimeModels()[runtime];
228
+ if (runtime === "pi" && piDistribution === "builtin") {
229
+ runtimeModels = [{ id: targetModelId, supportedReasoningEfforts: [] }];
230
+ }
231
+ else if (runtime === "pi") {
232
+ const piCatalog = await discoverPiModelCatalog({
233
+ cwd: layout.workspaceDir(profile.appId),
234
+ ...(process.env.PI_CODING_AGENT_DIR ? { agentDir: process.env.PI_CODING_AGENT_DIR } : {}),
235
+ });
236
+ const effective = piCatalog.models.find((model) => model.id === piCatalog.effectiveModel);
237
+ runtimeModels = [{ id: "default", supportedReasoningEfforts: effective?.supportedReasoningEfforts || [] }, ...piCatalog.models];
238
+ }
239
+ const targetModel = runtimeModels.find((model) => model.id === targetModelId);
240
+ if (!targetModel)
241
+ die(`目标模型 ${runtime}/${targetModelId} 不在 runtime 模型目录中;未修改 Agent 配置`);
242
+ const stored = planSingleRootBinding({
243
+ config: larkinConfig.toStored(config),
244
+ profile,
245
+ requestedAgent,
246
+ runtime: selection?.runtime || options.runtime,
247
+ ...(selection?.runtime === "pi" ? { piDistribution: selection.distribution } : {}),
248
+ ...(selectedModel ? { model: selectedModel } : {}),
249
+ defaultModel,
250
+ supportedReasoningEfforts: targetModel.supportedReasoningEfforts || [],
251
+ now: new Date().toISOString(),
252
+ });
253
+ const bound = stored.agents[profile.appId];
254
+ fs.mkdirSync(CFG_DIR, { recursive: true });
255
+ let providerTransaction = null;
256
+ let providerPublished = false;
257
+ const authAlreadyCompleted = selection?.runtime === "pi" && selection.distribution === "builtin"
258
+ && selection.authCompleted === true;
259
+ const readinessAlreadyCompleted = authAlreadyCompleted
260
+ && selection.readinessCompleted === true;
261
+ const authAbort = new AbortController();
262
+ const cancelProviderTransaction = (signal) => {
263
+ authAbort.abort();
264
+ if (!providerPublished)
265
+ providerTransaction?.rollback();
266
+ process.exit(signal === "SIGINT" ? 130 : 143);
267
+ };
268
+ const onSigint = () => cancelProviderTransaction("SIGINT");
269
+ const onSigterm = () => cancelProviderTransaction("SIGTERM");
270
+ process.once("SIGINT", onSigint);
271
+ process.once("SIGTERM", onSigterm);
272
+ let authQuestioner = null;
273
+ try {
274
+ providerTransaction = selection?.runtime === "pi" && selection.distribution === "builtin"
275
+ && !authAlreadyCompleted ? beginBuiltinPiCredentialTransaction(CFG_DIR, profile.appId)
276
+ : null;
277
+ if (selection?.runtime === "pi" && selection.distribution === "builtin") {
278
+ const official = selection.preset === "official" ? selection : null;
279
+ const configured = official ? null : configureBuiltinPiProviderModel(CFG_DIR, profile.appId, selection);
280
+ const providerId = official?.providerId || configured.provider;
281
+ const authType = official?.authType || "api_key";
282
+ let piRuntime = null;
283
+ if (!authAlreadyCompleted) {
284
+ piRuntime = await createOfficialPiModelRuntime(CFG_DIR, profile.appId);
285
+ authQuestioner = terminalSetupQuestioner();
286
+ say(`正在通过捆绑官方 Pi 登录 ${providerId}(${authType})…`);
287
+ try {
288
+ await runOfficialPiLogin(piRuntime, providerId, authType, createOfficialPiAuthInteraction({
289
+ questioner: authQuestioner,
290
+ report: (message) => say(message),
291
+ openUrl: openAuthUrl,
292
+ signal: authAbort.signal,
293
+ }));
294
+ }
295
+ catch {
296
+ throw new Error(`官方 Pi ${providerId} 登录失败或已取消;credential/config 未修改`);
297
+ }
298
+ }
299
+ if (!readinessAlreadyCompleted) {
300
+ piRuntime ??= await createOfficialPiModelRuntime(CFG_DIR, profile.appId);
301
+ say("正在验证内置 Pi provider(受控单轮,不发送飞书消息)…");
302
+ try {
303
+ if (!(process.env.LARKIN_TEST_SKIP_BUILTIN_PI_PROVIDER_TURN === "1" && process.env.LARKIN_TEST_BOT_REGISTER_MODULE)) {
304
+ await verifyOfficialPiProviderTurn(piRuntime, selection.model, authAbort.signal);
305
+ }
306
+ }
307
+ catch (error) {
308
+ throw safeProviderFailure(error);
309
+ }
310
+ }
311
+ }
312
+ larkinConfig.commitSetupConfig(process.env, loaded.revision, stored);
313
+ providerTransaction?.commit();
314
+ providerPublished = true;
315
+ }
316
+ catch (error) {
317
+ providerTransaction?.rollback();
318
+ throw error;
319
+ }
320
+ finally {
321
+ authQuestioner?.close?.();
322
+ process.off("SIGINT", onSigint);
323
+ process.off("SIGTERM", onSigterm);
324
+ }
325
+ fabricateAttachment(layout.root, stored.serverId);
326
+ say(`\n✓ 已写配置: ${CFG_FILE}`);
327
+ say(` agent=${profile.appId} runtime=${bound.runtime} model=${bound.model}`);
328
+ say(` 单服务 serverId=${stored.serverId} larkinHome=${layout.root}`);
329
+ say("\n启动这个 Agent:");
330
+ say(" larkin start # 单服务启动全部已配置 Agent");
331
+ say(` larkin start --agent ${profile.appId} # 仅调试这个 Agent`);
332
+ say("创建独立飞书机器人 + Agent:");
333
+ say(" larkin setup");
334
+ }
335
+ if (path.resolve(process.argv[1] || "") === path.resolve(fileURLToPath(import.meta.url))) {
336
+ main().catch((error) => die(error instanceof Error ? error.message : String(error)));
337
+ }
@@ -0,0 +1,52 @@
1
+ function cloneAgent(agent) {
2
+ const chatMentionPolicies = { ...(agent.chatMentionPolicies || {}) };
3
+ for (const chatId of agent.noMentionChats || [])
4
+ chatMentionPolicies[chatId] = "free";
5
+ return {
6
+ runtime: agent.runtime,
7
+ model: agent.model,
8
+ ...(agent.piDistribution ? { piDistribution: agent.piDistribution } : {}),
9
+ ...(agent.effort ? { effort: agent.effort } : {}),
10
+ ...(agent.mentionPolicy ? { mentionPolicy: agent.mentionPolicy } : {}),
11
+ ...(Object.keys(chatMentionPolicies).length ? { chatMentionPolicies } : {}),
12
+ ...(agent.createdAt ? { createdAt: agent.createdAt } : {}),
13
+ };
14
+ }
15
+ export function planSingleRootBinding({ config, profile, requestedAgent, runtime, piDistribution, model, defaultModel, supportedReasoningEfforts, now, }) {
16
+ if (!/^cli_[A-Za-z0-9]+$/.test(profile.appId))
17
+ throw new Error(`profile.appId 不是合法飞书 App ID:${profile.appId}`);
18
+ if (typeof defaultModel !== "string" || !defaultModel)
19
+ throw new Error("defaultModel 必须由 runtime 模型目录显式提供");
20
+ if (!Array.isArray(supportedReasoningEfforts) || supportedReasoningEfforts.some((effort) => typeof effort !== "string" || !effort)) {
21
+ throw new Error("supportedReasoningEfforts 必须由目标模型目录显式提供为字符串数组");
22
+ }
23
+ if (requestedAgent && requestedAgent !== profile.appId) {
24
+ throw new Error(`--agent 必须与 profile App ID 一致:${requestedAgent} != ${profile.appId}`);
25
+ }
26
+ const agents = Object.fromEntries(Object.entries(config.agents).map(([key, agent]) => [key, cloneAgent(agent)]));
27
+ const prior = agents[profile.appId];
28
+ const nextRuntime = runtime || prior?.runtime || "pi";
29
+ const nextPiDistribution = nextRuntime === "pi"
30
+ ? piDistribution || (prior?.runtime === "pi" ? prior.piDistribution : undefined)
31
+ : undefined;
32
+ if (prior) {
33
+ const { effort, piDistribution: _priorPiDistribution, ...withoutEffort } = prior;
34
+ agents[profile.appId] = {
35
+ ...withoutEffort,
36
+ runtime: nextRuntime,
37
+ model: model || (prior.runtime === nextRuntime ? prior.model : defaultModel),
38
+ ...(nextPiDistribution ? { piDistribution: nextPiDistribution } : {}),
39
+ ...(effort && supportedReasoningEfforts.includes(effort) ? { effort } : {}),
40
+ };
41
+ }
42
+ else {
43
+ agents[profile.appId] = { runtime: nextRuntime, model: model || "default", ...(nextPiDistribution ? { piDistribution: nextPiDistribution } : {}), createdAt: now };
44
+ }
45
+ return {
46
+ version: 4,
47
+ serverId: config.serverId,
48
+ mentionPolicy: config.mentionPolicy || "require",
49
+ activeAgent: config.activeAgent || profile.appId,
50
+ agents,
51
+ };
52
+ }
package/package.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "larkin",
3
+ "version": "0.2.70",
4
+ "private": false,
5
+ "larkinPackageRole": "npm-published",
6
+ "files": [
7
+ "dist/",
8
+ "README.md",
9
+ "LICENSE",
10
+ "SECURITY.md",
11
+ "CONTRIBUTING.md",
12
+ "artifacts/release/THIRD_PARTY_NOTICES.txt"
13
+ ],
14
+ "packageManager": "bun@1.3.14",
15
+ "license": "Apache-2.0",
16
+ "type": "module",
17
+ "description": "把 Codex、Claude Code 和 Pi Agent Runtime 接到飞书的本地 Runtime Host",
18
+ "bin": {
19
+ "larkin": "dist/app/cli.mjs"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/eddiearc/larkin.git"
24
+ },
25
+ "scripts": {
26
+ "clean": "bun run scripts/clean.mjs",
27
+ "typecheck": "bun run scripts/typecheck.mjs",
28
+ "build": "bun run scripts/build.mjs",
29
+ "prepare": "bun run build",
30
+ "version:bump": "bun run scripts/bump-version.mjs",
31
+ "release:check-version": "bun run scripts/check-release-version.mjs",
32
+ "release:smoke": "bun run scripts/release/smoke.ts",
33
+ "publication:check": "bun run scripts/check-publication.mjs",
34
+ "publication:check:tree": "bun run scripts/check-publication.mjs --tree-only",
35
+ "licenses:generate": "bun run scripts/generate-third-party-notices.mjs --output artifacts/release/THIRD_PARTY_NOTICES.txt",
36
+ "licenses:check": "bun run scripts/generate-third-party-notices.mjs --check",
37
+ "test:standalone-acceptance": "LARKIN_RUN_STANDALONE_BINARY_ACCEPTANCE=1 bun test --max-concurrency 1 test/integration/build/standalone-binary-acceptance.test.mjs",
38
+ "test:standalone-setup-workflow": "LARKIN_RUN_STANDALONE_SETUP_WORKFLOW=1 bun test --max-concurrency 1 test/integration/build/standalone-setup-workflow.test.mjs",
39
+ "test:pi-preflight-standalone": "LARKIN_RUN_PI_PREFLIGHT_STANDALONE=1 bun test --max-concurrency 1 test/integration/build/pi-prompt-preflight-standalone.test.mjs",
40
+ "test:unit": "bun test --max-concurrency 1 test/unit",
41
+ "test:frontend": "bun run vitest run --config src/dashboard/web/vitest.config.mjs",
42
+ "test:integration": "bun test --max-concurrency 1 test/integration",
43
+ "test:e2e": "bun test --max-concurrency 1 test/e2e",
44
+ "test:git-dependency": "LARKIN_RUN_GIT_DEPENDENCY_TEST=1 bun test --max-concurrency 1 test/integration/package/git-dependency-install.test.mjs",
45
+ "test:pi-model-catalog": "bun test --max-concurrency 1 test/unit/runtime/pi-model-catalog.test.mjs",
46
+ "test:live:three-agent": "bun test --max-concurrency 1 test/live/three-agent-live-acceptance.test.mjs",
47
+ "test:live:pi-catalog": "bun test --max-concurrency 1 test/live/pi-model-catalog-live.test.mjs",
48
+ "test:live:pi-local-rpc-oauth": "LARKIN_RUN_PI_LOCAL_RPC_OAUTH_E2E=1 bun test --max-concurrency 1 test/live/pi-local-rpc-oauth-live.test.mjs",
49
+ "test:live:pi-local-rpc-provider": "LARKIN_RUN_PI_LOCAL_RPC_OAUTH_E2E=1 LARKIN_PI_LOCAL_RPC_REQUIRE_MODEL_OVERRIDE=1 bun test --max-concurrency 1 test/live/pi-local-rpc-oauth-live.test.mjs",
50
+ "setup": "bun run dist/app/setup.mjs",
51
+ "status": "bun run dist/app/agent-config.mjs agents",
52
+ "agents": "bun run dist/app/agent-config.mjs agents",
53
+ "model": "bun run dist/app/agent-config.mjs model",
54
+ "runtime": "bun run dist/app/agent-config.mjs runtime",
55
+ "effort": "bun run dist/app/agent-config.mjs effort",
56
+ "chats": "bun run dist/app/agent-config.mjs chats",
57
+ "start": "bun run dist/app/run.mjs",
58
+ "test": "bun run typecheck && bun run build && bun run test:frontend && bun run test:unit && bun run test:integration && bun run test:e2e",
59
+ "test:live:lark-passthrough": "bun test --max-concurrency 1 test/live/lark-passthrough-live.test.mjs",
60
+ "test:live:document-comment": "bun run build && bun test --max-concurrency 1 test/live/document-comment-live.test.mjs",
61
+ "test:telemetry:lgtm": "bun run build && bun run scripts/telemetry/lgtm-acceptance.ts",
62
+ "test:live:card-interaction": "bun test --max-concurrency 1 test/live/card-interaction-live.test.mjs",
63
+ "test:live:card-interaction-evidence": "bun run test/live/card-interaction-evidence-driver.mjs",
64
+ "test:live:runtime-agent-interface-v2": "bun test --max-concurrency 1 test/live/runtime-agent-interface-v2-live.test.mjs",
65
+ "test:live:runtime-agent-interface-v2:read-only": "LARKIN_RUN_RUNTIME_AGENT_INTERFACE_V2_LIVE=1 bun test --max-concurrency 1 test/live/runtime-agent-interface-v2-live.test.mjs",
66
+ "test:live:runtime-agent-interface-v2:hold-host": "bun run build && bun run test/live/runtime-agent-interface-v2-hold-host.mjs app/runtime-process.mjs",
67
+ "test:eval:long-running-im-progress": "bun run build && bun test --max-concurrency 1 test/live/long-running-im-progress-live.test.mjs",
68
+ "test:eval:pi-subagents-background": "bun run build && bun test --max-concurrency 1 test/live/pi-subagents-background-live.test.mjs",
69
+ "test:eval:pi-bash-timeout": "bun run build && bun test --max-concurrency 1 test/live/pi-bash-timeout-live.test.mjs",
70
+ "test:eval:runtime-agent-interface-v2": "bun test --max-concurrency 1 test/unit/evals/runtime-agent-interface-v2.test.mjs",
71
+ "test:eval:authoritative-freshness-gate": "bun test --max-concurrency 1 test/unit/evals/authoritative-freshness-gate.test.mjs",
72
+ "test:eval:agent-experience-v6": "bun test --max-concurrency 1 test/unit/evals/agent-experience-v6.test.mjs",
73
+ "test:eval:public-contribution-governance": "bun test --max-concurrency 1 test/unit/evals/public-contribution-governance.test.mjs",
74
+ "test:live:public-contribution-governance": "bun test --max-concurrency 1 test/live/public-contribution-governance-live.test.mjs",
75
+ "test:eval:authoritative-freshness-gate:native": "bun run build && LARKIN_RUN_AUTHORITATIVE_FRESHNESS_EVAL=1 bun test --max-concurrency 1 test/live/authoritative-freshness-gate-agent-eval.test.mjs",
76
+ "test:live:authoritative-freshness-gate": "bun test --max-concurrency 1 test/live/authoritative-freshness-gate-live.test.mjs",
77
+ "test:eval:runtime-agent-interface-v2:native": "bun run build && LARKIN_RUN_RUNTIME_AGENT_INTERFACE_V2_EVAL=1 bun test --max-concurrency 1 test/live/runtime-agent-interface-v2-agent-eval.test.mjs",
78
+ "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"
79
+ },
80
+ "dependencies": {
81
+ "@earendil-works/pi-coding-agent": "0.83.0",
82
+ "@larksuite/channel": "^0.4.0",
83
+ "@opentelemetry/api": "^1.9.1",
84
+ "@opentelemetry/resources": "^2.10.0",
85
+ "@opentelemetry/sdk-trace-base": "^2.10.0",
86
+ "@opentelemetry/sdk-trace-node": "^2.10.0",
87
+ "@radix-ui/react-dialog": "^1.1.21",
88
+ "lucide-react": "^1.26.0",
89
+ "proper-lockfile": "4.1.2",
90
+ "qrcode-terminal": "^0.12.0",
91
+ "react": "^19.2.8",
92
+ "react-dom": "^19.2.8",
93
+ "undici": "8.5.0"
94
+ },
95
+ "devDependencies": {
96
+ "@tailwindcss/vite": "^4.3.3",
97
+ "@testing-library/jest-dom": "^7.0.0",
98
+ "@testing-library/react": "^16.3.2",
99
+ "@testing-library/user-event": "^14.6.1",
100
+ "@tintinweb/pi-subagents": "0.14.3",
101
+ "@types/node": "^26.1.1",
102
+ "@types/react": "19.2.14",
103
+ "@types/react-dom": "19.2.3",
104
+ "@vitejs/plugin-react": "^6.0.4",
105
+ "jsdom": "^29.1.1",
106
+ "playwright-core": "^1.61.1",
107
+ "tailwindcss": "^4.3.3",
108
+ "typescript": "^7.0.2",
109
+ "vite": "^8.1.5",
110
+ "vitest": "^4.1.10"
111
+ }
112
+ }