@modelzen/feishu-codex-bridge 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2500 -871
- package/dist/index.d.ts +9 -0
- package/dist/index.js +16 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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((
|
|
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,
|
|
2293
|
+
let stdout = "", stderr = "", truncated = false, settled2 = false;
|
|
536
2294
|
const timer = setTimeout(() => {
|
|
537
|
-
if (
|
|
538
|
-
|
|
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 (
|
|
556
|
-
|
|
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 (
|
|
562
|
-
|
|
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
|
|
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) {
|
|
@@ -822,243 +2580,32 @@ var BACKEND_CATALOG = [
|
|
|
822
2580
|
installCmd: "\u968F\u6865\u5DF2\u5185\u7F6E\uFF08\u5982\u7F3A\u5931\uFF1Anpm i @anthropic-ai/claude-agent-sdk\uFF09"
|
|
823
2581
|
},
|
|
824
2582
|
// 必须与 ClaudeAgentBackend.supportedModes 完全一致(单测强制)。
|
|
825
|
-
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
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
function withTrace(ctx, fn) {
|
|
1024
|
-
const traceId = ctx.traceId ?? newTraceId();
|
|
1025
|
-
return als.run({ ...ctx, traceId }, fn);
|
|
2583
|
+
supportedModes: ["qa", "write", "full"],
|
|
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"
|
|
2585
|
+
}
|
|
2586
|
+
];
|
|
2587
|
+
function visibleCatalog() {
|
|
2588
|
+
return BACKEND_CATALOG.filter((e) => !e.hidden);
|
|
1026
2589
|
}
|
|
1027
|
-
function
|
|
1028
|
-
return
|
|
2590
|
+
function catalogById(id) {
|
|
2591
|
+
return BACKEND_CATALOG.find((e) => e.id === id);
|
|
1029
2592
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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((
|
|
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((
|
|
1175
|
-
this.pending.set(id, { resolve:
|
|
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((
|
|
1207
|
-
let
|
|
2754
|
+
await new Promise((resolve9) => {
|
|
2755
|
+
let settled2 = false;
|
|
1208
2756
|
const done = () => {
|
|
1209
|
-
if (
|
|
1210
|
-
|
|
2757
|
+
if (settled2) return;
|
|
2758
|
+
settled2 = true;
|
|
1211
2759
|
clearTimeout(t);
|
|
1212
|
-
|
|
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((
|
|
2775
|
+
await new Promise((resolve9) => {
|
|
1228
2776
|
const t = setTimeout(() => {
|
|
1229
2777
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1230
|
-
|
|
2778
|
+
resolve9();
|
|
1231
2779
|
}, graceMs);
|
|
1232
2780
|
child.once("exit", () => {
|
|
1233
2781
|
clearTimeout(t);
|
|
1234
|
-
|
|
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((
|
|
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
|
-
|
|
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", () =>
|
|
1350
|
-
child.on("close", (code) =>
|
|
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((
|
|
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
|
-
|
|
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
|
|
1620
|
-
const root = cwd.endsWith(
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
3391
|
+
resolve9("start-failed");
|
|
1842
3392
|
});
|
|
1843
3393
|
});
|
|
1844
3394
|
let timer;
|
|
1845
|
-
const timeout = new Promise((
|
|
1846
|
-
timer = setTimeout(() =>
|
|
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
|
|
2378
|
-
const root = cwd.endsWith(
|
|
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((
|
|
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((
|
|
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((
|
|
2828
|
-
timer = setTimeout(() =>
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -3805,232 +5373,49 @@ async function getProjectByChatId(chatId) {
|
|
|
3805
5373
|
async function getProjectByName(name) {
|
|
3806
5374
|
return (await read()).find((p) => p.name === name);
|
|
3807
5375
|
}
|
|
3808
|
-
async function addProject(p) {
|
|
3809
|
-
return withLock(async () => {
|
|
3810
|
-
const projects = await read();
|
|
3811
|
-
if (projects.some((x) => x.name === p.name)) {
|
|
3812
|
-
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
3813
|
-
}
|
|
3814
|
-
if (p.chatId) {
|
|
3815
|
-
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
3816
|
-
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
3817
|
-
}
|
|
3818
|
-
projects.push(p);
|
|
3819
|
-
await write(projects);
|
|
3820
|
-
});
|
|
3821
|
-
}
|
|
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
|
-
};
|
|
5376
|
+
async function addProject(p) {
|
|
5377
|
+
return withLock(async () => {
|
|
5378
|
+
const projects = await read();
|
|
5379
|
+
if (projects.some((x) => x.name === p.name)) {
|
|
5380
|
+
throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
|
|
5381
|
+
}
|
|
5382
|
+
if (p.chatId) {
|
|
5383
|
+
const bound = projects.find((x) => x.chatId === p.chatId);
|
|
5384
|
+
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
5385
|
+
}
|
|
5386
|
+
projects.push(p);
|
|
5387
|
+
await write(projects);
|
|
5388
|
+
});
|
|
4022
5389
|
}
|
|
4023
|
-
function
|
|
4024
|
-
return {
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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((
|
|
5093
|
-
timer = setTimeout(() =>
|
|
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/
|
|
5240
|
-
|
|
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
|
|
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
|
|
6621
|
-
import { homedir as
|
|
6622
|
-
import { dirname as dirname7, join as
|
|
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
|
|
6628
|
-
import { dirname as dirname6, join as
|
|
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
|
|
8119
|
+
return join11(paths.appDir, "service.log");
|
|
6632
8120
|
}
|
|
6633
8121
|
function serviceStderrPath() {
|
|
6634
|
-
return
|
|
8122
|
+
return join11(paths.appDir, "service.err.log");
|
|
6635
8123
|
}
|
|
6636
8124
|
async function ensureLogFiles() {
|
|
6637
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8191
|
+
return resolve5(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
6704
8192
|
}
|
|
6705
8193
|
function escapeXml(value) {
|
|
6706
8194
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -6740,9 +8228,9 @@ function buildPlist() {
|
|
|
6740
8228
|
}
|
|
6741
8229
|
async function installLaunchd() {
|
|
6742
8230
|
const plistPath = launchAgentPlistPath();
|
|
6743
|
-
await
|
|
8231
|
+
await mkdir8(dirname7(plistPath), { recursive: true });
|
|
6744
8232
|
await ensureLogFiles();
|
|
6745
|
-
await
|
|
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
|
|
6851
|
-
import { join as
|
|
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
|
|
8345
|
+
return join13(paths.appDir, "service-launcher.cmd");
|
|
6857
8346
|
}
|
|
6858
8347
|
function launcherVbsPath() {
|
|
6859
|
-
return
|
|
8348
|
+
return join13(paths.appDir, "service-launcher.vbs");
|
|
6860
8349
|
}
|
|
6861
8350
|
function servicePidFile() {
|
|
6862
|
-
return
|
|
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
|
|
8383
|
+
await mkdir9(paths.appDir, { recursive: true });
|
|
6895
8384
|
await ensureLogFiles();
|
|
6896
|
-
await
|
|
6897
|
-
await
|
|
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
|
|
6989
|
-
import { homedir as
|
|
6990
|
-
import { dirname as dirname8, join as
|
|
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 ??
|
|
6994
|
-
return
|
|
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
|
|
8537
|
+
await mkdir10(dirname8(unitPath), { recursive: true });
|
|
7049
8538
|
await ensureLogFiles();
|
|
7050
|
-
await
|
|
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
|
|
8639
|
+
return resolve6(dirname9(fileURLToPath4(import.meta.url)), "..");
|
|
7151
8640
|
}
|
|
7152
8641
|
function pkgJson() {
|
|
7153
8642
|
try {
|
|
7154
|
-
return JSON.parse(readFileSync4(
|
|
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(
|
|
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
|
-
|
|
7231
|
-
import {
|
|
7232
|
-
import {
|
|
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 ??
|
|
8730
|
+
return process.env.CODEX_HOME ?? join16(homedir6(), ".codex");
|
|
7241
8731
|
}
|
|
7242
8732
|
async function readCodexAuth() {
|
|
7243
|
-
const file =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 :
|
|
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 =
|
|
8066
|
-
await
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
8240
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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;
|
|
@@ -8669,6 +10178,7 @@ function weaveSender(text, sender) {
|
|
|
8669
10178
|
}
|
|
8670
10179
|
|
|
8671
10180
|
// src/bot/comments.ts
|
|
10181
|
+
init_logger();
|
|
8672
10182
|
var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "file"]);
|
|
8673
10183
|
var REPLY_MAX_CHARS = 2e3;
|
|
8674
10184
|
function apiErrCode(err) {
|
|
@@ -8940,9 +10450,9 @@ var Semaphore = class {
|
|
|
8940
10450
|
const acquired = new Promise((res) => {
|
|
8941
10451
|
settle = res;
|
|
8942
10452
|
});
|
|
8943
|
-
let
|
|
10453
|
+
let settled2 = false;
|
|
8944
10454
|
const grant = () => {
|
|
8945
|
-
|
|
10455
|
+
settled2 = true;
|
|
8946
10456
|
this.active++;
|
|
8947
10457
|
let released = false;
|
|
8948
10458
|
settle(() => {
|
|
@@ -8966,11 +10476,11 @@ var Semaphore = class {
|
|
|
8966
10476
|
return i >= 0 ? i + 1 : 0;
|
|
8967
10477
|
},
|
|
8968
10478
|
cancel: () => {
|
|
8969
|
-
if (
|
|
10479
|
+
if (settled2) return false;
|
|
8970
10480
|
const i = this.waiters.indexOf(entry);
|
|
8971
10481
|
if (i < 0) return false;
|
|
8972
10482
|
this.waiters.splice(i, 1);
|
|
8973
|
-
|
|
10483
|
+
settled2 = true;
|
|
8974
10484
|
settle(null);
|
|
8975
10485
|
this.notifyAdvance(i);
|
|
8976
10486
|
return true;
|
|
@@ -9121,7 +10631,7 @@ function pickIdleSessions(keys, touchedAt, isBusy, idleMs, now) {
|
|
|
9121
10631
|
}
|
|
9122
10632
|
return out;
|
|
9123
10633
|
}
|
|
9124
|
-
function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
10634
|
+
function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
9125
10635
|
const backends = /* @__PURE__ */ new Map();
|
|
9126
10636
|
function backendFor(id) {
|
|
9127
10637
|
const key = id && catalogById(id) ? id : DEFAULT_BACKEND_ID;
|
|
@@ -9230,6 +10740,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
9230
10740
|
preview: msg.content.slice(0, 40)
|
|
9231
10741
|
});
|
|
9232
10742
|
if (msg.chatType === "p2p") {
|
|
10743
|
+
if (cliBridge?.onMessage({
|
|
10744
|
+
parentId: msg.replyToMessageId,
|
|
10745
|
+
rootId: msg.threadId,
|
|
10746
|
+
text: msg.content,
|
|
10747
|
+
messageId: msg.messageId
|
|
10748
|
+
})) {
|
|
10749
|
+
return;
|
|
10750
|
+
}
|
|
9233
10751
|
await handleDmConsole(channel, cfg, msg);
|
|
9234
10752
|
return;
|
|
9235
10753
|
}
|
|
@@ -9828,6 +11346,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
9828
11346
|
});
|
|
9829
11347
|
}
|
|
9830
11348
|
const dispatcher = new CardDispatcher(channel, cfg);
|
|
11349
|
+
cliBridge?.register(dispatcher);
|
|
9831
11350
|
const PENDING_TTL_MS = 30 * 6e4;
|
|
9832
11351
|
const GOAL_IDLE_MS = 30 * 6e4;
|
|
9833
11352
|
const CARD_SETTLE_MS = 500;
|
|
@@ -9985,13 +11504,27 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
9985
11504
|
if (op?.openId && op.name && !m.has(op.openId)) m.set(op.openId, op.name);
|
|
9986
11505
|
return m;
|
|
9987
11506
|
};
|
|
9988
|
-
function applyPref(evt, mut) {
|
|
11507
|
+
function applyPref(evt, mut, opts) {
|
|
9989
11508
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
9990
11509
|
const prefs = { ...cfg.preferences ?? {} };
|
|
9991
11510
|
mut(prefs);
|
|
9992
11511
|
cfg.preferences = prefs;
|
|
9993
11512
|
void saveConfig(cfg).catch((err) => log.fail("console", err, { phase: "save-config" }));
|
|
9994
|
-
patch(evt,
|
|
11513
|
+
if (opts?.render !== false) void patch(evt, renderSettings);
|
|
11514
|
+
}
|
|
11515
|
+
let cliHookStatuses;
|
|
11516
|
+
async function renderSettings(refreshHooks = false) {
|
|
11517
|
+
if (refreshHooks || !cliHookStatuses) cliHookStatuses = await inspectCliBridgeHooks();
|
|
11518
|
+
const cliPrefs = getCliBridgePreferences(cfg);
|
|
11519
|
+
const localAgents = cliBridgeSettingsSection({
|
|
11520
|
+
enabled: cliPrefs.enabled,
|
|
11521
|
+
statuses: cliHookStatuses,
|
|
11522
|
+
canEnable: canEnableCliBridge(cfg),
|
|
11523
|
+
notifyScope: cliPrefs.notifyScope,
|
|
11524
|
+
agents: cliPrefs.agents,
|
|
11525
|
+
keepAwake: cliPrefs.keepAwake.enabled
|
|
11526
|
+
});
|
|
11527
|
+
return buildSettingsCard(cfg, localAgents);
|
|
9995
11528
|
}
|
|
9996
11529
|
const freshMenu = (evt) => {
|
|
9997
11530
|
patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
|
|
@@ -10136,7 +11669,59 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
10136
11669
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
10137
11670
|
patch(evt, renderProjectList);
|
|
10138
11671
|
}).on(DM.settings, async ({ evt }) => {
|
|
10139
|
-
if (dmAdmin(evt.operator?.openId)) await patch(evt,
|
|
11672
|
+
if (dmAdmin(evt.operator?.openId)) await patch(evt, () => renderSettings(true));
|
|
11673
|
+
}).on(CLI.toggleEnabled, async ({ evt, value }) => {
|
|
11674
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
11675
|
+
const enabled = value.v === "on";
|
|
11676
|
+
if (enabled && !canEnableCliBridge(cfg).ok) return;
|
|
11677
|
+
try {
|
|
11678
|
+
if (enabled) {
|
|
11679
|
+
await cliBridge?.start?.();
|
|
11680
|
+
applyPref(evt, (p) => {
|
|
11681
|
+
p.cliBridge = { ...p.cliBridge ?? {}, enabled: true };
|
|
11682
|
+
}, { render: false });
|
|
11683
|
+
} else {
|
|
11684
|
+
applyPref(evt, (p) => {
|
|
11685
|
+
p.cliBridge = { ...p.cliBridge ?? {}, enabled: false };
|
|
11686
|
+
}, { render: false });
|
|
11687
|
+
await cliBridge?.shutdown?.();
|
|
11688
|
+
}
|
|
11689
|
+
} catch (err) {
|
|
11690
|
+
log.fail("cli-bridge", err, { phase: enabled ? "enable" : "disable" });
|
|
11691
|
+
}
|
|
11692
|
+
void patch(evt, renderSettings);
|
|
11693
|
+
}).on(CLI.repairHooks, async ({ evt }) => {
|
|
11694
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
11695
|
+
try {
|
|
11696
|
+
await installCliBridgeHooks({
|
|
11697
|
+
agents: { claude: true, codex: true },
|
|
11698
|
+
command: resolveBridgeHookCommand(cfg.accounts.app.id)
|
|
11699
|
+
});
|
|
11700
|
+
} catch (err) {
|
|
11701
|
+
log.fail("cli-bridge", err, { phase: "repair-hooks" });
|
|
11702
|
+
}
|
|
11703
|
+
await patch(evt, () => renderSettings(true));
|
|
11704
|
+
}).on(CLI.setNotifyScope, ({ evt, value }) => {
|
|
11705
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
11706
|
+
const scope = value.v === "bound_projects" || value.v === "none" ? value.v : "all";
|
|
11707
|
+
applyPref(evt, (p) => {
|
|
11708
|
+
p.cliBridge = { ...p.cliBridge ?? {}, notifyScope: scope };
|
|
11709
|
+
});
|
|
11710
|
+
}).on(CLI.toggleAgent, ({ evt, value }) => {
|
|
11711
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
11712
|
+
const agent = value.agent === "codex" ? "codex" : "claude";
|
|
11713
|
+
const on = value.v === "on";
|
|
11714
|
+
applyPref(evt, (p) => {
|
|
11715
|
+
const cur = p.cliBridge ?? {};
|
|
11716
|
+
p.cliBridge = { ...cur, agents: { ...cur.agents ?? {}, [agent]: on } };
|
|
11717
|
+
});
|
|
11718
|
+
}).on(CLI.toggleKeepAwake, ({ evt, value }) => {
|
|
11719
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
11720
|
+
const on = value.v === "on";
|
|
11721
|
+
applyPref(evt, (p) => {
|
|
11722
|
+
const cur = p.cliBridge ?? {};
|
|
11723
|
+
p.cliBridge = { ...cur, keepAwake: { ...cur.keepAwake ?? {}, enabled: on } };
|
|
11724
|
+
});
|
|
10140
11725
|
}).on(DM.doctor, async ({ evt }) => {
|
|
10141
11726
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
10142
11727
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
|
|
@@ -11340,6 +12925,18 @@ async function getThreadId(channel, messageId, attempts = 1) {
|
|
|
11340
12925
|
}
|
|
11341
12926
|
|
|
11342
12927
|
// src/bot/bridge.ts
|
|
12928
|
+
init_paths();
|
|
12929
|
+
init_cli_bridge();
|
|
12930
|
+
async function cwdIsBoundProject(cwd) {
|
|
12931
|
+
if (!cwd) return false;
|
|
12932
|
+
const strip = (p) => p.replace(/[/\\]+$/, "");
|
|
12933
|
+
const target = strip(cwd);
|
|
12934
|
+
const projects = await listProjects().catch(() => []);
|
|
12935
|
+
return projects.some((project) => {
|
|
12936
|
+
const root = strip(project.cwd);
|
|
12937
|
+
return root.length > 0 && (target === root || target.startsWith(root + sep2));
|
|
12938
|
+
});
|
|
12939
|
+
}
|
|
11343
12940
|
async function startBridge(opts) {
|
|
11344
12941
|
const app = opts.cfg.accounts.app;
|
|
11345
12942
|
const channel = createLarkChannel({
|
|
@@ -11365,7 +12962,13 @@ async function startBridge(opts) {
|
|
|
11365
12962
|
// sends are already prevented by startReservedRun's synchronous booking.
|
|
11366
12963
|
safety: { batch: { text: { delayMs: 0 } } }
|
|
11367
12964
|
});
|
|
11368
|
-
const
|
|
12965
|
+
const cliBridge = createCliBridgeService({
|
|
12966
|
+
cfg: opts.cfg,
|
|
12967
|
+
channel,
|
|
12968
|
+
socketPath: paths.cliBridgeSocket,
|
|
12969
|
+
isBoundProject: cwdIsBoundProject
|
|
12970
|
+
});
|
|
12971
|
+
const orchestrator = createOrchestrator(channel, opts.cfg, opts.fallbackCwd, cliBridge);
|
|
11369
12972
|
channel.on("message", orchestrator.onMessage);
|
|
11370
12973
|
channel.on("cardAction", orchestrator.dispatcher.handle);
|
|
11371
12974
|
channel.on("comment", orchestrator.onComment);
|
|
@@ -11403,41 +13006,48 @@ async function startBridge(opts) {
|
|
|
11403
13006
|
channel.on("reconnecting", () => log.info("ws", "reconnecting"));
|
|
11404
13007
|
channel.on("reconnected", () => log.info("ws", "reconnected"));
|
|
11405
13008
|
await channel.connect();
|
|
13009
|
+
if (shouldStartCliBridge(opts.cfg)) {
|
|
13010
|
+
await cliBridge.start().catch((err) => log.fail("cli-bridge", err, { phase: "start" }));
|
|
13011
|
+
}
|
|
11406
13012
|
log.info("ws", "connected", { appId: app.id, fallbackCwd: opts.fallbackCwd });
|
|
11407
13013
|
let closed = false;
|
|
11408
13014
|
const shutdown = async () => {
|
|
11409
13015
|
if (closed) return;
|
|
11410
13016
|
closed = true;
|
|
11411
13017
|
await orchestrator.shutdown();
|
|
13018
|
+
await cliBridge.shutdown().catch((err) => log.fail("cli-bridge", err, { phase: "shutdown" }));
|
|
11412
13019
|
await channel.disconnect().catch((err) => log.fail("ws", err, { phase: "disconnect" }));
|
|
11413
13020
|
};
|
|
11414
13021
|
return { channel, adminExecute: orchestrator.adminExecute, shutdown };
|
|
11415
13022
|
}
|
|
11416
13023
|
|
|
13024
|
+
// src/bot/supervisor.ts
|
|
13025
|
+
init_logger();
|
|
13026
|
+
|
|
11417
13027
|
// src/admin/ipc.ts
|
|
11418
13028
|
var ADMIN_IPC_REQ = "fcb.admin.req";
|
|
11419
13029
|
var ADMIN_IPC_RES = "fcb.admin.res";
|
|
11420
13030
|
var ADMIN_IPC_TIMEOUT_MS = 15e3;
|
|
11421
13031
|
function createAdminIpcCaller(send) {
|
|
11422
13032
|
let nextId = 1;
|
|
11423
|
-
const
|
|
13033
|
+
const pending2 = /* @__PURE__ */ new Map();
|
|
11424
13034
|
function settle(id) {
|
|
11425
|
-
const entry =
|
|
13035
|
+
const entry = pending2.get(id);
|
|
11426
13036
|
if (!entry) return void 0;
|
|
11427
|
-
|
|
13037
|
+
pending2.delete(id);
|
|
11428
13038
|
clearTimeout(entry.timer);
|
|
11429
13039
|
return entry;
|
|
11430
13040
|
}
|
|
11431
13041
|
return {
|
|
11432
13042
|
call(op, timeoutMs = ADMIN_IPC_TIMEOUT_MS) {
|
|
11433
13043
|
const id = nextId++;
|
|
11434
|
-
return new Promise((
|
|
13044
|
+
return new Promise((resolve9, reject) => {
|
|
11435
13045
|
const timer = setTimeout(() => {
|
|
11436
|
-
|
|
13046
|
+
pending2.delete(id);
|
|
11437
13047
|
reject(new Error(`bot \u8FDB\u7A0B ${timeoutMs}ms \u672A\u54CD\u5E94\uFF08\u53EF\u80FD\u6B63\u5FD9\u6216\u5DF2\u5047\u6B7B\uFF09`));
|
|
11438
13048
|
}, timeoutMs);
|
|
11439
13049
|
timer.unref?.();
|
|
11440
|
-
|
|
13050
|
+
pending2.set(id, { resolve: resolve9, reject, timer });
|
|
11441
13051
|
try {
|
|
11442
13052
|
send({ fcb: ADMIN_IPC_REQ, id, op });
|
|
11443
13053
|
} catch (err) {
|
|
@@ -11458,7 +13068,7 @@ function createAdminIpcCaller(send) {
|
|
|
11458
13068
|
entry.reject(msg.code === "ADMIN_WRITE_REJECTED" ? new AdminWriteError(reason) : new Error(reason));
|
|
11459
13069
|
},
|
|
11460
13070
|
rejectAll(reason) {
|
|
11461
|
-
for (const id of [...
|
|
13071
|
+
for (const id of [...pending2.keys()]) {
|
|
11462
13072
|
settle(id)?.reject(new Error(reason));
|
|
11463
13073
|
}
|
|
11464
13074
|
}
|
|
@@ -11493,9 +13103,19 @@ function createAdminIpcResponder(execute, send) {
|
|
|
11493
13103
|
}
|
|
11494
13104
|
|
|
11495
13105
|
// src/admin/service.ts
|
|
11496
|
-
|
|
13106
|
+
init_bots();
|
|
13107
|
+
init_paths();
|
|
13108
|
+
import { readFile as readFile12, rm as rm7 } from "fs/promises";
|
|
13109
|
+
init_schema();
|
|
13110
|
+
init_store();
|
|
13111
|
+
init_schema();
|
|
11497
13112
|
|
|
11498
13113
|
// src/bot/register-bot.ts
|
|
13114
|
+
init_store();
|
|
13115
|
+
init_paths();
|
|
13116
|
+
init_schema();
|
|
13117
|
+
init_bots();
|
|
13118
|
+
init_logger();
|
|
11499
13119
|
var APP_ID_RE = /^cli_[A-Za-z0-9]{6,}$/;
|
|
11500
13120
|
async function registerBotFromCredentials(input2, validate = validateAppCredentials) {
|
|
11501
13121
|
const appId = input2.appId?.trim() ?? "";
|
|
@@ -11552,9 +13172,13 @@ function withOwnerAdmin(base, ownerOpenId) {
|
|
|
11552
13172
|
};
|
|
11553
13173
|
}
|
|
11554
13174
|
|
|
13175
|
+
// src/admin/service.ts
|
|
13176
|
+
init_logger();
|
|
13177
|
+
|
|
11555
13178
|
// src/admin/host.ts
|
|
13179
|
+
init_paths();
|
|
11556
13180
|
import { readdir as readdir4, stat as stat4 } from "fs/promises";
|
|
11557
|
-
import { dirname as dirname12, join as
|
|
13181
|
+
import { dirname as dirname12, join as join20, resolve as resolve8 } from "path";
|
|
11558
13182
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11559
13183
|
import { arch, platform as platform2, release } from "os";
|
|
11560
13184
|
function toDaemonStatus(opts) {
|
|
@@ -11583,7 +13207,7 @@ async function dirBytes(dir) {
|
|
|
11583
13207
|
return 0;
|
|
11584
13208
|
}
|
|
11585
13209
|
for (const name of entries) {
|
|
11586
|
-
const full =
|
|
13210
|
+
const full = join20(dir, name);
|
|
11587
13211
|
try {
|
|
11588
13212
|
const st = await stat4(full);
|
|
11589
13213
|
if (st.isDirectory()) total += await dirBytes(full);
|
|
@@ -11593,7 +13217,7 @@ async function dirBytes(dir) {
|
|
|
11593
13217
|
}
|
|
11594
13218
|
return total;
|
|
11595
13219
|
}
|
|
11596
|
-
async function collectHostDoctor(logsDir2 =
|
|
13220
|
+
async function collectHostDoctor(logsDir2 = join20(paths.appDir, "logs")) {
|
|
11597
13221
|
return {
|
|
11598
13222
|
node: process.version,
|
|
11599
13223
|
platform: platform2(),
|
|
@@ -11607,7 +13231,7 @@ async function collectHostDoctor(logsDir2 = join18(paths.appDir, "logs")) {
|
|
|
11607
13231
|
}
|
|
11608
13232
|
function resolveCliBinPath3() {
|
|
11609
13233
|
const distDir = dirname12(fileURLToPath5(import.meta.url));
|
|
11610
|
-
return
|
|
13234
|
+
return resolve8(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
11611
13235
|
}
|
|
11612
13236
|
function buildDaemonControlCommand(action, binPath = resolveCliBinPath3(), nodePath = process.execPath) {
|
|
11613
13237
|
return { command: nodePath, args: [binPath, "__daemon-control", action] };
|
|
@@ -11654,7 +13278,7 @@ function createAdminService(deps = {}) {
|
|
|
11654
13278
|
}
|
|
11655
13279
|
async function lockFileRunState(botId) {
|
|
11656
13280
|
try {
|
|
11657
|
-
const raw = await
|
|
13281
|
+
const raw = await readFile12(botPaths(botId).processesFile, "utf8");
|
|
11658
13282
|
const rec = JSON.parse(raw);
|
|
11659
13283
|
if (typeof rec.pid === "number" && isAlive2(rec.pid)) {
|
|
11660
13284
|
return { running: true, pid: rec.pid, startedAt: rec.startedAt };
|
|
@@ -11970,7 +13594,7 @@ function createAdminService(deps = {}) {
|
|
|
11970
13594
|
}
|
|
11971
13595
|
await removeBot(appId);
|
|
11972
13596
|
await removeSecret(secretKeyForApp(appId)).catch(() => void 0);
|
|
11973
|
-
await
|
|
13597
|
+
await rm7(botDir(appId), { recursive: true, force: true }).catch(() => void 0);
|
|
11974
13598
|
return { ok: true };
|
|
11975
13599
|
}
|
|
11976
13600
|
};
|
|
@@ -12062,6 +13686,7 @@ function isAlive2(pid) {
|
|
|
12062
13686
|
}
|
|
12063
13687
|
|
|
12064
13688
|
// src/cli/commands/daemon-control.ts
|
|
13689
|
+
init_logger();
|
|
12065
13690
|
function spawnDaemonControl(action) {
|
|
12066
13691
|
const { command, args } = buildDaemonControlCommand(action);
|
|
12067
13692
|
const child = spawnProcess(command, args, {
|
|
@@ -12122,7 +13747,7 @@ function pidAlive2(pid, kill) {
|
|
|
12122
13747
|
return err.code === "EPERM";
|
|
12123
13748
|
}
|
|
12124
13749
|
}
|
|
12125
|
-
var napDefault = (ms) => new Promise((
|
|
13750
|
+
var napDefault = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
12126
13751
|
async function stopSelfHostedDaemon(deps = {}) {
|
|
12127
13752
|
const readDaemonPid = deps.readDaemonPid ?? (() => readWebConsole()?.pid);
|
|
12128
13753
|
const kill = deps.kill ?? ((p, s) => void process.kill(p, s));
|
|
@@ -12172,12 +13797,16 @@ async function doRestart(phase) {
|
|
|
12172
13797
|
log.info("daemon-control", "restart-issued", { phase });
|
|
12173
13798
|
}
|
|
12174
13799
|
|
|
13800
|
+
// src/web/mount.ts
|
|
13801
|
+
init_logger();
|
|
13802
|
+
|
|
12175
13803
|
// src/web/server.ts
|
|
13804
|
+
init_paths();
|
|
12176
13805
|
import { createServer } from "http";
|
|
12177
|
-
import { randomUUID as
|
|
13806
|
+
import { randomUUID as randomUUID7, timingSafeEqual } from "crypto";
|
|
12178
13807
|
import { mkdirSync as mkdirSync3, watch } from "fs";
|
|
12179
13808
|
import { open as open2, stat as stat5 } from "fs/promises";
|
|
12180
|
-
import { join as
|
|
13809
|
+
import { join as join21 } from "path";
|
|
12181
13810
|
|
|
12182
13811
|
// src/web/ui.ts
|
|
12183
13812
|
var UI_PURE_JS = `
|
|
@@ -14850,9 +16479,9 @@ var DEFAULT_WEB_PORT = 51847;
|
|
|
14850
16479
|
var COOKIE_NAME = "fcb_console_token";
|
|
14851
16480
|
var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
|
|
14852
16481
|
function createWebServer(opts) {
|
|
14853
|
-
const token = opts.token ??
|
|
16482
|
+
const token = opts.token ?? randomUUID7();
|
|
14854
16483
|
const html = opts.html ?? UI_HTML;
|
|
14855
|
-
const logDir = opts.logDir ??
|
|
16484
|
+
const logDir = opts.logDir ?? join21(paths.appDir, "logs");
|
|
14856
16485
|
const sseCleanups = /* @__PURE__ */ new Set();
|
|
14857
16486
|
const installJobs = /* @__PURE__ */ new Map();
|
|
14858
16487
|
let qrSession = null;
|
|
@@ -15203,7 +16832,7 @@ function createWebServer(opts) {
|
|
|
15203
16832
|
function handleRegisterQrStream(req, res) {
|
|
15204
16833
|
qrSession?.abort.abort();
|
|
15205
16834
|
const abort = new AbortController();
|
|
15206
|
-
const session = { id:
|
|
16835
|
+
const session = { id: randomUUID7(), abort };
|
|
15207
16836
|
qrSession = session;
|
|
15208
16837
|
res.writeHead(200, {
|
|
15209
16838
|
"Content-Type": "text/event-stream",
|
|
@@ -15347,7 +16976,7 @@ data: ${JSON.stringify(data)}
|
|
|
15347
16976
|
let currentFile = todayLogFile(logDir);
|
|
15348
16977
|
let offset = 0;
|
|
15349
16978
|
let reading = false;
|
|
15350
|
-
let
|
|
16979
|
+
let pending2 = false;
|
|
15351
16980
|
let closed = false;
|
|
15352
16981
|
const sendLines = (text) => {
|
|
15353
16982
|
for (const line of text.split("\n")) {
|
|
@@ -15360,7 +16989,7 @@ data: ${JSON.stringify(data)}
|
|
|
15360
16989
|
const pump = async () => {
|
|
15361
16990
|
if (closed) return;
|
|
15362
16991
|
if (reading) {
|
|
15363
|
-
|
|
16992
|
+
pending2 = true;
|
|
15364
16993
|
return;
|
|
15365
16994
|
}
|
|
15366
16995
|
reading = true;
|
|
@@ -15385,8 +17014,8 @@ data: ${JSON.stringify(data)}
|
|
|
15385
17014
|
}
|
|
15386
17015
|
} finally {
|
|
15387
17016
|
reading = false;
|
|
15388
|
-
if (
|
|
15389
|
-
|
|
17017
|
+
if (pending2 && !closed) {
|
|
17018
|
+
pending2 = false;
|
|
15390
17019
|
void pump();
|
|
15391
17020
|
}
|
|
15392
17021
|
}
|
|
@@ -15436,19 +17065,19 @@ data: ${JSON.stringify(data)}
|
|
|
15436
17065
|
server,
|
|
15437
17066
|
token,
|
|
15438
17067
|
listen(port) {
|
|
15439
|
-
return new Promise((
|
|
17068
|
+
return new Promise((resolve9, reject) => {
|
|
15440
17069
|
server.once("error", reject);
|
|
15441
17070
|
server.listen(port, "127.0.0.1", () => {
|
|
15442
17071
|
const addr = server.address();
|
|
15443
17072
|
const actual = typeof addr === "object" && addr ? addr.port : port;
|
|
15444
|
-
|
|
17073
|
+
resolve9({ port: actual, url: `http://127.0.0.1:${actual}/?token=${token}` });
|
|
15445
17074
|
});
|
|
15446
17075
|
});
|
|
15447
17076
|
},
|
|
15448
17077
|
close() {
|
|
15449
17078
|
for (const cleanup of [...sseCleanups]) cleanup();
|
|
15450
|
-
return new Promise((
|
|
15451
|
-
server.close((err) => err ? reject(err) :
|
|
17079
|
+
return new Promise((resolve9, reject) => {
|
|
17080
|
+
server.close((err) => err ? reject(err) : resolve9());
|
|
15452
17081
|
});
|
|
15453
17082
|
}
|
|
15454
17083
|
};
|
|
@@ -15490,7 +17119,7 @@ function isLoopbackOrigin(origin) {
|
|
|
15490
17119
|
}
|
|
15491
17120
|
}
|
|
15492
17121
|
function todayLogFile(dir) {
|
|
15493
|
-
return
|
|
17122
|
+
return join21(dir, `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.log`);
|
|
15494
17123
|
}
|
|
15495
17124
|
function clampInt(raw, min, max, fallback) {
|
|
15496
17125
|
const n = Number(raw);
|
|
@@ -15664,7 +17293,7 @@ async function runSupervisor(bots) {
|
|
|
15664
17293
|
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
17294
|
}
|
|
15666
17295
|
}
|
|
15667
|
-
await new Promise((
|
|
17296
|
+
await new Promise((resolve9) => {
|
|
15668
17297
|
const stop = (sig) => {
|
|
15669
17298
|
if (shuttingDown) return;
|
|
15670
17299
|
shuttingDown = true;
|
|
@@ -15681,7 +17310,7 @@ async function runSupervisor(bots) {
|
|
|
15681
17310
|
if (alive.length === 0 || Date.now() >= deadline) {
|
|
15682
17311
|
clearInterval(poll);
|
|
15683
17312
|
for (const c of alive) c.proc?.kill("SIGKILL");
|
|
15684
|
-
|
|
17313
|
+
resolve9();
|
|
15685
17314
|
}
|
|
15686
17315
|
}, 200);
|
|
15687
17316
|
};
|
|
@@ -15691,6 +17320,7 @@ async function runSupervisor(bots) {
|
|
|
15691
17320
|
}
|
|
15692
17321
|
|
|
15693
17322
|
// src/core/single-instance.ts
|
|
17323
|
+
init_paths();
|
|
15694
17324
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
15695
17325
|
import { dirname as dirname13 } from "path";
|
|
15696
17326
|
var CLAIM_ATTEMPTS = 5;
|
|
@@ -15801,6 +17431,8 @@ function acquireSingleInstanceLock(appId, file = paths.processesFile) {
|
|
|
15801
17431
|
}
|
|
15802
17432
|
|
|
15803
17433
|
// src/cli/commands/run.ts
|
|
17434
|
+
init_bots();
|
|
17435
|
+
init_logger();
|
|
15804
17436
|
async function runRun(botName) {
|
|
15805
17437
|
if (botName) {
|
|
15806
17438
|
await runSingle(botName);
|
|
@@ -15974,6 +17606,7 @@ async function runSingle(botName) {
|
|
|
15974
17606
|
}
|
|
15975
17607
|
|
|
15976
17608
|
// src/cli/commands/daemon.ts
|
|
17609
|
+
init_bots();
|
|
15977
17610
|
async function runStart() {
|
|
15978
17611
|
const active = activeBots(await loadBots());
|
|
15979
17612
|
if (active.length === 0) {
|
|
@@ -16118,7 +17751,10 @@ async function runUpdate(opts = {}) {
|
|
|
16118
17751
|
}
|
|
16119
17752
|
|
|
16120
17753
|
// src/cli/commands/bot.ts
|
|
16121
|
-
import { rm as
|
|
17754
|
+
import { rm as rm8 } from "fs/promises";
|
|
17755
|
+
init_bots();
|
|
17756
|
+
init_schema();
|
|
17757
|
+
init_paths();
|
|
16122
17758
|
|
|
16123
17759
|
// src/cli/checkbox.ts
|
|
16124
17760
|
import { emitKeypressEvents } from "readline";
|
|
@@ -16164,7 +17800,7 @@ async function checkboxSelect(title, items) {
|
|
|
16164
17800
|
output.write(`${ALT_SCREEN_ON}${HIDE_CURSOR}`);
|
|
16165
17801
|
process.once("exit", restore);
|
|
16166
17802
|
redraw();
|
|
16167
|
-
return await new Promise((
|
|
17803
|
+
return await new Promise((resolve9) => {
|
|
16168
17804
|
const cleanup = () => {
|
|
16169
17805
|
input2.off("keypress", onKey);
|
|
16170
17806
|
input2.setRawMode?.(wasRaw);
|
|
@@ -16176,12 +17812,12 @@ async function checkboxSelect(title, items) {
|
|
|
16176
17812
|
const name = key?.name;
|
|
16177
17813
|
if (key?.ctrl && name === "c" || name === "escape" || name === "q") {
|
|
16178
17814
|
cleanup();
|
|
16179
|
-
|
|
17815
|
+
resolve9(null);
|
|
16180
17816
|
return;
|
|
16181
17817
|
}
|
|
16182
17818
|
if (name === "return" || name === "enter") {
|
|
16183
17819
|
cleanup();
|
|
16184
|
-
|
|
17820
|
+
resolve9(checked.flatMap((on, i) => on ? [i] : []));
|
|
16185
17821
|
return;
|
|
16186
17822
|
}
|
|
16187
17823
|
if (name === "up" || name === "k") {
|
|
@@ -16301,7 +17937,7 @@ async function runBotRm(name) {
|
|
|
16301
17937
|
}
|
|
16302
17938
|
const after = await removeBot(bot2.appId);
|
|
16303
17939
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
16304
|
-
await
|
|
17940
|
+
await rm8(botDir(bot2.appId), { recursive: true, force: true });
|
|
16305
17941
|
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
17942
|
if (after.bots.length === 0) {
|
|
16307
17943
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
|
@@ -16369,6 +18005,7 @@ async function runWeb(opts = {}) {
|
|
|
16369
18005
|
}
|
|
16370
18006
|
|
|
16371
18007
|
// src/cli/commands/secrets.ts
|
|
18008
|
+
init_stdin();
|
|
16372
18009
|
async function secretsGet() {
|
|
16373
18010
|
const input2 = await readStdin();
|
|
16374
18011
|
let ids = [];
|
|
@@ -16410,18 +18047,6 @@ async function secretsRemove(id) {
|
|
|
16410
18047
|
const ok = await removeSecret(id);
|
|
16411
18048
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
16412
18049
|
}
|
|
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
18050
|
|
|
16426
18051
|
// src/cli/index.ts
|
|
16427
18052
|
var program = new Command();
|
|
@@ -16482,6 +18107,10 @@ secrets.command("list").description("\u5217\u51FA\u5BC6\u94A5 id").action(async
|
|
|
16482
18107
|
secrets.command("remove <id>").description("\u5220\u9664\u5BC6\u94A5").action(async (id) => {
|
|
16483
18108
|
await secretsRemove(id);
|
|
16484
18109
|
});
|
|
18110
|
+
program.command("hook", { hidden: true }).requiredOption("--agent <agent>", "claude or codex").option("--bot <nameOrAppId>", "target bot name or appId").action(async (options) => {
|
|
18111
|
+
const { runHookCommand: runHookCommand2 } = await Promise.resolve().then(() => (init_cli_bridge(), cli_bridge_exports));
|
|
18112
|
+
await runHookCommand2(options.agent, options.bot);
|
|
18113
|
+
});
|
|
16485
18114
|
program.parseAsync(process.argv).catch((err) => {
|
|
16486
18115
|
console.error(err instanceof Error ? err.message : String(err));
|
|
16487
18116
|
process.exit(1);
|