@modelzen/feishu-codex-bridge 0.5.0 → 0.6.1

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/dist/cli.js CHANGED
@@ -1,31 +1,17 @@
1
- // src/cli/index.ts
2
- import { Command } from "commander";
3
-
4
- // src/core/version.ts
5
- import { readFileSync } from "fs";
6
- import { dirname, resolve } from "path";
7
- import { fileURLToPath } from "url";
8
- function bridgeVersion() {
9
- try {
10
- const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
11
- return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? "0.0.0";
12
- } catch {
13
- return "0.0.0";
14
- }
15
- }
16
-
17
- // src/cli/commands/doctor.ts
18
- import { existsSync as existsSync5 } from "fs";
19
- import { homedir as homedir2 } from "os";
20
- import { join as join9 } from "path";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
21
10
 
22
11
  // src/config/paths.ts
12
+ import { createHash } from "crypto";
23
13
  import { homedir } from "os";
24
14
  import { join } from "path";
25
- var appDir = join(homedir(), ".feishu-codex-bridge");
26
- var larkCliDir = join(appDir, "lark-cli");
27
- var codexCliDir = join(appDir, "codex-cli");
28
- var currentBotDir = appDir;
29
15
  function botDir(appId) {
30
16
  return join(appDir, "bots", appId);
31
17
  }
@@ -42,71 +28,86 @@ function botPaths(appId) {
42
28
  processesFile: join(dir, "processes.json")
43
29
  };
44
30
  }
45
- var paths = {
46
- appDir,
47
- cacheDir: appDir,
48
- /** bot 注册表:保存的全部 bot + 当前选中的 appId */
49
- botsFile: join(appDir, "bots.json"),
50
- /** app id / 租户 / 偏好(当前 bot;不含明文密钥) */
51
- get configFile() {
52
- return join(currentBotDir, "config.json");
53
- },
54
- /** thread(话题) → codex thread_id + cwd + 会话级配置(当前 bot) */
55
- get sessionsFile() {
56
- return join(currentBotDir, "sessions.json");
57
- },
58
- /** project(群) cwd + 默认参数 注册表(当前 bot */
59
- get projectsFile() {
60
- return join(currentBotDir, "projects.json");
61
- },
62
- /** 在跑的 start 进程注册中心(同 App 冲突检测;当前 bot) */
63
- get processesFile() {
64
- return join(currentBotDir, "processes.json");
65
- },
66
- secretsFile: join(appDir, "secrets.enc"),
67
- keystoreSaltFile: join(appDir, ".keystore.salt"),
68
- npmCacheDir: join(appDir, "npm-cache"),
69
- /**
70
- * 按需后端(npm-ondemand 包)私装目录:一个扁平
71
- * `~/.feishu-codex-bridge/backends/node_modules` 放所有按需后端的 npm 包。
72
- * (通用基础设施;当前内置后端 codex 是 external-cli,不落此目录,保留以备将来。)
73
- * 永远在用户 HOME 下、用户可写(零 sudo/brew),与全局包目录的权限死结解耦。
74
- * 解析靠 createRequire(backendsDir/...).resolve(见 agent/backend-loader);
75
- * 安装靠 `npm install --prefix backendsDir`(见 agent/installer)。 */
76
- backendsDir: join(appDir, "backends"),
77
- /** 空白项目默认落地目录 */
78
- projectsRootDir: join(appDir, "projects"),
79
- larkCliDir,
80
- larkCliBinDir: join(larkCliDir, "node_modules", ".bin"),
81
- codexCliDir,
82
- codexCliBinDir: join(codexCliDir, "node_modules", ".bin"),
83
- /**
84
- * Thin shell wrapper that lark-cli invokes to resolve secrets from the
85
- * bridge's encrypted store. Written user-owned and non-symlinked so it
86
- * passes lark-cli's AssertSecurePath audit.
87
- */
88
- secretsGetterScript: join(appDir, "secrets-getter"),
89
- mediaDir: join(appDir, "media"),
90
- /** Inbound file attachments downloaded from chat, handed to codex by absolute
91
- * path (codex has no native file input). TTL-pruned like {@link mediaDir}. */
92
- inboundDir: join(appDir, "inbound"),
93
- /** daemon 内嵌 Web 控制台的发现文件 {port, token, pid}(0600,daemon 退出
94
- * 清理)——`web` 子命令据此直接打开 daemon 控制台而不是再起只读副本。 */
95
- webConsoleFile: join(appDir, "web-console.json"),
96
- /** 稳定的 Web 控制台 token(0600,**不随进程退出清理**)——让重启 / 预览→daemon
97
- * 切换后浏览器里那条带 token 的 URL 始终有效,不再 401。删此文件即轮换 token。 */
98
- webTokenFile: join(appDir, "web-token")
99
- };
100
-
101
- // src/config/bots.ts
102
- import { existsSync } from "fs";
103
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
104
- import { dirname as dirname3, join as join2 } from "path";
105
-
106
- // src/config/store.ts
107
- import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
108
- import { randomUUID } from "crypto";
109
- import { dirname as dirname2 } from "path";
31
+ var appDir, larkCliDir, codexCliDir, currentBotDir, paths;
32
+ var init_paths = __esm({
33
+ "src/config/paths.ts"() {
34
+ "use strict";
35
+ appDir = join(homedir(), ".feishu-codex-bridge");
36
+ larkCliDir = join(appDir, "lark-cli");
37
+ codexCliDir = join(appDir, "codex-cli");
38
+ currentBotDir = appDir;
39
+ paths = {
40
+ appDir,
41
+ cacheDir: appDir,
42
+ /** bot 注册表:保存的全部 bot + 当前选中的 appId */
43
+ botsFile: join(appDir, "bots.json"),
44
+ /** app id / 租户 / 偏好(当前 bot;不含明文密钥) */
45
+ get configFile() {
46
+ return join(currentBotDir, "config.json");
47
+ },
48
+ /** thread(话题) codex thread_id + cwd + 会话级配置(当前 bot) */
49
+ get sessionsFile() {
50
+ return join(currentBotDir, "sessions.json");
51
+ },
52
+ /** project(群) → cwd + 默认参数 注册表(当前 bot) */
53
+ get projectsFile() {
54
+ return join(currentBotDir, "projects.json");
55
+ },
56
+ /** 在跑的 start 进程注册中心(同 App 冲突检测;当前 bot) */
57
+ get processesFile() {
58
+ return join(currentBotDir, "processes.json");
59
+ },
60
+ /**
61
+ * Local CLI hook IPC endpoint for the current bot. macOS/Linux use a Unix
62
+ * domain socket file; Windows has none, so Node maps a `\\.\pipe\…` path to a
63
+ * named pipe. The pipe name is hashed from the per-bot dir so the daemon and
64
+ * the hook subprocess (both pointed at the same bot) derive the same name,
65
+ * while distinct bots/users on one machine can't collide in the global pipe
66
+ * namespace.
67
+ */
68
+ get cliBridgeSocket() {
69
+ if (process.platform === "win32") {
70
+ const tag = createHash("sha1").update(currentBotDir).digest("hex").slice(0, 16);
71
+ return `\\\\.\\pipe\\feishu-cli-bridge-${tag}`;
72
+ }
73
+ return join(currentBotDir, "cli-bridge.sock");
74
+ },
75
+ secretsFile: join(appDir, "secrets.enc"),
76
+ keystoreSaltFile: join(appDir, ".keystore.salt"),
77
+ npmCacheDir: join(appDir, "npm-cache"),
78
+ /**
79
+ * 按需后端(npm-ondemand 包)私装目录:一个扁平
80
+ * `~/.feishu-codex-bridge/backends/node_modules` 放所有按需后端的 npm 包。
81
+ * (通用基础设施;当前内置后端 codex 是 external-cli,不落此目录,保留以备将来。)
82
+ * 永远在用户 HOME 下、用户可写(零 sudo/brew),与全局包目录的权限死结解耦。
83
+ * 解析靠 createRequire(backendsDir/...).resolve(见 agent/backend-loader);
84
+ * 安装靠 `npm install --prefix backendsDir`(见 agent/installer)。 */
85
+ backendsDir: join(appDir, "backends"),
86
+ /** 空白项目默认落地目录 */
87
+ projectsRootDir: join(appDir, "projects"),
88
+ larkCliDir,
89
+ larkCliBinDir: join(larkCliDir, "node_modules", ".bin"),
90
+ codexCliDir,
91
+ codexCliBinDir: join(codexCliDir, "node_modules", ".bin"),
92
+ /**
93
+ * Thin shell wrapper that lark-cli invokes to resolve secrets from the
94
+ * bridge's encrypted store. Written user-owned and non-symlinked so it
95
+ * passes lark-cli's AssertSecurePath audit.
96
+ */
97
+ secretsGetterScript: join(appDir, "secrets-getter"),
98
+ mediaDir: join(appDir, "media"),
99
+ /** Inbound file attachments downloaded from chat, handed to codex by absolute
100
+ * path (codex has no native file input). TTL-pruned like {@link mediaDir}. */
101
+ inboundDir: join(appDir, "inbound"),
102
+ /** daemon 内嵌 Web 控制台的发现文件 {port, token, pid}(0600,daemon 退出
103
+ * 清理)——`web` 子命令据此直接打开 daemon 控制台而不是再起只读副本。 */
104
+ webConsoleFile: join(appDir, "web-console.json"),
105
+ /** 稳定的 Web 控制台 token(0600,**不随进程退出清理**)——让重启 / 预览→daemon
106
+ * 切换后浏览器里那条带 token 的 URL 始终有效,不再 401。删此文件即轮换 token。 */
107
+ webTokenFile: join(appDir, "web-token")
108
+ };
109
+ }
110
+ });
110
111
 
111
112
  // src/config/schema.ts
