codex-to-im 1.0.41 → 1.0.42
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.mjs +106 -57
- package/dist/daemon.mjs +132 -23
- package/dist/ui-server.mjs +181 -130
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -5,9 +5,9 @@ import { createRequire } from 'module'; const require = createRequire(import.met
|
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
|
|
7
7
|
// src/service-manager.ts
|
|
8
|
-
import
|
|
8
|
+
import fs3 from "node:fs";
|
|
9
9
|
import os2 from "node:os";
|
|
10
|
-
import
|
|
10
|
+
import path3 from "node:path";
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
|
|
@@ -233,42 +233,79 @@ function loadConfig() {
|
|
|
233
233
|
return expandConfig(empty);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
// src/
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
// src/bridge-instance-lock.ts
|
|
237
|
+
import fs2 from "node:fs";
|
|
238
|
+
import path2 from "node:path";
|
|
239
239
|
var runtimeDir = path2.join(CTI_HOME, "runtime");
|
|
240
|
-
var
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
var bridgeInstanceLockFile = path2.join(runtimeDir, "bridge.instance.lock");
|
|
241
|
+
function readJsonFile(filePath, fallback) {
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
244
|
+
} catch {
|
|
245
|
+
return fallback;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function isProcessAlive(pid) {
|
|
249
|
+
if (!pid) return false;
|
|
250
|
+
try {
|
|
251
|
+
process.kill(pid, 0);
|
|
252
|
+
return true;
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function readBridgeInstanceLock(filePath = bridgeInstanceLockFile) {
|
|
258
|
+
const parsed = readJsonFile(filePath, null);
|
|
259
|
+
const pid = Number(parsed?.pid);
|
|
260
|
+
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
261
|
+
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
262
|
+
return { pid, createdAt };
|
|
263
|
+
}
|
|
264
|
+
function clearStaleBridgeInstanceLock(filePath = bridgeInstanceLockFile, isAlive = isProcessAlive) {
|
|
265
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
266
|
+
if (existing && isAlive(existing.pid)) return;
|
|
267
|
+
try {
|
|
268
|
+
fs2.unlinkSync(filePath);
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/service-manager.ts
|
|
274
|
+
var moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
275
|
+
var packageRoot = path3.resolve(moduleDir, "..");
|
|
276
|
+
var runtimeDir2 = path3.join(CTI_HOME, "runtime");
|
|
277
|
+
var logsDir = path3.join(CTI_HOME, "logs");
|
|
278
|
+
var bridgePidFile = path3.join(runtimeDir2, "bridge.pid");
|
|
279
|
+
var bridgeStatusFile = path3.join(runtimeDir2, "status.json");
|
|
280
|
+
var bridgeStartLockFile = path3.join(runtimeDir2, "bridge.start.lock");
|
|
281
|
+
var uiStatusFile = path3.join(runtimeDir2, "ui-server.json");
|
|
245
282
|
var uiPort = 4781;
|
|
246
283
|
var bridgeAutostartTaskName = "CodexToIMBridge";
|
|
247
|
-
var bridgeAutostartLauncherFile =
|
|
248
|
-
var npmUninstallLogFile =
|
|
284
|
+
var bridgeAutostartLauncherFile = path3.join(runtimeDir2, "bridge-autostart.ps1");
|
|
285
|
+
var npmUninstallLogFile = path3.join(runtimeDir2, "npm-uninstall.log");
|
|
249
286
|
var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
|
|
250
287
|
var BRIDGE_START_LOCK_STALE_MS = 3e4;
|
|
251
288
|
function ensureDirs() {
|
|
252
|
-
|
|
253
|
-
|
|
289
|
+
fs3.mkdirSync(runtimeDir2, { recursive: true });
|
|
290
|
+
fs3.mkdirSync(logsDir, { recursive: true });
|
|
254
291
|
}
|
|
255
|
-
function
|
|
292
|
+
function readJsonFile2(filePath, fallback) {
|
|
256
293
|
try {
|
|
257
|
-
return JSON.parse(
|
|
294
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
258
295
|
} catch {
|
|
259
296
|
return fallback;
|
|
260
297
|
}
|
|
261
298
|
}
|
|
262
299
|
function readPid(filePath) {
|
|
263
300
|
try {
|
|
264
|
-
const raw =
|
|
301
|
+
const raw = fs3.readFileSync(filePath, "utf-8").trim();
|
|
265
302
|
const pid = Number(raw);
|
|
266
303
|
return Number.isFinite(pid) ? pid : void 0;
|
|
267
304
|
} catch {
|
|
268
305
|
return void 0;
|
|
269
306
|
}
|
|
270
307
|
}
|
|
271
|
-
function
|
|
308
|
+
function isProcessAlive2(pid) {
|
|
272
309
|
if (!pid) return false;
|
|
273
310
|
try {
|
|
274
311
|
process.kill(pid, 0);
|
|
@@ -277,32 +314,37 @@ function isProcessAlive(pid) {
|
|
|
277
314
|
return false;
|
|
278
315
|
}
|
|
279
316
|
}
|
|
280
|
-
function collectTrackedBridgePids(bridgePid, statusPid) {
|
|
317
|
+
function collectTrackedBridgePids(bridgePid, statusPid, instanceLockPid) {
|
|
281
318
|
const unique = /* @__PURE__ */ new Set();
|
|
282
|
-
for (const pid of [bridgePid, statusPid]) {
|
|
319
|
+
for (const pid of [bridgePid, statusPid, instanceLockPid]) {
|
|
283
320
|
if (Number.isFinite(pid) && pid > 0) {
|
|
284
321
|
unique.add(pid);
|
|
285
322
|
}
|
|
286
323
|
}
|
|
287
324
|
return [...unique];
|
|
288
325
|
}
|
|
289
|
-
function resolveTrackedBridgePid(bridgePid, statusPid, isAlive =
|
|
326
|
+
function resolveTrackedBridgePid(bridgePid, statusPid, instanceLockPid, isAlive = isProcessAlive2) {
|
|
290
327
|
if (isAlive(bridgePid)) return bridgePid;
|
|
291
328
|
if (isAlive(statusPid)) return statusPid;
|
|
292
|
-
|
|
329
|
+
if (isAlive(instanceLockPid)) return instanceLockPid;
|
|
330
|
+
return bridgePid ?? statusPid ?? instanceLockPid;
|
|
293
331
|
}
|
|
294
332
|
function getTrackedBridgePids(status) {
|
|
295
|
-
const resolvedStatus = status ??
|
|
296
|
-
return collectTrackedBridgePids(
|
|
333
|
+
const resolvedStatus = status ?? readJsonFile2(bridgeStatusFile, { running: false });
|
|
334
|
+
return collectTrackedBridgePids(
|
|
335
|
+
readPid(bridgePidFile),
|
|
336
|
+
resolvedStatus.pid,
|
|
337
|
+
readBridgeInstanceLock()?.pid
|
|
338
|
+
);
|
|
297
339
|
}
|
|
298
340
|
function clearBridgePidFile() {
|
|
299
341
|
try {
|
|
300
|
-
|
|
342
|
+
fs3.unlinkSync(bridgePidFile);
|
|
301
343
|
} catch {
|
|
302
344
|
}
|
|
303
345
|
}
|
|
304
346
|
function readBridgeStartLock(filePath = bridgeStartLockFile) {
|
|
305
|
-
const parsed =
|
|
347
|
+
const parsed = readJsonFile2(filePath, null);
|
|
306
348
|
const pid = Number(parsed?.pid);
|
|
307
349
|
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
308
350
|
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
@@ -312,7 +354,7 @@ function isBridgeStartLockStale(lock, options = {}) {
|
|
|
312
354
|
if (!lock) return true;
|
|
313
355
|
const nowMs = options.nowMs ?? Date.now();
|
|
314
356
|
const staleMs = options.staleMs ?? BRIDGE_START_LOCK_STALE_MS;
|
|
315
|
-
const isAlive = options.isAlive ??
|
|
357
|
+
const isAlive = options.isAlive ?? isProcessAlive2;
|
|
316
358
|
const createdAtMs = Date.parse(lock.createdAt);
|
|
317
359
|
if (!Number.isFinite(createdAtMs)) return true;
|
|
318
360
|
if (!isAlive(lock.pid)) return true;
|
|
@@ -328,7 +370,7 @@ function tryAcquireBridgeStartLock(options = {}) {
|
|
|
328
370
|
};
|
|
329
371
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
330
372
|
try {
|
|
331
|
-
|
|
373
|
+
fs3.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
332
374
|
return { acquired: true };
|
|
333
375
|
} catch (error) {
|
|
334
376
|
const code = error.code;
|
|
@@ -342,7 +384,7 @@ function tryAcquireBridgeStartLock(options = {}) {
|
|
|
342
384
|
return { acquired: false, holderPid: existing2?.pid };
|
|
343
385
|
}
|
|
344
386
|
try {
|
|
345
|
-
|
|
387
|
+
fs3.unlinkSync(filePath);
|
|
346
388
|
} catch {
|
|
347
389
|
}
|
|
348
390
|
}
|
|
@@ -354,14 +396,14 @@ function releaseBridgeStartLock(filePath = bridgeStartLockFile, ownerPid = proce
|
|
|
354
396
|
const existing = readBridgeStartLock(filePath);
|
|
355
397
|
if (!existing) {
|
|
356
398
|
try {
|
|
357
|
-
|
|
399
|
+
fs3.unlinkSync(filePath);
|
|
358
400
|
} catch {
|
|
359
401
|
}
|
|
360
402
|
return;
|
|
361
403
|
}
|
|
362
404
|
if (existing.pid !== ownerPid) return;
|
|
363
405
|
try {
|
|
364
|
-
|
|
406
|
+
fs3.unlinkSync(filePath);
|
|
365
407
|
} catch {
|
|
366
408
|
}
|
|
367
409
|
}
|
|
@@ -443,11 +485,11 @@ function ensureBridgeAutostartLauncher() {
|
|
|
443
485
|
" }",
|
|
444
486
|
" } catch { }",
|
|
445
487
|
"}",
|
|
446
|
-
`& $node '${escapePowerShellSingleQuoted(
|
|
488
|
+
`& $node '${escapePowerShellSingleQuoted(path3.join(packageRoot, "dist", "cli.mjs"))}' start`,
|
|
447
489
|
"exit $LASTEXITCODE",
|
|
448
490
|
""
|
|
449
491
|
].join("\r\n");
|
|
450
|
-
|
|
492
|
+
fs3.writeFileSync(bridgeAutostartLauncherFile, content, "utf-8");
|
|
451
493
|
return bridgeAutostartLauncherFile;
|
|
452
494
|
}
|
|
453
495
|
function parsePowerShellJson(raw) {
|
|
@@ -491,7 +533,7 @@ function buildDeferredGlobalNpmUninstallLaunch(options = {}) {
|
|
|
491
533
|
async function launchDeferredGlobalNpmUninstall() {
|
|
492
534
|
ensureDirs();
|
|
493
535
|
const launch = buildDeferredGlobalNpmUninstallLaunch();
|
|
494
|
-
|
|
536
|
+
fs3.writeFileSync(
|
|
495
537
|
launch.logPath,
|
|
496
538
|
[
|
|
497
539
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] Scheduling global uninstall.`,
|
|
@@ -519,14 +561,18 @@ function getUiServerUrl(port = uiPort) {
|
|
|
519
561
|
return `http://127.0.0.1:${port}`;
|
|
520
562
|
}
|
|
521
563
|
function getCurrentUiServerUrl() {
|
|
522
|
-
const status =
|
|
564
|
+
const status = readJsonFile2(uiStatusFile, null);
|
|
523
565
|
if (!status?.port) return void 0;
|
|
524
566
|
return getUiServerUrl(status.port);
|
|
525
567
|
}
|
|
526
568
|
function getBridgeStatus() {
|
|
527
|
-
const status =
|
|
528
|
-
const pid = resolveTrackedBridgePid(
|
|
529
|
-
|
|
569
|
+
const status = readJsonFile2(bridgeStatusFile, { running: false });
|
|
570
|
+
const pid = resolveTrackedBridgePid(
|
|
571
|
+
readPid(bridgePidFile),
|
|
572
|
+
status.pid,
|
|
573
|
+
readBridgeInstanceLock()?.pid
|
|
574
|
+
);
|
|
575
|
+
if (!isProcessAlive2(pid)) {
|
|
530
576
|
return {
|
|
531
577
|
...status,
|
|
532
578
|
pid,
|
|
@@ -540,8 +586,8 @@ function getBridgeStatus() {
|
|
|
540
586
|
};
|
|
541
587
|
}
|
|
542
588
|
function getUiServerStatus() {
|
|
543
|
-
const status =
|
|
544
|
-
if (!
|
|
589
|
+
const status = readJsonFile2(uiStatusFile, { running: false, port: uiPort });
|
|
590
|
+
if (!isProcessAlive2(status.pid)) {
|
|
545
591
|
return {
|
|
546
592
|
...status,
|
|
547
593
|
running: false,
|
|
@@ -626,7 +672,7 @@ async function waitForUiServer(timeoutMs = 15e3) {
|
|
|
626
672
|
async function startBridge() {
|
|
627
673
|
ensureDirs();
|
|
628
674
|
const current = getBridgeStatus();
|
|
629
|
-
const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid &&
|
|
675
|
+
const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive2(pid));
|
|
630
676
|
if (current.running && extraAlivePids.length === 0) return current;
|
|
631
677
|
if (current.running && extraAlivePids.length > 0) {
|
|
632
678
|
await stopBridge();
|
|
@@ -651,17 +697,17 @@ async function startBridge() {
|
|
|
651
697
|
startLockHeld = true;
|
|
652
698
|
try {
|
|
653
699
|
const currentAfterLock = getBridgeStatus();
|
|
654
|
-
const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid &&
|
|
700
|
+
const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid && isProcessAlive2(pid));
|
|
655
701
|
if (currentAfterLock.running && extraAlivePidsAfterLock.length === 0) return currentAfterLock;
|
|
656
702
|
if (currentAfterLock.running && extraAlivePidsAfterLock.length > 0) {
|
|
657
703
|
await stopBridge();
|
|
658
704
|
}
|
|
659
|
-
const daemonEntry =
|
|
660
|
-
if (!
|
|
705
|
+
const daemonEntry = path3.join(packageRoot, "dist", "daemon.mjs");
|
|
706
|
+
if (!fs3.existsSync(daemonEntry)) {
|
|
661
707
|
throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
|
|
662
708
|
}
|
|
663
|
-
const stdoutFd =
|
|
664
|
-
const stderrFd =
|
|
709
|
+
const stdoutFd = fs3.openSync(path3.join(logsDir, "bridge-launcher.out.log"), "a");
|
|
710
|
+
const stderrFd = fs3.openSync(path3.join(logsDir, "bridge-launcher.err.log"), "a");
|
|
665
711
|
const child = spawn(process.execPath, [daemonEntry], {
|
|
666
712
|
cwd: packageRoot,
|
|
667
713
|
detached: true,
|
|
@@ -684,10 +730,11 @@ async function startBridge() {
|
|
|
684
730
|
}
|
|
685
731
|
}
|
|
686
732
|
async function stopBridge() {
|
|
687
|
-
const status =
|
|
688
|
-
const pids = getTrackedBridgePids(status).filter((pid) =>
|
|
733
|
+
const status = readJsonFile2(bridgeStatusFile, { running: false });
|
|
734
|
+
const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive2(pid));
|
|
689
735
|
if (pids.length === 0) {
|
|
690
736
|
clearBridgePidFile();
|
|
737
|
+
clearStaleBridgeInstanceLock();
|
|
691
738
|
return { ...getBridgeStatus(), running: false };
|
|
692
739
|
}
|
|
693
740
|
for (const pid of pids) {
|
|
@@ -709,13 +756,15 @@ async function stopBridge() {
|
|
|
709
756
|
}
|
|
710
757
|
const startedAt = Date.now();
|
|
711
758
|
while (Date.now() - startedAt < 1e4) {
|
|
712
|
-
if (pids.every((pid) => !
|
|
759
|
+
if (pids.every((pid) => !isProcessAlive2(pid))) {
|
|
713
760
|
clearBridgePidFile();
|
|
761
|
+
clearStaleBridgeInstanceLock();
|
|
714
762
|
return getBridgeStatus();
|
|
715
763
|
}
|
|
716
764
|
await sleep(300);
|
|
717
765
|
}
|
|
718
766
|
clearBridgePidFile();
|
|
767
|
+
clearStaleBridgeInstanceLock();
|
|
719
768
|
return getBridgeStatus();
|
|
720
769
|
}
|
|
721
770
|
async function getBridgeAutostartStatus() {
|
|
@@ -804,8 +853,8 @@ async function uninstallBridgeAutostart() {
|
|
|
804
853
|
].join("; ");
|
|
805
854
|
await runPowerShell(script);
|
|
806
855
|
try {
|
|
807
|
-
if (
|
|
808
|
-
|
|
856
|
+
if (fs3.existsSync(bridgeAutostartLauncherFile)) {
|
|
857
|
+
fs3.unlinkSync(bridgeAutostartLauncherFile);
|
|
809
858
|
}
|
|
810
859
|
} catch {
|
|
811
860
|
}
|
|
@@ -836,12 +885,12 @@ async function ensureUiServerRunning() {
|
|
|
836
885
|
ensureDirs();
|
|
837
886
|
const current = getUiServerStatus();
|
|
838
887
|
if (current.running) return current;
|
|
839
|
-
const serverEntry =
|
|
840
|
-
if (!
|
|
888
|
+
const serverEntry = path3.join(packageRoot, "dist", "ui-server.mjs");
|
|
889
|
+
if (!fs3.existsSync(serverEntry)) {
|
|
841
890
|
throw new Error(`UI server bundle not found at ${serverEntry}. Run npm run build first.`);
|
|
842
891
|
}
|
|
843
|
-
const stdoutFd =
|
|
844
|
-
const stderrFd =
|
|
892
|
+
const stdoutFd = fs3.openSync(path3.join(logsDir, "ui-server.out.log"), "a");
|
|
893
|
+
const stderrFd = fs3.openSync(path3.join(logsDir, "ui-server.err.log"), "a");
|
|
845
894
|
const child = spawn(process.execPath, [serverEntry], {
|
|
846
895
|
cwd: packageRoot,
|
|
847
896
|
detached: true,
|
|
@@ -861,7 +910,7 @@ async function ensureUiServerRunning() {
|
|
|
861
910
|
}
|
|
862
911
|
async function stopUiServer() {
|
|
863
912
|
const status = getUiServerStatus();
|
|
864
|
-
if (!status.pid || !
|
|
913
|
+
if (!status.pid || !isProcessAlive2(status.pid)) {
|
|
865
914
|
const next2 = { ...status, running: false };
|
|
866
915
|
writeUiServerStatus(next2);
|
|
867
916
|
return next2;
|
|
@@ -898,7 +947,7 @@ async function stopUiServer() {
|
|
|
898
947
|
}
|
|
899
948
|
function writeUiServerStatus(status) {
|
|
900
949
|
ensureDirs();
|
|
901
|
-
|
|
950
|
+
fs3.writeFileSync(uiStatusFile, JSON.stringify(status, null, 2), "utf-8");
|
|
902
951
|
}
|
|
903
952
|
function openBrowser(url) {
|
|
904
953
|
if (process.platform === "win32") {
|
package/dist/daemon.mjs
CHANGED
|
@@ -3935,9 +3935,9 @@ var codex_provider_exports = {};
|
|
|
3935
3935
|
__export(codex_provider_exports, {
|
|
3936
3936
|
CodexProvider: () => CodexProvider
|
|
3937
3937
|
});
|
|
3938
|
-
import
|
|
3938
|
+
import fs15 from "node:fs";
|
|
3939
3939
|
import os4 from "node:os";
|
|
3940
|
-
import
|
|
3940
|
+
import path16 from "node:path";
|
|
3941
3941
|
function toApprovalPolicy(permissionMode) {
|
|
3942
3942
|
switch (permissionMode) {
|
|
3943
3943
|
case "never":
|
|
@@ -3964,7 +3964,7 @@ function normalizeCodexErrorMessage(message) {
|
|
|
3964
3964
|
if (!trimmed) return "Codex \u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
3965
3965
|
const lower = trimmed.toLowerCase();
|
|
3966
3966
|
if (lower.includes("timeout waiting for child process to exit") || lower.includes("reconnecting...")) {
|
|
3967
|
-
return "Codex \u4F1A\u8BDD\u6062\u590D\u5931\u8D25\uFF0C\u4E0A\u4E00\u8F6E\u6267\u884C\u8FDB\u7A0B\u672A\u6B63\u5E38\u9000\u51FA\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5982\u679C\u8FDE\u7EED\u5931\u8D25\uFF0C\u8BF7\u65B0\u5F00\u7EBF\u7A0B\u6216\u5207\u6362\u5230
|
|
3967
|
+
return "Codex \u4F1A\u8BDD\u6062\u590D\u5931\u8D25\uFF0C\u4E0A\u4E00\u8F6E\u6267\u884C\u8FDB\u7A0B\u672A\u6B63\u5E38\u9000\u51FA\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5982\u679C\u8FDE\u7EED\u5931\u8D25\uFF0C\u8BF7\u65B0\u5F00\u7EBF\u7A0B\u6216\u5207\u6362\u5230 /t 0\u3002";
|
|
3968
3968
|
}
|
|
3969
3969
|
return trimmed;
|
|
3970
3970
|
}
|
|
@@ -4028,6 +4028,9 @@ var init_codex_provider = __esm({
|
|
|
4028
4028
|
threadIds = /* @__PURE__ */ new Map();
|
|
4029
4029
|
constructor(_pendingPerms) {
|
|
4030
4030
|
}
|
|
4031
|
+
clearCachedThreadId(sessionId) {
|
|
4032
|
+
this.threadIds.delete(sessionId);
|
|
4033
|
+
}
|
|
4031
4034
|
/**
|
|
4032
4035
|
* Lazily load the Codex SDK. Throws a clear error if the installation is incomplete.
|
|
4033
4036
|
*/
|
|
@@ -4081,13 +4084,13 @@ var init_codex_provider = __esm({
|
|
|
4081
4084
|
{ type: "text", text: params.prompt }
|
|
4082
4085
|
];
|
|
4083
4086
|
for (const file of imageFiles) {
|
|
4084
|
-
if (file.filePath &&
|
|
4087
|
+
if (file.filePath && fs15.existsSync(file.filePath)) {
|
|
4085
4088
|
parts.push({ type: "local_image", path: file.filePath });
|
|
4086
4089
|
continue;
|
|
4087
4090
|
}
|
|
4088
4091
|
const ext = MIME_EXT[file.type] || ".png";
|
|
4089
|
-
const tmpPath =
|
|
4090
|
-
|
|
4092
|
+
const tmpPath = path16.join(os4.tmpdir(), `cti-img-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
|
|
4093
|
+
fs15.writeFileSync(tmpPath, Buffer.from(file.data, "base64"));
|
|
4091
4094
|
tempFiles.push(tmpPath);
|
|
4092
4095
|
parts.push({ type: "local_image", path: tmpPath });
|
|
4093
4096
|
}
|
|
@@ -4159,12 +4162,14 @@ var init_codex_provider = __esm({
|
|
|
4159
4162
|
}
|
|
4160
4163
|
case "turn.failed": {
|
|
4161
4164
|
const error = event.error?.message;
|
|
4165
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4162
4166
|
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Turn failed")));
|
|
4163
4167
|
sawTerminalEvent = true;
|
|
4164
4168
|
break;
|
|
4165
4169
|
}
|
|
4166
4170
|
case "error": {
|
|
4167
4171
|
const error = event.message;
|
|
4172
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4168
4173
|
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Thread error")));
|
|
4169
4174
|
sawTerminalEvent = true;
|
|
4170
4175
|
break;
|
|
@@ -4187,10 +4192,12 @@ var init_codex_provider = __esm({
|
|
|
4187
4192
|
const message = err instanceof Error ? err.message : String(err);
|
|
4188
4193
|
if (savedThreadId && !retryFresh && !sawAnyEvent && shouldRetryFreshThread(message)) {
|
|
4189
4194
|
console.warn("[codex-provider] Resume failed, retrying with a fresh thread:", message);
|
|
4195
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4190
4196
|
savedThreadId = void 0;
|
|
4191
4197
|
retryFresh = true;
|
|
4192
4198
|
continue;
|
|
4193
4199
|
}
|
|
4200
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4194
4201
|
throw err;
|
|
4195
4202
|
}
|
|
4196
4203
|
}
|
|
@@ -4198,6 +4205,7 @@ var init_codex_provider = __esm({
|
|
|
4198
4205
|
} catch (err) {
|
|
4199
4206
|
const message = err instanceof Error ? err.message : String(err);
|
|
4200
4207
|
console.error("[codex-provider] Error:", err instanceof Error ? err.stack || err.message : err);
|
|
4208
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4201
4209
|
try {
|
|
4202
4210
|
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(message)));
|
|
4203
4211
|
controller.close();
|
|
@@ -4206,7 +4214,7 @@ var init_codex_provider = __esm({
|
|
|
4206
4214
|
} finally {
|
|
4207
4215
|
for (const tmp of tempFiles) {
|
|
4208
4216
|
try {
|
|
4209
|
-
|
|
4217
|
+
fs15.unlinkSync(tmp);
|
|
4210
4218
|
} catch {
|
|
4211
4219
|
}
|
|
4212
4220
|
}
|
|
@@ -4318,6 +4326,7 @@ var init_codex_provider = __esm({
|
|
|
4318
4326
|
break;
|
|
4319
4327
|
}
|
|
4320
4328
|
case "error": {
|
|
4329
|
+
this.clearCachedThreadId(sessionId);
|
|
4321
4330
|
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(item.message || "Codex error")));
|
|
4322
4331
|
break;
|
|
4323
4332
|
}
|
|
@@ -4339,8 +4348,8 @@ var init_codex_provider = __esm({
|
|
|
4339
4348
|
});
|
|
4340
4349
|
|
|
4341
4350
|
// src/main.ts
|
|
4342
|
-
import
|
|
4343
|
-
import
|
|
4351
|
+
import fs16 from "node:fs";
|
|
4352
|
+
import path17 from "node:path";
|
|
4344
4353
|
import crypto10 from "node:crypto";
|
|
4345
4354
|
|
|
4346
4355
|
// src/lib/bridge/context.ts
|
|
@@ -7160,20 +7169,20 @@ ${trimmedResponse}`;
|
|
|
7160
7169
|
buffer = Buffer.concat(chunks);
|
|
7161
7170
|
} catch (streamErr) {
|
|
7162
7171
|
console.warn("[feishu-adapter] Stream read failed, falling back to writeFile:", streamErr instanceof Error ? streamErr.message : streamErr);
|
|
7163
|
-
const
|
|
7172
|
+
const fs17 = await import("fs");
|
|
7164
7173
|
const os5 = await import("os");
|
|
7165
|
-
const
|
|
7166
|
-
const tmpPath =
|
|
7174
|
+
const path18 = await import("path");
|
|
7175
|
+
const tmpPath = path18.join(os5.tmpdir(), `feishu-dl-${crypto2.randomUUID()}`);
|
|
7167
7176
|
try {
|
|
7168
7177
|
await res.writeFile(tmpPath);
|
|
7169
|
-
buffer =
|
|
7178
|
+
buffer = fs17.readFileSync(tmpPath);
|
|
7170
7179
|
if (buffer.length > MAX_FILE_SIZE) {
|
|
7171
7180
|
console.warn(`[feishu-adapter] Resource too large (>${MAX_FILE_SIZE} bytes), key: ${fileKey}`);
|
|
7172
7181
|
return null;
|
|
7173
7182
|
}
|
|
7174
7183
|
} finally {
|
|
7175
7184
|
try {
|
|
7176
|
-
|
|
7185
|
+
fs17.unlinkSync(tmpPath);
|
|
7177
7186
|
} catch {
|
|
7178
7187
|
}
|
|
7179
7188
|
}
|
|
@@ -23205,10 +23214,87 @@ function setupLogger() {
|
|
|
23205
23214
|
console.warn = (...args) => write("WARN", args);
|
|
23206
23215
|
}
|
|
23207
23216
|
|
|
23217
|
+
// src/bridge-instance-lock.ts
|
|
23218
|
+
import fs14 from "node:fs";
|
|
23219
|
+
import path15 from "node:path";
|
|
23220
|
+
var runtimeDir = path15.join(CTI_HOME, "runtime");
|
|
23221
|
+
var bridgeInstanceLockFile = path15.join(runtimeDir, "bridge.instance.lock");
|
|
23222
|
+
function readJsonFile(filePath, fallback) {
|
|
23223
|
+
try {
|
|
23224
|
+
return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
23225
|
+
} catch {
|
|
23226
|
+
return fallback;
|
|
23227
|
+
}
|
|
23228
|
+
}
|
|
23229
|
+
function isProcessAlive(pid) {
|
|
23230
|
+
if (!pid) return false;
|
|
23231
|
+
try {
|
|
23232
|
+
process.kill(pid, 0);
|
|
23233
|
+
return true;
|
|
23234
|
+
} catch {
|
|
23235
|
+
return false;
|
|
23236
|
+
}
|
|
23237
|
+
}
|
|
23238
|
+
function readBridgeInstanceLock(filePath = bridgeInstanceLockFile) {
|
|
23239
|
+
const parsed = readJsonFile(filePath, null);
|
|
23240
|
+
const pid = Number(parsed?.pid);
|
|
23241
|
+
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
23242
|
+
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
23243
|
+
return { pid, createdAt };
|
|
23244
|
+
}
|
|
23245
|
+
function tryAcquireBridgeInstanceLock(options = {}) {
|
|
23246
|
+
const filePath = options.filePath ?? bridgeInstanceLockFile;
|
|
23247
|
+
const ownerPid = options.ownerPid ?? process.pid;
|
|
23248
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
23249
|
+
const isAlive = options.isAlive ?? isProcessAlive;
|
|
23250
|
+
const payload = {
|
|
23251
|
+
pid: ownerPid,
|
|
23252
|
+
createdAt: new Date(nowMs).toISOString()
|
|
23253
|
+
};
|
|
23254
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
23255
|
+
try {
|
|
23256
|
+
fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
|
|
23257
|
+
fs14.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
23258
|
+
return { acquired: true };
|
|
23259
|
+
} catch (error) {
|
|
23260
|
+
const code2 = error.code;
|
|
23261
|
+
if (code2 !== "EEXIST") throw error;
|
|
23262
|
+
const existing2 = readBridgeInstanceLock(filePath);
|
|
23263
|
+
if (existing2 && existing2.pid !== ownerPid && isAlive(existing2.pid)) {
|
|
23264
|
+
return { acquired: false, holderPid: existing2.pid };
|
|
23265
|
+
}
|
|
23266
|
+
try {
|
|
23267
|
+
fs14.unlinkSync(filePath);
|
|
23268
|
+
} catch {
|
|
23269
|
+
}
|
|
23270
|
+
}
|
|
23271
|
+
}
|
|
23272
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
23273
|
+
if (existing && existing.pid !== ownerPid && isAlive(existing.pid)) {
|
|
23274
|
+
return { acquired: false, holderPid: existing.pid };
|
|
23275
|
+
}
|
|
23276
|
+
return existing?.pid === ownerPid ? { acquired: true } : { acquired: false, holderPid: existing?.pid };
|
|
23277
|
+
}
|
|
23278
|
+
function releaseBridgeInstanceLock(filePath = bridgeInstanceLockFile, ownerPid = process.pid) {
|
|
23279
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
23280
|
+
if (!existing) {
|
|
23281
|
+
try {
|
|
23282
|
+
fs14.unlinkSync(filePath);
|
|
23283
|
+
} catch {
|
|
23284
|
+
}
|
|
23285
|
+
return;
|
|
23286
|
+
}
|
|
23287
|
+
if (existing.pid !== ownerPid) return;
|
|
23288
|
+
try {
|
|
23289
|
+
fs14.unlinkSync(filePath);
|
|
23290
|
+
} catch {
|
|
23291
|
+
}
|
|
23292
|
+
}
|
|
23293
|
+
|
|
23208
23294
|
// src/main.ts
|
|
23209
|
-
var RUNTIME_DIR =
|
|
23210
|
-
var STATUS_FILE =
|
|
23211
|
-
var PID_FILE =
|
|
23295
|
+
var RUNTIME_DIR = path17.join(CTI_HOME, "runtime");
|
|
23296
|
+
var STATUS_FILE = path17.join(RUNTIME_DIR, "status.json");
|
|
23297
|
+
var PID_FILE = path17.join(RUNTIME_DIR, "bridge.pid");
|
|
23212
23298
|
async function resolveProvider(config2, pendingPerms) {
|
|
23213
23299
|
const runtime = config2.runtime;
|
|
23214
23300
|
if (runtime === "codex") {
|
|
@@ -23258,16 +23344,16 @@ async function resolveProvider(config2, pendingPerms) {
|
|
|
23258
23344
|
return new SDKLLMProvider(pendingPerms, cliPath, config2.autoApprove);
|
|
23259
23345
|
}
|
|
23260
23346
|
function writeStatus(info) {
|
|
23261
|
-
|
|
23347
|
+
fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
23262
23348
|
let existing = {};
|
|
23263
23349
|
try {
|
|
23264
|
-
existing = JSON.parse(
|
|
23350
|
+
existing = JSON.parse(fs16.readFileSync(STATUS_FILE, "utf-8"));
|
|
23265
23351
|
} catch {
|
|
23266
23352
|
}
|
|
23267
23353
|
const merged = { ...existing, ...info };
|
|
23268
23354
|
const tmp = STATUS_FILE + ".tmp";
|
|
23269
|
-
|
|
23270
|
-
|
|
23355
|
+
fs16.writeFileSync(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
23356
|
+
fs16.renameSync(tmp, STATUS_FILE);
|
|
23271
23357
|
}
|
|
23272
23358
|
function getRunningChannels() {
|
|
23273
23359
|
return getStatus().adapters.map((adapter) => adapter.channelType).sort();
|
|
@@ -23276,6 +23362,24 @@ function getAdapterStatuses() {
|
|
|
23276
23362
|
return getStatus().adapters;
|
|
23277
23363
|
}
|
|
23278
23364
|
async function main() {
|
|
23365
|
+
const lockState = tryAcquireBridgeInstanceLock();
|
|
23366
|
+
if (!lockState.acquired) {
|
|
23367
|
+
const holderPid = lockState.holderPid;
|
|
23368
|
+
writeStatus({
|
|
23369
|
+
running: true,
|
|
23370
|
+
...Number.isFinite(holderPid) && holderPid ? { pid: holderPid } : {}
|
|
23371
|
+
});
|
|
23372
|
+
console.log(
|
|
23373
|
+
`[codex-to-im] Another bridge daemon is already running${holderPid ? ` (PID: ${holderPid})` : ""}. Exiting duplicate launcher.`
|
|
23374
|
+
);
|
|
23375
|
+
process.exit(0);
|
|
23376
|
+
}
|
|
23377
|
+
let instanceLockHeld = true;
|
|
23378
|
+
const releaseInstanceLock = () => {
|
|
23379
|
+
if (!instanceLockHeld) return;
|
|
23380
|
+
releaseBridgeInstanceLock(void 0, process.pid);
|
|
23381
|
+
instanceLockHeld = false;
|
|
23382
|
+
};
|
|
23279
23383
|
const config2 = loadConfig();
|
|
23280
23384
|
setupLogger();
|
|
23281
23385
|
const runId = crypto10.randomUUID();
|
|
@@ -23294,8 +23398,8 @@ async function main() {
|
|
|
23294
23398
|
permissions: gateway,
|
|
23295
23399
|
lifecycle: {
|
|
23296
23400
|
onBridgeStart: () => {
|
|
23297
|
-
|
|
23298
|
-
|
|
23401
|
+
fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
23402
|
+
fs16.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
23299
23403
|
const channels = getRunningChannels();
|
|
23300
23404
|
writeStatus({
|
|
23301
23405
|
running: true,
|
|
@@ -23318,6 +23422,7 @@ async function main() {
|
|
|
23318
23422
|
console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
|
|
23319
23423
|
},
|
|
23320
23424
|
onBridgeStop: () => {
|
|
23425
|
+
releaseInstanceLock();
|
|
23321
23426
|
writeStatus({ running: false, channels: [], adapters: [] });
|
|
23322
23427
|
console.log("[codex-to-im] Bridge stopped");
|
|
23323
23428
|
}
|
|
@@ -23332,6 +23437,7 @@ async function main() {
|
|
|
23332
23437
|
console.log(`[codex-to-im] Shutting down (${reason})...`);
|
|
23333
23438
|
pendingPerms.denyAll();
|
|
23334
23439
|
await stop();
|
|
23440
|
+
releaseInstanceLock();
|
|
23335
23441
|
writeStatus({ running: false, lastExitReason: reason });
|
|
23336
23442
|
process.exit(0);
|
|
23337
23443
|
};
|
|
@@ -23344,6 +23450,7 @@ async function main() {
|
|
|
23344
23450
|
});
|
|
23345
23451
|
process.on("uncaughtException", (err) => {
|
|
23346
23452
|
console.error("[codex-to-im] uncaughtException:", err.stack || err.message);
|
|
23453
|
+
releaseInstanceLock();
|
|
23347
23454
|
writeStatus({ running: false, lastExitReason: `uncaughtException: ${err.message}` });
|
|
23348
23455
|
process.exit(1);
|
|
23349
23456
|
});
|
|
@@ -23351,6 +23458,7 @@ async function main() {
|
|
|
23351
23458
|
console.log(`[codex-to-im] beforeExit (code: ${code2})`);
|
|
23352
23459
|
});
|
|
23353
23460
|
process.on("exit", (code2) => {
|
|
23461
|
+
releaseInstanceLock();
|
|
23354
23462
|
console.log(`[codex-to-im] exit (code: ${code2})`);
|
|
23355
23463
|
});
|
|
23356
23464
|
setInterval(() => {
|
|
@@ -23358,6 +23466,7 @@ async function main() {
|
|
|
23358
23466
|
}
|
|
23359
23467
|
main().catch((err) => {
|
|
23360
23468
|
console.error("[codex-to-im] Fatal error:", err instanceof Error ? err.stack || err.message : err);
|
|
23469
|
+
releaseBridgeInstanceLock(void 0, process.pid);
|
|
23361
23470
|
try {
|
|
23362
23471
|
writeStatus({ running: false, lastExitReason: `fatal: ${err instanceof Error ? err.message : String(err)}` });
|
|
23363
23472
|
} catch {
|