@modelzen/feishu-codex-bridge 0.2.1 → 0.2.2-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 +479 -95
- 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}` };
|
|
@@ -3504,39 +3556,106 @@ function buildGroupSettingsCard(project) {
|
|
|
3504
3556
|
}
|
|
3505
3557
|
|
|
3506
3558
|
// src/service/update.ts
|
|
3507
|
-
import { execFile, spawn as
|
|
3508
|
-
import { existsSync as
|
|
3509
|
-
import { dirname as
|
|
3510
|
-
import { fileURLToPath as
|
|
3559
|
+
import { execFile, spawn as spawn4 } from "child_process";
|
|
3560
|
+
import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
|
|
3561
|
+
import { dirname as dirname10, 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,291 @@ 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/schtasks.ts
|
|
3796
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3797
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3798
|
+
import { mkdir as mkdir7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
3799
|
+
import { dirname as dirname8, join as join9 } from "path";
|
|
3800
|
+
var WINDOWS_TASK_NAME = "feishu-codex-bridge";
|
|
3801
|
+
function launcherCmdPath() {
|
|
3802
|
+
return join9(paths.appDir, "service-launcher.cmd");
|
|
3803
|
+
}
|
|
3804
|
+
function buildLauncherCmd() {
|
|
3805
|
+
const nodePath = process.execPath;
|
|
3806
|
+
const cliBinPath = resolveCliBinPath();
|
|
3807
|
+
const pathEnv = process.env.PATH ?? "";
|
|
3808
|
+
return [
|
|
3809
|
+
"@echo off",
|
|
3810
|
+
`set "PATH=${pathEnv}"`,
|
|
3811
|
+
`"${nodePath}" "${cliBinPath}" run >> "${serviceStdoutPath()}" 2>> "${serviceStderrPath()}"`,
|
|
3812
|
+
""
|
|
3813
|
+
].join("\r\n");
|
|
3814
|
+
}
|
|
3815
|
+
function runSchtasks(args) {
|
|
3816
|
+
const r = spawnSync2("schtasks", args, { encoding: "utf8" });
|
|
3817
|
+
return {
|
|
3818
|
+
ok: r.status === 0,
|
|
3819
|
+
status: r.status,
|
|
3820
|
+
stdout: r.stdout ?? "",
|
|
3821
|
+
stderr: r.stderr ?? ""
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
function schtasksError(command, r) {
|
|
3825
|
+
const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
|
|
3826
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
|
|
3827
|
+
}
|
|
3828
|
+
async function writeLauncherCmd() {
|
|
3829
|
+
const cmdPath = launcherCmdPath();
|
|
3830
|
+
await mkdir7(dirname8(cmdPath), { recursive: true });
|
|
3831
|
+
await ensureLogFiles();
|
|
3832
|
+
await writeFile6(cmdPath, buildLauncherCmd(), "utf8");
|
|
3833
|
+
}
|
|
3834
|
+
async function installSchtask() {
|
|
3835
|
+
await writeLauncherCmd();
|
|
3836
|
+
const create = runSchtasks([
|
|
3837
|
+
"/Create",
|
|
3838
|
+
"/F",
|
|
3839
|
+
"/SC",
|
|
3840
|
+
"ONLOGON",
|
|
3841
|
+
"/RL",
|
|
3842
|
+
"LIMITED",
|
|
3843
|
+
"/TN",
|
|
3844
|
+
WINDOWS_TASK_NAME,
|
|
3845
|
+
"/TR",
|
|
3846
|
+
`"${launcherCmdPath()}"`
|
|
3847
|
+
]);
|
|
3848
|
+
if (!create.ok) throw schtasksError("schtasks /Create", create);
|
|
3849
|
+
const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
|
|
3850
|
+
if (!run.ok) throw schtasksError("schtasks /Run", run);
|
|
3851
|
+
return statusSchtask();
|
|
3852
|
+
}
|
|
3853
|
+
async function uninstallSchtask() {
|
|
3854
|
+
runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
|
|
3855
|
+
const del = runSchtasks(["/Delete", "/F", "/TN", WINDOWS_TASK_NAME]);
|
|
3856
|
+
if (!del.ok && isTaskRegistered()) throw schtasksError("schtasks /Delete", del);
|
|
3857
|
+
if (existsSync5(launcherCmdPath())) await rm3(launcherCmdPath(), { force: true });
|
|
3858
|
+
}
|
|
3859
|
+
async function restartSchtask() {
|
|
3860
|
+
if (!isTaskRegistered()) {
|
|
3861
|
+
throw new Error(`\u8BA1\u5212\u4EFB\u52A1\u672A\u5B89\u88C5\uFF1A${WINDOWS_TASK_NAME}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
|
|
3862
|
+
}
|
|
3863
|
+
runSchtasks(["/End", "/TN", WINDOWS_TASK_NAME]);
|
|
3864
|
+
await waitUntilStopped();
|
|
3865
|
+
const run = runSchtasks(["/Run", "/TN", WINDOWS_TASK_NAME]);
|
|
3866
|
+
if (!run.ok) throw schtasksError("schtasks /Run", run);
|
|
3867
|
+
return statusSchtask();
|
|
3868
|
+
}
|
|
3869
|
+
function statusSchtask() {
|
|
3870
|
+
const installed = isTaskRegistered();
|
|
3871
|
+
const raw = installed ? describeTask() : "";
|
|
3872
|
+
return {
|
|
3873
|
+
platformName: "Task Scheduler (Windows)",
|
|
3874
|
+
installed,
|
|
3875
|
+
running: installed && /Status:\s+Running/i.test(raw),
|
|
3876
|
+
servicePath: WINDOWS_TASK_NAME,
|
|
3877
|
+
stdoutPath: serviceStdoutPath(),
|
|
3878
|
+
stderrPath: serviceStderrPath(),
|
|
3879
|
+
// `Process ID:` only appears in verbose output while the task is running.
|
|
3880
|
+
pid: raw.match(/Process ID:\s*(\d+)/i)?.[1],
|
|
3881
|
+
// `Last Result: 0` ⇒ last run succeeded. Surface it as the exit code.
|
|
3882
|
+
lastExit: raw.match(/Last Result:\s*(-?\d+)/i)?.[1],
|
|
3883
|
+
raw
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
function isTaskRegistered() {
|
|
3887
|
+
const r = spawnSync2("schtasks", ["/Query", "/TN", WINDOWS_TASK_NAME], {
|
|
3888
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3889
|
+
});
|
|
3890
|
+
return r.status === 0;
|
|
3891
|
+
}
|
|
3892
|
+
function schtaskRunning() {
|
|
3893
|
+
if (!isTaskRegistered()) return false;
|
|
3894
|
+
return /Status:\s+Running/i.test(describeTask());
|
|
3895
|
+
}
|
|
3896
|
+
function describeTask() {
|
|
3897
|
+
const r = runSchtasks(["/Query", "/V", "/FO", "LIST", "/TN", WINDOWS_TASK_NAME]);
|
|
3898
|
+
return r.stdout || r.stderr || "";
|
|
3899
|
+
}
|
|
3900
|
+
async function waitUntilStopped(timeoutMs = 5e3) {
|
|
3901
|
+
const deadline = Date.now() + timeoutMs;
|
|
3902
|
+
while (Date.now() < deadline) {
|
|
3903
|
+
if (!schtaskRunning()) return true;
|
|
3904
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3684
3905
|
}
|
|
3906
|
+
return false;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
// src/service/systemd.ts
|
|
3910
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3911
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3912
|
+
import { mkdir as mkdir8, rm as rm4, writeFile as writeFile7 } from "fs/promises";
|
|
3913
|
+
import { homedir as homedir4 } from "os";
|
|
3914
|
+
import { dirname as dirname9, join as join10 } from "path";
|
|
3915
|
+
var SYSTEMD_UNIT_NAME = "feishu-codex-bridge.service";
|
|
3916
|
+
function systemdUnitPath() {
|
|
3917
|
+
const base = process.env.XDG_CONFIG_HOME ?? join10(homedir4(), ".config");
|
|
3918
|
+
return join10(base, "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
3919
|
+
}
|
|
3920
|
+
function buildUnit() {
|
|
3921
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
3922
|
+
const nodePath = process.execPath;
|
|
3923
|
+
const cliBinPath = resolveCliBinPath();
|
|
3924
|
+
const pathEnv = process.env.PATH ?? "";
|
|
3925
|
+
return `[Unit]
|
|
3926
|
+
Description=feishu-codex-bridge bot
|
|
3927
|
+
After=network-online.target
|
|
3928
|
+
Wants=network-online.target
|
|
3929
|
+
|
|
3930
|
+
[Service]
|
|
3931
|
+
Type=simple
|
|
3932
|
+
ExecStart="${esc(nodePath)}" "${esc(cliBinPath)}" run
|
|
3933
|
+
Restart=always
|
|
3934
|
+
RestartSec=5
|
|
3935
|
+
StandardOutput=append:${serviceStdoutPath()}
|
|
3936
|
+
StandardError=append:${serviceStderrPath()}
|
|
3937
|
+
Environment="PATH=${esc(pathEnv)}"
|
|
3938
|
+
|
|
3939
|
+
[Install]
|
|
3940
|
+
WantedBy=default.target
|
|
3941
|
+
`;
|
|
3942
|
+
}
|
|
3943
|
+
function runSystemctl(args) {
|
|
3944
|
+
const r = spawnSync3("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
3685
3945
|
return {
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3946
|
+
ok: r.status === 0,
|
|
3947
|
+
status: r.status,
|
|
3948
|
+
stdout: r.stdout ?? "",
|
|
3949
|
+
stderr: (r.error ? `${r.error.message}
|
|
3950
|
+
` : "") + (r.stderr ?? "")
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
function systemctlError(command, r) {
|
|
3954
|
+
const out = [r.stderr.trim(), r.stdout.trim()].filter(Boolean).join("\n");
|
|
3955
|
+
return new Error(`${command} \u5931\u8D25\uFF08exit ${r.status ?? "unknown"}\uFF09${out ? `\uFF1A${out}` : ""}`);
|
|
3956
|
+
}
|
|
3957
|
+
function systemdAvailable() {
|
|
3958
|
+
const r = spawnSync3("systemctl", ["--user", "is-system-running"], { encoding: "utf8" });
|
|
3959
|
+
if (r.error) return false;
|
|
3960
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
3961
|
+
return !/not been booted with systemd|Failed to connect to (the )?bus|Failed to (connect|get) D-?Bus/i.test(out);
|
|
3962
|
+
}
|
|
3963
|
+
function ensureSystemdOrThrow() {
|
|
3964
|
+
if (systemdAvailable()) return;
|
|
3965
|
+
throw new Error(
|
|
3966
|
+
"\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"
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
async function installSystemd() {
|
|
3970
|
+
ensureSystemdOrThrow();
|
|
3971
|
+
const unitPath = systemdUnitPath();
|
|
3972
|
+
await mkdir8(dirname9(unitPath), { recursive: true });
|
|
3973
|
+
await ensureLogFiles();
|
|
3974
|
+
await writeFile7(unitPath, buildUnit(), "utf8");
|
|
3975
|
+
const reload = runSystemctl(["daemon-reload"]);
|
|
3976
|
+
if (!reload.ok) throw systemctlError("systemctl --user daemon-reload", reload);
|
|
3977
|
+
const enable = runSystemctl(["enable", "--now", SYSTEMD_UNIT_NAME]);
|
|
3978
|
+
if (!enable.ok) throw systemctlError("systemctl --user enable --now", enable);
|
|
3979
|
+
return statusSystemd();
|
|
3980
|
+
}
|
|
3981
|
+
async function uninstallSystemd() {
|
|
3982
|
+
if (systemdAvailable() && unitExists()) {
|
|
3983
|
+
runSystemctl(["disable", "--now", SYSTEMD_UNIT_NAME]);
|
|
3984
|
+
}
|
|
3985
|
+
await rm4(systemdUnitPath(), { force: true });
|
|
3986
|
+
if (systemdAvailable()) runSystemctl(["daemon-reload"]);
|
|
3987
|
+
}
|
|
3988
|
+
async function restartSystemd() {
|
|
3989
|
+
ensureSystemdOrThrow();
|
|
3990
|
+
if (!unitExists()) {
|
|
3991
|
+
throw new Error(`systemd unit \u672A\u5B89\u88C5\uFF1A${systemdUnitPath()}\uFF08\u5148\u8FD0\u884C \`feishu-codex-bridge start\`\uFF09`);
|
|
3992
|
+
}
|
|
3993
|
+
const restart = runSystemctl(["restart", SYSTEMD_UNIT_NAME]);
|
|
3994
|
+
if (!restart.ok) throw systemctlError("systemctl --user restart", restart);
|
|
3995
|
+
return statusSystemd();
|
|
3996
|
+
}
|
|
3997
|
+
function statusSystemd() {
|
|
3998
|
+
const installed = unitExists();
|
|
3999
|
+
const raw = installed && systemdAvailable() ? describeService() : "";
|
|
4000
|
+
return {
|
|
4001
|
+
platformName: "systemd (Linux user)",
|
|
4002
|
+
installed,
|
|
4003
|
+
running: systemdActive(),
|
|
4004
|
+
servicePath: systemdUnitPath(),
|
|
4005
|
+
stdoutPath: serviceStdoutPath(),
|
|
4006
|
+
stderrPath: serviceStderrPath(),
|
|
4007
|
+
pid: raw.match(/Main PID:\s*(\d+)/)?.[1],
|
|
4008
|
+
// On an inactive unit the "Process: <pid> ExecStart=... status=<n>" line
|
|
4009
|
+
// carries the last exit code.
|
|
4010
|
+
lastExit: raw.match(/Process:\s+\d+\s+ExecStart=.*status=(\d+)/)?.[1],
|
|
4011
|
+
raw
|
|
3691
4012
|
};
|
|
3692
4013
|
}
|
|
4014
|
+
function unitExists() {
|
|
4015
|
+
return existsSync6(systemdUnitPath());
|
|
4016
|
+
}
|
|
4017
|
+
function systemdActive() {
|
|
4018
|
+
const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
|
|
4019
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
4020
|
+
});
|
|
4021
|
+
return r.status === 0;
|
|
4022
|
+
}
|
|
4023
|
+
function describeService() {
|
|
4024
|
+
const r = runSystemctl(["status", SYSTEMD_UNIT_NAME, "--no-pager"]);
|
|
4025
|
+
return r.stdout || r.stderr || "";
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
// src/service/adapter.ts
|
|
4029
|
+
function getServiceAdapter() {
|
|
4030
|
+
if (process.platform === "darwin") {
|
|
4031
|
+
return {
|
|
4032
|
+
install: installLaunchd,
|
|
4033
|
+
uninstall: uninstallLaunchd,
|
|
4034
|
+
status: async () => statusLaunchd(),
|
|
4035
|
+
restart: restartLaunchd,
|
|
4036
|
+
logs: tailLaunchdLogs
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
if (process.platform === "win32") {
|
|
4040
|
+
return {
|
|
4041
|
+
install: installSchtask,
|
|
4042
|
+
uninstall: uninstallSchtask,
|
|
4043
|
+
status: async () => statusSchtask(),
|
|
4044
|
+
restart: restartSchtask,
|
|
4045
|
+
logs: tailServiceLogs
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
if (process.platform === "linux") {
|
|
4049
|
+
return {
|
|
4050
|
+
install: installSystemd,
|
|
4051
|
+
uninstall: uninstallSystemd,
|
|
4052
|
+
status: async () => statusSystemd(),
|
|
4053
|
+
restart: restartSystemd,
|
|
4054
|
+
logs: tailServiceLogs
|
|
4055
|
+
};
|
|
4056
|
+
}
|
|
4057
|
+
throw new Error(
|
|
4058
|
+
"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"
|
|
4059
|
+
);
|
|
4060
|
+
}
|
|
4061
|
+
function isServiceRunning() {
|
|
4062
|
+
try {
|
|
4063
|
+
if (process.platform === "darwin") return isLoaded();
|
|
4064
|
+
if (process.platform === "win32") return schtaskRunning();
|
|
4065
|
+
if (process.platform === "linux") return systemdActive();
|
|
4066
|
+
} catch {
|
|
4067
|
+
}
|
|
4068
|
+
return false;
|
|
4069
|
+
}
|
|
3693
4070
|
|
|
3694
4071
|
// src/service/update.ts
|
|
3695
4072
|
var execFileP = promisify(execFile);
|
|
3696
4073
|
var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3697
4074
|
function pkgRoot() {
|
|
3698
|
-
return
|
|
4075
|
+
return resolve5(dirname10(fileURLToPath4(import.meta.url)), "..");
|
|
3699
4076
|
}
|
|
3700
4077
|
function pkgJson() {
|
|
3701
4078
|
try {
|
|
3702
|
-
return JSON.parse(readFileSync2(
|
|
4079
|
+
return JSON.parse(readFileSync2(join11(pkgRoot(), "package.json"), "utf8"));
|
|
3703
4080
|
} catch {
|
|
3704
4081
|
return {};
|
|
3705
4082
|
}
|
|
@@ -3711,7 +4088,7 @@ function packageName() {
|
|
|
3711
4088
|
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
3712
4089
|
}
|
|
3713
4090
|
function isDevSource() {
|
|
3714
|
-
return
|
|
4091
|
+
return existsSync7(join11(pkgRoot(), ".git"));
|
|
3715
4092
|
}
|
|
3716
4093
|
function isNewer(a, b) {
|
|
3717
4094
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -3734,7 +4111,7 @@ async function latestVersion() {
|
|
|
3734
4111
|
async function installLatest(opts = {}) {
|
|
3735
4112
|
const target = `${packageName()}@latest`;
|
|
3736
4113
|
return await new Promise((resolveP) => {
|
|
3737
|
-
const child =
|
|
4114
|
+
const child = spawn4(NPM, ["install", "-g", target], {
|
|
3738
4115
|
stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"]
|
|
3739
4116
|
});
|
|
3740
4117
|
let out = "";
|
|
@@ -3750,20 +4127,16 @@ async function installLatest(opts = {}) {
|
|
|
3750
4127
|
});
|
|
3751
4128
|
}
|
|
3752
4129
|
function daemonRunning() {
|
|
3753
|
-
|
|
3754
|
-
return statusLaunchd().loaded;
|
|
3755
|
-
} catch {
|
|
3756
|
-
return false;
|
|
3757
|
-
}
|
|
4130
|
+
return isServiceRunning();
|
|
3758
4131
|
}
|
|
3759
4132
|
async function restartDaemon() {
|
|
3760
4133
|
await getServiceAdapter().restart();
|
|
3761
4134
|
}
|
|
3762
4135
|
|
|
3763
4136
|
// src/project/lifecycle.ts
|
|
3764
|
-
import { mkdir as
|
|
3765
|
-
import { existsSync as
|
|
3766
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4137
|
+
import { mkdir as mkdir9 } from "fs/promises";
|
|
4138
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4139
|
+
import { isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "path";
|
|
3767
4140
|
|
|
3768
4141
|
// src/project/git-info.ts
|
|
3769
4142
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -3898,12 +4271,12 @@ async function onboardGroup(channel, project) {
|
|
|
3898
4271
|
// src/project/lifecycle.ts
|
|
3899
4272
|
async function resolveCwd(name, existingPath) {
|
|
3900
4273
|
if (existingPath) {
|
|
3901
|
-
const cwd2 = isAbsolute2(existingPath) ? existingPath :
|
|
3902
|
-
if (!
|
|
4274
|
+
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve6(existingPath);
|
|
4275
|
+
if (!existsSync8(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
3903
4276
|
return { cwd: cwd2, blank: false };
|
|
3904
4277
|
}
|
|
3905
|
-
const cwd =
|
|
3906
|
-
await
|
|
4278
|
+
const cwd = join12(paths.projectsRootDir, name);
|
|
4279
|
+
await mkdir9(cwd, { recursive: true });
|
|
3907
4280
|
return { cwd, blank: true };
|
|
3908
4281
|
}
|
|
3909
4282
|
async function createProject(channel, input2) {
|
|
@@ -3970,12 +4343,12 @@ async function leaveChat(channel, chatId) {
|
|
|
3970
4343
|
}
|
|
3971
4344
|
|
|
3972
4345
|
// src/bot/session-store.ts
|
|
3973
|
-
import { mkdir as
|
|
3974
|
-
import { dirname as
|
|
4346
|
+
import { mkdir as mkdir10, readFile as readFile8, rename as rename5, writeFile as writeFile8 } from "fs/promises";
|
|
4347
|
+
import { dirname as dirname11 } from "path";
|
|
3975
4348
|
var FILE_VERSION3 = 1;
|
|
3976
4349
|
async function read2() {
|
|
3977
4350
|
try {
|
|
3978
|
-
const text = await
|
|
4351
|
+
const text = await readFile8(paths.sessionsFile, "utf8");
|
|
3979
4352
|
const parsed = JSON.parse(text);
|
|
3980
4353
|
return Array.isArray(parsed.sessions) ? parsed.sessions : [];
|
|
3981
4354
|
} catch (err) {
|
|
@@ -3984,10 +4357,10 @@ async function read2() {
|
|
|
3984
4357
|
}
|
|
3985
4358
|
}
|
|
3986
4359
|
async function write2(sessions) {
|
|
3987
|
-
await
|
|
4360
|
+
await mkdir10(dirname11(paths.sessionsFile), { recursive: true });
|
|
3988
4361
|
const tmp = `${paths.sessionsFile}.tmp-${process.pid}`;
|
|
3989
4362
|
const body = { version: FILE_VERSION3, sessions };
|
|
3990
|
-
await
|
|
4363
|
+
await writeFile8(tmp, `${JSON.stringify(body, null, 2)}
|
|
3991
4364
|
`, "utf8");
|
|
3992
4365
|
await rename5(tmp, paths.sessionsFile);
|
|
3993
4366
|
}
|
|
@@ -4031,8 +4404,8 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
4031
4404
|
}
|
|
4032
4405
|
|
|
4033
4406
|
// src/bot/media.ts
|
|
4034
|
-
import { mkdir as
|
|
4035
|
-
import { join as
|
|
4407
|
+
import { mkdir as mkdir11, readdir as readdir2, rm as rm5, stat as stat3 } from "fs/promises";
|
|
4408
|
+
import { join as join13 } from "path";
|
|
4036
4409
|
var MAX_IMAGES2 = 9;
|
|
4037
4410
|
var MEDIA_TTL_MS = 60 * 6e4;
|
|
4038
4411
|
var EXT_BY_CONTENT_TYPE = {
|
|
@@ -4061,7 +4434,7 @@ async function collectInboundImages(channel, msg) {
|
|
|
4061
4434
|
if (refs.length === 0) return [];
|
|
4062
4435
|
await pruneOldMedia();
|
|
4063
4436
|
try {
|
|
4064
|
-
await
|
|
4437
|
+
await mkdir11(paths.mediaDir, { recursive: true });
|
|
4065
4438
|
} catch {
|
|
4066
4439
|
}
|
|
4067
4440
|
const out = [];
|
|
@@ -4137,7 +4510,7 @@ async function downloadOne(channel, ref, index) {
|
|
|
4137
4510
|
params: { type: "image" }
|
|
4138
4511
|
});
|
|
4139
4512
|
const ext = extFromHeaders(res.headers);
|
|
4140
|
-
const file =
|
|
4513
|
+
const file = join13(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
4141
4514
|
await res.writeFile(file);
|
|
4142
4515
|
return file;
|
|
4143
4516
|
} catch (err) {
|
|
@@ -4171,10 +4544,10 @@ async function pruneOldMedia() {
|
|
|
4171
4544
|
}
|
|
4172
4545
|
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
4173
4546
|
for (const name of entries) {
|
|
4174
|
-
const file =
|
|
4547
|
+
const file = join13(paths.mediaDir, name);
|
|
4175
4548
|
try {
|
|
4176
4549
|
const st = await stat3(file);
|
|
4177
|
-
if (st.mtimeMs < cutoff) await
|
|
4550
|
+
if (st.mtimeMs < cutoff) await rm5(file, { force: true });
|
|
4178
4551
|
} catch {
|
|
4179
4552
|
}
|
|
4180
4553
|
}
|
|
@@ -5546,7 +5919,7 @@ async function startBridge(opts) {
|
|
|
5546
5919
|
|
|
5547
5920
|
// src/core/single-instance.ts
|
|
5548
5921
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync } from "fs";
|
|
5549
|
-
import { dirname as
|
|
5922
|
+
import { dirname as dirname12 } from "path";
|
|
5550
5923
|
var BridgeAlreadyRunningError = class extends Error {
|
|
5551
5924
|
constructor(pid) {
|
|
5552
5925
|
super(
|
|
@@ -5575,7 +5948,7 @@ function acquireSingleInstanceLock(appId) {
|
|
|
5575
5948
|
} catch (err) {
|
|
5576
5949
|
if (err instanceof BridgeAlreadyRunningError) throw err;
|
|
5577
5950
|
}
|
|
5578
|
-
mkdirSync2(
|
|
5951
|
+
mkdirSync2(dirname12(file), { recursive: true });
|
|
5579
5952
|
const record = { pid: process.pid, appId, startedAt: Date.now() };
|
|
5580
5953
|
writeFileSync(file, `${JSON.stringify(record)}
|
|
5581
5954
|
`, "utf8");
|
|
@@ -5644,12 +6017,12 @@ async function runStart() {
|
|
|
5644
6017
|
return;
|
|
5645
6018
|
}
|
|
5646
6019
|
const status = await getServiceAdapter().install();
|
|
5647
|
-
console.log(
|
|
6020
|
+
console.log(installedNote());
|
|
5648
6021
|
printStatus(status);
|
|
5649
6022
|
}
|
|
5650
6023
|
async function runStop() {
|
|
5651
6024
|
await getServiceAdapter().uninstall();
|
|
5652
|
-
console.log("\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u505C\u6B62\uFF0C\u5E76\u5DF2\u5173\u95ED\
|
|
6025
|
+
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
6026
|
}
|
|
5654
6027
|
async function runRestart() {
|
|
5655
6028
|
const status = await getServiceAdapter().restart();
|
|
@@ -5662,18 +6035,29 @@ async function runStatus() {
|
|
|
5662
6035
|
async function runLogs(follow) {
|
|
5663
6036
|
await getServiceAdapter().logs(follow);
|
|
5664
6037
|
}
|
|
6038
|
+
function installedNote() {
|
|
6039
|
+
switch (process.platform) {
|
|
6040
|
+
case "win32":
|
|
6041
|
+
return "\u2713 \u540E\u53F0\u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF08\u767B\u5F55\u81EA\u542F\uFF1B\u6CE8\u610F\uFF1AWindows \u8BA1\u5212\u4EFB\u52A1\u65E0\u5D29\u6E83\u81EA\u52A8\u62C9\u8D77\uFF09\u3002";
|
|
6042
|
+
case "linux":
|
|
6043
|
+
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";
|
|
6044
|
+
default:
|
|
6045
|
+
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";
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
5665
6048
|
function printStatus(status) {
|
|
5666
|
-
console.log(`
|
|
6049
|
+
console.log(`service: ${status.platformName}`);
|
|
6050
|
+
console.log(`path: ${status.servicePath}`);
|
|
5667
6051
|
console.log(`installed: ${status.installed ? "yes" : "no"}`);
|
|
5668
|
-
console.log(`
|
|
6052
|
+
console.log(`running: ${status.running ? "yes" : "no"}`);
|
|
5669
6053
|
console.log(`pid: ${status.pid ?? "-"}`);
|
|
5670
6054
|
console.log(`last exit: ${status.lastExit ?? "-"}`);
|
|
5671
6055
|
console.log(`stdout: ${status.stdoutPath}`);
|
|
5672
6056
|
console.log(`stderr: ${status.stderrPath}`);
|
|
5673
6057
|
if (!status.installed) {
|
|
5674
6058
|
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\
|
|
6059
|
+
} else if (!status.running) {
|
|
6060
|
+
console.log("\u63D0\u793A\uFF1A\u670D\u52A1\u5DF2\u6CE8\u518C\u4F46\u5F53\u524D\u672A\u8FD0\u884C\uFF08\u8BD5\u8BD5 `restart`\uFF09\u3002");
|
|
5677
6061
|
}
|
|
5678
6062
|
}
|
|
5679
6063
|
|
|
@@ -5726,7 +6110,7 @@ async function runUpdate(opts = {}) {
|
|
|
5726
6110
|
}
|
|
5727
6111
|
|
|
5728
6112
|
// src/cli/commands/bot.ts
|
|
5729
|
-
import { rm as
|
|
6113
|
+
import { rm as rm6 } from "fs/promises";
|
|
5730
6114
|
async function runBotInit(name) {
|
|
5731
6115
|
if (!ensureCodex()) {
|
|
5732
6116
|
process.exitCode = 1;
|
|
@@ -5781,7 +6165,7 @@ async function runBotRm(name) {
|
|
|
5781
6165
|
}
|
|
5782
6166
|
const after = await removeBot(bot2.appId);
|
|
5783
6167
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
5784
|
-
await
|
|
6168
|
+
await rm6(botDir(bot2.appId), { recursive: true, force: true });
|
|
5785
6169
|
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
6170
|
if (after.bots.length === 0) {
|
|
5787
6171
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|
|
@@ -5839,15 +6223,15 @@ async function secretsRemove(id) {
|
|
|
5839
6223
|
console.log(ok ? `\u2713 \u5DF2\u5220\u9664: ${id}` : `\u672A\u627E\u5230: ${id}`);
|
|
5840
6224
|
}
|
|
5841
6225
|
function readStdin() {
|
|
5842
|
-
return new Promise((
|
|
6226
|
+
return new Promise((resolve7) => {
|
|
5843
6227
|
let data = "";
|
|
5844
6228
|
if (process.stdin.isTTY) {
|
|
5845
|
-
|
|
6229
|
+
resolve7("");
|
|
5846
6230
|
return;
|
|
5847
6231
|
}
|
|
5848
6232
|
process.stdin.setEncoding("utf8");
|
|
5849
6233
|
process.stdin.on("data", (c) => data += c);
|
|
5850
|
-
process.stdin.on("end", () =>
|
|
6234
|
+
process.stdin.on("end", () => resolve7(data));
|
|
5851
6235
|
});
|
|
5852
6236
|
}
|
|
5853
6237
|
|
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.2-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",
|