@modelzen/feishu-codex-bridge 0.2.1 → 0.2.3-win
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 +506 -97
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,6 @@ function bridgeVersion() {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// src/cli/commands/doctor.ts
|
|
18
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
19
18
|
import { existsSync as existsSync3 } from "fs";
|
|
20
19
|
import { homedir as homedir2 } from "os";
|
|
21
20
|
import { join as join4 } from "path";
|
|
@@ -269,27 +268,56 @@ async function moveIfExists(src, dest) {
|
|
|
269
268
|
}
|
|
270
269
|
|
|
271
270
|
// src/agent/codex-appserver/locate.ts
|
|
272
|
-
import { execFileSync } from "child_process";
|
|
273
271
|
import { existsSync as existsSync2 } from "fs";
|
|
274
|
-
import { join as join3 } from "path";
|
|
272
|
+
import { extname, join as join3 } from "path";
|
|
273
|
+
|
|
274
|
+
// src/platform/spawn.ts
|
|
275
|
+
import crossSpawn from "cross-spawn";
|
|
276
|
+
function spawnProcess(command, args = [], options = {}) {
|
|
277
|
+
return crossSpawn(command, [...args], options);
|
|
278
|
+
}
|
|
279
|
+
function spawnProcessSync(command, args = [], options = {}) {
|
|
280
|
+
return crossSpawn.sync(command, [...args], options);
|
|
281
|
+
}
|
|
282
|
+
function mergeProcessEnv(base = process.env, overrides = {}) {
|
|
283
|
+
const out = { ...base };
|
|
284
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
285
|
+
for (const existing of Object.keys(out)) {
|
|
286
|
+
if (existing.toLowerCase() === key.toLowerCase()) delete out[existing];
|
|
287
|
+
}
|
|
288
|
+
if (value !== void 0) out[key] = value;
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/agent/codex-appserver/locate.ts
|
|
294
|
+
var IS_WIN = process.platform === "win32";
|
|
275
295
|
function resolveCodexBin() {
|
|
276
296
|
const env = process.env.CODEX_BIN;
|
|
277
297
|
if (env && existsSync2(env)) return env;
|
|
278
298
|
const onPath = which("codex");
|
|
279
299
|
if (onPath) return onPath;
|
|
280
|
-
const
|
|
281
|
-
|
|
300
|
+
for (const cand of execCandidates(paths.codexCliBinDir, "codex")) {
|
|
301
|
+
if (existsSync2(cand)) return cand;
|
|
302
|
+
}
|
|
282
303
|
const appBundle = "/Applications/Codex.app/Contents/Resources/codex";
|
|
283
304
|
if (process.platform === "darwin" && existsSync2(appBundle)) return appBundle;
|
|
284
305
|
return null;
|
|
285
306
|
}
|
|
307
|
+
function execCandidates(dir, base) {
|
|
308
|
+
const exact = join3(dir, base);
|
|
309
|
+
if (!IS_WIN || extname(base)) return [exact];
|
|
310
|
+
const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean);
|
|
311
|
+
return [exact, ...exts.map((e) => join3(dir, base + e.toLowerCase()))];
|
|
312
|
+
}
|
|
286
313
|
function which(cmd) {
|
|
287
314
|
try {
|
|
288
|
-
const
|
|
315
|
+
const res = spawnProcessSync(IS_WIN ? "where" : "which", [cmd], {
|
|
289
316
|
encoding: "utf8",
|
|
290
317
|
stdio: ["ignore", "pipe", "ignore"]
|
|
291
318
|
});
|
|
292
|
-
|
|
319
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
320
|
+
const first = res.stdout.split("\n").map((l) => l.trim()).find(Boolean);
|
|
293
321
|
return first && existsSync2(first) ? first : null;
|
|
294
322
|
} catch {
|
|
295
323
|
return null;
|
|
@@ -297,7 +325,9 @@ function which(cmd) {
|
|
|
297
325
|
}
|
|
298
326
|
function codexVersion(bin) {
|
|
299
327
|
try {
|
|
300
|
-
|
|
328
|
+
const res = spawnProcessSync(bin, ["--version"], { encoding: "utf8" });
|
|
329
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
330
|
+
return res.stdout.trim();
|
|
301
331
|
} catch {
|
|
302
332
|
return null;
|
|
303
333
|
}
|
|
@@ -355,7 +385,9 @@ ${failed === 0 ? "\u5168\u90E8\u901A\u8FC7 \u2713" : `${failed} \u9879\u9700\u59
|
|
|
355
385
|
}
|
|
356
386
|
function tryExec(cmd, args) {
|
|
357
387
|
try {
|
|
358
|
-
|
|
388
|
+
const res = spawnProcessSync(cmd, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
389
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return null;
|
|
390
|
+
return res.stdout.trim();
|
|
359
391
|
} catch {
|
|
360
392
|
return null;
|
|
361
393
|
}
|
|
@@ -538,7 +570,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
538
570
|
const timeoutMs = pc.noOutputTimeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
539
571
|
const maxOutput = pc.maxOutputBytes ?? DEFAULT_EXEC_MAX_OUTPUT;
|
|
540
572
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
541
|
-
return new Promise((
|
|
573
|
+
return new Promise((resolve7, reject) => {
|
|
542
574
|
const env = {};
|
|
543
575
|
if (pc.passEnv) for (const k of pc.passEnv) {
|
|
544
576
|
const v = process.env[k];
|
|
@@ -583,7 +615,7 @@ async function spawnExecProvider(pc, ref) {
|
|
|
583
615
|
try {
|
|
584
616
|
const parsed = JSON.parse(stdout);
|
|
585
617
|
const value = parsed.values?.[ref.id];
|
|
586
|
-
if (typeof value === "string") return
|
|
618
|
+
if (typeof value === "string") return resolve7(value);
|
|
587
619
|
const err = parsed.errors?.[ref.id]?.message;
|
|
588
620
|
reject(new Error(`exec provider did not return secret for ${ref.id}${err ? `: ${err}` : ""}`));
|
|
589
621
|
} catch (err) {
|
|
@@ -1131,7 +1163,6 @@ ${rule}`);
|
|
|
1131
1163
|
import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
|
|
1132
1164
|
|
|
1133
1165
|
// src/agent/codex-appserver/app-server-client.ts
|
|
1134
|
-
import { spawn as spawn3 } from "child_process";
|
|
1135
1166
|
var AsyncQueue = class {
|
|
1136
1167
|
items = [];
|
|
1137
1168
|
waiters = [];
|
|
@@ -1152,7 +1183,7 @@ var AsyncQueue = class {
|
|
|
1152
1183
|
continue;
|
|
1153
1184
|
}
|
|
1154
1185
|
if (this.closed) return;
|
|
1155
|
-
const next = await new Promise((
|
|
1186
|
+
const next = await new Promise((resolve7) => this.waiters.push(resolve7));
|
|
1156
1187
|
if (next.done) return;
|
|
1157
1188
|
yield next.value;
|
|
1158
1189
|
}
|
|
@@ -1174,9 +1205,9 @@ var AppServerClient = class {
|
|
|
1174
1205
|
}
|
|
1175
1206
|
/** spawn + initialize handshake. Throws if spawn/handshake fails. */
|
|
1176
1207
|
async connect() {
|
|
1177
|
-
const child =
|
|
1208
|
+
const child = spawnProcess(this.opts.bin, ["app-server", "--listen", "stdio://"], {
|
|
1178
1209
|
cwd: this.opts.cwd,
|
|
1179
|
-
env:
|
|
1210
|
+
env: mergeProcessEnv(process.env, { ...this.opts.env, FEISHU_CODEX_BRIDGE: "1" }),
|
|
1180
1211
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1181
1212
|
});
|
|
1182
1213
|
this.child = child;
|
|
@@ -1203,8 +1234,8 @@ var AppServerClient = class {
|
|
|
1203
1234
|
const id = ++this.nextId;
|
|
1204
1235
|
const payload = `${JSON.stringify({ jsonrpc: "2.0", id, method, params: params ?? {} })}
|
|
1205
1236
|
`;
|
|
1206
|
-
return new Promise((
|
|
1207
|
-
this.pending.set(id, { resolve:
|
|
1237
|
+
return new Promise((resolve7, reject) => {
|
|
1238
|
+
this.pending.set(id, { resolve: resolve7, reject });
|
|
1208
1239
|
this.child.stdin.write(payload, (err) => {
|
|
1209
1240
|
if (err) {
|
|
1210
1241
|
this.pending.delete(id);
|
|
@@ -1227,15 +1258,36 @@ var AppServerClient = class {
|
|
|
1227
1258
|
this.closed = true;
|
|
1228
1259
|
const child = this.child;
|
|
1229
1260
|
if (!child || child.exitCode !== null) return;
|
|
1261
|
+
if (process.platform === "win32" && child.pid) {
|
|
1262
|
+
await new Promise((resolve7) => {
|
|
1263
|
+
let settled = false;
|
|
1264
|
+
const done = () => {
|
|
1265
|
+
if (settled) return;
|
|
1266
|
+
settled = true;
|
|
1267
|
+
clearTimeout(t);
|
|
1268
|
+
resolve7();
|
|
1269
|
+
};
|
|
1270
|
+
const t = setTimeout(done, graceMs);
|
|
1271
|
+
child.once("exit", done);
|
|
1272
|
+
spawnProcess("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" }).on(
|
|
1273
|
+
"error",
|
|
1274
|
+
() => {
|
|
1275
|
+
child.kill();
|
|
1276
|
+
done();
|
|
1277
|
+
}
|
|
1278
|
+
);
|
|
1279
|
+
});
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1230
1282
|
child.kill("SIGTERM");
|
|
1231
|
-
await new Promise((
|
|
1283
|
+
await new Promise((resolve7) => {
|
|
1232
1284
|
const t = setTimeout(() => {
|
|
1233
1285
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
1234
|
-
|
|
1286
|
+
resolve7();
|
|
1235
1287
|
}, graceMs);
|
|
1236
1288
|
child.once("exit", () => {
|
|
1237
1289
|
clearTimeout(t);
|
|
1238
|
-
|
|
1290
|
+
resolve7();
|
|
1239
1291
|
});
|
|
1240
1292
|
});
|
|
1241
1293
|
}
|
|
@@ -1368,12 +1420,12 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
|
1368
1420
|
].join("\n");
|
|
1369
1421
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1370
1422
|
function withDeadline(p, ms, label) {
|
|
1371
|
-
return new Promise((
|
|
1423
|
+
return new Promise((resolve7, reject) => {
|
|
1372
1424
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
1373
1425
|
p.then(
|
|
1374
1426
|
(v) => {
|
|
1375
1427
|
clearTimeout(t);
|
|
1376
|
-
|
|
1428
|
+
resolve7(v);
|
|
1377
1429
|
},
|
|
1378
1430
|
(e) => {
|
|
1379
1431
|
clearTimeout(t);
|
|
@@ -1413,11 +1465,11 @@ var CodexThread = class {
|
|
|
1413
1465
|
if (self.model) params.model = self.model;
|
|
1414
1466
|
if (self.effort) params.effort = self.effort;
|
|
1415
1467
|
let startError;
|
|
1416
|
-
const startFailed = new Promise((
|
|
1468
|
+
const startFailed = new Promise((resolve7) => {
|
|
1417
1469
|
self.client.request("turn/start", params).then(void 0, (err) => {
|
|
1418
1470
|
startError = err instanceof Error ? err : new Error(String(err));
|
|
1419
1471
|
log.fail("agent", startError, { phase: "turn/start" });
|
|
1420
|
-
|
|
1472
|
+
resolve7("start-failed");
|
|
1421
1473
|
});
|
|
1422
1474
|
});
|
|
1423
1475
|
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
@@ -2907,7 +2959,7 @@ function structureSig(card2, eid) {
|
|
|
2907
2959
|
|
|
2908
2960
|
// src/card/outbound-images.ts
|
|
2909
2961
|
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
2910
|
-
import { extname, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2962
|
+
import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
|
|
2911
2963
|
var MAX_IMAGES = 9;
|
|
2912
2964
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
2913
2965
|
var DOWNLOAD_TIMEOUT_MS = 1e4;
|
|
@@ -2973,7 +3025,7 @@ async function loadLocal(src, cwd) {
|
|
|
2973
3025
|
log.warn("outbound", "image-outside-cwd", { src: src.slice(0, 80) });
|
|
2974
3026
|
return { cacheKey: `local:${abs}` };
|
|
2975
3027
|
}
|
|
2976
|
-
const ext =
|
|
3028
|
+
const ext = extname2(abs).slice(1).toLowerCase();
|
|
2977
3029
|
if (!ALLOWED_EXT.has(ext)) {
|
|
2978
3030
|
log.warn("outbound", "image-ext", { ext, src: src.slice(0, 80) });
|
|
2979
3031
|
return { cacheKey: `local:${abs}` };
|
|
@@ -3505,38 +3557,105 @@ function buildGroupSettingsCard(project) {
|
|
|
3505
3557
|
|
|
3506
3558
|
// src/service/update.ts
|
|
3507
3559
|
import { execFile, spawn as spawn5 } from "child_process";
|
|
3508
|
-
import { existsSync as
|
|
3509
|
-
import { dirname as
|
|
3510
|
-
import { fileURLToPath as
|
|
3560
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
3561
|
+
import { dirname as dirname9, join as join11, resolve as resolve5 } from "path";
|
|
3562
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3511
3563
|
import { promisify } from "util";
|
|
3512
3564
|
|
|
3513
3565
|
// src/service/launchd.ts
|
|
3514
|
-
import { spawn as
|
|
3566
|
+
import { spawn as spawn3, spawnSync } from "child_process";
|
|
3515
3567
|
import { existsSync as existsSync4 } from "fs";
|
|
3516
|
-
import {
|
|
3568
|
+
import { mkdir as mkdir6, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
3517
3569
|
import { homedir as homedir3, userInfo as userInfo2 } from "os";
|
|
3570
|
+
import { dirname as dirname7, join as join8, resolve as resolve4 } from "path";
|
|
3571
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3572
|
+
|
|
3573
|
+
// src/service/common.ts
|
|
3574
|
+
import { createReadStream, statSync } from "fs";
|
|
3575
|
+
import { appendFile, mkdir as mkdir5, readFile as readFile7 } from "fs/promises";
|
|
3518
3576
|
import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
|
|
3519
3577
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3520
|
-
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
3521
|
-
function launchAgentPlistPath() {
|
|
3522
|
-
return join7(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
3523
|
-
}
|
|
3524
3578
|
function serviceStdoutPath() {
|
|
3525
3579
|
return join7(paths.appDir, "service.log");
|
|
3526
3580
|
}
|
|
3527
3581
|
function serviceStderrPath() {
|
|
3528
3582
|
return join7(paths.appDir, "service.err.log");
|
|
3529
3583
|
}
|
|
3584
|
+
async function ensureLogFiles() {
|
|
3585
|
+
await mkdir5(paths.appDir, { recursive: true });
|
|
3586
|
+
await appendFile(serviceStdoutPath(), "");
|
|
3587
|
+
await appendFile(serviceStderrPath(), "");
|
|
3588
|
+
}
|
|
3530
3589
|
function resolveCliBinPath() {
|
|
3531
3590
|
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
3532
3591
|
return resolve3(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3533
3592
|
}
|
|
3593
|
+
async function tailServiceLogs(follow) {
|
|
3594
|
+
await ensureLogFiles();
|
|
3595
|
+
const files = [serviceStdoutPath(), serviceStderrPath()];
|
|
3596
|
+
for (const f of files) {
|
|
3597
|
+
const tail = await lastLines(f, 100);
|
|
3598
|
+
if (tail) process.stdout.write(`
|
|
3599
|
+
===== ${f} =====
|
|
3600
|
+
${tail}
|
|
3601
|
+
`);
|
|
3602
|
+
}
|
|
3603
|
+
if (!follow) return;
|
|
3604
|
+
const offsets = new Map(files.map((f) => [f, fileSize(f)]));
|
|
3605
|
+
await new Promise((resolvePromise) => {
|
|
3606
|
+
const onSigint = () => {
|
|
3607
|
+
clearInterval(timer);
|
|
3608
|
+
process.off("SIGINT", onSigint);
|
|
3609
|
+
resolvePromise();
|
|
3610
|
+
};
|
|
3611
|
+
process.on("SIGINT", onSigint);
|
|
3612
|
+
const timer = setInterval(() => {
|
|
3613
|
+
for (const f of files) {
|
|
3614
|
+
const size = fileSize(f);
|
|
3615
|
+
const from = offsets.get(f) ?? 0;
|
|
3616
|
+
if (size > from) {
|
|
3617
|
+
offsets.set(f, size);
|
|
3618
|
+
createReadStream(f, { start: from, end: size - 1, encoding: "utf8" }).pipe(process.stdout, {
|
|
3619
|
+
end: false
|
|
3620
|
+
});
|
|
3621
|
+
} else if (size < from) {
|
|
3622
|
+
offsets.set(f, size);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
}, 700);
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
function fileSize(file) {
|
|
3629
|
+
try {
|
|
3630
|
+
return statSync(file).size;
|
|
3631
|
+
} catch {
|
|
3632
|
+
return 0;
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
async function lastLines(file, n) {
|
|
3636
|
+
try {
|
|
3637
|
+
const text = await readFile7(file, "utf8");
|
|
3638
|
+
return text.split("\n").slice(-n - 1).join("\n").trimEnd();
|
|
3639
|
+
} catch {
|
|
3640
|
+
return "";
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
// src/service/launchd.ts
|
|
3645
|
+
var LAUNCHD_LABEL = "ai.feishu-codex-bridge.bot";
|
|
3646
|
+
function launchAgentPlistPath() {
|
|
3647
|
+
return join8(homedir3(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
3648
|
+
}
|
|
3649
|
+
function resolveCliBinPath2() {
|
|
3650
|
+
const distDir = dirname7(fileURLToPath3(import.meta.url));
|
|
3651
|
+
return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
3652
|
+
}
|
|
3534
3653
|
function escapeXml(value) {
|
|
3535
3654
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3536
3655
|
}
|
|
3537
3656
|
function buildPlist() {
|
|
3538
3657
|
const nodePath = process.execPath;
|
|
3539
|
-
const cliBinPath =
|
|
3658
|
+
const cliBinPath = resolveCliBinPath2();
|
|
3540
3659
|
const pathEnv = process.env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin";
|
|
3541
3660
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3542
3661
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -3569,7 +3688,7 @@ function buildPlist() {
|
|
|
3569
3688
|
}
|
|
3570
3689
|
async function installLaunchd() {
|
|
3571
3690
|
const plistPath = launchAgentPlistPath();
|
|
3572
|
-
await
|
|
3691
|
+
await mkdir6(dirname7(plistPath), { recursive: true });
|
|
3573
3692
|
await ensureLogFiles();
|
|
3574
3693
|
await writeFile5(plistPath, buildPlist(), "utf8");
|
|
3575
3694
|
if (isLoaded()) {
|
|
@@ -3607,9 +3726,10 @@ function statusLaunchd() {
|
|
|
3607
3726
|
const raw = result.stdout || result.stderr;
|
|
3608
3727
|
const parsed = parseLaunchdStatus(raw);
|
|
3609
3728
|
return {
|
|
3729
|
+
platformName: "launchd (macOS)",
|
|
3610
3730
|
installed: existsSync4(launchAgentPlistPath()),
|
|
3611
|
-
|
|
3612
|
-
|
|
3731
|
+
running: result.ok,
|
|
3732
|
+
servicePath: launchAgentPlistPath(),
|
|
3613
3733
|
stdoutPath: serviceStdoutPath(),
|
|
3614
3734
|
stderrPath: serviceStderrPath(),
|
|
3615
3735
|
pid: parsed.pid,
|
|
@@ -3621,7 +3741,7 @@ async function tailLaunchdLogs(follow) {
|
|
|
3621
3741
|
await ensureLogFiles();
|
|
3622
3742
|
const args = follow ? ["-f", serviceStdoutPath(), serviceStderrPath()] : ["-n", "100", serviceStdoutPath(), serviceStderrPath()];
|
|
3623
3743
|
await new Promise((resolvePromise, reject) => {
|
|
3624
|
-
const child =
|
|
3744
|
+
const child = spawn3("tail", args, { stdio: "inherit" });
|
|
3625
3745
|
child.on("error", reject);
|
|
3626
3746
|
child.on("close", (code) => {
|
|
3627
3747
|
if (code === 0 || follow && code === null) {
|
|
@@ -3652,11 +3772,6 @@ async function waitUntilUnloaded(timeoutMs = 5e3) {
|
|
|
3652
3772
|
}
|
|
3653
3773
|
throw new Error(`launchd service \u672A\u5728 ${timeoutMs}ms \u5185\u5378\u8F7D\u5B8C\u6210`);
|
|
3654
3774
|
}
|
|
3655
|
-
async function ensureLogFiles() {
|
|
3656
|
-
await mkdir5(paths.appDir, { recursive: true });
|
|
3657
|
-
await appendFile(serviceStdoutPath(), "");
|
|
3658
|
-
await appendFile(serviceStderrPath(), "");
|
|
3659
|
-
}
|
|
3660
3775
|
function userTarget() {
|
|
3661
3776
|
return `gui/${userInfo2().uid}`;
|
|
3662
3777
|
}
|
|
@@ -3677,29 +3792,315 @@ function launchctlError(command, result) {
|
|
|
3677
3792
|
return new Error(`${command} \u5931\u8D25\uFF08exit ${result.status ?? "unknown"}\uFF09${output ? `\uFF1A${output}` : ""}`);
|
|
3678
3793
|
}
|
|
3679
3794
|
|
|
3680
|
-
// src/service/
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3795
|
+
// src/service/win-startup.ts
|
|
3796
|
+
import { spawn as spawn4, spawnSync as spawnSync2 } from "child_process";
|
|
3797
|
+
import { openSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
3798
|
+
import { mkdir as mkdir7, writeFile as writeFile6 } from "fs/promises";
|
|
3799
|
+
import { join as join9 } from "path";
|
|
3800
|
+
var RUN_KEY_PATH = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
3801
|
+
var RUN_KEY_NAME = "feishu-codex-bridge";
|
|
3802
|
+
var SERVICE_ENV_FLAG = "FEISHU_CODEX_BRIDGE_SERVICE";
|
|
3803
|
+
function launcherCmdPath() {
|
|
3804
|
+
return join9(paths.appDir, "service-launcher.cmd");
|
|
3805
|
+
}
|
|
3806
|
+
function launcherVbsPath() {
|
|
3807
|
+
return join9(paths.appDir, "service-launcher.vbs");
|
|
3808
|
+
}
|
|
3809
|
+
function servicePidFile() {
|
|
3810
|
+
return join9(paths.appDir, "service.pid");
|
|
3811
|
+
}
|
|
3812
|
+
function buildLauncherCmd() {
|
|
3813
|
+
return [
|
|
3814
|
+
"@echo off",
|
|
3815
|
+
`set "PATH=${process.env.PATH ?? ""}"`,
|
|
3816
|
+
`set "${SERVICE_ENV_FLAG}=1"`,
|
|
3817
|
+
`"${process.execPath}" "${resolveCliBinPath()}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
|
|
3818
|
+
""
|
|
3819
|
+
].join("\r\n");
|
|
3820
|
+
}
|
|
3821
|
+
function buildLauncherVbs() {
|
|
3822
|
+
return [
|
|
3823
|
+
"' Auto-generated by feishu-codex-bridge. Do not edit.",
|
|
3824
|
+
'Set sh = CreateObject("WScript.Shell")',
|
|
3825
|
+
`sh.Run "cmd /c ""${launcherCmdPath()}""", 0, False`,
|
|
3826
|
+
""
|
|
3827
|
+
].join("\r\n");
|
|
3828
|
+
}
|
|
3829
|
+
function startNow() {
|
|
3830
|
+
const out = openSync(serviceStdoutPath(), "a");
|
|
3831
|
+
const err = openSync(serviceStderrPath(), "a");
|
|
3832
|
+
const child = spawn4(process.execPath, [resolveCliBinPath(), "run"], {
|
|
3833
|
+
detached: true,
|
|
3834
|
+
windowsHide: true,
|
|
3835
|
+
stdio: ["ignore", out, err],
|
|
3836
|
+
env: mergeProcessEnv(process.env, { [SERVICE_ENV_FLAG]: "1" })
|
|
3837
|
+
});
|
|
3838
|
+
if (child.pid) writeFileSync(servicePidFile(), String(child.pid), "utf8");
|
|
3839
|
+
child.unref();
|
|
3840
|
+
}
|
|
3841
|
+
async function installWinStartup() {
|
|
3842
|
+
await mkdir7(paths.appDir, { recursive: true });
|
|
3843
|
+
await ensureLogFiles();
|
|
3844
|
+
await writeFile6(launcherCmdPath(), buildLauncherCmd(), "utf8");
|
|
3845
|
+
await writeFile6(launcherVbsPath(), buildLauncherVbs(), "utf8");
|
|
3846
|
+
const reg = spawnSync2(
|
|
3847
|
+
"reg",
|
|
3848
|
+
["add", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/t", "REG_SZ", "/d", `wscript.exe "${launcherVbsPath()}"`, "/f"],
|
|
3849
|
+
{ encoding: "utf8" }
|
|
3850
|
+
);
|
|
3851
|
+
if (reg.status !== 0) {
|
|
3852
|
+
throw new Error(`\u5199\u5165\u767B\u5F55\u81EA\u542F\u6CE8\u518C\u8868\u9879\u5931\u8D25\uFF08exit ${reg.status ?? "unknown"}\uFF09\uFF1A${(reg.stderr || reg.stdout || "").trim()}`);
|
|
3853
|
+
}
|
|
3854
|
+
startNow();
|
|
3855
|
+
return statusWinStartup();
|
|
3856
|
+
}
|
|
3857
|
+
async function uninstallWinStartup() {
|
|
3858
|
+
spawnSync2("reg", ["delete", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/f"], { stdio: "ignore" });
|
|
3859
|
+
killService();
|
|
3860
|
+
rmSync(servicePidFile(), { force: true });
|
|
3861
|
+
}
|
|
3862
|
+
async function restartWinStartup() {
|
|
3863
|
+
if (!isInstalled()) {
|
|
3864
|
+
throw new Error("\u767B\u5F55\u81EA\u542F\u672A\u5B89\u88C5\uFF08\u5148\u8FD0\u884C `feishu-codex-bridge start`\uFF09\u3002");
|
|
3865
|
+
}
|
|
3866
|
+
await ensureLogFiles();
|
|
3867
|
+
killService();
|
|
3868
|
+
startNow();
|
|
3869
|
+
return statusWinStartup();
|
|
3870
|
+
}
|
|
3871
|
+
function statusWinStartup() {
|
|
3872
|
+
const installed = isInstalled();
|
|
3873
|
+
const pid = readServicePid();
|
|
3874
|
+
const running = pid !== null && pidAlive(pid);
|
|
3875
|
+
return {
|
|
3876
|
+
platformName: "Windows \u767B\u5F55\u81EA\u542F\uFF08HKCU Run\uFF0C\u514D\u7BA1\u7406\u5458\uFF09",
|
|
3877
|
+
installed,
|
|
3878
|
+
running,
|
|
3879
|
+
servicePath: `${RUN_KEY_PATH}\\${RUN_KEY_NAME}`,
|
|
3880
|
+
stdoutPath: serviceStdoutPath(),
|
|
3881
|
+
stderrPath: serviceStderrPath(),
|
|
3882
|
+
pid: running && pid !== null ? String(pid) : void 0,
|
|
3883
|
+
raw: ""
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
function winStartupRunning() {
|
|
3887
|
+
const pid = readServicePid();
|
|
3888
|
+
return pid !== null && pidAlive(pid);
|
|
3889
|
+
}
|
|
3890
|
+
function recordServicePid() {
|
|
3891
|
+
if (process.platform !== "win32" || !process.env[SERVICE_ENV_FLAG]) return;
|
|
3892
|
+
try {
|
|
3893
|
+
writeFileSync(servicePidFile(), String(process.pid), "utf8");
|
|
3894
|
+
} catch {
|
|
3895
|
+
}
|
|
3896
|
+
process.once("exit", clearServicePid);
|
|
3897
|
+
}
|
|
3898
|
+
function clearServicePid() {
|
|
3899
|
+
if (process.platform !== "win32" || !process.env[SERVICE_ENV_FLAG]) return;
|
|
3900
|
+
try {
|
|
3901
|
+
if (readServicePid() === process.pid) rmSync(servicePidFile(), { force: true });
|
|
3902
|
+
} catch {
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
function isInstalled() {
|
|
3906
|
+
const r = spawnSync2("reg", ["query", RUN_KEY_PATH, "/v", RUN_KEY_NAME], {
|
|
3907
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3908
|
+
});
|
|
3909
|
+
return r.status === 0;
|
|
3910
|
+
}
|
|
3911
|
+
function readServicePid() {
|
|
3912
|
+
try {
|
|
3913
|
+
const pid = Number.parseInt(readFileSync2(servicePidFile(), "utf8").trim(), 10);
|
|
3914
|
+
return Number.isFinite(pid) ? pid : null;
|
|
3915
|
+
} catch {
|
|
3916
|
+
return null;
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
function pidAlive(pid) {
|
|
3920
|
+
try {
|
|
3921
|
+
process.kill(pid, 0);
|
|
3922
|
+
return true;
|
|
3923
|
+
} catch (err) {
|
|
3924
|
+
return err.code === "EPERM";
|
|
3684
3925
|
}
|
|
3926
|
+
}
|
|
3927
|
+
function killService() {
|
|
3928
|
+
const pid = readServicePid();
|
|
3929
|
+
if (pid === null || !pidAlive(pid)) return;
|
|
3930
|
+
spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
// src/service/systemd.ts
|
|
3934
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3935
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3936
|
+
import { mkdir as mkdir8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
3937
|
+
import { homedir as homedir4 } from "os";
|
|
3938
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
3939
|
+
var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
|
|
3940
|
+
function systemdUnitPath() {
|
|
3941
|
+
const base = process.env.XDG_CONFIG_HOME ?? join10(homedir4(), ".config");
|
|
3942
|
+
return join10(base, "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
3943
|
+
}
|
|
3944
|
+
function buildUnit() {
|
|
3945
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3946
|
+
const nodePath = process.execPath;
|
|
3947
|
+
const cliBinPath = resolveCliBinPath();
|
|
3948
|
+
const pathEnv = process.env.PATH ?? "";
|
|
3949
|
+
return `[Unit]
|
|
3950
|
+
Description=feishu-codex-bridge bot
|
|
3951
|
+
After=network-online.target
|
|
3952
|
+
Wants=network-online.target
|
|
3953
|
+
|
|
3954
|
+
[Service]
|
|
3955
|
+
Type=simple
|
|
3956
|
+
ExecStart="${esc(nodePath)}" "${esc(cliBinPath)}" run
|
|
3957
|
+
Restart=always
|
|
3958
|
+
RestartSec=5
|
|
3959
|
+
StandardOutput=append:${serviceStdoutPath()}
|
|
3960
|
+
StandardError=append:${serviceStderrPath()}
|
|
3961
|
+
Environment="PATH=${esc(pathEnv)}"
|
|
3962
|
+
|
|
3963
|
+
[Install]
|
|
3964
|
+
WantedBy=default.target
|
|
3965
|
+
`;
|
|
3966
|
+
}
|
|
3967
|
+
function runSystemctl(args) {
|
|
3968
|
+
const r = spawnSync3("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
3685
3969
|
return {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3970
|
+
ok: r.status === 0,
|
|
3971
|
+
status: r.status,
|
|
3972
|
+
stdout: r.stdout ?? "",
|
|
3973
|
+
stderr: (r.error ? `${r.error.message}
|
|
3974
|
+
` : "") + (r.stderr ?? "")
|
|
3691
3975
|
};
|
|
3692
3976
|
}
|
|
3977
|
+
function systemctlError(command, r) {
|
|
3978
|
+
const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
|
|
3979
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
|
|
3980
|
+
}
|
|
3981
|
+
function systemdAvailable() {
|
|
3982
|
+
const r = spawnSync3("systemctl", ["--user", "is-system-running"], { encoding: "utf8" });
|
|
3983
|
+
if (r.error) return false;
|
|
3984
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
3985
|
+
return !/not been booted with systemd|Failed to connect to (the )?bus|Failed to (connect|get) D-?Bus/i.test(out);
|
|
3986
|
+
}
|
|
3987
|
+
function ensureSystemdOrThrow() {
|
|
3988
|
+
if (systemdAvailable()) return;
|
|
3989
|
+
throw new Error(
|
|
3990
|
+
"\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684\u7528\u6237\u7EA7 systemd\u3002WSL \u9700\u5728 /etc/wsl.conf \u5199\u5165 `[boot]\\nsystemd=true` \u540E\u6267\u884C `wsl --shutdown` \u91CD\u542F\uFF1B\u6216\u76F4\u63A5\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\uFF08\u65E0\u9700\u540E\u53F0\u670D\u52A1\uFF09\u3002"
|
|
3991
|
+
);
|
|
3992
|
+
}
|
|
3993
|
+
async function installSystemd() {
|
|
3994
|
+
ensureSystemdOrThrow();
|
|
3995
|
+
const unitPath = systemdUnitPath();
|
|
3996
|
+
await mkdir8(dirname8(unitPath), { recursive: true });
|
|
3997
|
+
await ensureLogFiles();
|
|
3998
|
+
await writeFile7(unitPath, buildUnit(), "utf8");
|
|
3999
|
+
const reload = runSystemctl(["daemon-reload"]);
|
|
4000
|
+
if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
|
|
4001
|
+
const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
|
|
4002
|
+
if (!enable.ok) throw systemctlError("systemctl --user enable --now", enable);
|
|
4003
|
+
return statusSystemd();
|
|
4004
|
+
}
|
|
4005
|
+
async function uninstallSystemd() {
|
|
4006
|
+
if (systemdAvailable() && unitExists()) {
|
|
4007
|
+
runSystemctl(["disable", "--now", SYSTEMD_UNIT_NAME]);
|
|
4008
|
+
}
|
|
4009
|
+
await rm3(systemdUnitPath(), { force: true });
|
|
4010
|
+
if (systemdAvailable()) runSystemctl(["daemon-reload"]);
|
|
4011
|
+
}
|
|
4012
|
+
async function restartSystemd() {
|
|
4013
|
+
ensureSystemdOrThrow();
|
|
4014
|
+
if (!unitExists()) {
|
|
4015
|
+
throw new Error(`systemd unit \u672A\u5B89\u88C5\uFF1A${systemdUnitPath()}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
|
|
4016
|
+
}
|
|
4017
|
+
const restart = runSystemctl(["restart", SYSTEMD_UNIT_NAME]);
|
|
4018
|
+
if (!restart.ok) throw systemctlError("systemctl --user restart", restart);
|
|
4019
|
+
return statusSystemd();
|
|
4020
|
+
}
|
|
4021
|
+
function statusSystemd() {
|
|
4022
|
+
const installed = unitExists();
|
|
4023
|
+
const raw = installed && systemdAvailable() ? describeService() : "";
|
|
4024
|
+
return {
|
|
4025
|
+
platformName: "systemd (Linux user)",
|
|
4026
|
+
installed,
|
|
4027
|
+
running: systemdActive(),
|
|
4028
|
+
servicePath: systemdUnitPath(),
|
|
4029
|
+
stdoutPath: serviceStdoutPath(),
|
|
4030
|
+
stderrPath: serviceStderrPath(),
|
|
4031
|
+
pid: raw.match(/Main PID:\s*(\d+)/)?.[1],
|
|
4032
|
+
// On an inactive unit the "Process: <pid> ExecStart=... status=<n>" line
|
|
4033
|
+
// carries the last exit code.
|
|
4034
|
+
lastExit: raw.match(/Process:\s+\d+\s+ExecStart=.*status=(\d+)/)?.[1],
|
|
4035
|
+
raw
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
function unitExists() {
|
|
4039
|
+
return existsSync5(systemdUnitPath());
|
|
4040
|
+
}
|
|
4041
|
+
function systemdActive() {
|
|
4042
|
+
const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
|
|
4043
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
4044
|
+
});
|
|
4045
|
+
return r.status === 0;
|
|
4046
|
+
}
|
|
4047
|
+
function describeService() {
|
|
4048
|
+
const r = runSystemctl(["status", SYSTEMD_UNIT_NAME, "--no-pager"]);
|
|
4049
|
+
return r.stdout || r.stderr || "";
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
// src/service/adapter.ts
|
|
4053
|
+
function getServiceAdapter() {
|
|
4054
|
+
if (process.platform === "darwin") {
|
|
4055
|
+
return {
|
|
4056
|
+
install: installLaunchd,
|
|
4057
|
+
uninstall: uninstallLaunchd,
|
|
4058
|
+
status: async () => statusLaunchd(),
|
|
4059
|
+
restart: restartLaunchd,
|
|
4060
|
+
logs: tailLaunchdLogs
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
if (process.platform === "win32") {
|
|
4064
|
+
return {
|
|
4065
|
+
install: installWinStartup,
|
|
4066
|
+
uninstall: uninstallWinStartup,
|
|
4067
|
+
status: async () => statusWinStartup(),
|
|
4068
|
+
restart: restartWinStartup,
|
|
4069
|
+
logs: tailServiceLogs
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
if (process.platform === "linux") {
|
|
4073
|
+
return {
|
|
4074
|
+
install: installSystemd,
|
|
4075
|
+
uninstall: uninstallSystemd,
|
|
4076
|
+
status: async () => statusSystemd(),
|
|
4077
|
+
restart: restartSystemd,
|
|
4078
|
+
logs: tailServiceLogs
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
throw new Error(
|
|
4082
|
+
"service\uFF1A\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u540E\u53F0\u670D\u52A1\uFF08\u4EC5 macOS launchd / Windows \u8BA1\u5212\u4EFB\u52A1 / Linux systemd\uFF09\u3002\u8BF7\u7528 `feishu-codex-bridge run` \u524D\u53F0\u8FD0\u884C\u3002"
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
function isServiceRunning() {
|
|
4086
|
+
try {
|
|
4087
|
+
if (process.platform === "darwin") return isLoaded();
|
|
4088
|
+
if (process.platform === "win32") return winStartupRunning();
|
|
4089
|
+
if (process.platform === "linux") return systemdActive();
|
|
4090
|
+
} catch {
|
|
4091
|
+
}
|
|
4092
|
+
return false;
|
|
4093
|
+
}
|
|
3693
4094
|
|
|
3694
4095
|
// src/service/update.ts
|
|
3695
4096
|
var execFileP = promisify(execFile);
|
|
3696
4097
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3697
4098
|
function pkgRoot() {
|
|
3698
|
-
return
|
|
4099
|
+
return resolve5(dirname9(fileURLToPath4(import.meta.url)), "..");
|
|
3699
4100
|
}
|
|
3700
4101
|
function pkgJson() {
|
|
3701
4102
|
try {
|
|
3702
|
-
return JSON.parse(
|
|
4103
|
+
return JSON.parse(readFileSync3(join11(pkgRoot(), "package.json"), "utf8"));
|
|
3703
4104
|
} catch {
|
|
3704
4105
|
return {};
|
|
3705
4106
|
}
|
|
@@ -3711,7 +4112,7 @@ function packageName() {
|
|
|
3711
4112
|
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
3712
4113
|
}
|
|
3713
4114
|
function isDevSource() {
|
|
3714
|
-
return
|
|
4115
|
+
return existsSync6(join11(pkgRoot(), ".git"));
|
|
3715
4116
|
}
|
|
3716
4117
|
function isNewer(a, b) {
|
|
3717
4118
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -3750,20 +4151,16 @@ async function installLatest(opts = {}) {
|
|
|
3750
4151
|
});
|
|
3751
4152
|
}
|
|
3752
4153
|
function daemonRunning() {
|
|
3753
|
-
|
|
3754
|
-
return statusLaunchd().loaded;
|
|
3755
|
-
} catch {
|
|
3756
|
-
return false;
|
|
3757
|
-
}
|
|
4154
|
+
return isServiceRunning();
|
|
3758
4155
|
}
|
|
3759
4156
|
async function restartDaemon() {
|
|
3760
4157
|
await getServiceAdapter().restart();
|
|
3761
4158
|
}
|
|
3762
4159
|
|
|
3763
4160
|
// src/project/lifecycle.ts
|
|
3764
|
-
import { mkdir as
|
|
3765
|
-
import { existsSync as
|
|
3766
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4161
|
+
import { mkdir as mkdir9 } from "fs/promises";
|
|
4162
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4163
|
+
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "path";
|
|
3767
4164
|
|
|
3768
4165
|
// src/project/git-info.ts
|
|
3769
4166
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -3898,12 +4295,12 @@ async function onboardGroup(channel, project) {
|
|
|
3898
4295
|
// src/project/lifecycle.ts
|
|
3899
4296
|
async function resolveCwd(name, existingPath) {
|
|
3900
4297
|
if (existingPath) {
|
|
3901
|
-
const cwd2 = isAbsolute2(existingPath) ? existingPath :
|
|
3902
|
-
if (!
|
|
4298
|
+
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
|
|
4299
|
+
if (!existsSync7(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
3903
4300
|
return { cwd: cwd2, blank: false };
|
|
3904
4301
|
}
|
|
3905
|
-
const cwd =
|
|
3906
|
-
await
|
|
4302
|
+
const cwd = join12(paths.projectsRootDir, name);
|
|
4303
|
+
await mkdir9(cwd, { recursive: true });
|
|
3907
4304
|
return { cwd, blank: true };
|
|
3908
4305
|
}
|
|
3909
4306
|
async function createProject(channel, input2) {
|
|
@@ -3970,12 +4367,12 @@ async function leaveChat(channel, chatId) {
|
|
|
3970
4367
|
}
|
|
3971
4368
|
|
|
3972
4369
|
// src/bot/session-store.ts
|
|
3973
|
-
import { mkdir as
|
|
3974
|
-
import { dirname as
|
|
4370
|
+
import { mkdir as mkdir10, readFile as readFile8, rename as rename5, writeFile as writeFile8 } from "fs/promises";
|
|
4371
|
+
import { dirname as dirname10 } from "path";
|
|
3975
4372
|
var FILE_VERSION3 = 1;
|
|
3976
4373
|
async function read2() {
|
|
3977
4374
|
try {
|
|
3978
|
-
const text = await
|
|
4375
|
+
const text = await readFile8(paths.sessionsFile, "utf8");
|
|
3979
4376
|
const parsed = JSON.parse(text);
|
|
3980
4377
|
return Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
3981
4378
|
} catch (err) {
|
|
@@ -3984,10 +4381,10 @@ async function read2() {
|
|
|
3984
4381
|
}
|
|
3985
4382
|
}
|
|
3986
4383
|
async function write2(sessions) {
|
|
3987
|
-
await
|
|
4384
|
+
await mkdir10(dirname10(paths.sessionsFile), { recursive: true });
|
|
3988
4385
|
const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
|
|
3989
4386
|
const body = { version: FILE_VERSION3, sessions };
|
|
3990
|
-
await
|
|
4387
|
+
await writeFile8(tmp, `${JSON.stringify(body, null, 2)}
|
|
3991
4388
|
`, "utf8");
|
|
3992
4389
|
await rename5(tmp, paths.sessionsFile);
|
|
3993
4390
|
}
|
|
@@ -4031,8 +4428,8 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
4031
4428
|
}
|
|
4032
4429
|
|
|
4033
4430
|
// src/bot/media.ts
|
|
4034
|
-
import { mkdir as
|
|
4035
|
-
import { join as
|
|
4431
|
+
import { mkdir as mkdir11, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
4432
|
+
import { join as join13 } from "path";
|
|
4036
4433
|
var MAX_IMAGES2 = 9;
|
|
4037
4434
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
4038
4435
|
var EXT_BY_CONTENT_TYPE = {
|
|
@@ -4061,7 +4458,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
4061
4458
|
if (refs.length === 0) return [];
|
|
4062
4459
|
await pruneOldMedia();
|
|
4063
4460
|
try {
|
|
4064
|
-
await
|
|
4461
|
+
await mkdir11(paths.mediaDir, { recursive: true });
|
|
4065
4462
|
} catch {
|
|
4066
4463
|
}
|
|
4067
4464
|
const out = [];
|
|
@@ -4137,7 +4534,7 @@ async function downloadOne(channel, ref, index) {
|
|
|
4137
4534
|
params: { type: "image" }
|
|
4138
4535
|
});
|
|
4139
4536
|
const ext = extFromHeaders(res.headers);
|
|
4140
|
-
const file =
|
|
4537
|
+
const file = join13(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
4141
4538
|
await res.writeFile(file);
|
|
4142
4539
|
return file;
|
|
4143
4540
|
} catch (err) {
|
|
@@ -4171,10 +4568,10 @@ async function pruneOldMedia() {
|
|
|
4171
4568
|
}
|
|
4172
4569
|
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
4173
4570
|
for (const name of entries) {
|
|
4174
|
-
const file =
|
|
4571
|
+
const file = join13(paths.mediaDir, name);
|
|
4175
4572
|
try {
|
|
4176
4573
|
const st = await stat3(file);
|
|
4177
|
-
if (st.mtimeMs < cutoff) await
|
|
4574
|
+
if (st.mtimeMs < cutoff) await rm4(file, { force: true });
|
|
4178
4575
|
} catch {
|
|
4179
4576
|
}
|
|
4180
4577
|
}
|
|
@@ -5545,8 +5942,8 @@ async function startBridge(opts) {
|
|
|
5545
5942
|
}
|
|
5546
5943
|
|
|
5547
5944
|
// src/core/single-instance.ts
|
|
5548
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
5549
|
-
import { dirname as
|
|
5945
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5946
|
+
import { dirname as dirname11 } from "path";
|
|
5550
5947
|
var BridgeAlreadyRunningError = class extends Error {
|
|
5551
5948
|
constructor(pid) {
|
|
5552
5949
|
super(
|
|
@@ -5568,20 +5965,20 @@ function isAlive(pid) {
|
|
|
5568
5965
|
function acquireSingleInstanceLock(appId) {
|
|
5569
5966
|
const file = paths.processesFile;
|
|
5570
5967
|
try {
|
|
5571
|
-
const rec = JSON.parse(
|
|
5968
|
+
const rec = JSON.parse(readFileSync4(file, "utf8"));
|
|
5572
5969
|
if (rec.pid && rec.pid !== process.pid && rec.appId === appId && isAlive(rec.pid)) {
|
|
5573
5970
|
throw new BridgeAlreadyRunningError(rec.pid);
|
|
5574
5971
|
}
|
|
5575
5972
|
} catch (err) {
|
|
5576
5973
|
if (err instanceof BridgeAlreadyRunningError) throw err;
|
|
5577
5974
|
}
|
|
5578
|
-
mkdirSync2(
|
|
5975
|
+
mkdirSync2(dirname11(file), { recursive: true });
|
|
5579
5976
|
const record = { pid: process.pid, appId, startedAt: Date.now() };
|
|
5580
|
-
|
|
5977
|
+
writeFileSync2(file, `${JSON.stringify(record)}
|
|
5581
5978
|
`, "utf8");
|
|
5582
5979
|
const release = () => {
|
|
5583
5980
|
try {
|
|
5584
|
-
const rec = JSON.parse(
|
|
5981
|
+
const rec = JSON.parse(readFileSync4(file, "utf8"));
|
|
5585
5982
|
if (rec.pid === process.pid) unlinkSync(file);
|
|
5586
5983
|
} catch {
|
|
5587
5984
|
}
|
|
@@ -5610,6 +6007,7 @@ async function runRun() {
|
|
|
5610
6007
|
}
|
|
5611
6008
|
throw err;
|
|
5612
6009
|
}
|
|
6010
|
+
recordServicePid();
|
|
5613
6011
|
const fallbackCwd = process.env.FEISHU_CODEX_CWD || process.cwd();
|
|
5614
6012
|
console.log("\n\u6B63\u5728\u542F\u52A8\u957F\u8FDE\u63A5 bot\u2026");
|
|
5615
6013
|
console.log("\u79C1\u804A\u6211 `/new <\u540D>` \u5EFA\u9879\u76EE\uFF1B\u5728\u9879\u76EE\u7FA4\u91CC @\u6211 \u5E72\u6D3B\u3002Ctrl+C \u9000\u51FA\u3002\n");
|
|
@@ -5644,12 +6042,12 @@ async function runStart() {
|
|
|
5644
6042
|
return;
|
|
5645
6043
|
}
|
|
5646
6044
|
const status = await getServiceAdapter().install();
|
|
5647
|
-
console.log(
|
|
6045
|
+
console.log(installedNote());
|
|
5648
6046
|
printStatus(status);
|
|
5649
6047
|
}
|
|
5650
6048
|
async function runStop() {
|
|
5651
6049
|
await getServiceAdapter().uninstall();
|
|
5652
|
-
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\
|
|
6050
|
+
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\u81EA\u542F\uFF08\u5DF2\u79FB\u9664\u670D\u52A1\u5B9A\u4E49\uFF09\u3002");
|
|
5653
6051
|
}
|
|
5654
6052
|
async function runRestart() {
|
|
5655
6053
|
const status = await getServiceAdapter().restart();
|
|
@@ -5662,18 +6060,29 @@ async function runStatus() {
|
|
|
5662
6060
|
async function runLogs(follow) {
|
|
5663
6061
|
await getServiceAdapter().logs(follow);
|
|
5664
6062
|
}
|
|
6063
|
+
function installedNote() {
|
|
6064
|
+
switch (process.platform) {
|
|
6065
|
+
case "win32":
|
|
6066
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF0C\u514D\u7BA1\u7406\u5458\uFF09\u3002\n \u63D0\u793A\uFF1A\u767B\u5F55\u81EA\u542F\u5728\u4E0B\u6B21\u767B\u5F55\u751F\u6548\uFF1B\u5F53\u524D\u5DF2\u5728\u540E\u53F0\u9690\u85CF\u542F\u52A8\u3002\u6CE8\u610F Windows \u767B\u5F55\u81EA\u542F\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\u3002";
|
|
6067
|
+
case "linux":
|
|
6068
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002\n \u63D0\u793A\uFF1A\u6CE8\u9500\u540E\u4ECD\u4FDD\u6301\u8FD0\u884C\u9700\u6267\u884C\u4E00\u6B21 `loginctl enable-linger $USER`\u3002";
|
|
6069
|
+
default:
|
|
6070
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u5F00\u673A\u81EA\u542F\u3001\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
|
|
6071
|
+
}
|
|
6072
|
+
}
|
|
5665
6073
|
function printStatus(status) {
|
|
5666
|
-
console.log(`
|
|
6074
|
+
console.log(`service: ${status.platformName}`);
|
|
6075
|
+
console.log(`path: ${status.servicePath}`);
|
|
5667
6076
|
console.log(`installed: ${status.installed ? "yes" : "no"}`);
|
|
5668
|
-
console.log(`
|
|
6077
|
+
console.log(`running: ${status.running ? "yes" : "no"}`);
|
|
5669
6078
|
console.log(`pid: ${status.pid ?? "-"}`);
|
|
5670
6079
|
console.log(`last exit: ${status.lastExit ?? "-"}`);
|
|
5671
6080
|
console.log(`stdout: ${status.stdoutPath}`);
|
|
5672
6081
|
console.log(`stderr: ${status.stderrPath}`);
|
|
5673
6082
|
if (!status.installed) {
|
|
5674
6083
|
console.log("\u63D0\u793A\uFF1A\u540E\u53F0\u670D\u52A1\u5C1A\u672A\u5B89\u88C5\uFF0C\u8FD0\u884C `feishu-codex-bridge start`\u3002");
|
|
5675
|
-
} else if (!status.
|
|
5676
|
-
console.log("\u63D0\u793A\
|
|
6084
|
+
} else if (!status.running) {
|
|
6085
|
+
console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
|
|
5677
6086
|
}
|
|
5678
6087
|
}
|
|
5679
6088
|
|
|
@@ -5726,7 +6135,7 @@ async function runUpdate(opts = {}) {
|
|
|
5726
6135
|
}
|
|
5727
6136
|
|
|
5728
6137
|
// src/cli/commands/bot.ts
|
|
5729
|
-
import { rm as
|
|
6138
|
+
import { rm as rm5 } from "fs/promises";
|
|
5730
6139
|
async function runBotInit(name) {
|
|
5731
6140
|
if (!ensureCodex()) {
|
|
5732
6141
|
process.exitCode = 1;
|
|
@@ -5781,7 +6190,7 @@ async function runBotRm(name) {
|
|
|
5781
6190
|
}
|
|
5782
6191
|
const after = await removeBot(bot2.appId);
|
|
5783
6192
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
5784
|
-
await
|
|
6193
|
+
await rm5(botDir(bot2.appId), { recursive: true, force: true });
|
|
5785
6194
|
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`);
|
|
5786
6195
|
if (after.bots.length === 0) {
|
|
5787
6196
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
|
@@ -5839,15 +6248,15 @@ async function secretsRemove(id) {
|
|
|
5839
6248
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5840
6249
|
}
|
|
5841
6250
|
function readStdin() {
|
|
5842
|
-
return new Promise((
|
|
6251
|
+
return new Promise((resolve7) => {
|
|
5843
6252
|
let data = "";
|
|
5844
6253
|
if (process.stdin.isTTY) {
|
|
5845
|
-
|
|
6254
|
+
resolve7("");
|
|
5846
6255
|
return;
|
|
5847
6256
|
}
|
|
5848
6257
|
process.stdin.setEncoding("utf8");
|
|
5849
6258
|
process.stdin.on("data", (c) => data += c);
|
|
5850
|
-
process.stdin.on("end", () =>
|
|
6259
|
+
process.stdin.on("end", () => resolve7(data));
|
|
5851
6260
|
});
|
|
5852
6261
|
}
|
|
5853
6262
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelzen/feishu-codex-bridge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3-win",
|
|
4
4
|
"description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@larksuiteoapi/node-sdk": "^1.65.0",
|
|
32
32
|
"commander": "^12.1.0",
|
|
33
|
+
"cross-spawn": "^7.0.6",
|
|
33
34
|
"qrcode-terminal": "^0.12.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"@types/cross-spawn": "^6.0.6",
|
|
36
38
|
"@types/node": "^22.10.0",
|
|
37
39
|
"@types/qrcode-terminal": "^0.12.2",
|
|
38
40
|
"tsup": "^8.3.5",
|