112
113
  function isComplete(cfg) {
@@ -142,8 +143,6 @@ function getMaxConcurrentRuns(cfg) {
142
143
  function getPendingPolicy(cfg) {
143
144
  return cfg.preferences?.pendingPolicy === "queue" ? "queue" : "steer";
144
145
  }
145
- var RUN_IDLE_TIMEOUT_MIN_SEC = 10;
146
- var RUN_IDLE_TIMEOUT_MAX_SEC = 3600;
147
146
  function getRunIdleTimeoutMs(cfg) {
148
147
  const raw = cfg.preferences?.runIdleTimeoutSeconds;
149
148
  if (raw === 0) return void 0;
@@ -171,8 +170,67 @@ function isUserAllowedInProject(cfg, project, senderId) {
171
170
  if (!list || list.length === 0) return true;
172
171
  return list.includes(senderId);
173
172
  }
173
+ function boolOr(value, fallback) {
174
+ return typeof value === "boolean" ? value : fallback;
175
+ }
176
+ function secondsOr(value, fallback, min, max) {
177
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return fallback;
178
+ return Math.min(max, Math.max(min, Math.floor(value)));
179
+ }
180
+ function getCliBridgePreferences(cfg) {
181
+ const raw = cfg.preferences?.cliBridge;
182
+ return {
183
+ enabled: boolOr(raw?.enabled, false),
184
+ delivery: "away_only",
185
+ includeBridgeOwnedSessionsForDebugging: boolOr(raw?.includeBridgeOwnedSessionsForDebugging, false),
186
+ agents: {
187
+ claude: boolOr(raw?.agents?.claude, true),
188
+ codex: boolOr(raw?.agents?.codex, true)
189
+ },
190
+ notifyScope: raw?.notifyScope === "bound_projects" || raw?.notifyScope === "none" ? raw.notifyScope : "all",
191
+ keepAwake: {
192
+ enabled: boolOr(raw?.keepAwake?.enabled, true)
193
+ },
194
+ approval: {
195
+ enabled: boolOr(raw?.approval?.enabled, true),
196
+ timeoutSeconds: secondsOr(raw?.approval?.timeoutSeconds, 86400, 1, 86400)
197
+ },
198
+ taskCompletion: {
199
+ enabled: boolOr(raw?.taskCompletion?.enabled, true),
200
+ replyEnabled: boolOr(raw?.taskCompletion?.replyEnabled, true),
201
+ replyTimeoutSeconds: secondsOr(raw?.taskCompletion?.replyTimeoutSeconds, 600, 1, 86400)
202
+ },
203
+ allowCache: {
204
+ enabled: boolOr(raw?.allowCache?.enabled, true),
205
+ scope: "session"
206
+ },
207
+ presence: {
208
+ enabled: boolOr(raw?.presence?.enabled, true),
209
+ platform: raw?.presence?.platform === "macos" ? "macos" : "auto",
210
+ idleThresholdSeconds: secondsOr(raw?.presence?.idleThresholdSeconds, 120, 10, 3600)
211
+ }
212
+ };
213
+ }
214
+ function resolveCliBridgeTarget(cfg) {
215
+ const owner = resolveOwner(cfg);
216
+ return owner ? { receiveIdType: "open_id", receiveId: owner } : void 0;
217
+ }
218
+ function canEnableCliBridge(cfg) {
219
+ return resolveCliBridgeTarget(cfg) ? { ok: true } : { ok: false, reason: "missing_owner" };
220
+ }
221
+ var RUN_IDLE_TIMEOUT_MIN_SEC, RUN_IDLE_TIMEOUT_MAX_SEC;
222
+ var init_schema = __esm({
223
+ "src/config/schema.ts"() {
224
+ "use strict";
225
+ RUN_IDLE_TIMEOUT_MIN_SEC = 10;
226
+ RUN_IDLE_TIMEOUT_MAX_SEC = 3600;
227
+ }
228
+ });
174
229
 
175
230
  // src/config/store.ts
231
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
232
+ import { randomUUID } from "crypto";
233
+ import { dirname as dirname2 } from "path";
176
234
  async function loadConfig(path = paths.configFile) {
177
235
  try {
178
236
  const text = await readFile(path, "utf8");
@@ -216,7 +274,6 @@ exec ${sq(node)} ${sq(bridgeEntry)} secrets get "$@"
216
274
  await rename(tmp, wrapperPath);
217
275
  return wrapperPath;
218
276
  }
219
- var saveChain = Promise.resolve();
220
277
  function saveConfig(cfg, path = paths.configFile) {
221
278
  const run = saveChain.then(async () => {
222
279
  await mkdir(dirname2(path), { recursive: true });
@@ -232,9 +289,20 @@ function saveConfig(cfg, path = paths.configFile) {
232
289
  );
233
290
  return run;
234
291
  }
292
+ var saveChain;
293
+ var init_store = __esm({
294
+ "src/config/store.ts"() {
295
+ "use strict";
296
+ init_paths();
297
+ init_schema();
298
+ saveChain = Promise.resolve();
299
+ }
300
+ });
235
301
 
236
302
  // src/config/bots.ts
237
- var EMPTY = { version: 1, bots: [] };
303
+ import { existsSync } from "fs";
304
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
305
+ import { dirname as dirname3, join as join2 } from "path";
238
306
  async function loadBots() {
239
307
  try {
240
308
  const text = await readFile2(paths.botsFile, "utf8");
@@ -264,68 +332,1755 @@ async function ensureRegistry() {
264
332
  for (const file of ["config.json", "projects.json", "sessions.json", "processes.json"]) {
265
333
  await moveIfExists(join2(paths.appDir, file), join2(dest, file));
266
334
  }
267
- const reg = {
268
- version: 1,
269
- current: appId,
270
- bots: [{ name: "default", appId, tenant, createdAt: nowMs() }]
335
+ const reg = {
336
+ version: 1,
337
+ current: appId,
338
+ bots: [{ name: "default", appId, tenant, createdAt: nowMs() }]
339
+ };
340
+ await saveBots(reg);
341
+ return reg;
342
+ }
343
+ function findBot(reg, nameOrAppId) {
344
+ return reg.bots.find((b) => b.name === nameOrAppId || b.appId === nameOrAppId);
345
+ }
346
+ function currentBot(reg) {
347
+ return reg.current ? reg.bots.find((b) => b.appId === reg.current) : void 0;
348
+ }
349
+ function activeBots(reg) {
350
+ const configured = reg.bots.some((b) => b.active !== void 0);
351
+ if (configured) return reg.bots.filter((b) => b.active === true);
352
+ const cur = currentBot(reg);
353
+ return cur ? [cur] : [];
354
+ }
355
+ async function setActiveBots(appIds) {
356
+ const reg = await loadBots();
357
+ const want = new Set(appIds);
358
+ for (const b of reg.bots) b.active = want.has(b.appId);
359
+ const firstActive = reg.bots.find((b) => b.active);
360
+ if (firstActive) reg.current = firstActive.appId;
361
+ await saveBots(reg);
362
+ return reg;
363
+ }
364
+ async function addBot(entry) {
365
+ const reg = await loadBots();
366
+ reg.bots = reg.bots.filter((b) => b.appId !== entry.appId);
367
+ reg.bots.push(entry);
368
+ if (!reg.current) reg.current = entry.appId;
369
+ await saveBots(reg);
370
+ return reg;
371
+ }
372
+ async function removeBot(appId) {
373
+ const reg = await loadBots();
374
+ reg.bots = reg.bots.filter((b) => b.appId !== appId);
375
+ if (reg.current === appId) reg.current = reg.bots[0]?.appId;
376
+ await saveBots(reg);
377
+ return reg;
378
+ }
379
+ function uniqueName(reg, desired) {
380
+ const base = slugify(desired) || "bot";
381
+ if (!reg.bots.some((b) => b.name === base)) return base;
382
+ for (let i = 2; ; i++) {
383
+ const candidate = `${base}-${i}`;
384
+ if (!reg.bots.some((b) => b.name === candidate)) return candidate;
385
+ }
386
+ }
387
+ function slugify(s) {
388
+ return s.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
389
+ }
390
+ function nowMs() {
391
+ return Date.now();
392
+ }
393
+ async function moveIfExists(src, dest) {
394
+ if (!existsSync(src)) return;
395
+ await rename2(src, dest);
396
+ }
397
+ var EMPTY;
398
+ var init_bots = __esm({
399
+ "src/config/bots.ts"() {
400
+ "use strict";
401
+ init_paths();
402
+ init_store();
403
+ init_schema();
404
+ EMPTY = { version: 1, bots: [] };
405
+ }
406
+ });
407
+
408
+ // src/core/logger.ts
409
+ import { AsyncLocalStorage } from "async_hooks";
410
+ import { createWriteStream, mkdirSync } from "fs";
411
+ import { open, readdir, rm, stat } from "fs/promises";
412
+ import { tmpdir } from "os";
413
+ import { join as join4 } from "path";
414
+ function todayKey() {
415
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
416
+ }
417
+ function inTestEnv() {
418
+ return Boolean(process.env.VITEST) || process.env.NODE_ENV === "test";
419
+ }
420
+ function logsDir() {
421
+ return inTestEnv() ? TEST_LOGS_DIR : join4(paths.appDir, "logs");
422
+ }
423
+ function getStream() {
424
+ const today = todayKey();
425
+ if (stream && currentDate === today) return stream;
426
+ if (stream) {
427
+ try {
428
+ stream.end();
429
+ } catch {
430
+ }
431
+ }
432
+ try {
433
+ mkdirSync(logsDir(), { recursive: true });
434
+ stream = createWriteStream(join4(logsDir(), `${today}.log`), { flags: "a" });
435
+ currentDate = today;
436
+ return stream;
437
+ } catch {
438
+ return null;
439
+ }
440
+ }
441
+ function emit(level, phase, event, fields = {}) {
442
+ const ctx = als.getStore() ?? {};
443
+ const entry = {
444
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
445
+ level,
446
+ phase,
447
+ event,
448
+ ...ctx
449
+ };
450
+ for (const [k2, v] of Object.entries(fields)) {
451
+ if (RESERVED_KEYS.has(k2)) {
452
+ entry[`_${k2}`] = v;
453
+ } else {
454
+ entry[k2] = v;
455
+ }
456
+ }
457
+ const s = getStream();
458
+ if (s) {
459
+ try {
460
+ s.write(`${JSON.stringify(entry)}
461
+ `);
462
+ } catch {
463
+ }
464
+ }
465
+ const showOnStdout = level !== "info" || STDOUT_INFO_ALLOWLIST.has(`${phase}.${event}`);
466
+ if (!showOnStdout) return;
467
+ const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
468
+ fn(formatStdout(level, phase, event, ctx, fields));
469
+ }
470
+ function formatStdout(level, phase, event, ctx, fields) {
471
+ if (phase === "ws") {
472
+ if (event === "connected") {
473
+ const bot2 = fields.bot ?? "-";
474
+ const appId = fields.appId ? ` (${fields.appId})` : "";
475
+ return `\u2713 \u5DF2\u8FDE\u63A5 bot: ${bot2}${appId}`;
476
+ }
477
+ if (event === "reconnecting") return "\u21BB \u6B63\u5728\u91CD\u8FDE\u2026";
478
+ if (event === "reconnected") return "\u2713 \u5DF2\u91CD\u8FDE";
479
+ if (event === "fail") return `\u2717 WS \u9519\u8BEF: ${fields.err ?? ""}`;
480
+ }
481
+ if (phase === "intake" && event === "enter") {
482
+ const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
483
+ const sender = fields.sender ?? "-";
484
+ const preview = fields.preview ?? "";
485
+ return `\u25B8 ${fields.chatType ?? "?"}/${c} ${sender}: ${preview}`;
486
+ }
487
+ if (phase === "card" && event === "final") {
488
+ const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
489
+ const t = fields.terminal;
490
+ const mark = t === "done" ? "\u2713" : t === "interrupted" ? "\u23F9" : "\u2717";
491
+ return ` ${mark} ${c} ${t}`;
492
+ }
493
+ const ctxBits = [];
494
+ if (ctx.traceId) ctxBits.push(`t=${ctx.traceId}`);
495
+ if (ctx.chatId) ctxBits.push(`c=${ctx.chatId.slice(-6)}`);
496
+ const ctxStr = ctxBits.length > 0 ? ` ${ctxBits.join(" ")}` : "";
497
+ const summary = formatFields(fields);
498
+ const tag = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\xB7";
499
+ return `${tag} [${phase}.${event}]${ctxStr}${summary ? ` ${summary}` : ""}`;
500
+ }
501
+ function formatFields(fields) {
502
+ const keys = Object.keys(fields);
503
+ if (keys.length === 0) return "";
504
+ const parts = [];
505
+ for (const k2 of keys) {
506
+ const v = fields[k2];
507
+ if (v === void 0 || v === null) continue;
508
+ if (k2 === "stack") continue;
509
+ if (typeof v === "string") {
510
+ parts.push(`${k2}=${v.length > 80 ? `${v.slice(0, 80)}\u2026` : v}`);
511
+ } else if (typeof v === "number" || typeof v === "boolean") {
512
+ parts.push(`${k2}=${v}`);
513
+ } else {
514
+ try {
515
+ const str = JSON.stringify(v);
516
+ parts.push(`${k2}=${str.length > 80 ? `${str.slice(0, 80)}\u2026` : str}`);
517
+ } catch {
518
+ parts.push(`${k2}=?`);
519
+ }
520
+ }
521
+ }
522
+ return parts.join(" ");
523
+ }
524
+ function withTrace(ctx, fn) {
525
+ const traceId = ctx.traceId ?? newTraceId();
526
+ return als.run({ ...ctx, traceId }, fn);
527
+ }
528
+ function newTraceId() {
529
+ return Math.random().toString(36).slice(2, 10);
530
+ }
531
+ async function readRecentLogs(opts) {
532
+ const today = todayKey();
533
+ const yesterday = new Date(Date.now() - 864e5).toISOString().slice(0, 10);
534
+ const tail = await readTail(join4(logsDir(), `${today}.log`), opts.maxBytes);
535
+ if (tail.length >= opts.maxBytes / 2) return tail;
536
+ const remaining = opts.maxBytes - Buffer.byteLength(tail, "utf8");
537
+ const earlier = await readTail(join4(logsDir(), `${yesterday}.log`), remaining);
538
+ return earlier + tail;
539
+ }
540
+ async function readTail(path, maxBytes) {
541
+ try {
542
+ const st = await stat(path);
543
+ const start = Math.max(0, st.size - maxBytes);
544
+ const handle = await open(path, "r");
545
+ try {
546
+ const buf = Buffer.alloc(st.size - start);
547
+ await handle.read(buf, 0, buf.length, start);
548
+ let content = buf.toString("utf8");
549
+ if (start > 0) {
550
+ const nl = content.indexOf("\n");
551
+ if (nl !== -1) content = content.slice(nl + 1);
552
+ }
553
+ return content;
554
+ } finally {
555
+ await handle.close();
556
+ }
557
+ } catch (err) {
558
+ if (err.code === "ENOENT") return "";
559
+ throw err;
560
+ }
561
+ }
562
+ var LOG_RETENTION_DAYS, STDOUT_INFO_ALLOWLIST, als, stream, currentDate, TEST_LOGS_DIR, RESERVED_KEYS, log;
563
+ var init_logger = __esm({
564
+ "src/core/logger.ts"() {
565
+ "use strict";
566
+ init_paths();
567
+ LOG_RETENTION_DAYS = Math.max(
568
+ 1,
569
+ Number(process.env.FEISHU_CODEX_LOG_DAYS ?? 7) || 7
570
+ );
571
+ STDOUT_INFO_ALLOWLIST = /* @__PURE__ */ new Set([
572
+ "ws.connected",
573
+ "ws.reconnecting",
574
+ "ws.reconnected",
575
+ "intake.enter",
576
+ "intake.recv",
577
+ "intake.reject",
578
+ "card.final",
579
+ "card.config",
580
+ "card.action",
581
+ "card.launch",
582
+ "agent.spawn",
583
+ "agent.exit",
584
+ // 自愈链路(kill -9 / 崩溃恢复)的关键节点——低频高信号,进终端便于 e2e 直读:
585
+ // 驱逐(轮间死 / 轮中死)与恢复来源(store resume / 重建 / 全新会话)。
586
+ "agent.dead-thread-evict",
587
+ "agent.session-evict",
588
+ "agent.resume-ok",
589
+ "agent.resume-recreate",
590
+ "agent.session-fresh"
591
+ ]);
592
+ als = new AsyncLocalStorage();
593
+ stream = null;
594
+ currentDate = "";
595
+ TEST_LOGS_DIR = join4(tmpdir(), "feishu-codex-bridge-test-logs");
596
+ RESERVED_KEYS = /* @__PURE__ */ new Set([
597
+ "ts",
598
+ "level",
599
+ "phase",
600
+ "event",
601
+ "traceId",
602
+ "chatId",
603
+ "msgId"
604
+ ]);
605
+ log = {
606
+ info(phase, event, fields) {
607
+ emit("info", phase, event, fields);
608
+ },
609
+ warn(phase, event, fields) {
610
+ emit("warn", phase, event, fields);
611
+ },
612
+ fail(phase, err, fields) {
613
+ const message = err instanceof Error ? err.message : String(err);
614
+ const stack = err instanceof Error ? err.stack : void 0;
615
+ const apiData = err?.response?.data;
616
+ const apiStatus = err?.response?.status;
617
+ emit("error", phase, "fail", {
618
+ ...fields,
619
+ err: message,
620
+ apiStatus,
621
+ apiData,
622
+ stack
623
+ });
624
+ }
625
+ };
626
+ }
627
+ });
628
+
629
+ // src/card/cards.ts
630
+ function card(elements, opts = {}) {
631
+ const config = { update_multi: true };
632
+ if (opts.forward === false) config.enable_forward = false;
633
+ if (opts.streaming) {
634
+ config.streaming_mode = true;
635
+ config.streaming_config = {
636
+ print_frequency_ms: { default: 25 },
637
+ print_step: { default: 6 },
638
+ print_strategy: "fast"
639
+ };
640
+ }
641
+ if (opts.summary) config.summary = { content: opts.summary };
642
+ const obj = {
643
+ schema: "2.0",
644
+ config,
645
+ body: { elements }
646
+ };
647
+ if (opts.header) {
648
+ obj.header = {
649
+ template: opts.header.template ?? "blue",
650
+ title: { tag: "plain_text", content: opts.header.title },
651
+ ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {},
652
+ ...opts.header.textTags?.length ? {
653
+ text_tag_list: opts.header.textTags.map((t) => ({
654
+ tag: "text_tag",
655
+ text: { tag: "plain_text", content: t.text },
656
+ color: t.color
657
+ }))
658
+ } : {}
659
+ };
660
+ }
661
+ return obj;
662
+ }
663
+ function md(content) {
664
+ return { tag: "markdown", content };
665
+ }
666
+ function mdStream(content, elementId) {
667
+ return { tag: "markdown", element_id: elementId, content };
668
+ }
669
+ function image(imgKey, alt = "") {
670
+ return {
671
+ tag: "img",
672
+ img_key: imgKey,
673
+ alt: { tag: "plain_text", content: alt },
674
+ mode: "fit_horizontal",
675
+ preview: true
676
+ };
677
+ }
678
+ function note(content) {
679
+ return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
680
+ }
681
+ function colorNote(content, color) {
682
+ return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: color } };
683
+ }
684
+ function hr() {
685
+ return { tag: "hr" };
686
+ }
687
+ function noteMd(content) {
688
+ return { tag: "markdown", content, text_size: "notation" };
689
+ }
690
+ function collapsiblePanel(opts) {
691
+ return {
692
+ tag: "collapsible_panel",
693
+ expanded: opts.expanded,
694
+ header: {
695
+ title: { tag: "markdown", content: opts.title },
696
+ vertical_align: "center",
697
+ icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
698
+ icon_position: "follow_text",
699
+ icon_expanded_angle: -180
700
+ },
701
+ border: { color: opts.border, corner_radius: "5px" },
702
+ vertical_spacing: "8px",
703
+ padding: "8px 8px 8px 8px",
704
+ elements: [{ tag: "markdown", content: opts.body, text_size: "notation" }]
705
+ };
706
+ }
707
+ function collapsiblePanelEl(opts) {
708
+ return {
709
+ tag: "collapsible_panel",
710
+ expanded: opts.expanded,
711
+ header: {
712
+ title: { tag: "markdown", content: opts.title },
713
+ vertical_align: "center",
714
+ icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
715
+ icon_position: "follow_text",
716
+ icon_expanded_angle: -180
717
+ },
718
+ border: { color: opts.border, corner_radius: "5px" },
719
+ vertical_spacing: "8px",
720
+ padding: "8px 8px 8px 8px",
721
+ elements: opts.elements
722
+ };
723
+ }
724
+ function actions(items, elementId) {
725
+ return {
726
+ tag: "column_set",
727
+ ...elementId ? { element_id: elementId } : {},
728
+ flex_mode: "flow",
729
+ horizontal_spacing: "small",
730
+ columns: items.map((it) => ({ tag: "column", width: "auto", elements: [it] }))
731
+ };
732
+ }
733
+ function actionsFixed(items, width, elementId) {
734
+ return {
735
+ tag: "column_set",
736
+ ...elementId ? { element_id: elementId } : {},
737
+ flex_mode: "flow",
738
+ horizontal_spacing: "8px",
739
+ columns: items.map((it) => ({ tag: "column", width: "auto", elements: [{ ...it, width, size: "large" }] }))
740
+ };
741
+ }
742
+ function splitRow(left, right, elementId) {
743
+ return {
744
+ tag: "column_set",
745
+ ...elementId ? { element_id: elementId } : {},
746
+ flex_mode: "none",
747
+ horizontal_spacing: "medium",
748
+ columns: [
749
+ { tag: "column", width: "auto", vertical_align: "center", elements: [left] },
750
+ { tag: "column", width: "weighted", weight: 1, vertical_align: "center", elements: [right] }
751
+ ]
752
+ };
753
+ }
754
+ function button(label, value, type = "default") {
755
+ return {
756
+ tag: "button",
757
+ text: { tag: "plain_text", content: label },
758
+ type,
759
+ behaviors: [{ type: "callback", value }]
760
+ };
761
+ }
762
+ function linkButton(label, url, type = "default", size) {
763
+ return {
764
+ tag: "button",
765
+ text: { tag: "plain_text", content: label },
766
+ type,
767
+ ...size ? { size } : {},
768
+ behaviors: [{ type: "open_url", default_url: url }]
769
+ };
770
+ }
771
+ function input(opts) {
772
+ return {
773
+ tag: "input",
774
+ name: opts.name,
775
+ ...opts.label ? { label: { tag: "plain_text", content: opts.label } } : {},
776
+ ...opts.placeholder ? { placeholder: { tag: "plain_text", content: opts.placeholder } } : {},
777
+ ...opts.value ? { default_value: opts.value } : {},
778
+ required: Boolean(opts.required)
779
+ };
780
+ }
781
+ function form(name, elements) {
782
+ return { tag: "form", name, elements };
783
+ }
784
+ function submitButton(label, value, type = "primary", name = "submit") {
785
+ return {
786
+ tag: "button",
787
+ name,
788
+ text: { tag: "plain_text", content: label },
789
+ type,
790
+ form_action_type: "submit",
791
+ behaviors: [{ type: "callback", value }]
792
+ };
793
+ }
794
+ function selectStatic(opts) {
795
+ return {
796
+ tag: "select_static",
797
+ placeholder: { tag: "plain_text", content: opts.placeholder },
798
+ ...opts.initial ? { initial_option: opts.initial } : {},
799
+ options: opts.options.map((o) => ({
800
+ text: { tag: "plain_text", content: o.label },
801
+ value: o.value
802
+ })),
803
+ behaviors: [{ type: "callback", value: { a: opts.actionId } }]
804
+ };
805
+ }
806
+ function selectMenu(opts) {
807
+ return {
808
+ tag: "select_static",
809
+ name: opts.name,
810
+ placeholder: { tag: "plain_text", content: opts.placeholder },
811
+ ...opts.initial ? { initial_option: opts.initial } : {},
812
+ options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
813
+ };
814
+ }
815
+ function multiSelectMenu(opts) {
816
+ return {
817
+ tag: "multi_select_static",
818
+ name: opts.name,
819
+ placeholder: { tag: "plain_text", content: opts.placeholder },
820
+ options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
821
+ };
822
+ }
823
+ var init_cards = __esm({
824
+ "src/card/cards.ts"() {
825
+ "use strict";
826
+ }
827
+ });
828
+
829
+ // src/card/managed.ts
830
+ function stampRenderToken(card2) {
831
+ const token = (++renderToken).toString(36);
832
+ const visit = (node) => {
833
+ if (Array.isArray(node)) {
834
+ node.forEach(visit);
835
+ return;
836
+ }
837
+ if (!node || typeof node !== "object") return;
838
+ const obj = node;
839
+ const behaviors = obj.behaviors;
840
+ if (Array.isArray(behaviors)) {
841
+ for (const b of behaviors) {
842
+ if (b && typeof b === "object" && b.type === "callback") {
843
+ const v = b.value;
844
+ if (v && typeof v === "object") v.__r = token;
845
+ }
846
+ }
847
+ }
848
+ for (const k2 of Object.keys(obj)) visit(obj[k2]);
849
+ };
850
+ visit(card2);
851
+ }
852
+ function isCardIdNotReady(err) {
853
+ const data = err?.response?.data;
854
+ return data?.code === 230099 || /11310|cardid is invalid/i.test(data?.msg ?? "");
855
+ }
856
+ async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
857
+ stampRenderToken(card2);
858
+ const data = JSON.stringify(card2);
859
+ const attempt = async () => {
860
+ const created = await channel.rawClient.cardkit.v1.card.create({ data: { type: "card_json", data } });
861
+ const cardId = created.data?.card_id;
862
+ if (!cardId) {
863
+ throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
864
+ }
865
+ const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
866
+ let messageId;
867
+ if (replyTo) {
868
+ const sent = await channel.rawClient.im.v1.message.reply({
869
+ path: { message_id: replyTo },
870
+ data: { msg_type: "interactive", content, reply_in_thread: replyInThread }
871
+ });
872
+ messageId = sent.data?.message_id;
873
+ } else {
874
+ const sent = await channel.rawClient.im.v1.message.create({
875
+ params: { receive_id_type: receiveIdType },
876
+ data: { receive_id: to, msg_type: "interactive", content }
877
+ });
878
+ messageId = sent.data?.message_id;
879
+ }
880
+ if (!messageId) {
881
+ throw new Error("send card-by-reference returned no message_id");
882
+ }
883
+ byMessageId.set(messageId, { cardId, sequence: 0 });
884
+ return { messageId, cardId };
885
+ };
886
+ for (let i = 0; ; i++) {
887
+ try {
888
+ return await attempt();
889
+ } catch (err) {
890
+ if (i >= 2 || !isCardIdNotReady(err)) throw err;
891
+ log.fail("card", err, { phase: "managed-send", attempt: i, retry: true });
892
+ await new Promise((r) => setTimeout(r, 400 * (i + 1)));
893
+ }
894
+ }
895
+ }
896
+ async function updateManagedCard(channel, messageId, card2) {
897
+ const entry = byMessageId.get(messageId);
898
+ if (!entry) {
899
+ log.info("card", "managed-update-no-entry", { messageId, known: byMessageId.size });
900
+ return false;
901
+ }
902
+ stampRenderToken(card2);
903
+ const data = JSON.stringify(card2);
904
+ const push = async () => {
905
+ entry.sequence += 1;
906
+ await channel.rawClient.cardkit.v1.card.update({
907
+ path: { card_id: entry.cardId },
908
+ data: { card: { type: "card_json", data }, sequence: entry.sequence, uuid: `u_${entry.cardId}_${entry.sequence}` }
909
+ });
910
+ };
911
+ try {
912
+ await push();
913
+ return true;
914
+ } catch (err) {
915
+ log.fail("card", err, { phase: "managed-update", cardId: entry.cardId, seq: entry.sequence, retry: true });
916
+ await new Promise((r) => setTimeout(r, 3200));
917
+ try {
918
+ await push();
919
+ return true;
920
+ } catch (err2) {
921
+ log.fail("card", err2, { phase: "managed-update-retry", cardId: entry.cardId, seq: entry.sequence });
922
+ return false;
923
+ }
924
+ }
925
+ }
926
+ var byMessageId, renderToken;
927
+ var init_managed = __esm({
928
+ "src/card/managed.ts"() {
929
+ "use strict";
930
+ init_logger();
931
+ byMessageId = /* @__PURE__ */ new Map();
932
+ renderToken = 0;
933
+ }
934
+ });
935
+
936
+ // src/cli-bridge/cards.ts
937
+ function pickCopy(pool, key) {
938
+ let h = 0;
939
+ for (let i = 0; i < key.length; i += 1) h = h * 31 + key.charCodeAt(i) >>> 0;
940
+ return pool[h % pool.length];
941
+ }
942
+ function sessionLabel(cwd) {
943
+ const base = (cwd || "").replace(/[/\\]+$/, "").split(/[/\\]/).pop();
944
+ return base || "session";
945
+ }
946
+ function titleEl(verb) {
947
+ return md(`**${BRAND} \xB7 ${verb}**`);
948
+ }
949
+ function metaLine(source, cwd, extra) {
950
+ const chips = "\u{1F916} `" + agentLabel[source] + "`\u3000\u{1F4AC} `" + sessionLabel(cwd) + "`" + (extra ? "\u3000" + extra : "");
951
+ return note(chips);
952
+ }
953
+ function clip(text, max = 3e3) {
954
+ return text.length > max ? text.slice(0, max) + "\n\u2026\uFF08\u5DF2\u622A\u65AD / truncated\uFF09" : text;
955
+ }
956
+ function codeBlock(content, language) {
957
+ const fence = content.includes("```") ? "````" : "```";
958
+ return `${fence}${language}
959
+ ${content}
960
+ ${fence}`;
961
+ }
962
+ function splitTextIntoChunks(value, chunkSize) {
963
+ const chunks = [];
964
+ for (let index = 0; index < value.length; index += chunkSize) chunks.push(value.slice(index, index + chunkSize));
965
+ return chunks.length > 0 ? chunks : [""];
966
+ }
967
+ function disabledButton(label, type = "default") {
968
+ return {
969
+ tag: "button",
970
+ text: { tag: "plain_text", content: label },
971
+ type,
972
+ disabled: true
973
+ };
974
+ }
975
+ function cliBridgeSettingsSection(input2) {
976
+ const scopeButton = (label, value) => button(label, { a: CLI.setNotifyScope, v: value }, input2.notifyScope === value ? "primary" : "default");
977
+ const agentButton = (label, agent, on) => button(`${label}\uFF1A${on ? "\u5F00" : "\u5173"}`, { a: CLI.toggleAgent, agent, v: on ? "off" : "on" }, on ? "primary" : "default");
978
+ return [
979
+ hr(),
980
+ md("**\u2615 \u5496\u5561\u4E00\u4E0B**"),
981
+ note("\u53BB\u5012\u676F\u5496\u5561\u7684\u5DE5\u592B\uFF0C\u6211\u66FF\u4F60\u76EF\u7740\u672C\u673A\u7684 Claude Code / Codex \u2014\u2014 \u5B83\u8981\u5BA1\u6279\u3001\u8981\u95EE\u4F60\u3001\u6216\u8DD1\u5B8C\u4E86\uFF0C\u90FD\u63A8\u5230\u8FD9\u4E2A\u79C1\u804A\uFF0C\u4F60\u5728\u624B\u673A\u4E0A\u63A5\u7740\u62CD\u677F\u5C31\u884C\u3002"),
982
+ actions([
983
+ button(input2.enabled ? "\u5496\u5561\u4E00\u4E0B\uFF1A\u5F00" : "\u5496\u5561\u4E00\u4E0B\uFF1A\u5173", { a: CLI.toggleEnabled, v: input2.enabled ? "off" : "on" }, input2.enabled ? "primary" : "default")
984
+ ]),
985
+ note("\u9501\u5C4F\u3001\u6216\u952E\u9F20\u7A7A\u95F2\u8D85\u8FC7\u8BBE\u5B9A\u65F6\u957F\uFF0C\u5C31\u5F53\u4F60\u53BB\u63A5\u5496\u5561\u4E86 \u2192 \u81EA\u52A8\u63A5\u7BA1\uFF1B\u56DE\u5230\u7535\u8111/\u89E3\u9501\u7ACB\u5373\u6536\u624B\u3002"),
986
+ // Shown only on win32 — macOS/Linux rendering is unchanged.
987
+ ...process.platform === "win32" ? [note("\u26A0\uFE0F Windows \u79BB\u5F00\u68C0\u6D4B\u4E3A\u5B9E\u9A8C\u6027\uFF08PowerShell\uFF09\uFF1B\u68C0\u6D4B\u4E0D\u53EF\u7528\u65F6\u4F1A\u76F4\u63A5\u8F6C\u53D1\u3002")] : [],
988
+ md("**\u{1F4E3} \u901A\u77E5\u8303\u56F4**"),
989
+ note("\u79BB\u5F00\u65F6\u628A\u54EA\u4E9B\u4F1A\u8BDD\u63A8\u5230\u98DE\u4E66\u3002"),
990
+ actions([
991
+ scopeButton("\u5168\u90E8", "all"),
992
+ scopeButton("\u4EC5\u7ED1\u5B9A\u9879\u76EE", "bound_projects"),
993
+ scopeButton("\u4E0D\u901A\u77E5", "none")
994
+ ]),
995
+ md("**\u{1F916} \u8F6C\u53D1\u54EA\u4E9B\u540E\u7AEF**"),
996
+ actions([
997
+ agentButton("Claude Code", "claude", input2.agents.claude),
998
+ agentButton("Codex", "codex", input2.agents.codex)
999
+ ]),
1000
+ md("**\u{1F50B} \u79BB\u5F00\u4FDD\u6D3B**"),
1001
+ note("\u79BB\u5F00\u4E14\u6709\u4EFB\u52A1\u5728\u8DD1\u65F6\u81EA\u52A8\u9876\u4F4F\u7CFB\u7EDF\u4F11\u7720\uFF08\u5C4F\u5E55\u7167\u5E38\u7184\u706D\uFF09\uFF0C\u56DE\u5230\u7535\u8111/\u89E3\u9501\u5373\u5173\u3002\u4EC5 macOS\u3002"),
1002
+ actions([
1003
+ button(input2.keepAwake ? "\u79BB\u5F00\u4FDD\u6D3B\uFF1A\u5F00" : "\u79BB\u5F00\u4FDD\u6D3B\uFF1A\u5173", { a: CLI.toggleKeepAwake, v: input2.keepAwake ? "off" : "on" }, input2.keepAwake ? "primary" : "default")
1004
+ ]),
1005
+ md(`**\u{1F527} hooks**\u3000Claude Code\uFF1A**${statusLabel[input2.statuses.claude.status]}**\u3000Codex\uFF1A**${statusLabel[input2.statuses.codex.status]}**`),
1006
+ // Repair replaces any agent2lark hooks in place — say so before the user clicks,
1007
+ // since it rewrites another tool's ~/.claude / ~/.codex hook config.
1008
+ ...input2.statuses.claude.status === "conflict_agent2lark" || input2.statuses.codex.status === "conflict_agent2lark" ? [note("\u26A0\uFE0F \u68C0\u6D4B\u5230 agent2lark \u7684 hook\uFF1B\u70B9\u300C\u4FEE\u590D hooks\u300D\u4F1A\u7528\u672C bridge \u8986\u76D6\u5B83\u3002")] : [],
1009
+ actions([button("\u4FEE\u590D hooks", { a: CLI.repairHooks }, "primary")]),
1010
+ input2.canEnable.ok ? note("\u76EE\u6807\uFF1A\u673A\u5668\u4EBA owner \u79C1\u804A\u3000\xB7\u3000hooks \u4E3A\u672C\u673A\u5168\u5C40\uFF0C\u591A\u4E2A\u673A\u5668\u4EBA\u5171\u7528\u4E00\u5957\uFF08\u4FEE\u590D\u4E0D\u4F1A\u91CD\u590D\u5B89\u88C5\uFF09\u3002") : note("\u5F00\u542F\u300C\u2615 \u5496\u5561\u4E00\u4E0B\u300D\u524D\u8BF7\u5148\u8BBE\u7F6E\u673A\u5668\u4EBA owner\u3002")
1011
+ ];
1012
+ }
1013
+ function buildCliBridgeAwayNoticeCard(input2) {
1014
+ const k2 = input2.key ?? input2.cwd ?? "";
1015
+ const c = pickCopy(COPY.away, k2);
1016
+ return card(
1017
+ [
1018
+ titleEl(c.title),
1019
+ metaLine(input2.source, input2.cwd),
1020
+ note(c.body),
1021
+ md(`\u{1F4C2} **\u5F53\u524D\u9879\u76EE**
1022
+ ${input2.cwd || "unknown"}`),
1023
+ note(pickCopy(COPY.footerAway, k2))
1024
+ ],
1025
+ { forward: false }
1026
+ );
1027
+ }
1028
+ function buildCliBridgeApprovalCard(input2) {
1029
+ const status = input2.status ?? "pending";
1030
+ const verb = status === "approved" ? "\u2705 \u5DF2\u5141\u8BB8" : status === "denied" ? "\u26D4 \u5DF2\u62D2\u7EDD" : status === "local" ? "\u21A9\uFE0F \u5DF2\u8F6C\u4EA4\u672C\u673A" : status === "timeout" ? "\u23F0 \u5DF2\u8D85\u65F6" : pickCopy(COPY.permission, input2.id || input2.cwd);
1031
+ const tool = input2.toolName ? "\u{1F6E0}\uFE0F `" + input2.toolName + "`" : void 0;
1032
+ const elements = [
1033
+ titleEl(verb),
1034
+ metaLine(input2.source, input2.cwd, tool),
1035
+ input2.command ? md(`\u{1F4BB} **\u547D\u4EE4**
1036
+ ${codeBlock(clip(input2.command), "bash")}`) : note("\uFF08hook \u672A\u5E26\u547D\u4EE4\u6587\u672C\uFF09")
1037
+ ];
1038
+ if (status === "pending") {
1039
+ elements.push(actions([
1040
+ button("\u2705 \u5141\u8BB8", { a: CLI.approveOnce, id: input2.id }, "primary"),
1041
+ ...input2.allowSession === false ? [] : [button("\u{1F501} \u59CB\u7EC8\u5141\u8BB8", { a: CLI.approveSession, id: input2.id })],
1042
+ button("\u26D4 \u62D2\u7EDD", { a: CLI.deny, id: input2.id }, "danger")
1043
+ ]));
1044
+ }
1045
+ return card(elements, { forward: false });
1046
+ }
1047
+ function questionChoiceField(index) {
1048
+ return `q${index}_choice`;
1049
+ }
1050
+ function questionCustomField(index) {
1051
+ return `q${index}_custom`;
1052
+ }
1053
+ function optionDisplay(o) {
1054
+ if (!o.description) return o.label;
1055
+ const desc = o.description.length > 36 ? o.description.slice(0, 36) + "\u2026" : o.description;
1056
+ return `${o.label} \u2014 ${desc}`;
1057
+ }
1058
+ function buildCliBridgeQuestionCard(input2) {
1059
+ const status = input2.status ?? "pending";
1060
+ const questions = input2.questions ?? [];
1061
+ const numbered = questions.length > 1;
1062
+ const verb = status === "approved" ? "\u2705 \u5DF2\u56DE\u7B54" : status === "denied" ? "\u26D4 \u5DF2\u62D2\u7EDD" : status === "local" ? "\u21A9\uFE0F \u5DF2\u8F6C\u4EA4\u672C\u673A" : status === "timeout" ? "\u23F0 \u5DF2\u8D85\u65F6" : pickCopy(COPY.question, input2.id || input2.cwd);
1063
+ const elements = [
1064
+ titleEl(verb),
1065
+ metaLine(input2.source, input2.cwd)
1066
+ ];
1067
+ if (status === "pending") {
1068
+ const formEls = [];
1069
+ questions.forEach((q, i) => {
1070
+ const head = `${numbered ? `${i + 1}. ` : ""}${q.header || "\u8BF7\u4F60\u5B9A\u4E00\u4E0B"}`;
1071
+ formEls.push(md(`\u{1F9E9} **${head}**
1072
+ ${clip(q.question, 600)}${q.multiSelect ? "\u3000_(\u53EF\u591A\u9009)_" : ""}`));
1073
+ const opts = q.options.map((o) => ({ label: optionDisplay(o), value: o.label }));
1074
+ formEls.push(q.multiSelect ? multiSelectMenu({ name: questionChoiceField(i), placeholder: "\u53EF\u591A\u9009\u2026", options: opts }) : selectMenu({ name: questionChoiceField(i), placeholder: "\u9009\u4E00\u4E2A\u2026", options: opts }));
1075
+ formEls.push(input({ name: questionCustomField(i), placeholder: "\u90FD\u4E0D\u5408\u9002\uFF1F\u76F4\u63A5\u5199\u8FD9\u91CC\uFF08\u586B\u4E86\u5C31\u7528\u4F60\u5199\u7684\uFF09" }));
1076
+ });
1077
+ formEls.push(actions([submitButton("\u2705 \u63D0\u4EA4", { a: CLI.questionSubmit, id: input2.id }, "primary", "submit")]));
1078
+ elements.push(form(`cli_question_${input2.id}`, formEls));
1079
+ elements.push(note("\u{1F419} \u9009\u9879\u548C\u300C\u81EA\u5DF1\u5199\u300D\u90FD\u5728\u5361\u7247\u91CC\uFF0C\u7B54\u5B8C\u70B9\u300C\u63D0\u4EA4\u300D\u5373\u53EF \u2014\u2014 \u4E0D\u7528\u56DE\u5230\u7535\u8111\u3002"));
1080
+ } else if (status === "approved") {
1081
+ const ans = input2.answers ?? {};
1082
+ const lines = questions.length ? questions.map((q, i) => `**${numbered ? `${i + 1}. ` : ""}${q.header || "\u56DE\u7B54"}**\uFF1A${ans[q.question] ?? "\uFF08\u672A\u7B54\uFF09"}`).join("\n") : Object.entries(ans).map(([k2, v]) => `**${k2}**\uFF1A${v}`).join("\n");
1083
+ elements.push(md(`\u2705 \u4F60\u7684\u56DE\u7B54
1084
+ ${lines || "\uFF08\u65E0\uFF09"}`));
1085
+ }
1086
+ return card(elements, { forward: false });
1087
+ }
1088
+ function buildCliBridgeTaskCompletionCard(input2) {
1089
+ const verb = input2.replyDoneAt ? "\u2705 \u5DF2\u786E\u8BA4\u5B8C\u6210" : input2.status === "failed" ? "\u274C \u4EFB\u52A1\u5931\u8D25" : pickCopy(COPY.completion, input2.id || input2.cwd);
1090
+ const elements = [
1091
+ titleEl(verb),
1092
+ metaLine(input2.source, input2.cwd)
1093
+ ];
1094
+ const summary = input2.summary?.trim();
1095
+ if (summary) {
1096
+ for (const [index, chunk] of splitTextIntoChunks(clip(summary, 5600), TASK_OUTPUT_CHUNK_SIZE).entries()) {
1097
+ const title = summary.length > TASK_OUTPUT_CHUNK_SIZE ? `Agent \u8F93\u51FA\uFF08${index + 1}\uFF09` : "Agent \u8F93\u51FA";
1098
+ elements.push(md(`\u{1F4DD} **${title}**
1099
+ ${codeBlock(chunk, "text")}`));
1100
+ }
1101
+ } else {
1102
+ elements.push(note("\uFF08hook \u672A\u5E26\u6700\u7EC8\u56DE\u7B54\uFF09"));
1103
+ }
1104
+ if (input2.replyEnabled) {
1105
+ const expiresAt = input2.replyExpiresAt ? `\uFF08\u6709\u6548\u671F\u81F3 ${new Date(input2.replyExpiresAt).toLocaleString("zh-CN")}\uFF09` : "";
1106
+ elements.push(actions([button("\u23F3 \u7B49\u5F85\u786E\u8BA4", { a: CLI.taskCompletionDone, id: input2.id }, "primary")]));
1107
+ elements.push(note(pickCopy(COPY.footerReply, input2.id || input2.cwd) + expiresAt));
1108
+ } else if (input2.replyDoneAt) {
1109
+ elements.push(actions([disabledButton("\u2705 \u5DF2\u5B8C\u6210", "primary")]));
1110
+ }
1111
+ return card(elements, { forward: false });
1112
+ }
1113
+ var CLI, BRAND, agentLabel, statusLabel, TASK_OUTPUT_CHUNK_SIZE, COPY;
1114
+ var init_cards2 = __esm({
1115
+ "src/cli-bridge/cards.ts"() {
1116
+ "use strict";
1117
+ init_cards();
1118
+ CLI = {
1119
+ toggleEnabled: "cli.toggle.enabled",
1120
+ setDelivery: "cli.set.delivery",
1121
+ setNotifyScope: "cli.set.notifyScope",
1122
+ toggleAgent: "cli.toggle.agent",
1123
+ toggleKeepAwake: "cli.toggle.keepAwake",
1124
+ toggleIncludeBridge: "cli.toggle.includeBridge",
1125
+ repairHooks: "cli.hooks.repair",
1126
+ approveOnce: "cli.approve.once",
1127
+ approveSession: "cli.approve.session",
1128
+ deny: "cli.deny",
1129
+ // One submit for the whole multi-question form (dropdown + custom text per question).
1130
+ questionSubmit: "cli.question.submit",
1131
+ taskCompletionDone: "cli.taskCompletion.done"
1132
+ };
1133
+ BRAND = "\u{1F308} Vonvon Bridge";
1134
+ agentLabel = { claude: "Claude Code", codex: "Codex" };
1135
+ statusLabel = {
1136
+ installed: "\u5DF2\u5B89\u88C5",
1137
+ not_installed: "\u672A\u5B89\u88C5",
1138
+ needs_repair: "\u9700\u4FEE\u590D",
1139
+ conflict_agent2lark: "\u4E0E agent2lark \u51B2\u7A81"
1140
+ };
1141
+ TASK_OUTPUT_CHUNK_SIZE = 2800;
1142
+ COPY = {
1143
+ away: [
1144
+ { title: "\u4F60\u6E9C\u5566?\u6865\u6211\u5148\u7ED9\u4F60\u67B6\u4E0A", body: "\u672C\u5730\u8FD8\u5728\u8DD1\u6D3B\u513F \u2014\u2014 \u63A5\u4E0B\u6765\u8981\u5BA1\u6279 / \u63D0\u95EE / \u6536\u5C3E,\u6211\u90FD\u987A\u7740\u6865\u9012\u7ED9\u4F60\u3002" },
1145
+ { title: "\u4EBA\u8D70\u6865\u4E0D\u65AD,\u6211\u63A5\u7BA1\u4E86", body: "\u68C0\u6D4B\u5230\u4F60\u79BB\u5F00\u3002\u672C\u673A Claude / Codex \u7684\u5927\u5C0F\u4E8B\u6211\u63A5\u7740,\u8981\u7D27\u7684\u5C31\u558A\u4F60\u3002" },
1146
+ { title: "\u4F60\u5FD9\u4F60\u7684,\u8FD9\u5934\u4EA4\u7ED9\u6211", body: "\u4F60\u4E0D\u5728\u952E\u76D8\u524D\u8FD9\u6BB5,\u672C\u5730\u7684\u5BA1\u6279 / \u63D0\u95EE / \u5B8C\u6210,\u6211\u90FD\u9001\u5230\u4F60\u624B\u4E0A\u3002" },
1147
+ { title: "\u6865\u5DF2\u5C31\u4F4D,\u63A5\u7BA1\u6210\u529F", body: "\u672C\u673A\u7684\u6D3B\u513F\u8FD8\u8DD1\u7740\u5462,\u6211\u66FF\u4F60\u5B88\u5728\u8FD9\u5934,\u8BE5\u4F60\u62CD\u677F\u7684\u4E00\u4E2A\u90FD\u4E0D\u6F0F\u3002" }
1148
+ ],
1149
+ permission: [
1150
+ "\u6865\u90A3\u5934\u60F3\u52A8\u624B,\u5148\u95EE\u4F60\u4E00\u58F0",
1151
+ "\u6709\u6761\u547D\u4EE4\u60F3\u8DD1,\u7B49\u4F60\u70B9\u4E2A\u5934",
1152
+ "\u5B83\u4E3E\u624B\u4E86:\u8FD9\u4E2A\u64CD\u4F5C\u80FD\u653E\u884C\u5417?",
1153
+ "\u672C\u5730\u8981\u6267\u884C\u70B9\u4E1C\u897F,\u4F60\u6765\u62CD\u677F"
1154
+ ],
1155
+ question: [
1156
+ "\u6865\u90A3\u5934\u5361\u4E86\u4E2A\u9009\u62E9,\u7B49\u4F60\u5B9A",
1157
+ "\u6709\u9053\u9009\u62E9\u9898\u9001\u5230\u4F60\u9762\u524D\u5566",
1158
+ "\u5B83\u62FF\u4E0D\u51C6,\u60F3\u542C\u542C\u4F60\u7684",
1159
+ "\u5E2E\u4F60\u63A5\u4F4F\u4E00\u4E2A\u9009\u62E9,\u9009\u54EA\u4E2A?"
1160
+ ],
1161
+ completion: [
1162
+ "\u6865\u90A3\u5934\u6536\u5DE5\u4E86,\u7784\u4E00\u773C?",
1163
+ "\u6D3B\u513F\u5E72\u5B8C\u4E86,\u7B49\u4F60\u4E00\u53E5\u8BDD",
1164
+ "\u641E\u5B9A!\u60F3\u63A5\u7740\u652F\u4F7F\u5C31\u56DE\u6211\u4E00\u53E5",
1165
+ "\u8FD9\u4E00\u8F6E\u7ED3\u675F,\u770B\u770B\u6210\u679C?"
1166
+ ],
1167
+ footerAway: [
1168
+ "\u4F60\u4E00\u56DE\u7535\u8111(\u89E3\u9501 / \u52A8\u952E\u9F20),\u6211\u7ACB\u523B\u6536\u6865,\u7EDD\u4E0D\u6253\u6270\u3002",
1169
+ "\u56DE\u5230\u952E\u76D8\u6211\u5C31\u628A\u6865\u64A4\u4E86,\u534A\u70B9\u4E0D\u70E6\u4F60\u3002",
1170
+ "\u4EBA\u5728\u684C\u524D\u6211\u5C31\u95ED\u5634,\u4E00\u5207\u56DE\u5F52\u7EC8\u7AEF\u3002"
1171
+ ],
1172
+ footerReply: [
1173
+ "\u{1F4AC} \u76F4\u63A5\u56DE\u590D\u8FD9\u6761\u6D88\u606F,\u5C31\u80FD\u63A5\u7740\u652F\u4F7F\u5B83\u5E72\u6D3B;\u6216\u70B9\u300C\u7B49\u5F85\u786E\u8BA4\u300D\u8BA9\u5B83\u6536\u5DE5\u3002",
1174
+ "\u{1F4AC} \u56DE\u6211\u4E00\u53E5\u8BDD,\u5B83\u7ACB\u523B\u63A5\u7740\u8DD1;\u4E0D\u60F3\u7EE7\u7EED\u5C31\u70B9\u300C\u7B49\u5F85\u786E\u8BA4\u300D\u3002"
1175
+ ]
1176
+ };
1177
+ }
1178
+ });
1179
+
1180
+ // src/core/stdin.ts
1181
+ function readStdin() {
1182
+ return new Promise((resolve9) => {
1183
+ if (process.stdin.isTTY) {
1184
+ resolve9("");
1185
+ return;
1186
+ }
1187
+ let data = "";
1188
+ process.stdin.setEncoding("utf8");
1189
+ process.stdin.on("data", (chunk) => data += chunk);
1190
+ process.stdin.on("end", () => resolve9(data));
1191
+ });
1192
+ }
1193
+ var init_stdin = __esm({
1194
+ "src/core/stdin.ts"() {
1195
+ "use strict";
1196
+ }
1197
+ });
1198
+
1199
+ // src/cli-bridge/ipc.ts
1200
+ import { chmod as chmod5, rm as rm6 } from "fs/promises";
1201
+ import net from "net";
1202
+ async function startCliBridgeIpcServer(opts) {
1203
+ if (process.platform !== "win32") {
1204
+ await rm6(opts.socketPath, { force: true }).catch(() => void 0);
1205
+ }
1206
+ const sockets = /* @__PURE__ */ new Set();
1207
+ const server = net.createServer((socket) => {
1208
+ sockets.add(socket);
1209
+ socket.on("close", () => sockets.delete(socket));
1210
+ socket.on("error", () => sockets.delete(socket));
1211
+ let data = "";
1212
+ let handled = false;
1213
+ socket.on("data", (chunk) => {
1214
+ if (handled) return;
1215
+ data += chunk.toString("utf8");
1216
+ if (!data.includes("\n")) return;
1217
+ handled = true;
1218
+ const line = data.split("\n")[0] ?? "";
1219
+ void (async () => {
1220
+ try {
1221
+ const msg = JSON.parse(line);
1222
+ const response = await opts.handleMessage(msg);
1223
+ socket.end(JSON.stringify(response) + "\n");
1224
+ } catch (err) {
1225
+ socket.end(JSON.stringify({ decision: "fallback_local", reason: err instanceof Error ? err.message : String(err) }) + "\n");
1226
+ }
1227
+ })();
1228
+ });
1229
+ });
1230
+ await new Promise((resolve9, reject) => {
1231
+ server.once("error", reject);
1232
+ server.listen(opts.socketPath, () => {
1233
+ server.off("error", reject);
1234
+ if (process.platform === "win32") {
1235
+ resolve9();
1236
+ return;
1237
+ }
1238
+ chmod5(opts.socketPath, 384).then(resolve9, (err) => {
1239
+ server.close(() => reject(err));
1240
+ });
1241
+ });
1242
+ });
1243
+ return {
1244
+ close: () => new Promise((resolve9) => {
1245
+ for (const socket of sockets) socket.destroy();
1246
+ sockets.clear();
1247
+ server.close(() => resolve9());
1248
+ })
1249
+ };
1250
+ }
1251
+ async function sendCliHookMessage(socketPath, msg) {
1252
+ return new Promise((resolve9, reject) => {
1253
+ const socket = net.createConnection(socketPath);
1254
+ let data = "";
1255
+ socket.setTimeout(864e5);
1256
+ socket.on("connect", () => socket.write(JSON.stringify(msg) + "\n"));
1257
+ socket.on("data", (chunk) => {
1258
+ data += chunk.toString("utf8");
1259
+ if (!data.includes("\n")) return;
1260
+ socket.end();
1261
+ try {
1262
+ resolve9(JSON.parse(data.split("\n")[0] ?? ""));
1263
+ } catch (err) {
1264
+ reject(err);
1265
+ }
1266
+ });
1267
+ socket.on("timeout", () => {
1268
+ socket.destroy();
1269
+ reject(new Error("cli bridge IPC timeout"));
1270
+ });
1271
+ socket.on("error", reject);
1272
+ });
1273
+ }
1274
+ var init_ipc = __esm({
1275
+ "src/cli-bridge/ipc.ts"() {
1276
+ "use strict";
1277
+ }
1278
+ });
1279
+
1280
+ // src/cli-bridge/parser.ts
1281
+ function normalizeEventName(eventName) {
1282
+ if (!eventName) return eventName;
1283
+ if (["PermissionRequest", "permission_request", "permission.asked", "permission_requested"].includes(eventName)) return "PermissionRequest";
1284
+ if (["PreToolUse", "pre_tool_use"].includes(eventName)) return "PreToolUse";
1285
+ if (["PostToolUse", "post_tool_use"].includes(eventName)) return "PostToolUse";
1286
+ if (["Stop", "stop", "SubagentStop", "subagent_stop", "session.idle", "session_idle"].includes(eventName)) return "TaskComplete";
1287
+ if (["StopFailure", "stop_failure", "session.error", "session_error"].includes(eventName)) return "TaskCompleteFailure";
1288
+ return eventName;
1289
+ }
1290
+ function stringifySummaryValue(value) {
1291
+ if (value == null) return "";
1292
+ if (typeof value === "string") return value.trim();
1293
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1294
+ if (Array.isArray(value)) return value.map(stringifySummaryValue).filter(Boolean).join("\n").trim();
1295
+ if (typeof value === "object") {
1296
+ const obj = value;
1297
+ for (const key of ["last_assistant_message", "lastAssistantMessage", "assistant_message", "assistantMessage", "assistant", "final", "completion", "answer", "response", "output", "result", "content", "text", "message", "summary", "error"]) {
1298
+ const text = stringifySummaryValue(obj[key]);
1299
+ if (text) return text;
1300
+ }
1301
+ }
1302
+ return "";
1303
+ }
1304
+ function parseHookPayload(source, rawPayload, env = process.env) {
1305
+ let data = {};
1306
+ try {
1307
+ const parsed = JSON.parse(rawPayload);
1308
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) data = parsed;
1309
+ } catch {
1310
+ data = {};
1311
+ }
1312
+ const hookEventName = String(data.hook_event_name ?? data.event_type ?? "PermissionRequest");
1313
+ const normalized = normalizeEventName(hookEventName);
1314
+ const typeByEvent = {
1315
+ PermissionRequest: "permission_request",
1316
+ PostToolUse: "post_tool_use",
1317
+ TaskComplete: "task_complete",
1318
+ TaskCompleteFailure: "task_complete"
1319
+ };
1320
+ const type = typeByEvent[normalized ?? ""] ?? "pre_tool_use";
1321
+ const toolInput = data.tool_input ?? data.toolInput ?? data.metadata ?? data.properties ?? {};
1322
+ return {
1323
+ type,
1324
+ source,
1325
+ sessionId: String(data.session_id ?? data.sessionId ?? ""),
1326
+ cwd: String(data.cwd ?? ""),
1327
+ toolName: String(data.tool_name ?? data.toolName ?? data.permission ?? ""),
1328
+ toolInput: toolInput && typeof toolInput === "object" && !Array.isArray(toolInput) ? toolInput : {},
1329
+ hookEventName,
1330
+ stopHookActive: data.stop_hook_active === true || data.stopHookActive === true,
1331
+ permissionMode: typeof data.permission_mode === "string" ? data.permission_mode : typeof data.permissionMode === "string" ? data.permissionMode : void 0,
1332
+ permissionSuggestions: Array.isArray(data.permission_suggestions) ? data.permission_suggestions : Array.isArray(data.permissionSuggestions) ? data.permissionSuggestions : void 0,
1333
+ taskStatus: normalized === "TaskCompleteFailure" ? "failed" : normalized === "TaskComplete" ? "completed" : void 0,
1334
+ summary: type === "task_complete" ? stringifySummaryValue(data) : void 0,
1335
+ bridgeOwned: env.FEISHU_CODEX_BRIDGE === "1",
1336
+ rawPayloadBytes: Buffer.byteLength(rawPayload, "utf8")
1337
+ };
1338
+ }
1339
+ function extractAskUserQuestion(toolInput) {
1340
+ const rawQuestions = toolInput.questions;
1341
+ if (!Array.isArray(rawQuestions) || rawQuestions.length < 1 || rawQuestions.length > 4) return void 0;
1342
+ const questions = rawQuestions.flatMap((raw) => {
1343
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
1344
+ const obj = raw;
1345
+ const question = typeof obj.question === "string" ? obj.question.trim() : "";
1346
+ if (!question) return [];
1347
+ const rawOptions = obj.options;
1348
+ if (!Array.isArray(rawOptions) || rawOptions.length < 2) return [];
1349
+ const options = rawOptions.flatMap((option) => {
1350
+ if (!option || typeof option !== "object" || Array.isArray(option)) return [];
1351
+ const o = option;
1352
+ if (typeof o.label !== "string" || !o.label.trim()) return [];
1353
+ return [{
1354
+ label: o.label.trim(),
1355
+ description: typeof o.description === "string" && o.description.trim() ? o.description.trim() : void 0,
1356
+ preview: typeof o.preview === "string" && o.preview.trim() ? o.preview.trim() : void 0
1357
+ }];
1358
+ });
1359
+ if (options.length !== rawOptions.length) return [];
1360
+ return [{
1361
+ question,
1362
+ header: typeof obj.header === "string" && obj.header.trim() ? obj.header.trim() : void 0,
1363
+ multiSelect: obj.multiSelect === true,
1364
+ options
1365
+ }];
1366
+ });
1367
+ if (questions.length !== rawQuestions.length) return void 0;
1368
+ return { questions };
1369
+ }
1370
+ var init_parser = __esm({
1371
+ "src/cli-bridge/parser.ts"() {
1372
+ "use strict";
1373
+ }
1374
+ });
1375
+
1376
+ // src/cli-bridge/protocol.ts
1377
+ function buildHookStdout(msg, response) {
1378
+ if (response.stdout !== void 0) return response.stdout;
1379
+ if (msg.type === "post_tool_use") return "{}";
1380
+ if (response.decision === "fallback_local") return "";
1381
+ if (msg.type === "task_complete") return "{}";
1382
+ const decision = response.decision === "deny" ? "deny" : "allow";
1383
+ if (msg.source === "codex") {
1384
+ if (msg.hookEventName === "PermissionRequest") {
1385
+ const decisionObj2 = { behavior: decision };
1386
+ if (decision === "deny") decisionObj2.message = response.reason || "Denied by feishu-codex-bridge.";
1387
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "PermissionRequest", decision: decisionObj2 } });
1388
+ }
1389
+ if (msg.hookEventName === "PreToolUse" && decision === "deny") {
1390
+ return JSON.stringify({
1391
+ hookSpecificOutput: {
1392
+ hookEventName: "PreToolUse",
1393
+ permissionDecision: "deny",
1394
+ permissionDecisionReason: response.reason || "Denied by feishu-codex-bridge."
1395
+ }
1396
+ });
1397
+ }
1398
+ return "{}";
1399
+ }
1400
+ const decisionObj = { behavior: decision };
1401
+ if (decision === "allow" && response.updatedInput) decisionObj.updatedInput = response.updatedInput;
1402
+ if (decision === "deny") {
1403
+ decisionObj.message = response.reason || "Denied by feishu-codex-bridge.";
1404
+ if (response.interrupt) decisionObj.interrupt = true;
1405
+ }
1406
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "PermissionRequest", decision: decisionObj } });
1407
+ }
1408
+ var init_protocol = __esm({
1409
+ "src/cli-bridge/protocol.ts"() {
1410
+ "use strict";
1411
+ }
1412
+ });
1413
+
1414
+ // src/cli-bridge/store.ts
1415
+ import { randomUUID as randomUUID6 } from "crypto";
1416
+ function prunePending(now) {
1417
+ for (const [id, item] of pending) {
1418
+ if (now - item.createdAt <= STALE_PENDING_MS) continue;
1419
+ pending.delete(id);
1420
+ waiters.delete(id);
1421
+ settled.delete(id);
1422
+ }
1423
+ }
1424
+ function createPendingCliInteraction(input2) {
1425
+ const now = Date.now();
1426
+ prunePending(now);
1427
+ const item = { ...input2, id: randomUUID6(), createdAt: now };
1428
+ pending.set(item.id, item);
1429
+ return item;
1430
+ }
1431
+ function setPendingCliMessageId(id, messageId) {
1432
+ const item = pending.get(id);
1433
+ if (item) pending.set(id, { ...item, messageId });
1434
+ }
1435
+ function getPendingCliInteraction(id) {
1436
+ return pending.get(id);
1437
+ }
1438
+ function findPendingCliInteractionByMessageReply(input2) {
1439
+ const targets = [input2.parentId, input2.rootId].filter((x) => Boolean(x));
1440
+ for (const item of pending.values()) {
1441
+ if (item.messageId && targets.includes(item.messageId)) return item;
1442
+ }
1443
+ return void 0;
1444
+ }
1445
+ function resolvePendingCliInteraction(id, response) {
1446
+ const item = pending.get(id);
1447
+ if (!item) return false;
1448
+ pending.delete(id);
1449
+ const waiter = waiters.get(id);
1450
+ if (waiter) {
1451
+ waiters.delete(id);
1452
+ waiter(response);
1453
+ } else {
1454
+ settled.set(id, response);
1455
+ }
1456
+ return true;
1457
+ }
1458
+ function waitForPendingCliInteraction(id, timeoutMs) {
1459
+ return new Promise((resolve9) => {
1460
+ const buffered = settled.get(id);
1461
+ if (buffered) {
1462
+ settled.delete(id);
1463
+ resolve9(buffered);
1464
+ return;
1465
+ }
1466
+ if (!pending.has(id)) {
1467
+ resolve9({ decision: "fallback_local", reason: "missing_pending" });
1468
+ return;
1469
+ }
1470
+ const timer = setTimeout(() => {
1471
+ pending.delete(id);
1472
+ waiters.delete(id);
1473
+ settled.delete(id);
1474
+ resolve9({ decision: "fallback_local", reason: "timeout" });
1475
+ }, Math.max(timeoutMs, 0));
1476
+ waiters.set(id, (response) => {
1477
+ clearTimeout(timer);
1478
+ resolve9(response);
1479
+ });
1480
+ });
1481
+ }
1482
+ var pending, waiters, settled, STALE_PENDING_MS;
1483
+ var init_store2 = __esm({
1484
+ "src/cli-bridge/store.ts"() {
1485
+ "use strict";
1486
+ pending = /* @__PURE__ */ new Map();
1487
+ waiters = /* @__PURE__ */ new Map();
1488
+ settled = /* @__PURE__ */ new Map();
1489
+ STALE_PENDING_MS = 25 * 60 * 6e4;
1490
+ }
1491
+ });
1492
+
1493
+ // src/cli-bridge/presence.ts
1494
+ import { execFile as execFile2 } from "child_process";
1495
+ import { promisify as promisify2 } from "util";
1496
+ async function resolveCliPresenceRoute(prefs) {
1497
+ const activity = await resolveCliLocalActivity(prefs);
1498
+ if (activity.localActive) return { routeToFeishu: false, reason: "local_active" };
1499
+ if (activity.reason === "away") return { routeToFeishu: true, reason: "away" };
1500
+ if (activity.reason === "presence_failed" && process.platform === "win32") {
1501
+ return { routeToFeishu: true, reason: "presence_failed" };
1502
+ }
1503
+ return { routeToFeishu: false, reason: activity.reason };
1504
+ }
1505
+ async function readMacIdleSeconds() {
1506
+ const now = Date.now();
1507
+ if (idleCache && now - idleCache.at < IDLE_READ_TTL_MS) return idleCache.seconds;
1508
+ const { stdout } = await execFileAsync2("/usr/sbin/ioreg", ["-c", "IOHIDSystem"]);
1509
+ const match = stdout.match(/HIDIdleTime"\s*=\s*(\d+)/);
1510
+ const seconds = Math.floor((match ? Number(match[1]) : 0) / 1e9);
1511
+ idleCache = { seconds, at: now };
1512
+ return seconds;
1513
+ }
1514
+ function parseScreenLocked(ioregStdout) {
1515
+ return /CGSSessionScreenIsLocked"?\s*=\s*Yes/.test(ioregStdout);
1516
+ }
1517
+ async function readMacScreenLocked() {
1518
+ const now = Date.now();
1519
+ if (lockCache && now - lockCache.at < IDLE_READ_TTL_MS) return lockCache.locked;
1520
+ const { stdout } = await execFileAsync2("/usr/sbin/ioreg", ["-n", "Root", "-d1", "-k", "IOConsoleUsers"]);
1521
+ const locked = parseScreenLocked(stdout);
1522
+ lockCache = { locked, at: now };
1523
+ return locked;
1524
+ }
1525
+ async function readWindowsIdleSeconds() {
1526
+ const now = Date.now();
1527
+ if (idleCache && now - idleCache.at < IDLE_READ_TTL_MS) return idleCache.seconds;
1528
+ const script = [
1529
+ "Add-Type @'",
1530
+ "using System;",
1531
+ "using System.Runtime.InteropServices;",
1532
+ "public class A2LIdle {",
1533
+ " [StructLayout(LayoutKind.Sequential)] struct LII { public uint cbSize; public uint dwTime; }",
1534
+ ' [DllImport("user32.dll")] static extern bool GetLastInputInfo(ref LII p);',
1535
+ " public static uint Ms() { LII l = new LII(); l.cbSize = (uint)Marshal.SizeOf(l); GetLastInputInfo(ref l); return ((uint)Environment.TickCount) - l.dwTime; }",
1536
+ "}",
1537
+ "'@",
1538
+ // Lock screen ⇒ LogonUI.exe is running ⇒ report a huge idle value so it counts
1539
+ // as away immediately, without waiting out the idle threshold (mirrors macOS,
1540
+ // where lock is instant-away). Also fires during login/UAC — harmless here.
1541
+ "if (Get-Process LogonUI -ErrorAction SilentlyContinue) { 0x7FFFFFFF } else { [A2LIdle]::Ms() }"
1542
+ ].join("\n");
1543
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
1544
+ const { stdout } = await execFileAsync2("powershell", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]);
1545
+ const ms = Number(stdout.trim());
1546
+ if (!Number.isFinite(ms)) throw new Error(`unparseable idle output: ${stdout.slice(0, 50)}`);
1547
+ const seconds = Math.floor(ms / 1e3);
1548
+ idleCache = { seconds, at: now };
1549
+ return seconds;
1550
+ }
1551
+ async function resolveCliLocalActivity(prefs) {
1552
+ if (!prefs.presence.enabled) return { localActive: false, reason: "disabled" };
1553
+ const useMacReaders = prefs.presence.platform === "macos" || prefs.presence.platform === "auto" && process.platform === "darwin";
1554
+ const readIdleSeconds = useMacReaders ? readMacIdleSeconds : process.platform === "win32" ? readWindowsIdleSeconds : void 0;
1555
+ if (!readIdleSeconds) return { localActive: false, reason: "presence_failed" };
1556
+ if (useMacReaders && await readMacScreenLocked().catch(() => false)) {
1557
+ return { localActive: false, reason: "away" };
1558
+ }
1559
+ try {
1560
+ const idleSeconds = await readIdleSeconds();
1561
+ return idleSeconds >= prefs.presence.idleThresholdSeconds ? { localActive: false, reason: "away" } : { localActive: true, reason: "local_active" };
1562
+ } catch {
1563
+ return { localActive: false, reason: "presence_failed" };
1564
+ }
1565
+ }
1566
+ var execFileAsync2, IDLE_READ_TTL_MS, idleCache, lockCache;
1567
+ var init_presence = __esm({
1568
+ "src/cli-bridge/presence.ts"() {
1569
+ "use strict";
1570
+ execFileAsync2 = promisify2(execFile2);
1571
+ IDLE_READ_TTL_MS = 2e3;
1572
+ }
1573
+ });
1574
+
1575
+ // src/cli-bridge/keep-awake.ts
1576
+ import { spawn as spawn4 } from "child_process";
1577
+ function spawnCaffeinate() {
1578
+ if (process.platform !== "darwin") return void 0;
1579
+ const child = spawn4("/usr/bin/caffeinate", ["-i", "-w", String(process.pid)], { stdio: "ignore" });
1580
+ child.on("error", () => void 0);
1581
+ return child;
1582
+ }
1583
+ function createKeepAwakeController(opts = {}) {
1584
+ const enabled = opts.enabled ?? (() => true);
1585
+ const spawnProcess2 = opts.spawnProcess ?? spawnCaffeinate;
1586
+ let count = 0;
1587
+ let proc;
1588
+ const stop = () => {
1589
+ if (!proc) return;
1590
+ try {
1591
+ proc.kill();
1592
+ } catch {
1593
+ }
1594
+ proc = void 0;
1595
+ };
1596
+ return {
1597
+ acquire() {
1598
+ count += 1;
1599
+ if (!proc && enabled()) proc = spawnProcess2();
1600
+ },
1601
+ release() {
1602
+ if (count === 0) return;
1603
+ count -= 1;
1604
+ if (count === 0) stop();
1605
+ },
1606
+ shutdown() {
1607
+ count = 0;
1608
+ stop();
1609
+ },
1610
+ isActive() {
1611
+ return Boolean(proc);
1612
+ }
1613
+ };
1614
+ }
1615
+ var init_keep_awake = __esm({
1616
+ "src/cli-bridge/keep-awake.ts"() {
1617
+ "use strict";
1618
+ }
1619
+ });
1620
+
1621
+ // src/cli-bridge/service.ts
1622
+ function createCliBridgeService(opts) {
1623
+ let ipc;
1624
+ const allowedSessions = /* @__PURE__ */ new Set();
1625
+ const prefs = () => getCliBridgePreferences(opts.cfg);
1626
+ const hasCustomPresence = Boolean(opts.presence);
1627
+ const presence = opts.presence ?? (() => resolveCliPresenceRoute(prefs()));
1628
+ const localActivity = opts.localActivity ?? (hasCustomPresence ? async () => !(await presence()).routeToFeishu : async () => (await resolveCliLocalActivity(prefs())).localActive);
1629
+ const localReturnPollMs = opts.localReturnPollMs ?? 5e3;
1630
+ const keepAwake = opts.keepAwake ?? createKeepAwakeController({ enabled: () => prefs().keepAwake.enabled });
1631
+ async function notifyAllowedForCwd(cwd) {
1632
+ const scope = prefs().notifyScope;
1633
+ if (scope === "none") return false;
1634
+ if (scope === "bound_projects") return opts.isBoundProject ? Boolean(await opts.isBoundProject(cwd)) : true;
1635
+ return true;
1636
+ }
1637
+ const sessionKey = (input2) => `${input2.source}:${input2.sessionId}`;
1638
+ const sendOwnerCard = (target, cardObject) => sendManagedCard(opts.channel, target.receiveId, cardObject, void 0, false, target.receiveIdType);
1639
+ let replyReaction;
1640
+ async function addTypingReaction(messageId) {
1641
+ try {
1642
+ const r = await opts.channel.rawClient.im.v1.messageReaction.create({
1643
+ path: { message_id: messageId },
1644
+ data: { reaction_type: { emoji_type: "Typing" } }
1645
+ });
1646
+ return r.data?.reaction_id;
1647
+ } catch (err) {
1648
+ log.fail("cli-bridge", err, { phase: "reply-typing-add" });
1649
+ return void 0;
1650
+ }
1651
+ }
1652
+ function armReplyTypingReaction(messageId) {
1653
+ clearReplyTypingReaction();
1654
+ replyReaction = { messageId, idPromise: addTypingReaction(messageId) };
1655
+ }
1656
+ function clearReplyTypingReaction() {
1657
+ const r = replyReaction;
1658
+ replyReaction = void 0;
1659
+ if (!r) return;
1660
+ void r.idPromise.then((id) => {
1661
+ if (!id) return void 0;
1662
+ return opts.channel.rawClient.im.v1.messageReaction.delete({ path: { message_id: r.messageId, reaction_id: id } }).catch((err) => log.fail("cli-bridge", err, { phase: "reply-typing-del" }));
1663
+ }).catch(() => {
1664
+ });
1665
+ }
1666
+ let awayNoticeSent = false;
1667
+ const markLocalActive = () => {
1668
+ awayNoticeSent = false;
1669
+ };
1670
+ async function ensureAwayNoticeSent(target, msg) {
1671
+ if (awayNoticeSent) return;
1672
+ awayNoticeSent = true;
1673
+ await sendOwnerCard(target, buildCliBridgeAwayNoticeCard({ source: msg.source, cwd: msg.cwd, key: msg.sessionId })).catch(
1674
+ (err) => log.fail("cli-bridge", err, { phase: "away-notice" })
1675
+ );
1676
+ }
1677
+ const updatePendingCard = (pending2, cardObject) => {
1678
+ if (!pending2.messageId) return;
1679
+ void updateManagedCard(opts.channel, pending2.messageId, cardObject).catch(
1680
+ (err) => log.fail("cli-bridge", err, { phase: "update-card" })
1681
+ );
1682
+ };
1683
+ const closeCard = (messageId, cardObject, phase) => {
1684
+ void updateManagedCard(opts.channel, messageId, cardObject).catch(
1685
+ (err) => log.fail("cli-bridge", err, { phase })
1686
+ );
1687
+ };
1688
+ function renderPendingCard(pending2, overrides = {}) {
1689
+ if (pending2.kind === "permission") {
1690
+ return buildCliBridgeApprovalCard({
1691
+ id: pending2.id,
1692
+ source: pending2.source,
1693
+ cwd: pending2.cwd,
1694
+ toolName: pending2.toolName,
1695
+ command: pending2.command,
1696
+ hookEventName: pending2.hookEventName,
1697
+ sessionId: pending2.sessionId,
1698
+ createdAt: pending2.createdAt,
1699
+ allowSession: overrides.allowSession,
1700
+ status: overrides.status
1701
+ });
1702
+ }
1703
+ if (pending2.kind === "question") {
1704
+ return buildCliBridgeQuestionCard({
1705
+ id: pending2.id,
1706
+ source: "claude",
1707
+ cwd: pending2.cwd,
1708
+ questions: pending2.questions ?? [],
1709
+ hookEventName: pending2.hookEventName,
1710
+ createdAt: pending2.createdAt,
1711
+ status: overrides.status,
1712
+ answers: overrides.answers
1713
+ });
1714
+ }
1715
+ return buildCliBridgeTaskCompletionCard({
1716
+ id: pending2.id,
1717
+ source: pending2.source,
1718
+ cwd: pending2.cwd,
1719
+ sessionId: pending2.sessionId,
1720
+ hookEventName: pending2.hookEventName,
1721
+ status: pending2.taskStatus ?? "completed",
1722
+ summary: pending2.summary,
1723
+ replyEnabled: overrides.replyEnabled ?? false,
1724
+ replyExpiresAt: overrides.replyExpiresAt,
1725
+ replyDoneAt: overrides.replyDoneAt,
1726
+ createdAt: pending2.createdAt
1727
+ });
1728
+ }
1729
+ async function waitWithLocalReturn(id, timeoutMs, onLocalReturn) {
1730
+ const waiter = waitForPendingCliInteraction(id, timeoutMs);
1731
+ const checkLocalReturn = () => {
1732
+ void localActivity().then((active) => {
1733
+ if (!active) return;
1734
+ markLocalActive();
1735
+ if (getPendingCliInteraction(id)) resolvePendingCliInteraction(id, onLocalReturn);
1736
+ }).catch(() => {
1737
+ });
1738
+ };
1739
+ checkLocalReturn();
1740
+ const poll = setInterval(checkLocalReturn, localReturnPollMs);
1741
+ keepAwake.acquire();
1742
+ try {
1743
+ return await waiter;
1744
+ } finally {
1745
+ clearInterval(poll);
1746
+ keepAwake.release();
1747
+ }
1748
+ }
1749
+ async function handleMessage(msg) {
1750
+ const p = prefs();
1751
+ if (!p.enabled || !p.agents[msg.source]) return { decision: "fallback_local", reason: "disabled" };
1752
+ if (msg.bridgeOwned && !p.includeBridgeOwnedSessionsForDebugging) {
1753
+ return { decision: "fallback_local", reason: "bridge_owned_session" };
1754
+ }
1755
+ if (msg.type === "post_tool_use") return { decision: "allow" };
1756
+ const route = await presence();
1757
+ log.info("cli-bridge", "hook-recv", {
1758
+ type: msg.type,
1759
+ source: msg.source,
1760
+ event: msg.hookEventName,
1761
+ stopHookActive: msg.stopHookActive === true,
1762
+ route: route.reason
1763
+ });
1764
+ if (route.reason === "local_active") markLocalActive();
1765
+ if (msg.type === "task_complete") {
1766
+ clearReplyTypingReaction();
1767
+ if (!p.taskCompletion.enabled) return { decision: "fallback_local", reason: "task_completion_disabled" };
1768
+ if (!route.routeToFeishu) return { decision: "fallback_local", reason: route.reason };
1769
+ if (!await notifyAllowedForCwd(msg.cwd)) return { decision: "fallback_local", reason: "notify_scope" };
1770
+ const target2 = resolveCliBridgeTarget(opts.cfg);
1771
+ if (!target2) return { decision: "fallback_local", reason: "missing_owner" };
1772
+ const canReplyFromFeishu = p.taskCompletion.replyEnabled && !await localActivity();
1773
+ const replyExpiresAt = canReplyFromFeishu ? Date.now() + p.taskCompletion.replyTimeoutSeconds * 1e3 : void 0;
1774
+ const pending3 = canReplyFromFeishu ? createPendingCliInteraction({
1775
+ kind: "task_completion",
1776
+ source: msg.source,
1777
+ sessionId: msg.sessionId,
1778
+ cwd: msg.cwd,
1779
+ hookEventName: msg.hookEventName,
1780
+ taskStatus: msg.taskStatus ?? "completed",
1781
+ summary: msg.summary,
1782
+ replyExpiresAt
1783
+ }) : void 0;
1784
+ if (route.reason === "away") await ensureAwayNoticeSent(target2, msg);
1785
+ const sent2 = await sendOwnerCard(
1786
+ target2,
1787
+ buildCliBridgeTaskCompletionCard({
1788
+ id: pending3?.id ?? "",
1789
+ source: msg.source,
1790
+ cwd: msg.cwd,
1791
+ sessionId: msg.sessionId,
1792
+ hookEventName: msg.hookEventName,
1793
+ status: msg.taskStatus ?? "completed",
1794
+ summary: msg.summary,
1795
+ replyEnabled: canReplyFromFeishu,
1796
+ replyExpiresAt,
1797
+ createdAt: pending3?.createdAt
1798
+ })
1799
+ );
1800
+ if (!pending3) return { decision: "allow" };
1801
+ setPendingCliMessageId(pending3.id, sent2.messageId);
1802
+ const result2 = await waitWithLocalReturn(pending3.id, p.taskCompletion.replyTimeoutSeconds * 1e3, { decision: "allow" });
1803
+ if (result2.reason !== TASK_DONE_CLICKED) {
1804
+ closeCard(sent2.messageId, renderPendingCard(pending3), "close-task-card");
1805
+ }
1806
+ return result2;
1807
+ }
1808
+ if (!route.routeToFeishu) return { decision: "fallback_local", reason: route.reason };
1809
+ const target = resolveCliBridgeTarget(opts.cfg);
1810
+ if (!target) return { decision: "fallback_local", reason: "missing_owner" };
1811
+ if (msg.source === "claude" && msg.toolName === "AskUserQuestion") {
1812
+ if (!await notifyAllowedForCwd(msg.cwd)) return { decision: "fallback_local", reason: "notify_scope" };
1813
+ const ask = extractAskUserQuestion(msg.toolInput);
1814
+ if (!ask) return { decision: "fallback_local", reason: "unsupported_ask_user_question" };
1815
+ const pending3 = createPendingCliInteraction({
1816
+ kind: "question",
1817
+ source: msg.source,
1818
+ sessionId: msg.sessionId,
1819
+ cwd: msg.cwd,
1820
+ questions: ask.questions,
1821
+ // First question text doubles as the reply-match anchor / log label.
1822
+ question: ask.questions[0]?.question,
1823
+ hookEventName: msg.hookEventName,
1824
+ toolInput: msg.toolInput
1825
+ });
1826
+ if (route.reason === "away") await ensureAwayNoticeSent(target, msg);
1827
+ const sent2 = await sendOwnerCard(target, renderPendingCard(pending3));
1828
+ setPendingCliMessageId(pending3.id, sent2.messageId);
1829
+ const result2 = await waitWithLocalReturn(
1830
+ pending3.id,
1831
+ p.approval.timeoutSeconds * 1e3,
1832
+ { decision: "fallback_local", reason: LOCAL_RETURN }
1833
+ );
1834
+ if (result2.reason === LOCAL_RETURN) {
1835
+ closeCard(sent2.messageId, renderPendingCard(pending3, { status: "local" }), "close-question-card");
1836
+ }
1837
+ return result2;
1838
+ }
1839
+ if (!p.approval.enabled) return { decision: "fallback_local", reason: "approval_disabled" };
1840
+ if (p.allowCache.enabled && allowedSessions.has(sessionKey(msg))) return { decision: "allow" };
1841
+ if (!await notifyAllowedForCwd(msg.cwd)) return { decision: "fallback_local", reason: "notify_scope" };
1842
+ const command = typeof msg.toolInput.command === "string" ? msg.toolInput.command : void 0;
1843
+ const pending2 = createPendingCliInteraction({
1844
+ kind: "permission",
1845
+ source: msg.source,
1846
+ sessionId: msg.sessionId,
1847
+ cwd: msg.cwd,
1848
+ toolName: msg.toolName,
1849
+ command,
1850
+ hookEventName: msg.hookEventName,
1851
+ question: "Permission request"
1852
+ });
1853
+ if (route.reason === "away") await ensureAwayNoticeSent(target, msg);
1854
+ const sent = await sendOwnerCard(target, renderPendingCard(pending2, { allowSession: p.allowCache.enabled }));
1855
+ setPendingCliMessageId(pending2.id, sent.messageId);
1856
+ const result = await waitWithLocalReturn(
1857
+ pending2.id,
1858
+ p.approval.timeoutSeconds * 1e3,
1859
+ { decision: "fallback_local", reason: LOCAL_RETURN }
1860
+ );
1861
+ if (result.reason === LOCAL_RETURN) {
1862
+ closeCard(sent.messageId, renderPendingCard(pending2, { status: "local" }), "close-approval-card");
1863
+ }
1864
+ return result;
1865
+ }
1866
+ function resolveAction(action) {
1867
+ if (!action.id) return false;
1868
+ if (action.actionId === CLI.approveOnce) {
1869
+ const pending2 = getPendingCliInteraction(action.id);
1870
+ if (!pending2 || pending2.kind !== "permission") return false;
1871
+ updatePendingCard(pending2, renderPendingCard(pending2, { status: "approved" }));
1872
+ return resolvePendingCliInteraction(action.id, { decision: "allow" });
1873
+ }
1874
+ if (action.actionId === CLI.approveSession) {
1875
+ const pending2 = getPendingCliInteraction(action.id);
1876
+ if (!pending2 || pending2.kind !== "permission") return false;
1877
+ updatePendingCard(pending2, renderPendingCard(pending2, { status: "approved" }));
1878
+ const ok = resolvePendingCliInteraction(action.id, { decision: "allow" });
1879
+ if (ok && prefs().allowCache.enabled) allowedSessions.add(sessionKey(pending2));
1880
+ return ok;
1881
+ }
1882
+ if (action.actionId === CLI.deny) {
1883
+ const pending2 = getPendingCliInteraction(action.id);
1884
+ if (!pending2 || pending2.kind !== "permission") return false;
1885
+ updatePendingCard(pending2, renderPendingCard(pending2, { status: "denied" }));
1886
+ return resolvePendingCliInteraction(action.id, { decision: "deny", interrupt: true, reason: "Denied from Feishu" });
1887
+ }
1888
+ if (action.actionId === CLI.taskCompletionDone) {
1889
+ const pending2 = getPendingCliInteraction(action.id);
1890
+ if (!pending2 || pending2.kind !== "task_completion") return false;
1891
+ updatePendingCard(pending2, renderPendingCard(pending2, { replyDoneAt: Date.now() }));
1892
+ return resolvePendingCliInteraction(action.id, { decision: "allow", reason: TASK_DONE_CLICKED });
1893
+ }
1894
+ return false;
1895
+ }
1896
+ function resolveQuestionSubmit(id, formValue) {
1897
+ const pending2 = getPendingCliInteraction(id);
1898
+ if (!pending2 || pending2.kind !== "question") return false;
1899
+ const questions = pending2.questions ?? [];
1900
+ const answers = {};
1901
+ questions.forEach((q, i) => {
1902
+ const custom = String(formValue[questionCustomField(i)] ?? "").trim();
1903
+ if (custom) {
1904
+ answers[q.question] = custom;
1905
+ return;
1906
+ }
1907
+ const choice = formValue[questionChoiceField(i)];
1908
+ if (Array.isArray(choice)) {
1909
+ const picked = choice.map((c) => String(c).trim()).filter(Boolean);
1910
+ if (picked.length) answers[q.question] = picked.join("\u3001");
1911
+ } else if (typeof choice === "string" && choice.trim()) {
1912
+ answers[q.question] = choice.trim();
1913
+ }
1914
+ });
1915
+ if (Object.keys(answers).length === 0) return false;
1916
+ updatePendingCard(pending2, renderPendingCard(pending2, { status: "approved", answers }));
1917
+ return resolvePendingCliInteraction(id, {
1918
+ decision: "allow",
1919
+ // 必须保留原始 toolInput(含 questions),仅追加 answers——否则 Claude Code
1920
+ // 用 updatedInput 整体替换入参后 questions 变 undefined,渲染时崩 "H.map"。
1921
+ updatedInput: { ...pending2.toolInput ?? {}, answers }
1922
+ });
1923
+ }
1924
+ function resolveReply(reply) {
1925
+ const pending2 = findPendingCliInteractionByMessageReply(reply);
1926
+ const text = reply.text?.trim();
1927
+ if (!pending2 || pending2.kind !== "task_completion" || !text) return false;
1928
+ const ok = resolvePendingCliInteraction(pending2.id, { decision: "allow", stdout: JSON.stringify({ decision: "block", reason: text }) });
1929
+ if (ok && reply.messageId) armReplyTypingReaction(reply.messageId);
1930
+ return ok;
1931
+ }
1932
+ return {
1933
+ start: async () => {
1934
+ if (ipc) return;
1935
+ ipc = await startCliBridgeIpcServer({ socketPath: opts.socketPath, handleMessage });
1936
+ log.info("cli-bridge", "started", { socketPath: opts.socketPath });
1937
+ },
1938
+ shutdown: async () => {
1939
+ await ipc?.close();
1940
+ ipc = void 0;
1941
+ allowedSessions.clear();
1942
+ keepAwake.shutdown();
1943
+ },
1944
+ handleMessage,
1945
+ resolveAction,
1946
+ resolveQuestionSubmit,
1947
+ resolveReply,
1948
+ onMessage: (msg) => {
1949
+ const reply = { parentId: msg.parentId, rootId: msg.rootId, text: msg.text, messageId: msg.messageId };
1950
+ if (resolveReply(reply)) return true;
1951
+ return Boolean(findPendingCliInteractionByMessageReply(reply));
1952
+ },
1953
+ register: (dispatcher) => {
1954
+ dispatcher.on(CLI.approveOnce, ({ value }) => {
1955
+ resolveAction({ actionId: CLI.approveOnce, id: String(value.id ?? "") });
1956
+ }).on(CLI.approveSession, ({ value }) => {
1957
+ resolveAction({ actionId: CLI.approveSession, id: String(value.id ?? "") });
1958
+ }).on(CLI.deny, ({ value }) => {
1959
+ resolveAction({ actionId: CLI.deny, id: String(value.id ?? "") });
1960
+ }).on(CLI.taskCompletionDone, ({ value }) => {
1961
+ resolveAction({ actionId: CLI.taskCompletionDone, id: String(value.id ?? "") });
1962
+ }).on(CLI.questionSubmit, ({ value, formValue }) => {
1963
+ resolveQuestionSubmit(String(value.id ?? ""), formValue ?? {});
1964
+ });
1965
+ }
271
1966
  };
272
- await saveBots(reg);
273
- return reg;
274
- }
275
- function findBot(reg, nameOrAppId) {
276
- return reg.bots.find((b) => b.name === nameOrAppId || b.appId === nameOrAppId);
277
- }
278
- function currentBot(reg) {
279
- return reg.current ? reg.bots.find((b) => b.appId === reg.current) : void 0;
280
- }
281
- function activeBots(reg) {
282
- const configured = reg.bots.some((b) => b.active !== void 0);
283
- if (configured) return reg.bots.filter((b) => b.active === true);
284
- const cur = currentBot(reg);
285
- return cur ? [cur] : [];
286
- }
287
- async function setActiveBots(appIds) {
288
- const reg = await loadBots();
289
- const want = new Set(appIds);
290
- for (const b of reg.bots) b.active = want.has(b.appId);
291
- const firstActive = reg.bots.find((b) => b.active);
292
- if (firstActive) reg.current = firstActive.appId;
293
- await saveBots(reg);
294
- return reg;
295
- }
296
- async function addBot(entry) {
297
- const reg = await loadBots();
298
- reg.bots = reg.bots.filter((b) => b.appId !== entry.appId);
299
- reg.bots.push(entry);
300
- if (!reg.current) reg.current = entry.appId;
301
- await saveBots(reg);
302
- return reg;
303
- }
304
- async function removeBot(appId) {
305
- const reg = await loadBots();
306
- reg.bots = reg.bots.filter((b) => b.appId !== appId);
307
- if (reg.current === appId) reg.current = reg.bots[0]?.appId;
308
- await saveBots(reg);
309
- return reg;
310
1967
  }
