larkway 0.3.64 → 0.3.66
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/README.md +7 -1
- package/README.zh.md +3 -1
- package/dist/cli/index.js +36 -8
- package/dist/main.js +97 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
|
|
10
10
|
|
|
11
|
-
**Current release: v0.3.
|
|
11
|
+
**Current release: v0.3.66**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -141,6 +141,12 @@ not enforcement.
|
|
|
141
141
|
- Secrets in `~/.larkway/.env` are shared across all bots on the bridge and
|
|
142
142
|
visible to any agent that can read your home directory — don't mix a
|
|
143
143
|
low-trust open bot and high-value credentials on the same host.
|
|
144
|
+
- By default all bots share the host's global lark-cli config dir — including
|
|
145
|
+
any personal `lark-cli auth login` you did there (calendar/mail/drive).
|
|
146
|
+
Set `lark_cli_isolated: true` in a bot's yaml to give it a private config
|
|
147
|
+
dir (`~/.larkway/<botId>/lark-cli/`) holding only its own app profile:
|
|
148
|
+
bot-only identity by default, personal access only if you explicitly
|
|
149
|
+
authorize inside that dir.
|
|
144
150
|
|
|
145
151
|
---
|
|
146
152
|
|
package/README.zh.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
|
|
10
10
|
|
|
11
|
-
**当前版本:v0.3.
|
|
11
|
+
**当前版本:v0.3.66**
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -115,6 +115,8 @@ Larkway 不注入 `ANTHROPIC_API_KEY` 或任何其他 API key。子进程继承
|
|
|
115
115
|
|
|
116
116
|
密钥只存在本机 `~/.larkway/.env`(权限 0600)。配置和 memory 不含密钥。
|
|
117
117
|
|
|
118
|
+
默认所有 bot 共享宿主机的全局 lark-cli 配置目录——包括你在里面做过的个人 `lark-cli auth login`(日历/邮箱/云盘)。在 bot 的 yaml 里设 `lark_cli_isolated: true` 可给它一个私有配置目录(`~/.larkway/<botId>/lark-cli/`),里面只有它自己的应用 profile:默认 bot-only 身份,只有你显式在该目录内授权,agent 才能获得个人资源访问。
|
|
119
|
+
|
|
118
120
|
---
|
|
119
121
|
|
|
120
122
|
## 功能
|
package/dist/cli/index.js
CHANGED
|
@@ -53,6 +53,7 @@ __export(paths_exports, {
|
|
|
53
53
|
resolveAgentWorkspacePathFromHome: () => resolveAgentWorkspacePathFromHome,
|
|
54
54
|
resolveAgentWorkspaceReposDir: () => resolveAgentWorkspaceReposDir,
|
|
55
55
|
resolveAgentWorkspaceSessionsDir: () => resolveAgentWorkspaceSessionsDir,
|
|
56
|
+
resolveBotLarkCliDir: () => resolveBotLarkCliDir,
|
|
56
57
|
resolveCandidateAlertsPath: () => resolveCandidateAlertsPath,
|
|
57
58
|
resolveKnowledgeDir: () => resolveKnowledgeDir,
|
|
58
59
|
resolveLarkwayDir: () => resolveLarkwayDir,
|
|
@@ -70,6 +71,9 @@ function larkwayHome() {
|
|
|
70
71
|
if (env && env.trim() !== "") return resolve(env);
|
|
71
72
|
return join(homedir(), ".larkway");
|
|
72
73
|
}
|
|
74
|
+
function resolveBotLarkCliDir(botId) {
|
|
75
|
+
return join(resolveLarkwayDir(botId), "lark-cli");
|
|
76
|
+
}
|
|
73
77
|
function resolveLarkwayDir(botId) {
|
|
74
78
|
if (botId === void 0 || botId === LEGACY_BOT_ID) {
|
|
75
79
|
return larkwayHome();
|
|
@@ -111848,6 +111852,9 @@ import { execFile as execFileCallback } from "node:child_process";
|
|
|
111848
111852
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile4, rename as rename3, unlink as unlink4 } from "node:fs/promises";
|
|
111849
111853
|
import path9 from "node:path";
|
|
111850
111854
|
import { promisify as promisify4 } from "node:util";
|
|
111855
|
+
function larkCliEnvFor(configDir) {
|
|
111856
|
+
return configDir ? { ...process.env, LARKSUITE_CLI_CONFIG_DIR: configDir } : void 0;
|
|
111857
|
+
}
|
|
111851
111858
|
function isChatAccessGoneError(e) {
|
|
111852
111859
|
const parts = [];
|
|
111853
111860
|
if (e instanceof Error) parts.push(e.message);
|
|
@@ -112783,7 +112790,10 @@ var init_channelClient = __esm({
|
|
|
112783
112790
|
for (let attempt = 1; attempt <= GAP_FILL_MAX_ATTEMPTS; attempt++) {
|
|
112784
112791
|
if (this.closed) throw lastErr ?? new Error("closed");
|
|
112785
112792
|
try {
|
|
112786
|
-
|
|
112793
|
+
{
|
|
112794
|
+
const isoEnv = larkCliEnvFor(this.opts.larkCliConfigDir);
|
|
112795
|
+
return isoEnv ? await execFile4(larkCli, args, { env: isoEnv }) : await execFile4(larkCli, args);
|
|
112796
|
+
}
|
|
112787
112797
|
} catch (e) {
|
|
112788
112798
|
lastErr = e;
|
|
112789
112799
|
if (isChatAccessGoneError(e)) throw e;
|
|
@@ -112945,7 +112955,8 @@ var init_channelClient = __esm({
|
|
|
112945
112955
|
...profileArgs
|
|
112946
112956
|
];
|
|
112947
112957
|
if (pageToken) args.push("--page-token", pageToken);
|
|
112948
|
-
const
|
|
112958
|
+
const isoEnv = larkCliEnvFor(this.opts.larkCliConfigDir);
|
|
112959
|
+
const { stdout: stdout2 } = isoEnv ? await execFile4(larkCli, args, { env: isoEnv }) : await execFile4(larkCli, args);
|
|
112949
112960
|
const parsed = JSON.parse(stdout2);
|
|
112950
112961
|
const chats = parseLarkCliMessages(stdout2) ?? [];
|
|
112951
112962
|
fetched += chats.length;
|
|
@@ -126009,6 +126020,17 @@ var BotConfigSchema = external_exports.object({
|
|
|
126009
126020
|
* V1 single-bot path doesn't need this (uses default profile naturally).
|
|
126010
126021
|
*/
|
|
126011
126022
|
lark_cli_profile: external_exports.string().min(1).optional(),
|
|
126023
|
+
/**
|
|
126024
|
+
* BL-50: per-bot lark-cli identity isolation. When true, every lark-cli
|
|
126025
|
+
* invocation for this bot (agent subprocess, gap-fill, roster, health scan)
|
|
126026
|
+
* runs with LARKSUITE_CLI_CONFIG_DIR pointed at ~/.larkway/<botId>/lark-cli/
|
|
126027
|
+
* — a private config dir holding ONLY this bot's app profile. The
|
|
126028
|
+
* maintainer's personal lark-cli login (calendar/mail/drive access) in the
|
|
126029
|
+
* shared global dir becomes invisible to this bot's agent ("bot-only" by
|
|
126030
|
+
* default; grant user identity by running `lark-cli auth login` with the
|
|
126031
|
+
* same LARKSUITE_CLI_CONFIG_DIR). Default false = shared dir (pre-BL-50).
|
|
126032
|
+
*/
|
|
126033
|
+
lark_cli_isolated: external_exports.boolean().optional(),
|
|
126012
126034
|
/**
|
|
126013
126035
|
* Env-var *name* (not value) that holds this bot's Git PAT.
|
|
126014
126036
|
* Read from process.env at startup and injected as GITLAB_TOKEN into the
|
|
@@ -130180,6 +130202,7 @@ import { spawn as spawn2, execFile as execFile7 } from "node:child_process";
|
|
|
130180
130202
|
import { openSync, constants as fsConstants2 } from "node:fs";
|
|
130181
130203
|
import { readFile as readFile10, writeFile as writeFile9, unlink as unlink6, mkdir as mkdir8, readdir as readdir5, access as access8 } from "node:fs/promises";
|
|
130182
130204
|
import { promisify as promisify7 } from "node:util";
|
|
130205
|
+
import { fileURLToPath } from "node:url";
|
|
130183
130206
|
import path17 from "node:path";
|
|
130184
130207
|
import process5 from "node:process";
|
|
130185
130208
|
import { stat as stat4 } from "node:fs/promises";
|
|
@@ -130555,7 +130578,7 @@ function bridgePidPath(larkwayDir) {
|
|
|
130555
130578
|
return path17.join(larkwayDir, "bridge.pid");
|
|
130556
130579
|
}
|
|
130557
130580
|
function resolveRepoRoot() {
|
|
130558
|
-
const here =
|
|
130581
|
+
const here = fileURLToPath(import.meta.url);
|
|
130559
130582
|
return path17.resolve(path17.dirname(here), "..", "..");
|
|
130560
130583
|
}
|
|
130561
130584
|
async function resolveDistMain() {
|
|
@@ -131532,7 +131555,7 @@ import { randomBytes } from "node:crypto";
|
|
|
131532
131555
|
import { existsSync } from "node:fs";
|
|
131533
131556
|
import { readFile as readFile14, stat as stat6 } from "node:fs/promises";
|
|
131534
131557
|
import path27 from "node:path";
|
|
131535
|
-
import { fileURLToPath as
|
|
131558
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
131536
131559
|
|
|
131537
131560
|
// src/web/api.ts
|
|
131538
131561
|
import { readdir as readdir6, readFile as readFile13, stat as stat5 } from "node:fs/promises";
|
|
@@ -131694,10 +131717,10 @@ async function summarizeMemoryMetrics(filePath = resolveMemoryMetricsPath(), sin
|
|
|
131694
131717
|
|
|
131695
131718
|
// src/version.ts
|
|
131696
131719
|
import { readFileSync } from "node:fs";
|
|
131697
|
-
import { fileURLToPath } from "node:url";
|
|
131720
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
131698
131721
|
import path23 from "node:path";
|
|
131699
131722
|
function resolveLarkwayVersion(importMetaUrl, fallback = "unknown") {
|
|
131700
|
-
const here = path23.dirname(
|
|
131723
|
+
const here = path23.dirname(fileURLToPath2(importMetaUrl));
|
|
131701
131724
|
const candidates = [
|
|
131702
131725
|
path23.join(here, "..", "package.json"),
|
|
131703
131726
|
path23.join(here, "..", "..", "package.json")
|
|
@@ -132395,7 +132418,12 @@ async function runHealthScan(bot, hostConfigStore) {
|
|
|
132395
132418
|
["--profile", profile, "auth", "status", "--json"],
|
|
132396
132419
|
// commandProbeEnv:补齐 ~/.local/bin 等用户级安装目录(mini 实测:精简
|
|
132397
132420
|
// PATH 的 UI 进程探测不到工具,与登录态探测自相矛盾)
|
|
132398
|
-
{
|
|
132421
|
+
{
|
|
132422
|
+
timeout: HEALTH_SCAN_PROBE_TIMEOUT_MS,
|
|
132423
|
+
maxBuffer: 1024 * 1024,
|
|
132424
|
+
// BL-50: probe an isolated bot inside its private config dir.
|
|
132425
|
+
env: bot.lark_cli_isolated ? { ...commandProbeEnv(), LARKSUITE_CLI_CONFIG_DIR: resolveBotLarkCliDir(bot.id) } : commandProbeEnv()
|
|
132426
|
+
}
|
|
132399
132427
|
);
|
|
132400
132428
|
const payload = healthScanParseJson(stdout2);
|
|
132401
132429
|
const botIdentity = payload?.identities?.bot;
|
|
@@ -133462,7 +133490,7 @@ function splitPath(p) {
|
|
|
133462
133490
|
|
|
133463
133491
|
// src/web/server.ts
|
|
133464
133492
|
var HOST = "127.0.0.1";
|
|
133465
|
-
var __dirname2 = path27.dirname(
|
|
133493
|
+
var __dirname2 = path27.dirname(fileURLToPath3(import.meta.url));
|
|
133466
133494
|
var PUBLIC_DIR = resolvePublicDir();
|
|
133467
133495
|
function resolvePublicDir() {
|
|
133468
133496
|
const candidates = [
|
package/dist/main.js
CHANGED
|
@@ -115576,6 +115576,9 @@ function larkwayHome() {
|
|
|
115576
115576
|
if (env && env.trim() !== "") return resolve(env);
|
|
115577
115577
|
return join(homedir(), ".larkway");
|
|
115578
115578
|
}
|
|
115579
|
+
function resolveBotLarkCliDir(botId) {
|
|
115580
|
+
return join(resolveLarkwayDir(botId), "lark-cli");
|
|
115581
|
+
}
|
|
115579
115582
|
function resolveLarkwayDir(botId) {
|
|
115580
115583
|
if (botId === void 0 || botId === LEGACY_BOT_ID) {
|
|
115581
115584
|
return larkwayHome();
|
|
@@ -116486,6 +116489,9 @@ var ChannelMessageLookupClient = class {
|
|
|
116486
116489
|
|
|
116487
116490
|
// src/lark/channelClient.ts
|
|
116488
116491
|
var execFile = promisify(execFileCallback);
|
|
116492
|
+
function larkCliEnvFor(configDir) {
|
|
116493
|
+
return configDir ? { ...process.env, LARKSUITE_CLI_CONFIG_DIR: configDir } : void 0;
|
|
116494
|
+
}
|
|
116489
116495
|
var LEARNED_CHATS_LIMIT = 100;
|
|
116490
116496
|
var SEEN_MESSAGES_LIMIT = 1e3;
|
|
116491
116497
|
var MAX_MESSAGE_ATTEMPTS = 5;
|
|
@@ -117409,7 +117415,10 @@ var ChannelClient = class {
|
|
|
117409
117415
|
for (let attempt = 1; attempt <= GAP_FILL_MAX_ATTEMPTS; attempt++) {
|
|
117410
117416
|
if (this.closed) throw lastErr ?? new Error("closed");
|
|
117411
117417
|
try {
|
|
117412
|
-
|
|
117418
|
+
{
|
|
117419
|
+
const isoEnv = larkCliEnvFor(this.opts.larkCliConfigDir);
|
|
117420
|
+
return isoEnv ? await execFile(larkCli, args, { env: isoEnv }) : await execFile(larkCli, args);
|
|
117421
|
+
}
|
|
117413
117422
|
} catch (e) {
|
|
117414
117423
|
lastErr = e;
|
|
117415
117424
|
if (isChatAccessGoneError(e)) throw e;
|
|
@@ -117571,7 +117580,8 @@ var ChannelClient = class {
|
|
|
117571
117580
|
...profileArgs
|
|
117572
117581
|
];
|
|
117573
117582
|
if (pageToken) args.push("--page-token", pageToken);
|
|
117574
|
-
const
|
|
117583
|
+
const isoEnv = larkCliEnvFor(this.opts.larkCliConfigDir);
|
|
117584
|
+
const { stdout } = isoEnv ? await execFile(larkCli, args, { env: isoEnv }) : await execFile(larkCli, args);
|
|
117575
117585
|
const parsed = JSON.parse(stdout);
|
|
117576
117586
|
const chats = parseLarkCliMessages(stdout) ?? [];
|
|
117577
117587
|
fetched += chats.length;
|
|
@@ -118952,6 +118962,7 @@ function deriveTriggerFacts(parsed, isNewThread, larkCliProfile) {
|
|
|
118952
118962
|
var ANSWER_BEGIN_MARKER = "LARKWAY_ANSWER_BEGIN";
|
|
118953
118963
|
var ANSWER_END_MARKER = "LARKWAY_ANSWER_END";
|
|
118954
118964
|
var STREAM_HOLD_CHARS = ANSWER_END_MARKER.length + 2;
|
|
118965
|
+
var WAITING_CATCH_UP_MAX_CHARS = 16 * 1024;
|
|
118955
118966
|
function stripLeadingNewline(text) {
|
|
118956
118967
|
return text.replace(/^\r?\n/, "");
|
|
118957
118968
|
}
|
|
@@ -118989,6 +119000,7 @@ var AnswerChannelExtractor = class {
|
|
|
118989
119000
|
buffer = "";
|
|
118990
119001
|
visibleText = "";
|
|
118991
119002
|
lastSnapshotText = "";
|
|
119003
|
+
lastWaitingCatchUpText = "";
|
|
118992
119004
|
ingestDelta(text, raw) {
|
|
118993
119005
|
if (!text || this.mode === "closed") return [];
|
|
118994
119006
|
this.buffer += text;
|
|
@@ -119018,15 +119030,45 @@ var AnswerChannelExtractor = class {
|
|
|
119018
119030
|
if (this.lastSnapshotText && text.startsWith(this.lastSnapshotText)) {
|
|
119019
119031
|
const delta = text.slice(this.lastSnapshotText.length);
|
|
119020
119032
|
this.lastSnapshotText = text;
|
|
119021
|
-
|
|
119033
|
+
const events = delta ? this.ingestDelta(delta, raw) : [];
|
|
119034
|
+
return this.withWaitingCatchUp(events, text, raw);
|
|
119022
119035
|
}
|
|
119023
119036
|
if (this.lastSnapshotText === "") {
|
|
119024
119037
|
this.lastSnapshotText = text;
|
|
119025
|
-
return this.ingestDelta(text, raw);
|
|
119038
|
+
return this.withWaitingCatchUp(this.ingestDelta(text, raw), text, raw);
|
|
119026
119039
|
}
|
|
119027
119040
|
this.lastSnapshotText = text;
|
|
119028
119041
|
return this.ingestSnapshot(text, raw);
|
|
119029
119042
|
}
|
|
119043
|
+
/**
|
|
119044
|
+
* Markerless catch-up for the growing-snapshot (delta-routed) path.
|
|
119045
|
+
*
|
|
119046
|
+
* The two ingestDelta-backed branches above emit NOTHING while the
|
|
119047
|
+
* extractor is still waiting for ANSWER_BEGIN — drain() just trims the
|
|
119048
|
+
* waiting buffer. For a turn whose entire reply carries no marker at all
|
|
119049
|
+
* (the agent forgot to write LARKWAY_ANSWER_BEGIN), the claude streaming
|
|
119050
|
+
* path therefore produced ZERO events, so the bridge's untrusted-text
|
|
119051
|
+
* rescue (handler.ts lastInternalText) could never fire and the user got
|
|
119052
|
+
* the "没有产出正文" error card while the full answer existed in the
|
|
119053
|
+
* transcript. The plain ingestSnapshot path already emits such text as
|
|
119054
|
+
* internal_text (splitAnswerChannelText's no-marker branch) — this aligns
|
|
119055
|
+
* the growing-snapshot path with that behavior: after a snapshot has been
|
|
119056
|
+
* delta-routed, if the WHOLE snapshot still contains no BEGIN marker,
|
|
119057
|
+
* re-emit it (bounded, deduped against the previous catch-up) as
|
|
119058
|
+
* internal_text.
|
|
119059
|
+
*
|
|
119060
|
+
* Marker semantics are untouched: the moment BEGIN appears anywhere in the
|
|
119061
|
+
* snapshot, ingestDelta's drain() has already transitioned out of waiting
|
|
119062
|
+
* mode (emitting the before-text exactly once), so the catch-up never
|
|
119063
|
+
* fires for marker-bearing turns.
|
|
119064
|
+
*/
|
|
119065
|
+
withWaitingCatchUp(events, snapshotText, raw) {
|
|
119066
|
+
if (this.mode !== "waiting") return events;
|
|
119067
|
+
const bounded = snapshotText.length > WAITING_CATCH_UP_MAX_CHARS ? snapshotText.slice(snapshotText.length - WAITING_CATCH_UP_MAX_CHARS) : snapshotText;
|
|
119068
|
+
if (!bounded.trim() || bounded === this.lastWaitingCatchUpText) return events;
|
|
119069
|
+
this.lastWaitingCatchUpText = bounded;
|
|
119070
|
+
return [...events, { type: "internal_text", text: bounded, raw }];
|
|
119071
|
+
}
|
|
119030
119072
|
drain(raw) {
|
|
119031
119073
|
const events = [];
|
|
119032
119074
|
if (this.mode === "waiting") {
|
|
@@ -119687,7 +119729,7 @@ async function resolveChatBotRoster(chatId, opts = {}) {
|
|
|
119687
119729
|
const cli = opts.larkCliPath ?? "lark-cli";
|
|
119688
119730
|
const args = ["im", "chat.members", "bots", "--chat-id", chatId, "--as", "bot"];
|
|
119689
119731
|
if (opts.profile) args.push("--profile", opts.profile);
|
|
119690
|
-
const exec = opts.exec ?? defaultExec(opts.timeoutMs ?? 1e4);
|
|
119732
|
+
const exec = opts.exec ?? defaultExec(opts.timeoutMs ?? 1e4, opts.larkCliConfigDir);
|
|
119691
119733
|
try {
|
|
119692
119734
|
const stdout = await exec(cli, args);
|
|
119693
119735
|
const roster = parseBotRoster(stdout);
|
|
@@ -119696,9 +119738,10 @@ async function resolveChatBotRoster(chatId, opts = {}) {
|
|
|
119696
119738
|
return null;
|
|
119697
119739
|
}
|
|
119698
119740
|
}
|
|
119699
|
-
function defaultExec(timeoutMs) {
|
|
119741
|
+
function defaultExec(timeoutMs, larkCliConfigDir) {
|
|
119742
|
+
const env = larkCliConfigDir ? { ...process.env, LARKSUITE_CLI_CONFIG_DIR: larkCliConfigDir } : void 0;
|
|
119700
119743
|
return (cmd, args) => new Promise((resolve2, reject) => {
|
|
119701
|
-
execFile2(cmd, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (err, stdout) => {
|
|
119744
|
+
execFile2(cmd, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024, ...env ? { env } : {} }, (err, stdout) => {
|
|
119702
119745
|
if (err) reject(err);
|
|
119703
119746
|
else resolve2(stdout);
|
|
119704
119747
|
});
|
|
@@ -124194,6 +124237,8 @@ var BridgeHandler = class {
|
|
|
124194
124237
|
// V2: inject per-bot git identity; absent in V1 → runner.ts uses "larkway-bot" fallback
|
|
124195
124238
|
botGitIdentity: this.deps.botConfig?.git_identity,
|
|
124196
124239
|
gitlabToken: this.deps.gitlabToken,
|
|
124240
|
+
// BL-50: isolated bots get a private lark-cli config dir.
|
|
124241
|
+
...this.deps.botConfig?.lark_cli_isolated && this.deps.botConfig.id ? { larkCliConfigDir: resolveBotLarkCliDir(this.deps.botConfig.id) } : {},
|
|
124197
124242
|
model: this.deps.botConfig?.model,
|
|
124198
124243
|
effort: this.deps.botConfig?.effort,
|
|
124199
124244
|
onPerfMarker: (marker, atMs) => {
|
|
@@ -128438,6 +128483,17 @@ var BotConfigSchema = external_exports.object({
|
|
|
128438
128483
|
* V1 single-bot path doesn't need this (uses default profile naturally).
|
|
128439
128484
|
*/
|
|
128440
128485
|
lark_cli_profile: external_exports.string().min(1).optional(),
|
|
128486
|
+
/**
|
|
128487
|
+
* BL-50: per-bot lark-cli identity isolation. When true, every lark-cli
|
|
128488
|
+
* invocation for this bot (agent subprocess, gap-fill, roster, health scan)
|
|
128489
|
+
* runs with LARKSUITE_CLI_CONFIG_DIR pointed at ~/.larkway/<botId>/lark-cli/
|
|
128490
|
+
* — a private config dir holding ONLY this bot's app profile. The
|
|
128491
|
+
* maintainer's personal lark-cli login (calendar/mail/drive access) in the
|
|
128492
|
+
* shared global dir becomes invisible to this bot's agent ("bot-only" by
|
|
128493
|
+
* default; grant user identity by running `lark-cli auth login` with the
|
|
128494
|
+
* same LARKSUITE_CLI_CONFIG_DIR). Default false = shared dir (pre-BL-50).
|
|
128495
|
+
*/
|
|
128496
|
+
lark_cli_isolated: external_exports.boolean().optional(),
|
|
128441
128497
|
/**
|
|
128442
128498
|
* Env-var *name* (not value) that holds this bot's Git PAT.
|
|
128443
128499
|
* Read from process.env at startup and injected as GITLAB_TOKEN into the
|
|
@@ -129669,9 +129725,12 @@ import { writeFile as writeFile3, unlink as unlink3, mkdir as mkdir3 } from "nod
|
|
|
129669
129725
|
import { join as join2 } from "node:path";
|
|
129670
129726
|
import { createInterface } from "node:readline";
|
|
129671
129727
|
var SIGKILL_GRACE_MS = 5e3;
|
|
129672
|
-
function buildEnv(botGitIdentity, gitlabToken) {
|
|
129728
|
+
function buildEnv(botGitIdentity, gitlabToken, larkCliConfigDir) {
|
|
129673
129729
|
const env = { ...process.env };
|
|
129674
129730
|
delete env["ANTHROPIC_API_KEY"];
|
|
129731
|
+
if (larkCliConfigDir !== void 0) {
|
|
129732
|
+
env["LARKSUITE_CLI_CONFIG_DIR"] = larkCliConfigDir;
|
|
129733
|
+
}
|
|
129675
129734
|
if (botGitIdentity) {
|
|
129676
129735
|
env["GIT_AUTHOR_NAME"] = botGitIdentity.name;
|
|
129677
129736
|
env["GIT_AUTHOR_EMAIL"] = botGitIdentity.email;
|
|
@@ -129831,7 +129890,7 @@ function* parseLinesMulti(line, answerExtractor = new AnswerChannelExtractor())
|
|
|
129831
129890
|
function runClaude(opts) {
|
|
129832
129891
|
const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
|
|
129833
129892
|
const [bin, args] = buildCommand(opts);
|
|
129834
|
-
const env = buildEnv(opts.botGitIdentity, opts.gitlabToken);
|
|
129893
|
+
const env = buildEnv(opts.botGitIdentity, opts.gitlabToken, opts.larkCliConfigDir);
|
|
129835
129894
|
const markPerf = createPerfMarker(opts.onPerfMarker);
|
|
129836
129895
|
const child = spawnPipedOutput(bin, args, {
|
|
129837
129896
|
env,
|
|
@@ -130078,6 +130137,7 @@ var ClaudeProcessPool = class {
|
|
|
130078
130137
|
#botId;
|
|
130079
130138
|
#botGitIdentity;
|
|
130080
130139
|
#gitlabToken;
|
|
130140
|
+
#larkCliConfigDir;
|
|
130081
130141
|
#idleMs;
|
|
130082
130142
|
#maxProcesses;
|
|
130083
130143
|
#pidListFilePath;
|
|
@@ -130120,6 +130180,7 @@ var ClaudeProcessPool = class {
|
|
|
130120
130180
|
this.#botId = opts.botId;
|
|
130121
130181
|
this.#botGitIdentity = opts.botGitIdentity;
|
|
130122
130182
|
this.#gitlabToken = opts.gitlabToken;
|
|
130183
|
+
this.#larkCliConfigDir = opts.larkCliConfigDir;
|
|
130123
130184
|
this.#idleMs = opts.idleMs ?? DEFAULT_WARM_PROCESS_IDLE_MS;
|
|
130124
130185
|
this.#maxProcesses = opts.maxProcesses ?? DEFAULT_MAX_PROCESSES;
|
|
130125
130186
|
this.#pidListFilePath = opts.pidListFilePath;
|
|
@@ -130524,7 +130585,7 @@ var ClaudeProcessPool = class {
|
|
|
130524
130585
|
}
|
|
130525
130586
|
#spawnEntry(key, threadId, opts, flags) {
|
|
130526
130587
|
const [bin, args] = buildWarmCommand(opts);
|
|
130527
|
-
const env = buildEnv(this.#botGitIdentity, this.#gitlabToken);
|
|
130588
|
+
const env = buildEnv(this.#botGitIdentity, this.#gitlabToken, this.#larkCliConfigDir);
|
|
130528
130589
|
const child = spawnPiped(bin, args, {
|
|
130529
130590
|
env,
|
|
130530
130591
|
...opts.cwd != null ? { cwd: opts.cwd } : {}
|
|
@@ -130872,10 +130933,13 @@ function productizeCodexFailure(stderr) {
|
|
|
130872
130933
|
}
|
|
130873
130934
|
return void 0;
|
|
130874
130935
|
}
|
|
130875
|
-
function buildCodexEnv(botGitIdentity, gitlabToken) {
|
|
130936
|
+
function buildCodexEnv(botGitIdentity, gitlabToken, larkCliConfigDir) {
|
|
130876
130937
|
const env = { ...process.env };
|
|
130877
130938
|
delete env["OPENAI_API_KEY"];
|
|
130878
130939
|
delete env["ANTHROPIC_API_KEY"];
|
|
130940
|
+
if (larkCliConfigDir !== void 0) {
|
|
130941
|
+
env["LARKSUITE_CLI_CONFIG_DIR"] = larkCliConfigDir;
|
|
130942
|
+
}
|
|
130879
130943
|
if (botGitIdentity) {
|
|
130880
130944
|
env["GIT_AUTHOR_NAME"] = botGitIdentity.name;
|
|
130881
130945
|
env["GIT_AUTHOR_EMAIL"] = botGitIdentity.email;
|
|
@@ -131002,7 +131066,7 @@ function extractThreadIdFromThreadResponse(obj) {
|
|
|
131002
131066
|
function runCodex(opts, codexBinPath = "codex") {
|
|
131003
131067
|
const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
|
|
131004
131068
|
const [bin, args] = buildCodexCommand(opts, codexBinPath);
|
|
131005
|
-
const env = buildCodexEnv(opts.botGitIdentity, opts.gitlabToken);
|
|
131069
|
+
const env = buildCodexEnv(opts.botGitIdentity, opts.gitlabToken, opts.larkCliConfigDir);
|
|
131006
131070
|
const mode = opts.permissionMode ?? "acceptEdits";
|
|
131007
131071
|
const requestById = /* @__PURE__ */ new Map();
|
|
131008
131072
|
let nextRequestId = 1;
|
|
@@ -131262,6 +131326,7 @@ var CodexProcessPool = class {
|
|
|
131262
131326
|
#codexBinPath;
|
|
131263
131327
|
#botGitIdentity;
|
|
131264
131328
|
#gitlabToken;
|
|
131329
|
+
#larkCliConfigDir;
|
|
131265
131330
|
#idleMs;
|
|
131266
131331
|
#pidFilePath;
|
|
131267
131332
|
#child;
|
|
@@ -131290,6 +131355,7 @@ var CodexProcessPool = class {
|
|
|
131290
131355
|
this.#codexBinPath = opts.codexBinPath;
|
|
131291
131356
|
this.#botGitIdentity = opts.botGitIdentity;
|
|
131292
131357
|
this.#gitlabToken = opts.gitlabToken;
|
|
131358
|
+
this.#larkCliConfigDir = opts.larkCliConfigDir;
|
|
131293
131359
|
this.#idleMs = opts.idleMs ?? DEFAULT_WARM_PROCESS_IDLE_MS2;
|
|
131294
131360
|
this.#pidFilePath = opts.pidFilePath;
|
|
131295
131361
|
}
|
|
@@ -131521,7 +131587,7 @@ var CodexProcessPool = class {
|
|
|
131521
131587
|
// -- process lifecycle -------------------------------------------------------
|
|
131522
131588
|
#spawnChild() {
|
|
131523
131589
|
const [bin, args] = buildCodexCommand({ prompt: "" }, this.#codexBinPath);
|
|
131524
|
-
const env = buildCodexEnv(this.#botGitIdentity, this.#gitlabToken);
|
|
131590
|
+
const env = buildCodexEnv(this.#botGitIdentity, this.#gitlabToken, this.#larkCliConfigDir);
|
|
131525
131591
|
const child = spawnPiped(bin, args, { env });
|
|
131526
131592
|
this.#child = child;
|
|
131527
131593
|
this.#pending.clear();
|
|
@@ -131807,7 +131873,7 @@ async function reapOrphanedWarmProcess(pidFilePath) {
|
|
|
131807
131873
|
}
|
|
131808
131874
|
|
|
131809
131875
|
// src/lark/profileBootstrap.ts
|
|
131810
|
-
function ensureLarkCliProfile(botId, profileName, appId, appSecret, _spawnSync = spawnProcessSync, _console = console) {
|
|
131876
|
+
function ensureLarkCliProfile(botId, profileName, appId, appSecret, _spawnSync = spawnProcessSync, _console = console, opts) {
|
|
131811
131877
|
_console.log(
|
|
131812
131878
|
`[larkway] bot "${botId}": provisioning lark-cli profile "${profileName}" for app ${appId.slice(0, 8)}\u2026`
|
|
131813
131879
|
);
|
|
@@ -131818,7 +131884,9 @@ function ensureLarkCliProfile(botId, profileName, appId, appSecret, _spawnSync =
|
|
|
131818
131884
|
{
|
|
131819
131885
|
input: appSecret,
|
|
131820
131886
|
encoding: "utf-8",
|
|
131821
|
-
timeout: 1e4
|
|
131887
|
+
timeout: 1e4,
|
|
131888
|
+
// BL-50: provision inside the bot's private config dir when isolated.
|
|
131889
|
+
...opts?.configDir ? { env: { ...process.env, LARKSUITE_CLI_CONFIG_DIR: opts.configDir } } : {}
|
|
131822
131890
|
}
|
|
131823
131891
|
);
|
|
131824
131892
|
if (initResult.status === 0) {
|
|
@@ -134578,7 +134646,14 @@ async function runV2Mode({
|
|
|
134578
134646
|
);
|
|
134579
134647
|
}
|
|
134580
134648
|
const larkCliProfile = deriveLarkCliProfile(bot.lark_cli_profile, bot.app_id);
|
|
134581
|
-
|
|
134649
|
+
const larkCliConfigDir = bot.lark_cli_isolated ? resolveBotLarkCliDir(bot.id) : void 0;
|
|
134650
|
+
if (larkCliConfigDir) {
|
|
134651
|
+
mkdirSync(larkCliConfigDir, { recursive: true });
|
|
134652
|
+
console.log(`[larkway] bot "${bot.id}": lark-cli identity isolation ON (${larkCliConfigDir})`);
|
|
134653
|
+
}
|
|
134654
|
+
ensureLarkCliProfile(bot.id, larkCliProfile, bot.app_id, appSecret, void 0, void 0, {
|
|
134655
|
+
...larkCliConfigDir ? { configDir: larkCliConfigDir } : {}
|
|
134656
|
+
});
|
|
134582
134657
|
const botDir = resolveLarkwayDir(bot.id);
|
|
134583
134658
|
const sharedReposDir = path25.join(larkwayHome(), "repos");
|
|
134584
134659
|
const worktreesDir = path25.join(botDir, "worktrees");
|
|
@@ -134605,6 +134680,7 @@ async function runV2Mode({
|
|
|
134605
134680
|
appId: bot.app_id,
|
|
134606
134681
|
appSecret,
|
|
134607
134682
|
larkCliProfile,
|
|
134683
|
+
...larkCliConfigDir ? { larkCliConfigDir } : {},
|
|
134608
134684
|
larkwayDir: larkwayHome()
|
|
134609
134685
|
});
|
|
134610
134686
|
client.seedTrackedChats(
|
|
@@ -134868,6 +134944,7 @@ async function runV2Mode({
|
|
|
134868
134944
|
codexPool = new CodexProcessPool({
|
|
134869
134945
|
botGitIdentity: bot.git_identity,
|
|
134870
134946
|
gitlabToken: effectiveGitlabToken,
|
|
134947
|
+
...larkCliConfigDir ? { larkCliConfigDir } : {},
|
|
134871
134948
|
idleMs: bot.warmProcessIdleMs,
|
|
134872
134949
|
pidFilePath
|
|
134873
134950
|
});
|
|
@@ -134885,6 +134962,7 @@ async function runV2Mode({
|
|
|
134885
134962
|
botId: bot.id,
|
|
134886
134963
|
botGitIdentity: bot.git_identity,
|
|
134887
134964
|
gitlabToken: effectiveGitlabToken,
|
|
134965
|
+
...larkCliConfigDir ? { larkCliConfigDir } : {},
|
|
134888
134966
|
idleMs: bot.warmProcessIdleMs,
|
|
134889
134967
|
maxProcesses: bot.warmProcessMaxProcesses,
|
|
134890
134968
|
pidListFilePath
|
|
@@ -134938,7 +135016,8 @@ async function runV2Mode({
|
|
|
134938
135016
|
p2pStickyIdleMs: bot.p2pStickyIdleMs,
|
|
134939
135017
|
owner_open_id: bot.owner_open_id,
|
|
134940
135018
|
cot: bot.cot,
|
|
134941
|
-
cotSurface: bot.cotSurface
|
|
135019
|
+
cotSurface: bot.cotSurface,
|
|
135020
|
+
lark_cli_isolated: bot.lark_cli_isolated
|
|
134942
135021
|
},
|
|
134943
135022
|
cardKitClient,
|
|
134944
135023
|
cotClient,
|
|
@@ -134953,7 +135032,7 @@ async function runV2Mode({
|
|
|
134953
135032
|
larkCliProfile,
|
|
134954
135033
|
// PRB-6/§11.3: resolve peer @ targets to same-app-scope open_ids from the
|
|
134955
135034
|
// live chat roster (per-chat cached), only when this bot actually has peers.
|
|
134956
|
-
resolveLiveRoster: resolvedPeers.length > 0 ? createCachedRosterResolver({ profile: larkCliProfile }) : void 0,
|
|
135035
|
+
resolveLiveRoster: resolvedPeers.length > 0 ? createCachedRosterResolver({ profile: larkCliProfile, ...larkCliConfigDir ? { larkCliConfigDir } : {} }) : void 0,
|
|
134957
135036
|
runtimeRequirements: runtimeRequirementsForBots([bot]),
|
|
134958
135037
|
recordRuntimeEvent: async (patch) => {
|
|
134959
135038
|
await upsertRuntimeEvent(larkwayHome(), bot.id, patch);
|