codex-to-im 1.0.31 → 1.0.33
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 +141 -31
- package/dist/daemon.mjs +4966 -4126
- package/dist/ui-server.mjs +150 -44
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -15,6 +15,22 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
// src/runtime-options.ts
|
|
20
|
+
function parseSandboxMode(value) {
|
|
21
|
+
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
function parseReasoningEffort(value) {
|
|
27
|
+
if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/config.ts
|
|
18
34
|
var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
|
|
19
35
|
var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
|
|
20
36
|
var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
|
|
@@ -61,18 +77,6 @@ function parsePositiveInt(value) {
|
|
|
61
77
|
if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
|
|
62
78
|
return Math.floor(parsed);
|
|
63
79
|
}
|
|
64
|
-
function parseSandboxMode(value) {
|
|
65
|
-
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
return void 0;
|
|
69
|
-
}
|
|
70
|
-
function parseReasoningEffort(value) {
|
|
71
|
-
if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
return void 0;
|
|
75
|
-
}
|
|
76
80
|
function normalizeFeishuSite(value) {
|
|
77
81
|
const normalized = (value || "").trim().replace(/\/+$/, "").toLowerCase();
|
|
78
82
|
if (!normalized) return "feishu";
|
|
@@ -228,12 +232,14 @@ var runtimeDir = path2.join(CTI_HOME, "runtime");
|
|
|
228
232
|
var logsDir = path2.join(CTI_HOME, "logs");
|
|
229
233
|
var bridgePidFile = path2.join(runtimeDir, "bridge.pid");
|
|
230
234
|
var bridgeStatusFile = path2.join(runtimeDir, "status.json");
|
|
235
|
+
var bridgeStartLockFile = path2.join(runtimeDir, "bridge.start.lock");
|
|
231
236
|
var uiStatusFile = path2.join(runtimeDir, "ui-server.json");
|
|
232
237
|
var uiPort = 4781;
|
|
233
238
|
var bridgeAutostartTaskName = "CodexToIMBridge";
|
|
234
239
|
var bridgeAutostartLauncherFile = path2.join(runtimeDir, "bridge-autostart.ps1");
|
|
235
240
|
var npmUninstallLogFile = path2.join(runtimeDir, "npm-uninstall.log");
|
|
236
241
|
var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
|
|
242
|
+
var BRIDGE_START_LOCK_STALE_MS = 3e4;
|
|
237
243
|
function ensureDirs() {
|
|
238
244
|
fs2.mkdirSync(runtimeDir, { recursive: true });
|
|
239
245
|
fs2.mkdirSync(logsDir, { recursive: true });
|
|
@@ -287,6 +293,70 @@ function clearBridgePidFile() {
|
|
|
287
293
|
} catch {
|
|
288
294
|
}
|
|
289
295
|
}
|
|
296
|
+
function readBridgeStartLock(filePath = bridgeStartLockFile) {
|
|
297
|
+
const parsed = readJsonFile(filePath, null);
|
|
298
|
+
const pid = Number(parsed?.pid);
|
|
299
|
+
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
300
|
+
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
301
|
+
return { pid, createdAt };
|
|
302
|
+
}
|
|
303
|
+
function isBridgeStartLockStale(lock, options = {}) {
|
|
304
|
+
if (!lock) return true;
|
|
305
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
306
|
+
const staleMs = options.staleMs ?? BRIDGE_START_LOCK_STALE_MS;
|
|
307
|
+
const isAlive = options.isAlive ?? isProcessAlive;
|
|
308
|
+
const createdAtMs = Date.parse(lock.createdAt);
|
|
309
|
+
if (!Number.isFinite(createdAtMs)) return true;
|
|
310
|
+
if (!isAlive(lock.pid)) return true;
|
|
311
|
+
return nowMs - createdAtMs > staleMs;
|
|
312
|
+
}
|
|
313
|
+
function tryAcquireBridgeStartLock(options = {}) {
|
|
314
|
+
const filePath = options.filePath ?? bridgeStartLockFile;
|
|
315
|
+
const ownerPid = options.ownerPid ?? process.pid;
|
|
316
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
317
|
+
const payload = {
|
|
318
|
+
pid: ownerPid,
|
|
319
|
+
createdAt: new Date(nowMs).toISOString()
|
|
320
|
+
};
|
|
321
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
322
|
+
try {
|
|
323
|
+
fs2.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
324
|
+
return { acquired: true };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const code = error.code;
|
|
327
|
+
if (code !== "EEXIST") throw error;
|
|
328
|
+
const existing2 = readBridgeStartLock(filePath);
|
|
329
|
+
if (!isBridgeStartLockStale(existing2, {
|
|
330
|
+
nowMs,
|
|
331
|
+
staleMs: options.staleMs,
|
|
332
|
+
isAlive: options.isAlive
|
|
333
|
+
})) {
|
|
334
|
+
return { acquired: false, holderPid: existing2?.pid };
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
fs2.unlinkSync(filePath);
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const existing = readBridgeStartLock(filePath);
|
|
343
|
+
return { acquired: false, holderPid: existing?.pid };
|
|
344
|
+
}
|
|
345
|
+
function releaseBridgeStartLock(filePath = bridgeStartLockFile, ownerPid = process.pid) {
|
|
346
|
+
const existing = readBridgeStartLock(filePath);
|
|
347
|
+
if (!existing) {
|
|
348
|
+
try {
|
|
349
|
+
fs2.unlinkSync(filePath);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (existing.pid !== ownerPid) return;
|
|
355
|
+
try {
|
|
356
|
+
fs2.unlinkSync(filePath);
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
359
|
+
}
|
|
290
360
|
function sleep(ms) {
|
|
291
361
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
292
362
|
}
|
|
@@ -515,6 +585,21 @@ async function waitForBridgeRunning(timeoutMs = 2e4) {
|
|
|
515
585
|
}
|
|
516
586
|
return getBridgeStatus();
|
|
517
587
|
}
|
|
588
|
+
async function waitForBridgeStartupTurn(timeoutMs = 2e4) {
|
|
589
|
+
const startedAt = Date.now();
|
|
590
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
591
|
+
const status = getBridgeStatus();
|
|
592
|
+
if (status.running) return status;
|
|
593
|
+
const lock = readBridgeStartLock();
|
|
594
|
+
if (!lock) return status;
|
|
595
|
+
if (isBridgeStartLockStale(lock)) {
|
|
596
|
+
releaseBridgeStartLock();
|
|
597
|
+
return getBridgeStatus();
|
|
598
|
+
}
|
|
599
|
+
await sleep(300);
|
|
600
|
+
}
|
|
601
|
+
return getBridgeStatus();
|
|
602
|
+
}
|
|
518
603
|
async function waitForUiServer(timeoutMs = 15e3) {
|
|
519
604
|
const startedAt = Date.now();
|
|
520
605
|
while (Date.now() - startedAt < timeoutMs) {
|
|
@@ -543,27 +628,52 @@ async function startBridge() {
|
|
|
543
628
|
if (preflightFailure) {
|
|
544
629
|
throw new Error(preflightFailure);
|
|
545
630
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
631
|
+
let startLockHeld = false;
|
|
632
|
+
let lockState = tryAcquireBridgeStartLock();
|
|
633
|
+
if (!lockState.acquired) {
|
|
634
|
+
const status = await waitForBridgeStartupTurn();
|
|
635
|
+
if (status.running) return status;
|
|
636
|
+
lockState = tryAcquireBridgeStartLock();
|
|
637
|
+
if (!lockState.acquired) {
|
|
638
|
+
throw new Error(
|
|
639
|
+
describeBridgeActivationFailure(status, config.channels) || `\u53E6\u4E00\u4E2A\u6865\u63A5\u670D\u52A1\u542F\u52A8\u8BF7\u6C42\u4ECD\u5728\u8FDB\u884C\u4E2D\uFF08PID: ${lockState.holderPid || "unknown"}\uFF09\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
549
642
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
643
|
+
startLockHeld = true;
|
|
644
|
+
try {
|
|
645
|
+
const currentAfterLock = getBridgeStatus();
|
|
646
|
+
const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid && isProcessAlive(pid));
|
|
647
|
+
if (currentAfterLock.running && extraAlivePidsAfterLock.length === 0) return currentAfterLock;
|
|
648
|
+
if (currentAfterLock.running && extraAlivePidsAfterLock.length > 0) {
|
|
649
|
+
await stopBridge();
|
|
650
|
+
}
|
|
651
|
+
const daemonEntry = path2.join(packageRoot, "dist", "daemon.mjs");
|
|
652
|
+
if (!fs2.existsSync(daemonEntry)) {
|
|
653
|
+
throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
|
|
654
|
+
}
|
|
655
|
+
const stdoutFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.out.log"), "a");
|
|
656
|
+
const stderrFd = fs2.openSync(path2.join(logsDir, "bridge-launcher.err.log"), "a");
|
|
657
|
+
const child = spawn(process.execPath, [daemonEntry], {
|
|
658
|
+
cwd: packageRoot,
|
|
659
|
+
detached: true,
|
|
660
|
+
env: buildDaemonEnv(),
|
|
661
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
662
|
+
...WINDOWS_HIDE
|
|
663
|
+
});
|
|
664
|
+
child.unref();
|
|
665
|
+
const status = await waitForBridgeRunning();
|
|
666
|
+
if (!status.running) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
describeBridgeActivationFailure(status, config.channels) || "Bridge failed to report running=true."
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
return status;
|
|
672
|
+
} finally {
|
|
673
|
+
if (startLockHeld) {
|
|
674
|
+
releaseBridgeStartLock();
|
|
675
|
+
}
|
|
565
676
|
}
|
|
566
|
-
return status;
|
|
567
677
|
}
|
|
568
678
|
async function stopBridge() {
|
|
569
679
|
const status = readJsonFile(bridgeStatusFile, { running: false });
|