311
- function uniqueName(reg, desired) {
312
- const base = slugify(desired) || "bot";
313
- if (!reg.bots.some((b) => b.name === base)) return base;
314
- for (let i = 2; ; i++) {
315
- const candidate = `${base}-${i}`;
316
- if (!reg.bots.some((b) => b.name === candidate)) return candidate;
1968
+ function shouldStartCliBridge(cfg) {
1969
+ const prefs = getCliBridgePreferences(cfg);
1970
+ return prefs.enabled && Boolean(resolveCliBridgeTarget(cfg));
1971
+ }
1972
+ var TASK_DONE_CLICKED, LOCAL_RETURN;
1973
+ var init_service = __esm({
1974
+ "src/cli-bridge/service.ts"() {
1975
+ "use strict";
1976
+ init_schema();
1977
+ init_logger();
1978
+ init_managed();
1979
+ init_ipc();
1980
+ init_cards2();
1981
+ init_parser();
1982
+ init_store2();
1983
+ init_presence();
1984
+ init_keep_awake();
1985
+ TASK_DONE_CLICKED = "task_done_clicked";
1986
+ LOCAL_RETURN = "local_return";
1987
+ }
1988
+ });
1989
+
1990
+ // src/cli-bridge/index.ts
1991
+ var cli_bridge_exports = {};
1992
+ __export(cli_bridge_exports, {
1993
+ createCliBridgeService: () => createCliBridgeService,
1994
+ runHookCommand: () => runHookCommand,
1995
+ selectCliBridgeHookBot: () => selectCliBridgeHookBot,
1996
+ shouldStartCliBridge: () => shouldStartCliBridge
1997
+ });
1998
+ import { join as join19 } from "path";
1999
+ async function selectCliBridgeHookBot(reg, opts = {}) {
2000
+ const requested = opts.requested?.trim();
2001
+ if (requested) {
2002
+ return findBot(reg, requested) ?? { name: requested, appId: requested, tenant: "feishu", createdAt: 0 };
2003
+ }
2004
+ const loadConfigForBot = opts.loadConfigForBot ?? ((appId) => loadConfig(join19(botDir(appId), "config.json")));
2005
+ const current = currentBot(reg);
2006
+ const active = activeBots(reg);
2007
+ const candidates = active.length > 0 ? active : current ? [current] : reg.bots;
2008
+ let firstEnabled;
2009
+ for (const bot2 of candidates) {
2010
+ const cfg = await loadConfigForBot(bot2.appId).catch(() => void 0);
2011
+ if (!cfg || !isComplete(cfg) || !getCliBridgePreferences(cfg).enabled) continue;
2012
+ if (bot2.appId === current?.appId) return bot2;
2013
+ firstEnabled ??= bot2;
2014
+ }
2015
+ return firstEnabled ?? current ?? candidates[0];
2016
+ }
2017
+ async function runHookCommand(agent, bot2) {
2018
+ if (agent !== "claude" && agent !== "codex") {
2019
+ process.stderr.write(`Unsupported hook agent: ${agent}
2020
+ `);
2021
+ process.exitCode = 2;
2022
+ return;
2023
+ }
2024
+ try {
2025
+ const selected = await selectCliBridgeHookBot(await loadBots(), { requested: bot2 });
2026
+ if (selected) useBotDir(selected.appId);
2027
+ } catch {
2028
+ if (bot2?.trim()) useBotDir(bot2.trim());
2029
+ }
2030
+ const raw = await readStdin();
2031
+ const msg = parseHookPayload(agent, raw);
2032
+ if (msg.type === "post_tool_use") {
2033
+ process.stdout.write("{}\n");
2034
+ return;
2035
+ }
2036
+ let response;
2037
+ try {
2038
+ response = await sendCliHookMessage(paths.cliBridgeSocket, msg);
2039
+ } catch {
2040
+ response = { decision: "fallback_local", reason: "daemon_unavailable" };
2041
+ }
2042
+ const stdout = buildHookStdout(msg, response);
2043
+ if (stdout) process.stdout.write(stdout + (stdout.endsWith("\n") ? "" : "\n"));
2044
+ }
2045
+ var init_cli_bridge = __esm({
2046
+ "src/cli-bridge/index.ts"() {
2047
+ "use strict";
2048
+ init_paths();
2049
+ init_bots();
2050
+ init_store();
2051
+ init_schema();
2052
+ init_stdin();
2053
+ init_ipc();
2054
+ init_parser();
2055
+ init_protocol();
2056
+ init_service();
2057
+ }
2058
+ });
2059
+
2060
+ // src/cli/index.ts
2061
+ import { Command } from "commander";
2062
+
2063
+ // src/core/version.ts
2064
+ import { readFileSync } from "fs";
2065
+ import { dirname, resolve } from "path";
2066
+ import { fileURLToPath } from "url";
2067
+ function bridgeVersion() {
2068
+ try {
2069
+ const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
2070
+ return JSON.parse(readFileSync(pkgPath, "utf8")).version ?? "0.0.0";
2071
+ } catch {
2072
+ return "0.0.0";
317
2073
  }
318
2074
  }
319
- function slugify(s) {
320
- return s.trim().toLowerCase().replace(/[^a-z0-9一-龥]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
321
- }
322
- function nowMs() {
323
- return Date.now();
324
- }
325
- async function moveIfExists(src, dest) {
326
- if (!existsSync(src)) return;
327
- await rename2(src, dest);
328
- }
2075
+
2076
+ // src/cli/commands/doctor.ts
2077
+ init_paths();
2078
+ init_bots();
2079
+ init_store();
2080
+ init_schema();
2081
+ import { existsSync as existsSync5 } from "fs";
2082
+ import { homedir as homedir2 } from "os";
2083
+ import { join as join9 } from "path";
329
2084
 
330
2085
  // src/config/secret-resolver.ts
331
2086
  import { readFile as readFile4 } from "fs/promises";
@@ -351,6 +2106,7 @@ function mergeProcessEnv(base = process.env, overrides = {}) {
351
2106
  }
352
2107
 
353
2108
  // src/config/keystore.ts
2109
+ init_paths();
354
2110
  import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
355
2111
  import { chmod as chmod3, mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile3 } from "fs/promises";
356
2112
  import { hostname, userInfo } from "os";
@@ -445,6 +2201,8 @@ async function listSecretIds() {
445
2201
  }
446
2202
 
447
2203
  // src/config/secret-resolver.ts
2204
+ init_paths();
2205
+ init_schema();
448
2206
  var ENV_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
449
2207
  var DEFAULT_PROVIDER = "default";
450
2208
  var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
@@ -521,7 +2279,7 @@ async function spawnExecProvider(pc, ref) {
521
2279
  const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
522
2280
  const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
523
2281
  const providerName = ref.provider ?? DEFAULT_PROVIDER;
524
- return new Promise((resolve8, reject) => {
2282
+ return new Promise((resolve9, reject) => {
525
2283
  const env = {};
526
2284
  if (pc.passEnv) for (const k2 of pc.passEnv) {
527
2285
  const v = process.env[k2];
@@ -532,10 +2290,10 @@ async function spawnExecProvider(pc, ref) {
532
2290
  env,
533
2291
  stdio: ["pipe", "pipe", "pipe"]
534
2292
  });
535
- let stdout = "", stderr = "", truncated = false, settled = false;
2293
+ let stdout = "", stderr = "", truncated = false, settled2 = false;
536
2294
  const timer = setTimeout(() => {
537
- if (settled) return;
538
- settled = true;
2295
+ if (settled2) return;
2296
+ settled2 = true;
539
2297
  child.kill("SIGKILL");
540
2298
  reject(new Error(`exec provider timed out after ${timeoutMs}ms`));
541
2299
  }, timeoutMs);
@@ -552,14 +2310,14 @@ async function spawnExecProvider(pc, ref) {
552
2310
  stderr += chunk.toString("utf8");
553
2311
  });
554
2312
  child.on("error", (err) => {
555
- if (settled) return;
556
- settled = true;
2313
+ if (settled2) return;
2314
+ settled2 = true;
557
2315
  clearTimeout(timer);
558
2316
  reject(new Error(`exec provider failed to start: ${err.message}`));
559
2317
  });
560
2318
  child.on("close", (code) => {
561
- if (settled) return;
562
- settled = true;
2319
+ if (settled2) return;
2320
+ settled2 = true;
563
2321
  clearTimeout(timer);
564
2322
  if (truncated) return reject(new Error(`exec provider stdout exceeded ${maxOutput} bytes`));
565
2323
  if (code !== 0) {
@@ -569,7 +2327,7 @@ async function spawnExecProvider(pc, ref) {
569
2327
  try {
570
2328
  const parsed = JSON.parse(stdout);
571
2329
  const value = parsed.values?.[ref.id];
572
- if (typeof value === "string") return resolve8(value);
2330
+ if (typeof value === "string") return resolve9(value);
573
2331
  const err = parsed.errors?.[ref.id]?.message;
574
2332
  reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
575
2333
  } catch (err) {
@@ -823,242 +2581,31 @@ var BACKEND_CATALOG = [
823
2581
  },
824
2582
  // 必须与 ClaudeAgentBackend.supportedModes 完全一致(单测强制)。
825
2583
  supportedModes: ["qa", "write", "full"],
826
- blurb: "Claude Code\uFF08SDK \u5185\u7F6E\uFF0C\u590D\u7528\u672C\u673A\u767B\u5F55\uFF1Bqa/write \u8D70 OS \u6C99\u7BB1\uFF0C\u80FD\u529B\u8F83 Codex \u7CBE\u7B80\uFF09"
827
- }
828
- ];
829
- function visibleCatalog() {
830
- return BACKEND_CATALOG.filter((e) => !e.hidden);
831
- }
832
- function catalogById(id) {
833
- return BACKEND_CATALOG.find((e) => e.id === id);
834
- }
835
- function catalogBackendIds() {
836
- return BACKEND_CATALOG.map((e) => e.id);
837
- }
838
- function projectCreatableBackends(mode, isInstalled2) {
839
- return BACKEND_CATALOG.filter((e) => {
840
- if (e.hidden) return false;
841
- const installed = e.id === DEFAULT_BACKEND_ID || isInstalled2(e);
842
- if (!installed) return false;
843
- if (e.supportedModes && !e.supportedModes.includes(mode)) return false;
844
- return true;
845
- });
846
- }
847
-
848
- // src/core/logger.ts
849
- import { AsyncLocalStorage } from "async_hooks";
850
- import { createWriteStream, mkdirSync } from "fs";
851
- import { open, readdir, rm, stat } from "fs/promises";
852
- import { tmpdir } from "os";
853
- import { join as join4 } from "path";
854
- var LOG_RETENTION_DAYS = Math.max(
855
- 1,
856
- Number(process.env.FEISHU_CODEX_LOG_DAYS ?? 7) || 7
857
- );
858
- var STDOUT_INFO_ALLOWLIST = /* @__PURE__ */ new Set([
859
- "ws.connected",
860
- "ws.reconnecting",
861
- "ws.reconnected",
862
- "intake.enter",
863
- "intake.recv",
864
- "intake.reject",
865
- "card.final",
866
- "card.config",
867
- "card.action",
868
- "card.launch",
869
- "agent.spawn",
870
- "agent.exit",
871
- // 自愈链路(kill -9 / 崩溃恢复)的关键节点——低频高信号,进终端便于 e2e 直读:
872
- // 驱逐(轮间死 / 轮中死)与恢复来源(store resume / 重建 / 全新会话)。
873
- "agent.dead-thread-evict",
874
- "agent.session-evict",
875
- "agent.resume-ok",
876
- "agent.resume-recreate",
877
- "agent.session-fresh"
878
- ]);
879
- var als = new AsyncLocalStorage();
880
- var stream = null;
881
- var currentDate = "";
882
- function todayKey() {
883
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
884
- }
885
- function inTestEnv() {
886
- return Boolean(process.env.VITEST) || process.env.NODE_ENV === "test";
887
- }
888
- var TEST_LOGS_DIR = join4(tmpdir(), "feishu-codex-bridge-test-logs");
889
- function logsDir() {
890
- return inTestEnv() ? TEST_LOGS_DIR : join4(paths.appDir, "logs");
891
- }
892
- function getStream() {
893
- const today = todayKey();
894
- if (stream && currentDate === today) return stream;
895
- if (stream) {
896
- try {
897
- stream.end();
898
- } catch {
899
- }
900
- }
901
- try {
902
- mkdirSync(logsDir(), { recursive: true });
903
- stream = createWriteStream(join4(logsDir(), `${today}.log`), { flags: "a" });
904
- currentDate = today;
905
- return stream;
906
- } catch {
907
- return null;
908
- }
909
- }
910
- var RESERVED_KEYS = /* @__PURE__ */ new Set([
911
- "ts",
912
- "level",
913
- "phase",
914
- "event",
915
- "traceId",
916
- "chatId",
917
- "msgId"
918
- ]);
919
- function emit(level, phase, event, fields = {}) {
920
- const ctx = als.getStore() ?? {};
921
- const entry = {
922
- ts: (/* @__PURE__ */ new Date()).toISOString(),
923
- level,
924
- phase,
925
- event,
926
- ...ctx
927
- };
928
- for (const [k2, v] of Object.entries(fields)) {
929
- if (RESERVED_KEYS.has(k2)) {
930
- entry[`_${k2}`] = v;
931
- } else {
932
- entry[k2] = v;
933
- }
934
- }
935
- const s = getStream();
936
- if (s) {
937
- try {
938
- s.write(`${JSON.stringify(entry)}
939
- `);
940
- } catch {
941
- }
942
- }
943
- const showOnStdout = level !== "info" || STDOUT_INFO_ALLOWLIST.has(`${phase}.${event}`);
944
- if (!showOnStdout) return;
945
- const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
946
- fn(formatStdout(level, phase, event, ctx, fields));
947
- }
948
- function formatStdout(level, phase, event, ctx, fields) {
949
- if (phase === "ws") {
950
- if (event === "connected") {
951
- const bot2 = fields.bot ?? "-";
952
- const appId = fields.appId ? ` (${fields.appId})` : "";
953
- return `\u2713 \u5DF2\u8FDE\u63A5 bot: ${bot2}${appId}`;
954
- }
955
- if (event === "reconnecting") return "\u21BB \u6B63\u5728\u91CD\u8FDE\u2026";
956
- if (event === "reconnected") return "\u2713 \u5DF2\u91CD\u8FDE";
957
- if (event === "fail") return `\u2717 WS \u9519\u8BEF: ${fields.err ?? ""}`;
958
- }
959
- if (phase === "intake" && event === "enter") {
960
- const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
961
- const sender = fields.sender ?? "-";
962
- const preview = fields.preview ?? "";
963
- return `\u25B8 ${fields.chatType ?? "?"}/${c} ${sender}: ${preview}`;
964
- }
965
- if (phase === "card" && event === "final") {
966
- const c = ctx.chatId ? ctx.chatId.slice(-6) : "-";
967
- const t = fields.terminal;
968
- const mark = t === "done" ? "\u2713" : t === "interrupted" ? "\u23F9" : "\u2717";
969
- return ` ${mark} ${c} ${t}`;
970
- }
971
- const ctxBits = [];
972
- if (ctx.traceId) ctxBits.push(`t=${ctx.traceId}`);
973
- if (ctx.chatId) ctxBits.push(`c=${ctx.chatId.slice(-6)}`);
974
- const ctxStr = ctxBits.length > 0 ? ` ${ctxBits.join(" ")}` : "";
975
- const summary = formatFields(fields);
976
- const tag = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\xB7";
977
- return `${tag} [${phase}.${event}]${ctxStr}${summary ? ` ${summary}` : ""}`;
978
- }
979
- function formatFields(fields) {
980
- const keys = Object.keys(fields);
981
- if (keys.length === 0) return "";
982
- const parts = [];
983
- for (const k2 of keys) {
984
- const v = fields[k2];
985
- if (v === void 0 || v === null) continue;
986
- if (k2 === "stack") continue;
987
- if (typeof v === "string") {
988
- parts.push(`${k2}=${v.length > 80 ? `${v.slice(0, 80)}\u2026` : v}`);
989
- } else if (typeof v === "number" || typeof v === "boolean") {
990
- parts.push(`${k2}=${v}`);
991
- } else {
992
- try {
993
- const str = JSON.stringify(v);
994
- parts.push(`${k2}=${str.length > 80 ? `${str.slice(0, 80)}\u2026` : str}`);
995
- } catch {
996
- parts.push(`${k2}=?`);
997
- }
998
- }
999
- }
1000
- return parts.join(" ");
1001
- }
1002
- var log = {
1003
- info(phase, event, fields) {
1004
- emit("info", phase, event, fields);
1005
- },
1006
- warn(phase, event, fields) {
1007
- emit("warn", phase, event, fields);
1008
- },
1009
- fail(phase, err, fields) {
1010
- const message = err instanceof Error ? err.message : String(err);
1011
- const stack = err instanceof Error ? err.stack : void 0;
1012
- const apiData = err?.response?.data;
1013
- const apiStatus = err?.response?.status;
1014
- emit("error", phase, "fail", {
1015
- ...fields,
1016
- err: message,
1017
- apiStatus,
1018
- apiData,
1019
- stack
1020
- });
2584
+ blurb: "Claude Code\uFF08SDK \u5185\u7F6E\uFF0C\u590D\u7528\u672C\u673A\u767B\u5F55\uFF1Bqa/write \u8D70 OS \u6C99\u7BB1\uFF0C\u80FD\u529B\u8F83 Codex \u7CBE\u7B80\uFF09"
1021
2585
  }
1022
- };
1023
- function withTrace(ctx, fn) {
1024
- const traceId = ctx.traceId ?? newTraceId();
1025
- return als.run({ ...ctx, traceId }, fn);
2586
+ ];
2587
+ function visibleCatalog() {
2588
+ return BACKEND_CATALOG.filter((e) => !e.hidden);
1026
2589
  }
1027
- function newTraceId() {
1028
- return Math.random().toString(36).slice(2, 10);
2590
+ function catalogById(id) {
2591
+ return BACKEND_CATALOG.find((e) => e.id === id);
1029
2592
  }
1030
- async function readRecentLogs(opts) {
1031
- const today = todayKey();
1032
- const yesterday = new Date(Date.now() - 864e5).toISOString().slice(0, 10);
1033
- const tail = await readTail(join4(logsDir(), `${today}.log`), opts.maxBytes);
1034
- if (tail.length >= opts.maxBytes / 2) return tail;
1035
- const remaining = opts.maxBytes - Buffer.byteLength(tail, "utf8");
1036
- const earlier = await readTail(join4(logsDir(), `${yesterday}.log`), remaining);
1037
- return earlier + tail;
2593
+ function catalogBackendIds() {
2594
+ return BACKEND_CATALOG.map((e) => e.id);
1038
2595
  }
1039
- async function readTail(path, maxBytes) {
1040
- try {
1041
- const st = await stat(path);
1042
- const start = Math.max(0, st.size - maxBytes);
1043
- const handle = await open(path, "r");
1044
- try {
1045
- const buf = Buffer.alloc(st.size - start);
1046
- await handle.read(buf, 0, buf.length, start);
1047
- let content = buf.toString("utf8");
1048
- if (start > 0) {
1049
- const nl = content.indexOf("\n");
1050
- if (nl !== -1) content = content.slice(nl + 1);
1051
- }
1052
- return content;
1053
- } finally {
1054
- await handle.close();
1055
- }
1056
- } catch (err) {
1057
- if (err.code === "ENOENT") return "";
1058
- throw err;
1059
- }
2596
+ function projectCreatableBackends(mode, isInstalled2) {
2597
+ return BACKEND_CATALOG.filter((e) => {
2598
+ if (e.hidden) return false;
2599
+ const installed = e.id === DEFAULT_BACKEND_ID || isInstalled2(e);
2600
+ if (!installed) return false;
2601
+ if (e.supportedModes && !e.supportedModes.includes(mode)) return false;
2602
+ return true;
2603
+ });
1060
2604
  }
1061
2605
 
2606
+ // src/agent/codex-appserver/backend.ts
2607
+ init_logger();
2608
+
1062
2609
  // src/agent/bridge-instructions.ts
1063
2610
  var BRIDGE_DEVELOPER_INSTRUCTIONS = [
1064
2611
  "\u4F60\u73B0\u5728\u901A\u8FC7\u300C\u98DE\u4E66\u6865\u300D\u4E0E\u7528\u6237\u5BF9\u8BDD\uFF1A\u4F60\u7684\u56DE\u590D\u4F1A\u88AB\u6E32\u67D3\u6210\u98DE\u4E66\u6D88\u606F\u3002\u8BF7\u9075\u5B88\u4E24\u6761\u8F93\u51FA\u7EA6\u5B9A\u3002",
@@ -1076,6 +2623,7 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
1076
2623
  ].join("\n");
1077
2624
 
1078
2625
  // src/agent/codex-appserver/app-server-client.ts
2626
+ init_logger();
1079
2627
  var AsyncQueue = class {
1080
2628
  items = [];
1081
2629
  waiters = [];
@@ -1100,7 +2648,7 @@ var AsyncQueue = class {
1100
2648
  continue;
1101
2649
  }
1102
2650
  if (this.closed) return;
1103
- const next = await new Promise((resolve8) => this.waiters.push(resolve8));
2651
+ const next = await new Promise((resolve9) => this.waiters.push(resolve9));
1104
2652
  if (next.done) return;
1105
2653
  yield next.value;
1106
2654
  }
@@ -1171,8 +2719,8 @@ var AppServerClient = class {
1171
2719
  const id = ++this.nextId;
1172
2720
  const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
1173
2721
  `;
1174
- return new Promise((resolve8, reject) => {
1175
- this.pending.set(id, { resolve: resolve8, reject });
2722
+ return new Promise((resolve9, reject) => {
2723
+ this.pending.set(id, { resolve: resolve9, reject });
1176
2724
  this.child.stdin.write(payload, (err) => {
1177
2725
  if (err) {
1178
2726
  this.pending.delete(id);
@@ -1203,13 +2751,13 @@ var AppServerClient = class {
1203
2751
  const child = this.child;
1204
2752
  if (!child || child.exitCode !== null) return;
1205
2753
  if (process.platform === "win32" && child.pid) {
1206
- await new Promise((resolve8) => {
1207
- let settled = false;
2754
+ await new Promise((resolve9) => {
2755
+ let settled2 = false;
1208
2756
  const done = () => {
1209
- if (settled) return;
1210
- settled = true;
2757
+ if (settled2) return;
2758
+ settled2 = true;
1211
2759
  clearTimeout(t);
1212
- resolve8();
2760
+ resolve9();
1213
2761
  };
1214
2762
  const t = setTimeout(done, graceMs);
1215
2763
  child.once("exit", done);
@@ -1224,14 +2772,14 @@ var AppServerClient = class {
1224
2772
  return;
1225
2773
  }
1226
2774
  child.kill("SIGTERM");
1227
- await new Promise((resolve8) => {
2775
+ await new Promise((resolve9) => {
1228
2776
  const t = setTimeout(() => {
1229
2777
  if (child.exitCode === null) child.kill("SIGKILL");
1230
- resolve8();
2778
+ resolve9();
1231
2779
  }, graceMs);
1232
2780
  child.once("exit", () => {
1233
2781
  clearTimeout(t);
1234
- resolve8();
2782
+ resolve9();
1235
2783
  });
1236
2784
  });
1237
2785
  }
@@ -1283,10 +2831,12 @@ var AppServerClient = class {
1283
2831
  };
1284
2832
 
1285
2833
  // src/agent/codex-appserver/client-pool.ts
2834
+ init_logger();
1286
2835
  import { statSync } from "fs";
1287
2836
  import { tmpdir as tmpdir2 } from "os";
1288
2837
 
1289
2838
  // src/agent/codex-appserver/locate.ts
2839
+ init_paths();
1290
2840
  import { existsSync as existsSync2 } from "fs";
1291
2841
  import { extname, join as join5 } from "path";
1292
2842
  var IS_WIN = process.platform === "win32";
@@ -1333,12 +2883,12 @@ async function codexVersionAsync(bin, opts) {
1333
2883
  const hit = versionCache.get(bin);
1334
2884
  if (hit !== void 0) return hit;
1335
2885
  }
1336
- const out = await new Promise((resolve8) => {
2886
+ const out = await new Promise((resolve9) => {
1337
2887
  let child;
1338
2888
  try {
1339
2889
  child = spawnProcess(bin, ["--version"], { stdio: ["ignore", "pipe", "ignore"] });
1340
2890
  } catch {
1341
- resolve8(null);
2891
+ resolve9(null);
1342
2892
  return;
1343
2893
  }
1344
2894
  let stdout = "";
@@ -1346,8 +2896,8 @@ async function codexVersionAsync(bin, opts) {
1346
2896
  child.stdout?.on("data", (d) => {
1347
2897
  stdout += d;
1348
2898
  });
1349
- child.on("error", () => resolve8(null));
1350
- child.on("close", (code) => resolve8(code === 0 ? stdout.trim() : null));
2899
+ child.on("error", () => resolve9(null));
2900
+ child.on("close", (code) => resolve9(code === 0 ? stdout.trim() : null));
1351
2901
  });
1352
2902
  if (out !== null) versionCache.set(bin, out);
1353
2903
  return out;
@@ -1359,12 +2909,12 @@ var CONNECT_TIMEOUT_MS = 15e3;
1359
2909
  var PREWARM_TIMEOUT_MS = 6e4;
1360
2910
  var DEFAULT_UTILITY_TIMEOUT_MS = 3e4;
1361
2911
  function withDeadline(p, ms, label) {
1362
- return new Promise((resolve8, reject) => {
2912
+ return new Promise((resolve9, reject) => {
1363
2913
  const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
1364
2914
  p.then(
1365
2915
  (v) => {
1366
2916
  clearTimeout(t);
1367
- resolve8(v);
2917
+ resolve9(v);
1368
2918
  },
1369
2919
  (e) => {
1370
2920
  clearTimeout(t);
@@ -1616,8 +3166,8 @@ function countChange(c) {
1616
3166
  }
1617
3167
  function displayPath(p, cwd) {
1618
3168
  if (cwd) {
1619
- const sep2 = cwd.includes("\\") ? "\\" : "/";
1620
- const root = cwd.endsWith(sep2) ? cwd : cwd + sep2;
3169
+ const sep3 = cwd.includes("\\") ? "\\" : "/";
3170
+ const root = cwd.endsWith(sep3) ? cwd : cwd + sep3;
1621
3171
  return p.startsWith(root) && p.length > root.length ? p.slice(root.length) : p;
1622
3172
  }
1623
3173
  if (p.length <= PATH_TAIL_MAX || !p.includes("/")) return p;
@@ -1735,11 +3285,11 @@ var CodexThread = class {
1735
3285
  if (self.model) params.model = self.model;
1736
3286
  if (self.effort) params.effort = self.effort;
1737
3287
  let startError;
1738
- const startFailed = new Promise((resolve8) => {
3288
+ const startFailed = new Promise((resolve9) => {
1739
3289
  self.client.request("turn/start", params).then(void 0, (err) => {
1740
3290
  startError = err instanceof Error ? err : new Error(String(err));
1741
3291
  log.fail("agent", startError, { phase: "turn/start" });
1742
- resolve8("start-failed");
3292
+ resolve9("start-failed");
1743
3293
  });
1744
3294
  });
1745
3295
  async function* gen() {
@@ -1769,11 +3319,11 @@ var CodexThread = class {
1769
3319
  async function* gen() {
1770
3320
  await self.client.request("thread/goal/clear", { threadId: self.sessionId }).catch(() => void 0);
1771
3321
  let setError;
1772
- const setFailed = new Promise((resolve8) => {
3322
+ const setFailed = new Promise((resolve9) => {
1773
3323
  self.client.request("thread/goal/set", { threadId: self.sessionId, objective }).then(void 0, (err) => {
1774
3324
  setError = err instanceof Error ? err : new Error(String(err));
1775
3325
  log.fail("agent", setError, { phase: "thread/goal/set" });
1776
- resolve8("set-failed");
3326
+ resolve9("set-failed");
1777
3327
  });
1778
3328
  });
1779
3329
  const stream2 = self.client.stream()[Symbol.asyncIterator]();
@@ -1834,16 +3384,16 @@ var CodexThread = class {
1834
3384
  }
1835
3385
  async compact() {
1836
3386
  let startError;
1837
- const startFailed = new Promise((resolve8) => {
3387
+ const startFailed = new Promise((resolve9) => {
1838
3388
  this.client.request("thread/compact/start", { threadId: this.sessionId }).then(void 0, (err) => {
1839
3389
  startError = err instanceof Error ? err : new Error(String(err));
1840
3390
  log.fail("agent", startError, { phase: "thread/compact/start" });
1841
- resolve8("start-failed");
3391
+ resolve9("start-failed");
1842
3392
  });
1843
3393
  });
1844
3394
  let timer;
1845
- const timeout = new Promise((resolve8) => {
1846
- timer = setTimeout(() => resolve8("timeout"), COMPACT_TIMEOUT_MS);
3395
+ const timeout = new Promise((resolve9) => {
3396
+ timer = setTimeout(() => resolve9("timeout"), COMPACT_TIMEOUT_MS);
1847
3397
  });
1848
3398
  const stream2 = this.client.stream()[Symbol.asyncIterator]();
1849
3399
  let compacted = false;
@@ -2074,9 +3624,11 @@ var STATIC_MODELS = [
2074
3624
  ];
2075
3625
 
2076
3626
  // src/agent/claude-agent/backend.ts
3627
+ init_logger();
2077
3628
  import { randomUUID as randomUUID2 } from "crypto";
2078
3629
 
2079
3630
  // src/agent/backend-loader.ts
3631
+ init_paths();
2080
3632
  import { createRequire } from "module";
2081
3633
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2082
3634
  import { join as join6 } from "path";
@@ -2374,8 +3926,8 @@ var PATH_TAIL_MAX2 = 40;
2374
3926
  function displayPath2(p, cwd) {
2375
3927
  if (!p) return "\u6587\u4EF6";
2376
3928
  if (cwd) {
2377
- const sep2 = cwd.includes("\\") ? "\\" : "/";
2378
- const root = cwd.endsWith(sep2) ? cwd : cwd + sep2;
3929
+ const sep3 = cwd.includes("\\") ? "\\" : "/";
3930
+ const root = cwd.endsWith(sep3) ? cwd : cwd + sep3;
2379
3931
  if (p.startsWith(root) && p.length > root.length) return p.slice(root.length);
2380
3932
  }
2381
3933
  if (p.length <= PATH_TAIL_MAX2 || !p.includes("/")) return p;
@@ -2512,6 +4064,7 @@ ${b.thinking}` : b.thinking;
2512
4064
  }
2513
4065
 
2514
4066
  // src/agent/claude-agent/thread.ts
4067
+ init_logger();
2515
4068
  var COMPACT_TIMEOUT_MS2 = 12e4;
2516
4069
  var ABORT_ESCALATE_MS = 4e3;
2517
4070
  var Inbox = class {
@@ -2525,7 +4078,7 @@ var Inbox = class {
2525
4078
  next() {
2526
4079
  const v = this.buf.shift();
2527
4080
  if (v !== void 0) return Promise.resolve(v);
2528
- return new Promise((resolve8) => this.waiters.push(resolve8));
4081
+ return new Promise((resolve9) => this.waiters.push(resolve9));
2529
4082
  }
2530
4083
  };
2531
4084
  var PushablePrompt = class {
@@ -2550,7 +4103,7 @@ var PushablePrompt = class {
2550
4103
  continue;
2551
4104
  }
2552
4105
  if (this.closed) return;
2553
- const next = await new Promise((resolve8) => this.waiters.push(resolve8));
4106
+ const next = await new Promise((resolve9) => this.waiters.push(resolve9));
2554
4107
  if (next == null) return;
2555
4108
  yield next;
2556
4109
  }
@@ -2824,8 +4377,8 @@ var ClaudeAgentThread = class {
2824
4377
  this.sink = mySink;
2825
4378
  this.interruptRequested = false;
2826
4379
  let timer;
2827
- const timeout = new Promise((resolve8) => {
2828
- timer = setTimeout(() => resolve8("timeout"), COMPACT_TIMEOUT_MS2);
4380
+ const timeout = new Promise((resolve9) => {
4381
+ timer = setTimeout(() => resolve9("timeout"), COMPACT_TIMEOUT_MS2);
2829
4382
  });
2830
4383
  let compacted = false;
2831
4384
  try {
@@ -3074,8 +4627,12 @@ async function effectiveDefaultBackend(opts) {
3074
4627
  import { mkdir as mkdir4, rm as rm2, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
3075
4628
  import { existsSync as existsSync4 } from "fs";
3076
4629
  import { join as join8 } from "path";
4630
+ init_paths();
4631
+ init_logger();
3077
4632
 
3078
4633
  // src/agent/native-helpers.ts
4634
+ init_paths();
4635
+ init_logger();
3079
4636
  import { chmod as chmod4, readdir as readdir2 } from "fs/promises";
3080
4637
  import { join as join7 } from "path";
3081
4638
  async function fixNativeHelperPerms(rootDir = paths.backendsDir) {
@@ -3106,14 +4663,14 @@ async function fixNativeHelperPerms(rootDir = paths.backendsDir) {
3106
4663
  var NPM = "npm";
3107
4664
  async function latestNpmVersion(pkg, timeoutMs = 8e3) {
3108
4665
  const bare = stripVersion(pkg);
3109
- return new Promise((resolve8) => {
4666
+ return new Promise((resolve9) => {
3110
4667
  let child;
3111
4668
  try {
3112
4669
  child = spawnProcess(NPM, ["view", bare, "version", "--no-fund", "--no-audit"], {
3113
4670
  stdio: ["ignore", "pipe", "ignore"]
3114
4671
  });
3115
4672
  } catch {
3116
- resolve8(null);
4673
+ resolve9(null);
3117
4674
  return;
3118
4675
  }
3119
4676
  let out = "";
@@ -3122,7 +4679,7 @@ async function latestNpmVersion(pkg, timeoutMs = 8e3) {
3122
4679
  child.kill("SIGTERM");
3123
4680
  } catch {
3124
4681
  }
3125
- resolve8(null);
4682
+ resolve9(null);
3126
4683
  }, timeoutMs);
3127
4684
  child.stdout?.setEncoding("utf8");
3128
4685
  child.stdout?.on("data", (d) => {
@@ -3130,12 +4687,12 @@ async function latestNpmVersion(pkg, timeoutMs = 8e3) {
3130
4687
  });
3131
4688
  child.on("error", () => {
3132
4689
  clearTimeout(timer);
3133
- resolve8(null);
4690
+ resolve9(null);
3134
4691
  });
3135
4692
  child.on("close", (code) => {
3136
4693
  clearTimeout(timer);
3137
4694
  const v = out.trim().split("\n").pop()?.trim() ?? "";
3138
- resolve8(code === 0 && /^\d+\.\d+\.\d+/.test(v) ? v : null);
4695
+ resolve9(code === 0 && /^\d+\.\d+\.\d+/.test(v) ? v : null);
3139
4696
  });
3140
4697
  });
3141
4698
  }
@@ -3185,13 +4742,13 @@ async function installBackendDep(pkg, onProgress, signal, opts) {
3185
4742
  }
3186
4743
  const { command, args } = buildInstallCommand(pkg);
3187
4744
  log.info("agent", "backend-install-start", { pkg });
3188
- const result = await new Promise((resolve8) => {
4745
+ const result = await new Promise((resolve9) => {
3189
4746
  let child;
3190
4747
  try {
3191
4748
  child = spawnProcess(command, args, { stdio: ["ignore", "pipe", "pipe"] });
3192
4749
  } catch (err) {
3193
4750
  const msg = err instanceof Error ? err.message : String(err);
3194
- resolve8({ ok: false, code: null, aborted: false, tail: `spawn npm \u5931\u8D25\uFF1A${msg}` });
4751
+ resolve9({ ok: false, code: null, aborted: false, tail: `spawn npm \u5931\u8D25\uFF1A${msg}` });
3195
4752
  return;
3196
4753
  }
3197
4754
  let out = "";
@@ -3214,11 +4771,11 @@ async function installBackendDep(pkg, onProgress, signal, opts) {
3214
4771
  }
3215
4772
  child.on("error", (e) => {
3216
4773
  if (signal) signal.removeEventListener("abort", onAbort);
3217
- resolve8({ ok: false, code: null, aborted, tail: (out + e.message).slice(-2e3) });
4774
+ resolve9({ ok: false, code: null, aborted, tail: (out + e.message).slice(-2e3) });
3218
4775
  });
3219
4776
  child.on("close", (code) => {
3220
4777
  if (signal) signal.removeEventListener("abort", onAbort);
3221
- resolve8({ ok: code === 0 && !aborted, code, aborted, tail: out.slice(-2e3) });
4778
+ resolve9({ ok: code === 0 && !aborted, code, aborted, tail: out.slice(-2e3) });
3222
4779
  });
3223
4780
  });
3224
4781
  if (!result.ok) {
@@ -3373,7 +4930,9 @@ function tryExec(cmd, args) {
3373
4930
  }
3374
4931
 
3375
4932
  // src/bot/onboarding.ts
4933
+ init_store();
3376
4934
  import { createInterface } from "readline/promises";
4935
+ init_schema();
3377
4936
 
3378
4937
  // src/bot/wizard.ts
3379
4938
  import { registerApp } from "@larksuiteoapi/node-sdk";
@@ -3532,6 +5091,9 @@ function openUrl(url) {
3532
5091
  }
3533
5092
 
3534
5093
  // src/bot/onboarding.ts
5094
+ init_logger();
5095
+ init_paths();
5096
+ init_bots();
3535
5097
  async function ensureAnyAgent() {
3536
5098
  const agents = await detectAgents().catch(() => []);
3537
5099
  const anyAvailable = agents.some((a) => a.backends.some((b) => b.available));
@@ -3741,9 +5303,15 @@ async function announceEventsWhenLive(result) {
3741
5303
  }
3742
5304
 
3743
5305
  // src/bot/bridge.ts
5306
+ init_logger();
3744
5307
  import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
5308
+ import { sep as sep2 } from "path";
5309
+
5310
+ // src/card/dm-cards.ts
5311
+ init_schema();
3745
5312
 
3746
5313
  // src/project/registry.ts
5314
+ init_paths();
3747
5315
  import { mkdir as mkdir5, readFile as readFile6, rename as rename4, writeFile as writeFile5 } from "fs/promises";
3748
5316
  import { randomUUID as randomUUID3 } from "crypto";
3749
5317
  import { dirname as dirname5 } from "path";
@@ -3819,218 +5387,35 @@ async function addProject(p) {
3819
5387
  await write(projects);
3820
5388
  });
3821
5389
  }
3822
- async function updateProject(name, patch) {
3823
- return withLock(async () => {
3824
- const projects = await read();
3825
- const p = projects.find((x) => x.name === name);
3826
- if (!p) return;
3827
- const actual = typeof patch === "function" ? patch(p) : patch;
3828
- const target = p;
3829
- for (const [k2, v] of Object.entries(actual)) {
3830
- if (v !== void 0) target[k2] = v;
3831
- }
3832
- await write(projects);
3833
- });
3834
- }
3835
- async function removeProject(name) {
3836
- return withLock(async () => {
3837
- const projects = await read();
3838
- const idx = projects.findIndex((p) => p.name === name);
3839
- if (idx === -1) return void 0;
3840
- const [removed] = projects.splice(idx, 1);
3841
- await write(projects);
3842
- return removed;
3843
- });
3844
- }
3845
-
3846
- // src/card/cards.ts
3847
- function card(elements, opts = {}) {
3848
- const config = { update_multi: true };
3849
- if (opts.forward === false) config.enable_forward = false;
3850
- if (opts.streaming) {
3851
- config.streaming_mode = true;
3852
- config.streaming_config = {
3853
- print_frequency_ms: { default: 25 },
3854
- print_step: { default: 6 },
3855
- print_strategy: "fast"
3856
- };
3857
- }
3858
- if (opts.summary) config.summary = { content: opts.summary };
3859
- const obj = {
3860
- schema: "2.0",
3861
- config,
3862
- body: { elements }
3863
- };
3864
- if (opts.header) {
3865
- obj.header = {
3866
- template: opts.header.template ?? "blue",
3867
- title: { tag: "plain_text", content: opts.header.title },
3868
- ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {},
3869
- ...opts.header.textTags?.length ? {
3870
- text_tag_list: opts.header.textTags.map((t) => ({
3871
- tag: "text_tag",
3872
- text: { tag: "plain_text", content: t.text },
3873
- color: t.color
3874
- }))
3875
- } : {}
3876
- };
3877
- }
3878
- return obj;
3879
- }
3880
- function md(content) {
3881
- return { tag: "markdown", content };
3882
- }
3883
- function mdStream(content, elementId) {
3884
- return { tag: "markdown", element_id: elementId, content };
3885
- }
3886
- function image(imgKey, alt = "") {
3887
- return {
3888
- tag: "img",
3889
- img_key: imgKey,
3890
- alt: { tag: "plain_text", content: alt },
3891
- mode: "fit_horizontal",
3892
- preview: true
3893
- };
3894
- }
3895
- function note(content) {
3896
- return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
3897
- }
3898
- function colorNote(content, color) {
3899
- return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: color } };
3900
- }
3901
- function hr() {
3902
- return { tag: "hr" };
3903
- }
3904
- function noteMd(content) {
3905
- return { tag: "markdown", content, text_size: "notation" };
3906
- }
3907
- function collapsiblePanel(opts) {
3908
- return {
3909
- tag: "collapsible_panel",
3910
- expanded: opts.expanded,
3911
- header: {
3912
- title: { tag: "markdown", content: opts.title },
3913
- vertical_align: "center",
3914
- icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
3915
- icon_position: "follow_text",
3916
- icon_expanded_angle: -180
3917
- },
3918
- border: { color: opts.border, corner_radius: "5px" },
3919
- vertical_spacing: "8px",
3920
- padding: "8px 8px 8px 8px",
3921
- elements: [{ tag: "markdown", content: opts.body, text_size: "notation" }]
3922
- };
3923
- }
3924
- function collapsiblePanelEl(opts) {
3925
- return {
3926
- tag: "collapsible_panel",
3927
- expanded: opts.expanded,
3928
- header: {
3929
- title: { tag: "markdown", content: opts.title },
3930
- vertical_align: "center",
3931
- icon: { tag: "standard_icon", token: "down-small-ccm_outlined", size: "16px 16px" },
3932
- icon_position: "follow_text",
3933
- icon_expanded_angle: -180
3934
- },
3935
- border: { color: opts.border, corner_radius: "5px" },
3936
- vertical_spacing: "8px",
3937
- padding: "8px 8px 8px 8px",
3938
- elements: opts.elements
3939
- };
3940
- }
3941
- function actions(items, elementId) {
3942
- return {
3943
- tag: "column_set",
3944
- ...elementId ? { element_id: elementId } : {},
3945
- flex_mode: "flow",
3946
- horizontal_spacing: "small",
3947
- columns: items.map((it) => ({ tag: "column", width: "auto", elements: [it] }))
3948
- };
3949
- }
3950
- function actionsFixed(items, width, elementId) {
3951
- return {
3952
- tag: "column_set",
3953
- ...elementId ? { element_id: elementId } : {},
3954
- flex_mode: "flow",
3955
- horizontal_spacing: "8px",
3956
- columns: items.map((it) => ({ tag: "column", width: "auto", elements: [{ ...it, width, size: "large" }] }))
3957
- };
3958
- }
3959
- function splitRow(left, right, elementId) {
3960
- return {
3961
- tag: "column_set",
3962
- ...elementId ? { element_id: elementId } : {},
3963
- flex_mode: "none",
3964
- horizontal_spacing: "medium",
3965
- columns: [
3966
- { tag: "column", width: "auto", vertical_align: "center", elements: [left] },
3967
- { tag: "column", width: "weighted", weight: 1, vertical_align: "center", elements: [right] }
3968
- ]
3969
- };
3970
- }
3971
- function button(label, value, type = "default") {
3972
- return {
3973
- tag: "button",
3974
- text: { tag: "plain_text", content: label },
3975
- type,
3976
- behaviors: [{ type: "callback", value }]
3977
- };
3978
- }
3979
- function linkButton(label, url, type = "default", size) {
3980
- return {
3981
- tag: "button",
3982
- text: { tag: "plain_text", content: label },
3983
- type,
3984
- ...size ? { size } : {},
3985
- behaviors: [{ type: "open_url", default_url: url }]
3986
- };
3987
- }
3988
- function input(opts) {
3989
- return {
3990
- tag: "input",
3991
- name: opts.name,
3992
- ...opts.label ? { label: { tag: "plain_text", content: opts.label } } : {},
3993
- ...opts.placeholder ? { placeholder: { tag: "plain_text", content: opts.placeholder } } : {},
3994
- ...opts.value ? { default_value: opts.value } : {},
3995
- required: Boolean(opts.required)
3996
- };
3997
- }
3998
- function form(name, elements) {
3999
- return { tag: "form", name, elements };
4000
- }
4001
- function submitButton(label, value, type = "primary", name = "submit") {
4002
- return {
4003
- tag: "button",
4004
- name,
4005
- text: { tag: "plain_text", content: label },
4006
- type,
4007
- form_action_type: "submit",
4008
- behaviors: [{ type: "callback", value }]
4009
- };
4010
- }
4011
- function selectStatic(opts) {
4012
- return {
4013
- tag: "select_static",
4014
- placeholder: { tag: "plain_text", content: opts.placeholder },
4015
- ...opts.initial ? { initial_option: opts.initial } : {},
4016
- options: opts.options.map((o) => ({
4017
- text: { tag: "plain_text", content: o.label },
4018
- value: o.value
4019
- })),
4020
- behaviors: [{ type: "callback", value: { a: opts.actionId } }]
4021
- };
4022
- }
4023
- function selectMenu(opts) {
4024
- return {
4025
- tag: "select_static",
4026
- name: opts.name,
4027
- placeholder: { tag: "plain_text", content: opts.placeholder },
4028
- ...opts.initial ? { initial_option: opts.initial } : {},
4029
- options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
4030
- };
5390
+ async function updateProject(name, patch) {
5391
+ return withLock(async () => {
5392
+ const projects = await read();
5393
+ const p = projects.find((x) => x.name === name);
5394
+ if (!p) return;
5395
+ const actual = typeof patch === "function" ? patch(p) : patch;
5396
+ const target = p;
5397
+ for (const [k2, v] of Object.entries(actual)) {
5398
+ if (v !== void 0) target[k2] = v;
5399
+ }
5400
+ await write(projects);
5401
+ });
5402
+ }
5403
+ async function removeProject(name) {
5404
+ return withLock(async () => {
5405
+ const projects = await read();
5406
+ const idx = projects.findIndex((p) => p.name === name);
5407
+ if (idx === -1) return void 0;
5408
+ const [removed] = projects.splice(idx, 1);
5409
+ await write(projects);
5410
+ return removed;
5411
+ });
4031
5412
  }
4032
5413
 
5414
+ // src/card/dm-cards.ts
5415
+ init_cards();
5416
+
4033
5417
  // src/card/command-cards.ts
5418
+ init_cards();
4034
5419
  var MC = {
4035
5420
  model: "model.set",
4036
5421
  effort: "model.effort"
@@ -4746,7 +6131,7 @@ function settingItem(name, desc, actionId, current, opts) {
4746
6131
  actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
4747
6132
  ];
4748
6133
  }
4749
- function buildSettingsCard(cfg) {
6134
+ function buildSettingsCard(cfg, localAgents = []) {
4750
6135
  const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
4751
6136
  return card(
4752
6137
  [
@@ -4804,6 +6189,7 @@ function buildSettingsCard(cfg) {
4804
6189
  { label: "20", value: "20" }
4805
6190
  ]
4806
6191
  ),
6192
+ ...localAgents,
4807
6193
  hr(),
4808
6194
  actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
4809
6195
  ],
@@ -5089,8 +6475,8 @@ async function probeBackends(backends, timeoutMs = BACKEND_PROBE_TIMEOUT_MS) {
5089
6475
  return Promise.all(
5090
6476
  backends.map(async (be) => {
5091
6477
  let timer;
5092
- const timeout = new Promise((resolve8) => {
5093
- timer = setTimeout(() => resolve8(void 0), timeoutMs);
6478
+ const timeout = new Promise((resolve9) => {
6479
+ timer = setTimeout(() => resolve9(void 0), timeoutMs);
5094
6480
  });
5095
6481
  const probe = await Promise.race([be.doctor({ force: true }).catch(() => void 0), timeout]).finally(
5096
6482
  () => clearTimeout(timer)
@@ -5189,7 +6575,12 @@ async function runAdminWriteOp(op, deps) {
5189
6575
  }
5190
6576
  }
5191
6577
 
6578
+ // src/bot/handle-message.ts
6579
+ init_schema();
6580
+ init_store();
6581
+
5192
6582
  // src/card/dispatcher.ts
6583
+ init_logger();
5193
6584
  var CardDispatcher = class {
5194
6585
  constructor(channel, cfg) {
5195
6586
  this.channel = channel;
@@ -5236,105 +6627,8 @@ var CardDispatcher = class {
5236
6627
  };
5237
6628
  };
5238
6629
 
5239
- // src/card/managed.ts
5240
- var byMessageId = /* @__PURE__ */ new Map();
5241
- var renderToken = 0;
5242
- function stampRenderToken(card2) {
5243
- const token = (++renderToken).toString(36);
5244
- const visit = (node) => {
5245
- if (Array.isArray(node)) {
5246
- node.forEach(visit);
5247
- return;
5248
- }
5249
- if (!node || typeof node !== "object") return;
5250
- const obj = node;
5251
- const behaviors = obj.behaviors;
5252
- if (Array.isArray(behaviors)) {
5253
- for (const b of behaviors) {
5254
- if (b && typeof b === "object" && b.type === "callback") {
5255
- const v = b.value;
5256
- if (v && typeof v === "object") v.__r = token;
5257
- }
5258
- }
5259
- }
5260
- for (const k2 of Object.keys(obj)) visit(obj[k2]);
5261
- };
5262
- visit(card2);
5263
- }
5264
- function isCardIdNotReady(err) {
5265
- const data = err?.response?.data;
5266
- return data?.code === 230099 || /11310|cardid is invalid/i.test(data?.msg ?? "");
5267
- }
5268
- async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
5269
- stampRenderToken(card2);
5270
- const data = JSON.stringify(card2);
5271
- const attempt = async () => {
5272
- const created = await channel.rawClient.cardkit.v1.card.create({ data: { type: "card_json", data } });
5273
- const cardId = created.data?.card_id;
5274
- if (!cardId) {
5275
- throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
5276
- }
5277
- const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
5278
- let messageId;
5279
- if (replyTo) {
5280
- const sent = await channel.rawClient.im.v1.message.reply({
5281
- path: { message_id: replyTo },
5282
- data: { msg_type: "interactive", content, reply_in_thread: replyInThread }
5283
- });
5284
- messageId = sent.data?.message_id;
5285
- } else {
5286
- const sent = await channel.rawClient.im.v1.message.create({
5287
- params: { receive_id_type: receiveIdType },
5288
- data: { receive_id: to, msg_type: "interactive", content }
5289
- });
5290
- messageId = sent.data?.message_id;
5291
- }
5292
- if (!messageId) {
5293
- throw new Error("send card-by-reference returned no message_id");
5294
- }
5295
- byMessageId.set(messageId, { cardId, sequence: 0 });
5296
- return { messageId, cardId };
5297
- };
5298
- for (let i = 0; ; i++) {
5299
- try {
5300
- return await attempt();
5301
- } catch (err) {
5302
- if (i >= 2 || !isCardIdNotReady(err)) throw err;
5303
- log.fail("card", err, { phase: "managed-send", attempt: i, retry: true });
5304
- await new Promise((r) => setTimeout(r, 400 * (i + 1)));
5305
- }
5306
- }
5307
- }
5308
- async function updateManagedCard(channel, messageId, card2) {
5309
- const entry = byMessageId.get(messageId);
5310
- if (!entry) {
5311
- log.info("card", "managed-update-no-entry", { messageId, known: byMessageId.size });
5312
- return false;
5313
- }
5314
- stampRenderToken(card2);
5315
- const data = JSON.stringify(card2);
5316
- const push = async () => {
5317
- entry.sequence += 1;
5318
- await channel.rawClient.cardkit.v1.card.update({
5319
- path: { card_id: entry.cardId },
5320
- data: { card: { type: "card_json", data }, sequence: entry.sequence, uuid: `u_${entry.cardId}_${entry.sequence}` }
5321
- });
5322
- };
5323
- try {
5324
- await push();
5325
- return true;
5326
- } catch (err) {
5327
- log.fail("card", err, { phase: "managed-update", cardId: entry.cardId, seq: entry.sequence, retry: true });
5328
- await new Promise((r) => setTimeout(r, 3200));
5329
- try {
5330
- await push();
5331
- return true;
5332
- } catch (err2) {
5333
- log.fail("card", err2, { phase: "managed-update-retry", cardId: entry.cardId, seq: entry.sequence });
5334
- return false;
5335
- }
5336
- }
5337
- }
6630
+ // src/bot/handle-message.ts
6631
+ init_managed();
5338
6632
 
5339
6633
  // src/card/run-state.ts
5340
6634
  var initialState = {
@@ -5515,6 +6809,7 @@ var RunRender = class {
5515
6809
  };
5516
6810
 
5517
6811
  // src/card/history-card.ts
6812
+ init_cards();
5518
6813
  var USER_MAX = 300;
5519
6814
  var ASSIST_MAX = 800;
5520
6815
  var REASON_MAX = 600;
@@ -5658,7 +6953,11 @@ function truncateTail(s, n) {
5658
6953
  return t.length > n ? `\u2026${t.slice(t.length - n)}` : t;
5659
6954
  }
5660
6955
 
6956
+ // src/card/run-card.ts
6957
+ init_cards();
6958
+
5661
6959
  // src/card/markdown-render.ts
6960
+ init_cards();
5662
6961
  var NO_IMAGES = /* @__PURE__ */ new Map();
5663
6962
  var IMG_RE = /!\[([^\]]*)\]\(\s*(<[^>]+>|[^)\s]+)(?:\s+(?:"[^"]*"|'[^']*'))?\s*\)/g;
5664
6963
  var FENCE_RE = /```feishu-card[^\n]*\n([\s\S]*?)```/g;
@@ -5780,6 +7079,7 @@ function escapeInline2(s) {
5780
7079
  }
5781
7080
 
5782
7081
  // src/card/context-gauge.ts
7082
+ init_cards();
5783
7083
  var CTX_WARN = 0.7;
5784
7084
  var CTX_HIGH = 0.85;
5785
7085
  var CTX_CRIT = 0.95;
@@ -6115,6 +7415,7 @@ function truncate4(s, n) {
6115
7415
  }
6116
7416
 
6117
7417
  // src/card/goal-card.ts
7418
+ init_cards();
6118
7419
  function fmtTokens(n) {
6119
7420
  return Math.max(0, Math.round(n)).toLocaleString("en-US");
6120
7421
  }
@@ -6155,6 +7456,8 @@ function buildGoalDoneCard(d) {
6155
7456
  }
6156
7457
 
6157
7458
  // src/card/run-card-stream.ts
7459
+ init_logger();
7460
+ init_managed();
6158
7461
  var STREAM_THROTTLE_MS = 150;
6159
7462
  var ERR_STREAMING_OFF = 300309;
6160
7463
  var ERR_SEQ_OUT_OF_ORDER = 300317;
@@ -6474,6 +7777,7 @@ function structureSig(card2, eid) {
6474
7777
  }
6475
7778
 
6476
7779
  // src/card/outbound-images.ts
7780
+ init_logger();
6477
7781
  import { readFile as readFile7, stat as stat2 } from "fs/promises";
6478
7782
  import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
6479
7783
  var MAX_IMAGES = 9;
@@ -6609,38 +7913,222 @@ async function uploadBuffer(channel, buffer) {
6609
7913
  return key;
6610
7914
  }
6611
7915
 
7916
+ // src/bot/handle-message.ts
7917
+ init_logger();
7918
+ init_cards2();
7919
+
7920
+ // src/cli-bridge/hooks.ts
7921
+ import { mkdir as mkdir6, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
7922
+ import { homedir as homedir3 } from "os";
7923
+ import { join as join10, resolve as resolve3 } from "path";
7924
+ var AGENT2LARK_MARKER = "agent2lark-hook";
7925
+ var CODEX_EVENTS = ["PermissionRequest", "Stop"];
7926
+ var CLAUDE_EVENTS = ["PermissionRequest", "Stop"];
7927
+ function resolveBridgeHookCommand(botAppId) {
7928
+ const script = process.argv[1];
7929
+ if (process.platform === "win32") {
7930
+ const winBase = script ? `${win32Quote(process.execPath)} ${win32Quote(resolve3(script))} hook` : "feishu-codex-bridge hook";
7931
+ return botAppId ? `${winBase} --bot ${win32Quote(botAppId)}` : winBase;
7932
+ }
7933
+ const base = script ? `"${process.execPath}" "${resolve3(script)}" hook` : "feishu-codex-bridge hook";
7934
+ return botAppId ? `${base} --bot ${shellQuote(botAppId)}` : base;
7935
+ }
7936
+ async function inspectCliBridgeHooks(opts = {}) {
7937
+ const home = resolveHome(opts.homeDir);
7938
+ const claude = await readJson(join10(home, ".claude", "settings.json"));
7939
+ const codex = await readJson(join10(home, ".codex", "hooks.json"));
7940
+ let codexStatus = inspectAgent("codex", codex, [...CODEX_EVENTS]);
7941
+ if (codexStatus.status === "installed") {
7942
+ const toml = await readFile8(join10(home, ".codex", "config.toml"), "utf8").catch(() => "");
7943
+ if (!hasCodexHooksFeature(toml)) {
7944
+ codexStatus = {
7945
+ agent: "codex",
7946
+ status: "needs_repair",
7947
+ details: [...codexStatus.details, "config.toml [features] hooks=true missing"]
7948
+ };
7949
+ }
7950
+ }
7951
+ return {
7952
+ claude: inspectAgent("claude", claude, [...CLAUDE_EVENTS]),
7953
+ codex: codexStatus
7954
+ };
7955
+ }
7956
+ async function installCliBridgeHooks(opts) {
7957
+ const home = resolveHome(opts.homeDir);
7958
+ if (opts.agents.claude) {
7959
+ const file = join10(home, ".claude", "settings.json");
7960
+ const json = await readJson(file);
7961
+ json.hooks = installAgentGroups(json.hooks, "claude", [...CLAUDE_EVENTS], opts.command);
7962
+ await writeJson(file, json);
7963
+ }
7964
+ if (opts.agents.codex) {
7965
+ const file = join10(home, ".codex", "hooks.json");
7966
+ const json = await readJson(file);
7967
+ json.hooks = installAgentGroups(json.hooks, "codex", [...CODEX_EVENTS], opts.command);
7968
+ await writeJson(file, json);
7969
+ const tomlPath = join10(home, ".codex", "config.toml");
7970
+ const existing = await readFile8(tomlPath, "utf8").catch(() => "");
7971
+ await mkdir6(join10(home, ".codex"), { recursive: true });
7972
+ await writeFile6(tomlPath, withCodexHooksFeature(existing), "utf8");
7973
+ }
7974
+ }
7975
+ function resolveHome(homeDir) {
7976
+ return homeDir ?? homedir3();
7977
+ }
7978
+ async function readJson(path) {
7979
+ const text = await readFile8(path, "utf8").catch(() => "{}");
7980
+ const parsed = JSON.parse(text || "{}");
7981
+ return typeof parsed === "object" && parsed ? parsed : {};
7982
+ }
7983
+ async function writeJson(path, value) {
7984
+ await mkdir6(join10(path, ".."), { recursive: true });
7985
+ await writeFile6(path, JSON.stringify(value, null, 2) + "\n", "utf8");
7986
+ }
7987
+ function inspectAgent(agent, root, events) {
7988
+ const hooks = root.hooks ?? {};
7989
+ const commands = Object.values(hooks).flatMap(
7990
+ (groups) => groups.flatMap((group) => group.hooks ?? []).map((hook) => hook.command ?? "")
7991
+ );
7992
+ if (commands.some((command) => command.includes(AGENT2LARK_MARKER))) {
7993
+ return { agent, status: "conflict_agent2lark", details: ["agent2lark hook command found"] };
7994
+ }
7995
+ const installedEvents = events.filter(
7996
+ (event) => (hooks[event] ?? []).some(
7997
+ (group) => (group.hooks ?? []).some((hook) => isBridgeAgentCommand(hook.command, agent))
7998
+ )
7999
+ );
8000
+ if (installedEvents.length === events.length) {
8001
+ return { agent, status: "installed", details: installedEvents };
8002
+ }
8003
+ if (installedEvents.length > 0 || commands.some((command) => isBridgeCommand(command))) {
8004
+ return { agent, status: "needs_repair", details: installedEvents };
8005
+ }
8006
+ return { agent, status: "not_installed", details: [] };
8007
+ }
8008
+ function installAgentGroups(hooks, agent, events, command) {
8009
+ const next = removeHookGroups(hooks ?? {}, (hook) => isBridgeCommand(hook.command) || isAgent2larkCommand(hook.command));
8010
+ for (const event of events) {
8011
+ const groups = next[event] ?? [];
8012
+ next[event] = [
8013
+ ...groups,
8014
+ {
8015
+ matcher: "*",
8016
+ // timeout 必须 ≥ IPC 等待上限(24h)——否则 CLI 会在人到飞书点批准前先杀掉 hook。
8017
+ // agent2lark 在每个 Claude/Codex hook 上都写了 86400(见 installer.ts)。
8018
+ hooks: [{ type: "command", command: `${command} --agent ${agent}`, timeout: 86400 }]
8019
+ }
8020
+ ];
8021
+ }
8022
+ return next;
8023
+ }
8024
+ function removeHookGroups(hooks, shouldRemove) {
8025
+ const next = {};
8026
+ for (const [event, groups] of Object.entries(hooks)) {
8027
+ const kept = groups.flatMap((group) => {
8028
+ if (!group.hooks) return [group];
8029
+ const keptHooks = group.hooks.filter((hook) => !shouldRemove(hook));
8030
+ return keptHooks.length > 0 ? [{ ...group, hooks: keptHooks }] : [];
8031
+ });
8032
+ if (kept.length > 0) next[event] = kept;
8033
+ }
8034
+ return next;
8035
+ }
8036
+ function isBridgeAgentCommand(command, agent) {
8037
+ return isBridgeCommand(command) && (command ?? "").includes(`--agent ${agent}`);
8038
+ }
8039
+ function isBridgeCommand(command) {
8040
+ const raw = command ?? "";
8041
+ return raw.includes(" hook") && raw.includes("--agent") && !raw.includes(AGENT2LARK_MARKER);
8042
+ }
8043
+ function isAgent2larkCommand(command) {
8044
+ return Boolean(command?.includes(AGENT2LARK_MARKER));
8045
+ }
8046
+ function shellQuote(value) {
8047
+ return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
8048
+ }
8049
+ function win32Quote(value) {
8050
+ return `"${value.replace(/"/g, '\\"')}"`;
8051
+ }
8052
+ function hasCodexHooksFeature(text) {
8053
+ let inFeatures = false;
8054
+ for (const raw of text.split(/\r?\n/)) {
8055
+ const trimmed = raw.trim();
8056
+ if (/^\[features\]$/.test(trimmed)) {
8057
+ inFeatures = true;
8058
+ continue;
8059
+ }
8060
+ if (inFeatures && /^\[.+\]$/.test(trimmed)) {
8061
+ inFeatures = false;
8062
+ continue;
8063
+ }
8064
+ if (inFeatures && /^hooks\s*=\s*true\b/.test(trimmed)) return true;
8065
+ }
8066
+ return false;
8067
+ }
8068
+ function withCodexHooksFeature(text) {
8069
+ const lines = text.split(/\r?\n/).filter((line, index, array) => index < array.length - 1 || line.length > 0);
8070
+ let featuresIndex = -1;
8071
+ let nextSectionIndex = lines.length;
8072
+ let codexHooksIndex = -1;
8073
+ for (let index = 0; index < lines.length; index += 1) {
8074
+ const trimmed = (lines[index] ?? "").trim();
8075
+ if (/^\[features\]$/.test(trimmed)) {
8076
+ featuresIndex = index;
8077
+ nextSectionIndex = lines.length;
8078
+ continue;
8079
+ }
8080
+ if (featuresIndex >= 0 && index > featuresIndex && /^\[.+\]$/.test(trimmed)) {
8081
+ nextSectionIndex = Math.min(nextSectionIndex, index);
8082
+ }
8083
+ if (featuresIndex >= 0 && index > featuresIndex && index < nextSectionIndex && /^hooks\s*=/.test(trimmed)) {
8084
+ codexHooksIndex = index;
8085
+ }
8086
+ }
8087
+ if (featuresIndex < 0) {
8088
+ const prefix = lines.length > 0 ? [...lines, ""] : [];
8089
+ return [...prefix, "[features]", "hooks = true", ""].join("\n");
8090
+ }
8091
+ if (codexHooksIndex >= 0) {
8092
+ lines[codexHooksIndex] = "hooks = true";
8093
+ return [...lines, ""].join("\n");
8094
+ }
8095
+ lines.splice(featuresIndex + 1, 0, "hooks = true");
8096
+ return [...lines, ""].join("\n");
8097
+ }
8098
+
6612
8099
  // src/service/update.ts
6613
8100
  import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
6614
- import { dirname as dirname9, join as join14, resolve as resolve5 } from "path";
8101
+ import { dirname as dirname9, join as join15, resolve as resolve6 } from "path";
6615
8102
  import { fileURLToPath as fileURLToPath4 } from "url";
6616
8103
 
6617
8104
  // src/service/launchd.ts
6618
8105
  import { spawn as spawn2, spawnSync } from "child_process";
6619
8106
  import { existsSync as existsSync6 } from "fs";
6620
- import { mkdir as mkdir7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
6621
- import { homedir as homedir3, userInfo as userInfo2 } from "os";
6622
- import { dirname as dirname7, join as join11, resolve as resolve4 } from "path";
8107
+ import { mkdir as mkdir8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
8108
+ import { homedir as homedir4, userInfo as userInfo2 } from "os";
8109
+ import { dirname as dirname7, join as join12, resolve as resolve5 } from "path";
6623
8110
  import { fileURLToPath as fileURLToPath3 } from "url";
6624
8111
 
6625
8112
  // src/service/common.ts
8113
+ init_paths();
6626
8114
  import { createReadStream, statSync as statSync2 } from "fs";
6627
- import { appendFile, mkdir as mkdir6, readFile as readFile8 } from "fs/promises";
6628
- import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
8115
+ import { appendFile, mkdir as mkdir7, readFile as readFile9 } from "fs/promises";
8116
+ import { dirname as dirname6, join as join11, resolve as resolve4 } from "path";
6629
8117
  import { fileURLToPath as fileURLToPath2 } from "url";
6630
8118
  function serviceStdoutPath() {
6631
- return join10(paths.appDir, "service.log");
8119
+ return join11(paths.appDir, "service.log");
6632
8120
  }
6633
8121
  function serviceStderrPath() {
6634
- return join10(paths.appDir, "service.err.log");
8122
+ return join11(paths.appDir, "service.err.log");
6635
8123
  }
6636
8124
  async function ensureLogFiles() {
6637
- await mkdir6(paths.appDir, { recursive: true });
8125
+ await mkdir7(paths.appDir, { recursive: true });
6638
8126
  await appendFile(serviceStdoutPath(), "");
6639
8127
  await appendFile(serviceStderrPath(), "");
6640
8128
  }
6641
8129
  function resolveCliBinPath() {
6642
8130
  const distDir = dirname6(fileURLToPath2(import.meta.url));
6643
- return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
8131
+ return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
6644
8132
  }
6645
8133
  async function tailServiceLogs(follow) {
6646
8134
  await ensureLogFiles();
@@ -6686,7 +8174,7 @@ function fileSize(file) {
6686
8174
  }
6687
8175
  async function lastLines(file, n) {
6688
8176
  try {
6689
- const text = await readFile8(file, "utf8");
8177
+ const text = await readFile9(file, "utf8");
6690
8178
  return text.split("\n").slice(-n - 1).join("\n").trimEnd();
6691
8179
  } catch {
6692
8180
  return "";
@@ -6696,11 +8184,11 @@ async function lastLines(file, n) {
6696
8184
  // src/service/launchd.ts
6697
8185
  var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
6698
8186
  function launchAgentPlistPath() {
6699
- return join11(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
8187
+ return join12(homedir4(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
6700
8188
  }
6701
8189
  function resolveCliBinPath2() {
6702
8190
  const distDir = dirname7(fileURLToPath3(import.meta.url));
6703
- return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
8191
+ return resolve5(distDir, "..", "bin", "feishu-codex-bridge.mjs");
6704
8192
  }
6705
8193
  function escapeXml(value) {
6706
8194
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -6740,9 +8228,9 @@ function buildPlist() {
6740
8228
  }
6741
8229
  async function installLaunchd() {
6742
8230
  const plistPath = launchAgentPlistPath();
6743
- await mkdir7(dirname7(plistPath), { recursive: true });
8231
+ await mkdir8(dirname7(plistPath), { recursive: true });
6744
8232
  await ensureLogFiles();
6745
- await writeFile6(plistPath, buildPlist(), "utf8");
8233
+ await writeFile7(plistPath, buildPlist(), "utf8");
6746
8234
  if (isLoaded()) {
6747
8235
  const bootout = runLaunchctl(["bootout", serviceTarget()]);
6748
8236
  if (!bootout.ok) throw launchctlError("launchctl bootout", bootout);
@@ -6845,21 +8333,22 @@ function launchctlError(command, result) {
6845
8333
  }
6846
8334
 
6847
8335
  // src/service/win-startup.ts
8336
+ init_paths();
6848
8337
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
6849
8338
  import { openSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
6850
- import { mkdir as mkdir8, writeFile as writeFile7 } from "fs/promises";
6851
- import { join as join12 } from "path";
8339
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "fs/promises";
8340
+ import { join as join13 } from "path";
6852
8341
  var RUN_KEY_PATH = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
6853
8342
  var RUN_KEY_NAME = "feishu-codex-bridge";
6854
8343
  var SERVICE_ENV_FLAG = "FEISHU_CODEX_BRIDGE_SERVICE";
6855
8344
  function launcherCmdPath() {
6856
- return join12(paths.appDir, "service-launcher.cmd");
8345
+ return join13(paths.appDir, "service-launcher.cmd");
6857
8346
  }
6858
8347
  function launcherVbsPath() {
6859
- return join12(paths.appDir, "service-launcher.vbs");
8348
+ return join13(paths.appDir, "service-launcher.vbs");
6860
8349
  }
6861
8350
  function servicePidFile() {
6862
- return join12(paths.appDir, "service.pid");
8351
+ return join13(paths.appDir, "service.pid");
6863
8352
  }
6864
8353
  function buildLauncherCmd() {
6865
8354
  return [
@@ -6891,10 +8380,10 @@ function startNow() {
6891
8380
  child.unref();
6892
8381
  }
6893
8382
  async function installWinStartup() {
6894
- await mkdir8(paths.appDir, { recursive: true });
8383
+ await mkdir9(paths.appDir, { recursive: true });
6895
8384
  await ensureLogFiles();
6896
- await writeFile7(launcherCmdPath(), buildLauncherCmd(), "utf8");
6897
- await writeFile7(launcherVbsPath(), buildLauncherVbs(), "utf8");
8385
+ await writeFile8(launcherCmdPath(), buildLauncherCmd(), "utf8");
8386
+ await writeFile8(launcherVbsPath(), buildLauncherVbs(), "utf8");
6898
8387
  const reg = spawnSync2(
6899
8388
  "reg",
6900
8389
  ["add", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/t", "REG_SZ", "/d", `wscript.exe "${launcherVbsPath()}"`, "/f"],
@@ -6985,13 +8474,13 @@ function killService() {
6985
8474
  // src/service/systemd.ts
6986
8475
  import { spawnSync as spawnSync3 } from "child_process";
6987
8476
  import { existsSync as existsSync7 } from "fs";
6988
- import { mkdir as mkdir9, rm as rm4, writeFile as writeFile8 } from "fs/promises";
6989
- import { homedir as homedir4 } from "os";
6990
- import { dirname as dirname8, join as join13 } from "path";
8477
+ import { mkdir as mkdir10, rm as rm4, writeFile as writeFile9 } from "fs/promises";
8478
+ import { homedir as homedir5 } from "os";
8479
+ import { dirname as dirname8, join as join14 } from "path";
6991
8480
  var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
6992
8481
  function systemdUnitPath() {
6993
- const base = process.env.XDG_CONFIG_HOME ?? join13(homedir4(), ".config");
6994
- return join13(base, "systemd", "user", SYSTEMD_UNIT_NAME);
8482
+ const base = process.env.XDG_CONFIG_HOME ?? join14(homedir5(), ".config");
8483
+ return join14(base, "systemd", "user", SYSTEMD_UNIT_NAME);
6995
8484
  }
6996
8485
  function buildUnit() {
6997
8486
  const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -7045,9 +8534,9 @@ function ensureSystemdOrThrow() {
7045
8534
  async function installSystemd() {
7046
8535
  ensureSystemdOrThrow();
7047
8536
  const unitPath = systemdUnitPath();
7048
- await mkdir9(dirname8(unitPath), { recursive: true });
8537
+ await mkdir10(dirname8(unitPath), { recursive: true });
7049
8538
  await ensureLogFiles();
7050
- await writeFile8(unitPath, buildUnit(), "utf8");
8539
+ await writeFile9(unitPath, buildUnit(), "utf8");
7051
8540
  const reload = runSystemctl(["daemon-reload"]);
7052
8541
  if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
7053
8542
  const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
@@ -7147,11 +8636,11 @@ function isServiceRunning() {
7147
8636
  // src/service/update.ts
7148
8637
  var NPM2 = "npm";
7149
8638
  function pkgRoot() {
7150
- return resolve5(dirname9(fileURLToPath4(import.meta.url)), "..");
8639
+ return resolve6(dirname9(fileURLToPath4(import.meta.url)), "..");
7151
8640
  }
7152
8641
  function pkgJson() {
7153
8642
  try {
7154
- return JSON.parse(readFileSync4(join14(pkgRoot(), "package.json"), "utf8"));
8643
+ return JSON.parse(readFileSync4(join15(pkgRoot(), "package.json"), "utf8"));
7155
8644
  } catch {
7156
8645
  return {};
7157
8646
  }
@@ -7163,7 +8652,7 @@ function packageName() {
7163
8652
  return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
7164
8653
  }
7165
8654
  function isDevSource() {
7166
- return existsSync8(join14(pkgRoot(), ".git"));
8655
+ return existsSync8(join15(pkgRoot(), ".git"));
7167
8656
  }
7168
8657
  function isNewer(a, b) {
7169
8658
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -7227,9 +8716,10 @@ async function restartDaemon() {
7227
8716
  }
7228
8717
 
7229
8718
  // src/agent/codex-appserver/usage.ts
7230
- import { readFile as readFile9 } from "fs/promises";
7231
- import { homedir as homedir5 } from "os";
7232
- import { join as join15 } from "path";
8719
+ init_logger();
8720
+ import { readFile as readFile10 } from "fs/promises";
8721
+ import { homedir as homedir6 } from "os";
8722
+ import { join as join16 } from "path";
7233
8723
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
7234
8724
  var HTTP_TIMEOUT_MS = 15e3;
7235
8725
  var REFRESH_TIMEOUT_MS = 2e4;
@@ -7237,15 +8727,15 @@ var EXP_SKEW_MS = 6e4;
7237
8727
  var PROFILE_CACHE_MS = 5 * 6e4;
7238
8728
  var USAGE_CACHE_MS = 3e4;
7239
8729
  function resolveCodexHome() {
7240
- return process.env.CODEX_HOME ?? join15(homedir5(), ".codex");
8730
+ return process.env.CODEX_HOME ?? join16(homedir6(), ".codex");
7241
8731
  }
7242
8732
  async function readCodexAuth() {
7243
- const file = join15(resolveCodexHome(), "auth.json");
8733
+ const file = join16(resolveCodexHome(), "auth.json");
7244
8734
  let lastErr;
7245
8735
  for (let i = 0; i < 3; i++) {
7246
8736
  let raw;
7247
8737
  try {
7248
- raw = await readFile9(file, "utf8");
8738
+ raw = await readFile10(file, "utf8");
7249
8739
  } catch (err) {
7250
8740
  throw new UsageError("no-auth", `\u8BFB\u4E0D\u5230 ${file}\uFF1A${err instanceof Error ? err.message : String(err)}`);
7251
8741
  }
@@ -7274,7 +8764,7 @@ function jwtExpMs(token) {
7274
8764
  }
7275
8765
  async function chatgptBaseUrl() {
7276
8766
  try {
7277
- const raw = await readFile9(join15(resolveCodexHome(), "config.toml"), "utf8");
8767
+ const raw = await readFile10(join16(resolveCodexHome(), "config.toml"), "utf8");
7278
8768
  for (const line of raw.split("\n")) {
7279
8769
  const t = line.trim();
7280
8770
  if (t.startsWith("[")) break;
@@ -7437,6 +8927,7 @@ async function fetchUsageBundle(force = false) {
7437
8927
  }
7438
8928
 
7439
8929
  // src/card/usage-cards.ts
8930
+ init_cards();
7440
8931
  function formatTokensZh(n) {
7441
8932
  if (n === void 0 || n === null || Number.isNaN(n)) return "\u2014";
7442
8933
  const fmt = (v) => {
@@ -7804,6 +9295,7 @@ function buildUsageShareCard(data, opts = {}) {
7804
9295
  }
7805
9296
 
7806
9297
  // src/web/discovery.ts
9298
+ init_paths();
7807
9299
  import { randomUUID as randomUUID4 } from "crypto";
7808
9300
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
7809
9301
  import { dirname as dirname10 } from "path";
@@ -7868,10 +9360,18 @@ function isAlive(pid) {
7868
9360
  }
7869
9361
  }
7870
9362
 
9363
+ // src/bot/handle-message.ts
9364
+ init_paths();
9365
+
7871
9366
  // src/project/lifecycle.ts
7872
- import { mkdir as mkdir10 } from "fs/promises";
9367
+ init_paths();
9368
+ init_logger();
9369
+ import { mkdir as mkdir11 } from "fs/promises";
7873
9370
  import { existsSync as existsSync9 } from "fs";
7874
- import { isAbsolute as isAbsolute2, join as join16, resolve as resolve6 } from "path";
9371
+ import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7 } from "path";
9372
+
9373
+ // src/project/announcement.ts
9374
+ init_logger();
7875
9375
 
7876
9376
  // src/project/git-info.ts
7877
9377
  import { execFile } from "child_process";
@@ -7971,6 +9471,7 @@ async function refreshBranch(channel, project) {
7971
9471
  }
7972
9472
 
7973
9473
  // src/project/onboarding.ts
9474
+ init_logger();
7974
9475
  function welcomeCaps(backend) {
7975
9476
  try {
7976
9477
  return createBackend(backend).capabilities;
@@ -8058,12 +9559,12 @@ function assertBackendUsable(backend, mode) {
8058
9559
  }
8059
9560
  async function resolveCwd(name, existingPath) {
8060
9561
  if (existingPath) {
8061
- const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
9562
+ const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve7(existingPath);
8062
9563
  if (!existsSync9(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
8063
9564
  return { cwd: cwd2, blank: false };
8064
9565
  }
8065
- const cwd = join16(paths.projectsRootDir, name);
8066
- await mkdir10(cwd, { recursive: true });
9566
+ const cwd = join17(paths.projectsRootDir, name);
9567
+ await mkdir11(cwd, { recursive: true });
8067
9568
  return { cwd, blank: true };
8068
9569
  }
8069
9570
  async function createProject(channel, input2) {
@@ -8129,6 +9630,7 @@ async function joinExistingGroup(channel, input2) {
8129
9630
  }
8130
9631
 
8131
9632
  // src/project/group-ops.ts
9633
+ init_logger();
8132
9634
  async function transferOwnership(channel, chatId, toOpenId) {
8133
9635
  await channel.rawClient.im.v1.chat.update({
8134
9636
  path: { chat_id: chatId },
@@ -8146,7 +9648,8 @@ async function leaveChat(channel, chatId) {
8146
9648
  }
8147
9649
 
8148
9650
  // src/bot/session-store.ts
8149
- import { mkdir as mkdir11, readFile as readFile10, rename as rename5, writeFile as writeFile9 } from "fs/promises";
9651
+ init_paths();
9652
+ import { mkdir as mkdir12, readFile as readFile11, rename as rename5, writeFile as writeFile10 } from "fs/promises";
8150
9653
  import { randomUUID as randomUUID5 } from "crypto";
8151
9654
  import { dirname as dirname11 } from "path";
8152
9655
  var FILE_VERSION3 = 2;
@@ -8165,7 +9668,7 @@ async function read2() {
8165
9668
  }
8166
9669
  async function listSessionsIn(file) {
8167
9670
  try {
8168
- const text = await readFile10(file, "utf8");
9671
+ const text = await readFile11(file, "utf8");
8169
9672
  const parsed = JSON.parse(text);
8170
9673
  if (!Array.isArray(parsed.sessions)) return [];
8171
9674
  return parsed.sessions.map(migrate);
@@ -8184,10 +9687,10 @@ function withLock2(fn) {
8184
9687
  return run;
8185
9688
  }
8186
9689
  async function write2(sessions) {
8187
- await mkdir11(dirname11(paths.sessionsFile), { recursive: true });
9690
+ await mkdir12(dirname11(paths.sessionsFile), { recursive: true });
8188
9691
  const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID5()}`;
8189
9692
  const body = { version: FILE_VERSION3, sessions };
8190
- await writeFile9(tmp, `${JSON.stringify(body, null, 2)}
9693
+ await writeFile10(tmp, `${JSON.stringify(body, null, 2)}
8191
9694
  `, "utf8");
8192
9695
  await rename5(tmp, paths.sessionsFile);
8193
9696
  }
@@ -8222,6 +9725,9 @@ async function patchSession(threadId, patch) {
8222
9725
  }
8223
9726
 
8224
9727
  // src/bot/dm-console.ts
9728
+ init_schema();
9729
+ init_managed();
9730
+ init_logger();
8225
9731
  async function handleDmConsole(channel, cfg, msg) {
8226
9732
  await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
8227
9733
  if (!isAdmin(cfg, msg.senderId)) {
@@ -8236,8 +9742,10 @@ async function handleDmConsole(channel, cfg, msg) {
8236
9742
  }
8237
9743
 
8238
9744
  // src/bot/media.ts
8239
- import { mkdir as mkdir12, readdir as readdir3, rm as rm5, stat as stat3 } from "fs/promises";
8240
- import { join as join17 } from "path";
9745
+ init_paths();
9746
+ init_logger();
9747
+ import { mkdir as mkdir13, readdir as readdir3, rm as rm5, stat as stat3 } from "fs/promises";
9748
+ import { join as join18 } from "path";
8241
9749
  var MAX_IMAGES2 = 9;
8242
9750
  var MEDIA_TTL_MS = 60 * 6e4;
8243
9751
  var EXT_BY_CONTENT_TYPE = {
@@ -8266,7 +9774,7 @@ async function collectInboundImages(channel, msg) {
8266
9774
  if (refs.length === 0) return [];
8267
9775
  await pruneOldMedia(paths.mediaDir);
8268
9776
  try {
8269
- await mkdir12(paths.mediaDir, { recursive: true });
9777
+ await mkdir13(paths.mediaDir, { recursive: true });
8270
9778
  } catch {
8271
9779
  }
8272
9780
  const out = [];
@@ -8342,7 +9850,7 @@ async function downloadOne(channel, ref, index) {
8342
9850
  params: { type: "image" }
8343
9851
  });
8344
9852
  const ext = extFromHeaders(res.headers);
8345
- const file = join17(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
9853
+ const file = join18(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
8346
9854
  await res.writeFile(file);
8347
9855
  return file;
8348
9856
  } catch (err) {
@@ -8376,7 +9884,7 @@ async function pruneOldMedia(dir) {
8376
9884
  }
8377
9885
  const cutoff = Date.now() - MEDIA_TTL_MS;
8378
9886
  for (const name of entries) {
8379
- const file = join17(dir, name);
9887
+ const file = join18(dir, name);
8380
9888
  try {
8381
9889
  const st = await stat3(file);
8382
9890
  if (st.mtimeMs < cutoff) await rm5(file, { force: true });
@@ -8401,7 +9909,7 @@ async function collectInboundFiles(channel, msg) {
8401
9909
  if (refs.length === 0) return [];
8402
9910
  await pruneOldMedia(paths.inboundDir);
8403
9911
  try {
8404
- await mkdir12(paths.inboundDir, { recursive: true });
9912
+ await mkdir13(paths.inboundDir, { recursive: true });
8405
9913
  } catch {
8406
9914
  }
8407
9915
  const out = [];
@@ -8425,7 +9933,7 @@ async function downloadOneFile(channel, ref) {
8425
9933
  }
8426
9934
  const name = cleanFileName(ref.fileName) || "attachment";
8427
9935
  const onDisk = `${safeName(ref.fileKey)}-${name}`;
8428
- const file = join17(paths.inboundDir, onDisk);
9936
+ const file = join18(paths.inboundDir, onDisk);
8429
9937
  await res.writeFile(file);
8430
9938
  try {
8431
9939
  const st = await stat3(file);
@@ -8464,6 +9972,7 @@ ${lines}
8464
9972
  }
8465
9973
 
8466
9974
  // src/bot/context-weave.ts
9975
+ init_logger();
8467
9976
  var QUOTE_MAX = 800;
8468
9977
  var LINE_MAX = 280;
8469
9978
  var THREAD_WEAVE_MAX = 20;
@@ -8552,7 +10061,7 @@ function extractMessageText(msgType, content, mentions) {
8552
10061
  case "sticker":
8553
10062
  return "[\u8868\u60C5]";
8554
10063
  case "interactive":
8555
- return "[\u5361\u7247\u6D88\u606F]";
10064
+ return extractCardText(parsed) || "[\u5361\u7247\u6D88\u606F]";
8556
10065
  case "share_chat":
8557
10066
  return "[\u5206\u4EAB\u7FA4\u540D\u7247]";
8558
10067
  case "share_user":
@@ -8614,6 +10123,55 @@ function nodeToText(node) {
8614
10123
  return typeof n.text === "string" ? n.text : "";
8615
10124
  }
8616
10125
  }
10126
+ var CARD_UPGRADE_HINT = "\u8BF7\u5347\u7EA7\u81F3\u6700\u65B0\u7248\u672C\u5BA2\u6237\u7AEF";
10127
+ function extractCardText(parsed) {
10128
+ if (!parsed || typeof parsed !== "object") return "";
10129
+ const obj = parsed;
10130
+ const parts = [];
10131
+ const title = textValue(obj.title);
10132
+ if (title.trim()) parts.push(title.trim());
10133
+ if (Array.isArray(obj.elements)) {
10134
+ for (const line of obj.elements) {
10135
+ const nodes = Array.isArray(line) ? line : [line];
10136
+ const lineText = nodes.map(cardNodeToText).join("").trim();
10137
+ if (lineText && !lineText.includes(CARD_UPGRADE_HINT)) parts.push(lineText);
10138
+ }
10139
+ }
10140
+ return parts.join("\n").trim();
10141
+ }
10142
+ function textValue(t) {
10143
+ if (typeof t === "string") return t;
10144
+ if (t && typeof t === "object") {
10145
+ const c = t.content;
10146
+ if (typeof c === "string") return c;
10147
+ }
10148
+ return "";
10149
+ }
10150
+ function cardNodeToText(node) {
10151
+ if (typeof node === "string") return node;
10152
+ if (!node || typeof node !== "object") return "";
10153
+ const n = node;
10154
+ switch (n.tag) {
10155
+ case "text":
10156
+ return textValue(n.text);
10157
+ case "a":
10158
+ return textValue(n.text) || (typeof n.href === "string" ? n.href : "");
10159
+ case "at": {
10160
+ const name = typeof n.user_name === "string" ? n.user_name : "";
10161
+ return name ? `@${name}` : "@\u67D0\u4EBA";
10162
+ }
10163
+ case "note":
10164
+ return Array.isArray(n.elements) ? n.elements.map(cardNodeToText).join("") : "";
10165
+ case "button": {
10166
+ const label = textValue(n.text);
10167
+ return label ? `[\u6309\u94AE\uFF1A${label}]` : "";
10168
+ }
10169
+ case "img":
10170
+ return "";
10171
+ default:
10172
+ return textValue(n.text);
10173
+ }
10174
+ }
8617
10175
  function replaceMentions(text, mentions) {
8618
10176
  if (!text || !mentions?.length) return text;
8619
10177
  let out = text;
@@ -8669,6 +10227,7 @@ function weaveSender(text, sender) {
8669
10227
  }
8670
10228
 
8671
10229
  // src/bot/comments.ts
10230
+ init_logger();
8672
10231
  var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "file"]);
8673
10232
  var REPLY_MAX_CHARS = 2e3;
8674
10233
  function apiErrCode(err) {
@@ -8940,9 +10499,9 @@ var Semaphore = class {
8940
10499
  const acquired = new Promise((res) => {
8941
10500
  settle = res;
8942
10501
  });
8943
- let settled = false;
10502
+ let settled2 = false;
8944
10503
  const grant = () => {
8945
- settled = true;
10504
+ settled2 = true;
8946
10505
  this.active++;
8947
10506
  let released = false;
8948
10507
  settle(() => {
@@ -8966,11 +10525,11 @@ var Semaphore = class {
8966
10525
  return i >= 0 ? i + 1 : 0;
8967
10526
  },
8968
10527
  cancel: () => {
8969
- if (settled) return false;
10528
+ if (settled2) return false;
8970
10529
  const i = this.waiters.indexOf(entry);
8971
10530
  if (i < 0) return false;
8972
10531
  this.waiters.splice(i, 1);
8973
- settled = true;
10532
+ settled2 = true;
8974
10533
  settle(null);
8975
10534
  this.notifyAdvance(i);
8976
10535
  return true;
@@ -9121,7 +10680,7 @@ function pickIdleSessions(keys, touchedAt, isBusy, idleMs, now) {
9121
10680
  }
9122
10681
  return out;
9123
10682
  }
9124
- function createOrchestrator(channel, cfg, fallbackCwd) {
10683
+ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
9125
10684
  const backends = /* @__PURE__ */ new Map();
9126
10685
  function backendFor(id) {
9127
10686
  const key = id && catalogById(id) ? id : DEFAULT_BACKEND_ID;
@@ -9230,6 +10789,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
9230
10789
  preview: msg.content.slice(0, 40)
9231
10790
  });
9232
10791
  if (msg.chatType === "p2p") {
10792
+ if (cliBridge?.onMessage({
10793
+ parentId: msg.replyToMessageId,
10794
+ rootId: msg.threadId,
10795
+ text: msg.content,
10796
+ messageId: msg.messageId
10797
+ })) {
10798
+ return;
10799
+ }
9233
10800
  await handleDmConsole(channel, cfg, msg);
9234
10801
  return;
9235
10802
  }
@@ -9828,6 +11395,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
9828
11395
  });
9829
11396
  }
9830
11397
  const dispatcher = new CardDispatcher(channel, cfg);
11398
+ cliBridge?.register(dispatcher);
9831
11399
  const PENDING_TTL_MS = 30 * 6e4;
9832
11400
  const GOAL_IDLE_MS = 30 * 6e4;
9833
11401
  const CARD_SETTLE_MS = 500;
@@ -9985,13 +11553,27 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
9985
11553
  if (op?.openId && op.name && !m.has(op.openId)) m.set(op.openId, op.name);
9986
11554
  return m;
9987
11555
  };
9988
- function applyPref(evt, mut) {
11556
+ function applyPref(evt, mut, opts) {
9989
11557
  if (!dmAdmin(evt.operator?.openId)) return;
9990
11558
  const prefs = { ...cfg.preferences ?? {} };
9991
11559
  mut(prefs);
9992
11560
  cfg.preferences = prefs;
9993
11561
  void saveConfig(cfg).catch((err) => log.fail("console", err, { phase: "save-config" }));
9994
- patch(evt, buildSettingsCard(cfg));
11562
+ if (opts?.render !== false) void patch(evt, renderSettings);
11563
+ }
11564
+ let cliHookStatuses;
11565
+ async function renderSettings(refreshHooks = false) {
11566
+ if (refreshHooks || !cliHookStatuses) cliHookStatuses = await inspectCliBridgeHooks();
11567
+ const cliPrefs = getCliBridgePreferences(cfg);
11568
+ const localAgents = cliBridgeSettingsSection({
11569
+ enabled: cliPrefs.enabled,
11570
+ statuses: cliHookStatuses,
11571
+ canEnable: canEnableCliBridge(cfg),
11572
+ notifyScope: cliPrefs.notifyScope,
11573
+ agents: cliPrefs.agents,
11574
+ keepAwake: cliPrefs.keepAwake.enabled
11575
+ });
11576
+ return buildSettingsCard(cfg, localAgents);
9995
11577
  }
9996
11578
  const freshMenu = (evt) => {
9997
11579
  patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
@@ -10136,7 +11718,59 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
10136
11718
  if (!dmAdmin(evt.operator?.openId)) return;
10137
11719
  patch(evt, renderProjectList);
10138
11720
  }).on(DM.settings, async ({ evt }) => {
10139
- if (dmAdmin(evt.operator?.openId)) await patch(evt, buildSettingsCard(cfg));
11721
+ if (dmAdmin(evt.operator?.openId)) await patch(evt, () => renderSettings(true));
11722
+ }).on(CLI.toggleEnabled, async ({ evt, value }) => {
11723
+ if (!dmAdmin(evt.operator?.openId)) return;
11724
+ const enabled = value.v === "on";
11725
+ if (enabled && !canEnableCliBridge(cfg).ok) return;
11726
+ try {
11727
+ if (enabled) {
11728
+ await cliBridge?.start?.();
11729
+ applyPref(evt, (p) => {
11730
+ p.cliBridge = { ...p.cliBridge ?? {}, enabled: true };
11731
+ }, { render: false });
11732
+ } else {
11733
+ applyPref(evt, (p) => {
11734
+ p.cliBridge = { ...p.cliBridge ?? {}, enabled: false };
11735
+ }, { render: false });
11736
+ await cliBridge?.shutdown?.();
11737
+ }
11738
+ } catch (err) {
11739
+ log.fail("cli-bridge", err, { phase: enabled ? "enable" : "disable" });
11740
+ }
11741
+ void patch(evt, renderSettings);
11742
+ }).on(CLI.repairHooks, async ({ evt }) => {
11743
+ if (!dmAdmin(evt.operator?.openId)) return;
11744
+ try {
11745
+ await installCliBridgeHooks({
11746
+ agents: { claude: true, codex: true },
11747
+ command: resolveBridgeHookCommand(cfg.accounts.app.id)
11748
+ });
11749
+ } catch (err) {
11750
+ log.fail("cli-bridge", err, { phase: "repair-hooks" });
11751
+ }
11752
+ await patch(evt, () => renderSettings(true));
11753
+ }).on(CLI.setNotifyScope, ({ evt, value }) => {
11754
+ if (!dmAdmin(evt.operator?.openId)) return;
11755
+ const scope = value.v === "bound_projects" || value.v === "none" ? value.v : "all";
11756
+ applyPref(evt, (p) => {
11757
+ p.cliBridge = { ...p.cliBridge ?? {}, notifyScope: scope };
11758
+ });
11759
+ }).on(CLI.toggleAgent, ({ evt, value }) => {
11760
+ if (!dmAdmin(evt.operator?.openId)) return;
11761
+ const agent = value.agent === "codex" ? "codex" : "claude";
11762
+ const on = value.v === "on";
11763
+ applyPref(evt, (p) => {
11764
+ const cur = p.cliBridge ?? {};
11765
+ p.cliBridge = { ...cur, agents: { ...cur.agents ?? {}, [agent]: on } };
11766
+ });
11767
+ }).on(CLI.toggleKeepAwake, ({ evt, value }) => {
11768
+ if (!dmAdmin(evt.operator?.openId)) return;
11769
+ const on = value.v === "on";
11770
+ applyPref(evt, (p) => {
11771
+ const cur = p.cliBridge ?? {};
11772
+ p.cliBridge = { ...cur, keepAwake: { ...cur.keepAwake ?? {}, enabled: on } };
11773
+ });
10140
11774
  }).on(DM.doctor, async ({ evt }) => {
10141
11775
  if (!dmAdmin(evt.operator?.openId)) return;
10142
11776
  await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
@@ -11340,6 +12974,18 @@ async function getThreadId(channel, messageId, attempts = 1) {
11340
12974
  }
11341
12975
 
11342
12976
  // src/bot/bridge.ts
12977
+ init_paths();
12978
+ init_cli_bridge();
12979
+ async function cwdIsBoundProject(cwd) {
12980
+ if (!cwd) return false;
12981
+ const strip = (p) => p.replace(/[/\\]+$/, "");
12982
+ const target = strip(cwd);
12983
+ const projects = await listProjects().catch(() => []);
12984
+ return projects.some((project) => {
12985
+ const root = strip(project.cwd);
12986
+ return root.length > 0 && (target === root || target.startsWith(root + sep2));
12987
+ });
12988
+ }
11343
12989
  async function startBridge(opts) {
11344
12990
  const app = opts.cfg.accounts.app;
11345
12991
  const channel = createLarkChannel({
@@ -11365,7 +13011,13 @@ async function startBridge(opts) {
11365
13011
  // sends are already prevented by startReservedRun's synchronous booking.
11366
13012
  safety: { batch: { text: { delayMs: 0 } } }
11367
13013
  });
11368
- const orchestrator = createOrchestrator(channel, opts.cfg, opts.fallbackCwd);
13014
+ const cliBridge = createCliBridgeService({
13015
+ cfg: opts.cfg,
13016
+ channel,
13017
+ socketPath: paths.cliBridgeSocket,
13018
+ isBoundProject: cwdIsBoundProject
13019
+ });
13020
+ const orchestrator = createOrchestrator(channel, opts.cfg, opts.fallbackCwd, cliBridge);
11369
13021
  channel.on("message", orchestrator.onMessage);
11370
13022
  channel.on("cardAction", orchestrator.dispatcher.handle);
11371
13023
  channel.on("comment", orchestrator.onComment);
@@ -11403,41 +13055,48 @@ async function startBridge(opts) {
11403
13055
  channel.on("reconnecting", () => log.info("ws", "reconnecting"));
11404
13056
  channel.on("reconnected", () => log.info("ws", "reconnected"));
11405
13057
  await channel.connect();
13058
+ if (shouldStartCliBridge(opts.cfg)) {
13059
+ await cliBridge.start().catch((err) => log.fail("cli-bridge", err, { phase: "start" }));
13060
+ }
11406
13061
  log.info("ws", "connected", { appId: app.id, fallbackCwd: opts.fallbackCwd });
11407
13062
  let closed = false;
11408
13063
  const shutdown = async () => {
11409
13064
  if (closed) return;
11410
13065
  closed = true;
11411
13066
  await orchestrator.shutdown();
13067
+ await cliBridge.shutdown().catch((err) => log.fail("cli-bridge", err, { phase: "shutdown" }));
11412
13068
  await channel.disconnect().catch((err) => log.fail("ws", err, { phase: "disconnect" }));
11413
13069
  };
11414
13070
  return { channel, adminExecute: orchestrator.adminExecute, shutdown };
11415
13071
  }
11416
13072
 
13073
+ // src/bot/supervisor.ts
13074
+ init_logger();
13075
+
11417
13076
  // src/admin/ipc.ts
11418
13077
  var ADMIN_IPC_REQ = "fcb.admin.req";
11419
13078
  var ADMIN_IPC_RES = "fcb.admin.res";
11420
13079
  var ADMIN_IPC_TIMEOUT_MS = 15e3;
11421
13080
  function createAdminIpcCaller(send) {
11422
13081
  let nextId = 1;
11423
- const pending = /* @__PURE__ */ new Map();
13082
+ const pending2 = /* @__PURE__ */ new Map();
11424
13083
  function settle(id) {
11425
- const entry = pending.get(id);
13084
+ const entry = pending2.get(id);
11426
13085
  if (!entry) return void 0;
11427
- pending.delete(id);
13086
+ pending2.delete(id);
11428
13087
  clearTimeout(entry.timer);
11429
13088
  return entry;
11430
13089
  }
11431
13090
  return {
11432
13091
  call(op, timeoutMs = ADMIN_IPC_TIMEOUT_MS) {
11433
13092
  const id = nextId++;
11434
- return new Promise((resolve8, reject) => {
13093
+ return new Promise((resolve9, reject) => {
11435
13094
  const timer = setTimeout(() => {
11436
- pending.delete(id);
13095
+ pending2.delete(id);
11437
13096
  reject(new Error(`bot \u8FDB\u7A0B ${timeoutMs}ms \u672A\u54CD\u5E94\uFF08\u53EF\u80FD\u6B63\u5FD9\u6216\u5DF2\u5047\u6B7B\uFF09`));
11438
13097
  }, timeoutMs);
11439
13098
  timer.unref?.();
11440
- pending.set(id, { resolve: resolve8, reject, timer });
13099
+ pending2.set(id, { resolve: resolve9, reject, timer });
11441
13100
  try {
11442
13101
  send({ fcb: ADMIN_IPC_REQ, id, op });
11443
13102
  } catch (err) {
@@ -11458,7 +13117,7 @@ function createAdminIpcCaller(send) {
11458
13117
  entry.reject(msg.code === "ADMIN_WRITE_REJECTED" ? new AdminWriteError(reason) : new Error(reason));
11459
13118
  },
11460
13119
  rejectAll(reason) {
11461
- for (const id of [...pending.keys()]) {
13120
+ for (const id of [...pending2.keys()]) {
11462
13121
  settle(id)?.reject(new Error(reason));
11463
13122
  }
11464
13123
  }
@@ -11493,9 +13152,19 @@ function createAdminIpcResponder(execute, send) {
11493
13152
  }
11494
13153
 
11495
13154
  // src/admin/service.ts
11496
- import { readFile as readFile11, rm as rm6 } from "fs/promises";
13155
+ init_bots();
13156
+ init_paths();
13157
+ import { readFile as readFile12, rm as rm7 } from "fs/promises";
13158
+ init_schema();
13159
+ init_store();
13160
+ init_schema();
11497
13161
 
11498
13162
  // src/bot/register-bot.ts
13163
+ init_store();
13164
+ init_paths();
13165
+ init_schema();
13166
+ init_bots();
13167
+ init_logger();
11499
13168
  var APP_ID_RE = /^cli_[A-Za-z0-9]{6,}$/;
11500
13169
  async function registerBotFromCredentials(input2, validate = validateAppCredentials) {
11501
13170
  const appId = input2.appId?.trim() ?? "";
@@ -11552,9 +13221,13 @@ function withOwnerAdmin(base, ownerOpenId) {
11552
13221
  };
11553
13222
  }
11554
13223
 
13224
+ // src/admin/service.ts
13225
+ init_logger();
13226
+
11555
13227
  // src/admin/host.ts
13228
+ init_paths();
11556
13229
  import { readdir as readdir4, stat as stat4 } from "fs/promises";
11557
- import { dirname as dirname12, join as join18, resolve as resolve7 } from "path";
13230
+ import { dirname as dirname12, join as join20, resolve as resolve8 } from "path";
11558
13231
  import { fileURLToPath as fileURLToPath5 } from "url";
11559
13232
  import { arch, platform as platform2, release } from "os";
11560
13233
  function toDaemonStatus(opts) {
@@ -11583,7 +13256,7 @@ async function dirBytes(dir) {
11583
13256
  return 0;
11584
13257
  }
11585
13258
  for (const name of entries) {
11586
- const full = join18(dir, name);
13259
+ const full = join20(dir, name);
11587
13260
  try {
11588
13261
  const st = await stat4(full);
11589
13262
  if (st.isDirectory()) total += await dirBytes(full);
@@ -11593,7 +13266,7 @@ async function dirBytes(dir) {
11593
13266
  }
11594
13267
  return total;
11595
13268
  }
11596
- async function collectHostDoctor(logsDir2 = join18(paths.appDir, "logs")) {
13269
+ async function collectHostDoctor(logsDir2 = join20(paths.appDir, "logs")) {
11597
13270
  return {
11598
13271
  node: process.version,
11599
13272
  platform: platform2(),
@@ -11607,7 +13280,7 @@ async function collectHostDoctor(logsDir2 = join18(paths.appDir, "logs")) {
11607
13280
  }
11608
13281
  function resolveCliBinPath3() {
11609
13282
  const distDir = dirname12(fileURLToPath5(import.meta.url));
11610
- return resolve7(distDir, "..", "bin", "feishu-codex-bridge.mjs");
13283
+ return resolve8(distDir, "..", "bin", "feishu-codex-bridge.mjs");
11611
13284
  }
11612
13285
  function buildDaemonControlCommand(action, binPath = resolveCliBinPath3(), nodePath = process.execPath) {
11613
13286
  return { command: nodePath, args: [binPath, "__daemon-control", action] };
@@ -11654,7 +13327,7 @@ function createAdminService(deps = {}) {
11654
13327
  }
11655
13328
  async function lockFileRunState(botId) {
11656
13329
  try {
11657
- const raw = await readFile11(botPaths(botId).processesFile, "utf8");
13330
+ const raw = await readFile12(botPaths(botId).processesFile, "utf8");
11658
13331
  const rec = JSON.parse(raw);
11659
13332
  if (typeof rec.pid === "number" && isAlive2(rec.pid)) {
11660
13333
  return { running: true, pid: rec.pid, startedAt: rec.startedAt };
@@ -11970,7 +13643,7 @@ function createAdminService(deps = {}) {
11970
13643
  }
11971
13644
  await removeBot(appId);
11972
13645
  await removeSecret(secretKeyForApp(appId)).catch(() => void 0);
11973
- await rm6(botDir(appId), { recursive: true, force: true }).catch(() => void 0);
13646
+ await rm7(botDir(appId), { recursive: true, force: true }).catch(() => void 0);
11974
13647
  return { ok: true };
11975
13648
  }
11976
13649
  };
@@ -12062,6 +13735,7 @@ function isAlive2(pid) {
12062
13735
  }
12063
13736
 
12064
13737
  // src/cli/commands/daemon-control.ts
13738
+ init_logger();
12065
13739
  function spawnDaemonControl(action) {
12066
13740
  const { command, args } = buildDaemonControlCommand(action);
12067
13741
  const child = spawnProcess(command, args, {
@@ -12122,7 +13796,7 @@ function pidAlive2(pid, kill) {
12122
13796
  return err.code === "EPERM";
12123
13797
  }
12124
13798
  }
12125
- var napDefault = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
13799
+ var napDefault = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
12126
13800
  async function stopSelfHostedDaemon(deps = {}) {
12127
13801
  const readDaemonPid = deps.readDaemonPid ?? (() => readWebConsole()?.pid);
12128
13802
  const kill = deps.kill ?? ((p, s) => void process.kill(p, s));
@@ -12172,12 +13846,16 @@ async function doRestart(phase) {
12172
13846
  log.info("daemon-control", "restart-issued", { phase });
12173
13847
  }
12174
13848
 
13849
+ // src/web/mount.ts
13850
+ init_logger();
13851
+
12175
13852
  // src/web/server.ts
13853
+ init_paths();
12176
13854
  import { createServer } from "http";
12177
- import { randomUUID as randomUUID6, timingSafeEqual } from "crypto";
13855
+ import { randomUUID as randomUUID7, timingSafeEqual } from "crypto";
12178
13856
  import { mkdirSync as mkdirSync3, watch } from "fs";
12179
13857
  import { open as open2, stat as stat5 } from "fs/promises";
12180
- import { join as join19 } from "path";
13858
+ import { join as join21 } from "path";
12181
13859
 
12182
13860
  // src/web/ui.ts
12183
13861
  var UI_PURE_JS = `
@@ -14850,9 +16528,9 @@ var DEFAULT_WEB_PORT = 51847;
14850
16528
  var COOKIE_NAME = "fcb_console_token";
14851
16529
  var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
14852
16530
  function createWebServer(opts) {
14853
- const token = opts.token ?? randomUUID6();
16531
+ const token = opts.token ?? randomUUID7();
14854
16532
  const html = opts.html ?? UI_HTML;
14855
- const logDir = opts.logDir ?? join19(paths.appDir, "logs");
16533
+ const logDir = opts.logDir ?? join21(paths.appDir, "logs");
14856
16534
  const sseCleanups = /* @__PURE__ */ new Set();
14857
16535
  const installJobs = /* @__PURE__ */ new Map();
14858
16536
  let qrSession = null;
@@ -15203,7 +16881,7 @@ function createWebServer(opts) {
15203
16881
  function handleRegisterQrStream(req, res) {
15204
16882
  qrSession?.abort.abort();
15205
16883
  const abort = new AbortController();
15206
- const session = { id: randomUUID6(), abort };
16884
+ const session = { id: randomUUID7(), abort };
15207
16885
  qrSession = session;
15208
16886
  res.writeHead(200, {
15209
16887
  "Content-Type": "text/event-stream",
@@ -15347,7 +17025,7 @@ data: ${JSON.stringify(data)}
15347
17025
  let currentFile = todayLogFile(logDir);
15348
17026
  let offset = 0;
15349
17027
  let reading = false;
15350
- let pending = false;
17028
+ let pending2 = false;
15351
17029
  let closed = false;
15352
17030
  const sendLines = (text) => {
15353
17031
  for (const line of text.split("\n")) {
@@ -15360,7 +17038,7 @@ data: ${JSON.stringify(data)}
15360
17038
  const pump = async () => {
15361
17039
  if (closed) return;
15362
17040
  if (reading) {
15363
- pending = true;
17041
+ pending2 = true;
15364
17042
  return;
15365
17043
  }
15366
17044
  reading = true;
@@ -15385,8 +17063,8 @@ data: ${JSON.stringify(data)}
15385
17063
  }
15386
17064
  } finally {
15387
17065
  reading = false;
15388
- if (pending && !closed) {
15389
- pending = false;
17066
+ if (pending2 && !closed) {
17067
+ pending2 = false;
15390
17068
  void pump();
15391
17069
  }
15392
17070
  }
@@ -15436,19 +17114,19 @@ data: ${JSON.stringify(data)}
15436
17114
  server,
15437
17115
  token,
15438
17116
  listen(port) {
15439
- return new Promise((resolve8, reject) => {
17117
+ return new Promise((resolve9, reject) => {
15440
17118
  server.once("error", reject);
15441
17119
  server.listen(port, "127.0.0.1", () => {
15442
17120
  const addr = server.address();
15443
17121
  const actual = typeof addr === "object" && addr ? addr.port : port;
15444
- resolve8({ port: actual, url: `http://127.0.0.1:${actual}/?token=${token}` });
17122
+ resolve9({ port: actual, url: `http://127.0.0.1:${actual}/?token=${token}` });
15445
17123
  });
15446
17124
  });
15447
17125
  },
15448
17126
  close() {
15449
17127
  for (const cleanup of [...sseCleanups]) cleanup();
15450
- return new Promise((resolve8, reject) => {
15451
- server.close((err) => err ? reject(err) : resolve8());
17128
+ return new Promise((resolve9, reject) => {
17129
+ server.close((err) => err ? reject(err) : resolve9());
15452
17130
  });
15453
17131
  }
15454
17132
  };
@@ -15490,7 +17168,7 @@ function isLoopbackOrigin(origin) {
15490
17168
  }
15491
17169
  }
15492
17170
  function todayLogFile(dir) {
15493
- return join19(dir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.log`);
17171
+ return join21(dir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.log`);
15494
17172
  }
15495
17173
  function clampInt(raw, min, max, fallback) {
15496
17174
  const n = Number(raw);
@@ -15664,7 +17342,7 @@ async function runSupervisor(bots) {
15664
17342
  console.log(`\u{1F310} Web \u63A7\u5236\u53F0\u5DF2\u5185\u5D4C\u542F\u52A8\uFF08127.0.0.1:${webConsole.port}\uFF09\uFF1A\u8FD0\u884C \`feishu-codex-bridge web\` \u83B7\u53D6\u767B\u5F55\u94FE\u63A5\u3002`);
15665
17343
  }
15666
17344
  }
15667
- await new Promise((resolve8) => {
17345
+ await new Promise((resolve9) => {
15668
17346
  const stop = (sig) => {
15669
17347
  if (shuttingDown) return;
15670
17348
  shuttingDown = true;
@@ -15681,7 +17359,7 @@ async function runSupervisor(bots) {
15681
17359
  if (alive.length === 0 || Date.now() >= deadline) {
15682
17360
  clearInterval(poll);
15683
17361
  for (const c of alive) c.proc?.kill("SIGKILL");
15684
- resolve8();
17362
+ resolve9();
15685
17363
  }
15686
17364
  }, 200);
15687
17365
  };
@@ -15691,6 +17369,7 @@ async function runSupervisor(bots) {
15691
17369
  }
15692
17370
 
15693
17371
  // src/core/single-instance.ts
17372
+ init_paths();
15694
17373
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
15695
17374
  import { dirname as dirname13 } from "path";
15696
17375
  var CLAIM_ATTEMPTS = 5;
@@ -15801,6 +17480,8 @@ function acquireSingleInstanceLock(appId, file = paths.processesFile) {
15801
17480
  }
15802
17481
 
15803
17482
  // src/cli/commands/run.ts
17483
+ init_bots();
17484
+ init_logger();
15804
17485
  async function runRun(botName) {
15805
17486
  if (botName) {
15806
17487
  await runSingle(botName);
@@ -15974,6 +17655,7 @@ async function runSingle(botName) {
15974
17655
  }
15975
17656
 
15976
17657
  // src/cli/commands/daemon.ts
17658
+ init_bots();
15977
17659
  async function runStart() {
15978
17660
  const active = activeBots(await loadBots());
15979
17661
  if (active.length === 0) {
@@ -16118,7 +17800,10 @@ async function runUpdate(opts = {}) {
16118
17800
  }
16119
17801
 
16120
17802
  // src/cli/commands/bot.ts
16121
- import { rm as rm7 } from "fs/promises";
17803
+ import { rm as rm8 } from "fs/promises";
17804
+ init_bots();
17805
+ init_schema();
17806
+ init_paths();
16122
17807
 
16123
17808
  // src/cli/checkbox.ts
16124
17809
  import { emitKeypressEvents } from "readline";
@@ -16164,7 +17849,7 @@ async function checkboxSelect(title, items) {
16164
17849
  output.write(`${ALT_SCREEN_ON}${HIDE_CURSOR}`);
16165
17850
  process.once("exit", restore);
16166
17851
  redraw();
16167
- return await new Promise((resolve8) => {
17852
+ return await new Promise((resolve9) => {
16168
17853
  const cleanup = () => {
16169
17854
  input2.off("keypress", onKey);
16170
17855
  input2.setRawMode?.(wasRaw);
@@ -16176,12 +17861,12 @@ async function checkboxSelect(title, items) {
16176
17861
  const name = key?.name;
16177
17862
  if (key?.ctrl && name === "c" || name === "escape" || name === "q") {
16178
17863
  cleanup();
16179
- resolve8(null);
17864
+ resolve9(null);
16180
17865
  return;
16181
17866
  }
16182
17867
  if (name === "return" || name === "enter") {
16183
17868
  cleanup();
16184
- resolve8(checked.flatMap((on, i) => on ? [i] : []));
17869
+ resolve9(checked.flatMap((on, i) => on ? [i] : []));
16185
17870
  return;
16186
17871
  }
16187
17872
  if (name === "up" || name === "k") {
@@ -16301,7 +17986,7 @@ async function runBotRm(name) {
16301
17986
  }
16302
17987
  const after = await removeBot(bot2.appId);
16303
17988
  await removeSecret(secretKeyForApp(bot2.appId));
16304
- await rm7(botDir(bot2.appId), { recursive: true, force: true });
17989
+ await rm8(botDir(bot2.appId), { recursive: true, force: true });
16305
17990
  console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
16306
17991
  if (after.bots.length === 0) {
16307
17992
  console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
@@ -16369,6 +18054,7 @@ async function runWeb(opts = {}) {
16369
18054
  }
16370
18055
 
16371
18056
  // src/cli/commands/secrets.ts
18057
+ init_stdin();
16372
18058
  async function secretsGet() {
16373
18059
  const input2 = await readStdin();
16374
18060
  let ids = [];
@@ -16410,18 +18096,6 @@ async function secretsRemove(id) {
16410
18096
  const ok = await removeSecret(id);
16411
18097
  console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
16412
18098
  }
16413
- function readStdin() {
16414
- return new Promise((resolve8) => {
16415
- let data = "";
16416
- if (process.stdin.isTTY) {
16417
- resolve8("");
16418
- return;
16419
- }
16420
- process.stdin.setEncoding("utf8");
16421
- process.stdin.on("data", (c) => data += c);
16422
- process.stdin.on("end", () => resolve8(data));
16423
- });
16424
- }
16425
18099
 
16426
18100
  // src/cli/index.ts
16427
18101
  var program = new Command();
@@ -16482,6 +18156,10 @@ secrets.command("list").description("\u5217\u51FA\u5BC6\u94A5 id").action(async
16482
18156
  secrets.command("remove <id>").description("\u5220\u9664\u5BC6\u94A5").action(async (id) => {
16483
18157
  await secretsRemove(id);
16484
18158
  });
18159
+ program.command("hook", { hidden: true }).requiredOption("--agent <agent>", "claude or codex").option("--bot <nameOrAppId>", "target bot name or appId").action(async (options) => {
18160
+ const { runHookCommand: runHookCommand2 } = await Promise.resolve().then(() => (init_cli_bridge(), cli_bridge_exports));
18161
+ await runHookCommand2(options.agent, options.bot);
18162
+ });
16485
18163
  program.parseAsync(process.argv).catch((err) => {
16486
18164
  console.error(err instanceof Error ? err.message : String(err));
16487
18165
  process.exit(1);