codex-to-im 1.0.41 → 1.0.43
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 +143 -66
- package/dist/daemon.mjs +2949 -8988
- package/dist/ui-server.mjs +292 -191
- package/package.json +2 -4
- package/references/setup-guides.md +34 -156
- package/references/token-validation.md +28 -44
- package/references/troubleshooting.md +10 -11
- package/scripts/build.js +2 -7
- package/scripts/daemon.sh +11 -30
- package/scripts/doctor.sh +35 -280
- package/scripts/supervisor-macos.sh +9 -28
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
|
|
|
@@ -29,8 +29,14 @@ function parseReasoningEffort(value) {
|
|
|
29
29
|
}
|
|
30
30
|
return void 0;
|
|
31
31
|
}
|
|
32
|
+
function normalizeChannelId(value) {
|
|
33
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "channel";
|
|
34
|
+
}
|
|
32
35
|
|
|
33
36
|
// src/config.ts
|
|
37
|
+
function isSupportedChannelProvider(value) {
|
|
38
|
+
return value === "feishu" || value === "weixin";
|
|
39
|
+
}
|
|
34
40
|
var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
|
|
35
41
|
var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
|
|
36
42
|
var CTI_HOME = process.env.CTI_HOME || DEFAULT_CTI_HOME;
|
|
@@ -97,6 +103,8 @@ function readConfigV2File() {
|
|
|
97
103
|
try {
|
|
98
104
|
const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
|
|
99
105
|
if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
|
|
106
|
+
parsed.runtime.provider = normalizeRuntimeProvider(parsed.runtime.provider);
|
|
107
|
+
parsed.channels = normalizeChannelInstances(parsed.channels);
|
|
100
108
|
return parsed;
|
|
101
109
|
}
|
|
102
110
|
return null;
|
|
@@ -116,9 +124,32 @@ function defaultAliasForProvider(provider) {
|
|
|
116
124
|
function buildDefaultChannelId(provider) {
|
|
117
125
|
return `${provider}-default`;
|
|
118
126
|
}
|
|
127
|
+
function normalizeRuntimeProvider(_value) {
|
|
128
|
+
return "codex";
|
|
129
|
+
}
|
|
130
|
+
function normalizeChannelInstances(value) {
|
|
131
|
+
if (!Array.isArray(value)) return [];
|
|
132
|
+
return value.flatMap((entry) => {
|
|
133
|
+
if (!entry || typeof entry !== "object") return [];
|
|
134
|
+
const record = entry;
|
|
135
|
+
if (!isSupportedChannelProvider(record.provider)) return [];
|
|
136
|
+
const provider = record.provider;
|
|
137
|
+
const config = record.config && typeof record.config === "object" ? record.config : {};
|
|
138
|
+
const timestamp = nowIso();
|
|
139
|
+
return [{
|
|
140
|
+
id: normalizeChannelId(
|
|
141
|
+
typeof record.id === "string" && record.id.trim() ? record.id : buildDefaultChannelId(provider)
|
|
142
|
+
),
|
|
143
|
+
alias: typeof record.alias === "string" && record.alias.trim() ? record.alias.trim() : defaultAliasForProvider(provider),
|
|
144
|
+
provider,
|
|
145
|
+
enabled: record.enabled === true,
|
|
146
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : timestamp,
|
|
147
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : timestamp,
|
|
148
|
+
config
|
|
149
|
+
}];
|
|
150
|
+
});
|
|
151
|
+
}
|
|
119
152
|
function migrateLegacyEnvToV2(env) {
|
|
120
|
-
const rawRuntime = env.get("CTI_RUNTIME") || "codex";
|
|
121
|
-
const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
|
|
122
153
|
const enabledChannels = splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"];
|
|
123
154
|
const timestamp = nowIso();
|
|
124
155
|
const channels = [];
|
|
@@ -165,7 +196,7 @@ function migrateLegacyEnvToV2(env) {
|
|
|
165
196
|
return {
|
|
166
197
|
schemaVersion: 2,
|
|
167
198
|
runtime: {
|
|
168
|
-
provider:
|
|
199
|
+
provider: "codex",
|
|
169
200
|
defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
|
|
170
201
|
defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
|
|
171
202
|
defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
|
|
@@ -176,8 +207,7 @@ function migrateLegacyEnvToV2(env) {
|
|
|
176
207
|
codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
|
|
177
208
|
codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
|
|
178
209
|
uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
|
|
179
|
-
uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0
|
|
180
|
-
autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
|
|
210
|
+
uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0
|
|
181
211
|
},
|
|
182
212
|
channels
|
|
183
213
|
};
|
|
@@ -200,8 +230,7 @@ function expandConfig(v2) {
|
|
|
200
230
|
codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
|
|
201
231
|
codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
|
|
202
232
|
uiAllowLan: v2.runtime.uiAllowLan === true,
|
|
203
|
-
uiAccessToken: v2.runtime.uiAccessToken || void 0
|
|
204
|
-
autoApprove: v2.runtime.autoApprove === true
|
|
233
|
+
uiAccessToken: v2.runtime.uiAccessToken || void 0
|
|
205
234
|
};
|
|
206
235
|
}
|
|
207
236
|
function loadConfig() {
|
|
@@ -225,50 +254,86 @@ function loadConfig() {
|
|
|
225
254
|
codexSkipGitRepoCheck: true,
|
|
226
255
|
codexSandboxMode: "workspace-write",
|
|
227
256
|
codexReasoningEffort: "medium",
|
|
228
|
-
uiAllowLan: false
|
|
229
|
-
autoApprove: false
|
|
257
|
+
uiAllowLan: false
|
|
230
258
|
},
|
|
231
259
|
channels: []
|
|
232
260
|
};
|
|
233
261
|
return expandConfig(empty);
|
|
234
262
|
}
|
|
235
263
|
|
|
236
|
-
// src/
|
|
237
|
-
|
|
238
|
-
|
|
264
|
+
// src/bridge-instance-lock.ts
|
|
265
|
+
import fs2 from "node:fs";
|
|
266
|
+
import path2 from "node:path";
|
|
239
267
|
var runtimeDir = path2.join(CTI_HOME, "runtime");
|
|
240
|
-
var
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
var bridgeInstanceLockFile = path2.join(runtimeDir, "bridge.instance.lock");
|
|
269
|
+
function readJsonFile(filePath, fallback) {
|
|
270
|
+
try {
|
|
271
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
272
|
+
} catch {
|
|
273
|
+
return fallback;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function isProcessAlive(pid) {
|
|
277
|
+
if (!pid) return false;
|
|
278
|
+
try {
|
|
279
|
+
process.kill(pid, 0);
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function readBridgeInstanceLock(filePath = bridgeInstanceLockFile) {
|
|
286
|
+
const parsed = readJsonFile(filePath, null);
|
|
287
|
+
const pid = Number(parsed?.pid);
|
|
288
|
+
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
289
|
+
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
290
|
+
return { pid, createdAt };
|
|
291
|
+
}
|
|
292
|
+
function clearStaleBridgeInstanceLock(filePath = bridgeInstanceLockFile, isAlive = isProcessAlive) {
|
|
293
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
294
|
+
if (existing && isAlive(existing.pid)) return;
|
|
295
|
+
try {
|
|
296
|
+
fs2.unlinkSync(filePath);
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/service-manager.ts
|
|
302
|
+
var moduleDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
303
|
+
var packageRoot = path3.resolve(moduleDir, "..");
|
|
304
|
+
var runtimeDir2 = path3.join(CTI_HOME, "runtime");
|
|
305
|
+
var logsDir = path3.join(CTI_HOME, "logs");
|
|
306
|
+
var bridgePidFile = path3.join(runtimeDir2, "bridge.pid");
|
|
307
|
+
var bridgeStatusFile = path3.join(runtimeDir2, "status.json");
|
|
308
|
+
var bridgeStartLockFile = path3.join(runtimeDir2, "bridge.start.lock");
|
|
309
|
+
var uiStatusFile = path3.join(runtimeDir2, "ui-server.json");
|
|
245
310
|
var uiPort = 4781;
|
|
246
311
|
var bridgeAutostartTaskName = "CodexToIMBridge";
|
|
247
|
-
var bridgeAutostartLauncherFile =
|
|
248
|
-
var npmUninstallLogFile =
|
|
312
|
+
var bridgeAutostartLauncherFile = path3.join(runtimeDir2, "bridge-autostart.ps1");
|
|
313
|
+
var npmUninstallLogFile = path3.join(runtimeDir2, "npm-uninstall.log");
|
|
249
314
|
var WINDOWS_HIDE = process.platform === "win32" ? { windowsHide: true } : {};
|
|
250
315
|
var BRIDGE_START_LOCK_STALE_MS = 3e4;
|
|
251
316
|
function ensureDirs() {
|
|
252
|
-
|
|
253
|
-
|
|
317
|
+
fs3.mkdirSync(runtimeDir2, { recursive: true });
|
|
318
|
+
fs3.mkdirSync(logsDir, { recursive: true });
|
|
254
319
|
}
|
|
255
|
-
function
|
|
320
|
+
function readJsonFile2(filePath, fallback) {
|
|
256
321
|
try {
|
|
257
|
-
return JSON.parse(
|
|
322
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
258
323
|
} catch {
|
|
259
324
|
return fallback;
|
|
260
325
|
}
|
|
261
326
|
}
|
|
262
327
|
function readPid(filePath) {
|
|
263
328
|
try {
|
|
264
|
-
const raw =
|
|
329
|
+
const raw = fs3.readFileSync(filePath, "utf-8").trim();
|
|
265
330
|
const pid = Number(raw);
|
|
266
331
|
return Number.isFinite(pid) ? pid : void 0;
|
|
267
332
|
} catch {
|
|
268
333
|
return void 0;
|
|
269
334
|
}
|
|
270
335
|
}
|
|
271
|
-
function
|
|
336
|
+
function isProcessAlive2(pid) {
|
|
272
337
|
if (!pid) return false;
|
|
273
338
|
try {
|
|
274
339
|
process.kill(pid, 0);
|
|
@@ -277,32 +342,37 @@ function isProcessAlive(pid) {
|
|
|
277
342
|
return false;
|
|
278
343
|
}
|
|
279
344
|
}
|
|
280
|
-
function collectTrackedBridgePids(bridgePid, statusPid) {
|
|
345
|
+
function collectTrackedBridgePids(bridgePid, statusPid, instanceLockPid) {
|
|
281
346
|
const unique = /* @__PURE__ */ new Set();
|
|
282
|
-
for (const pid of [bridgePid, statusPid]) {
|
|
347
|
+
for (const pid of [bridgePid, statusPid, instanceLockPid]) {
|
|
283
348
|
if (Number.isFinite(pid) && pid > 0) {
|
|
284
349
|
unique.add(pid);
|
|
285
350
|
}
|
|
286
351
|
}
|
|
287
352
|
return [...unique];
|
|
288
353
|
}
|
|
289
|
-
function resolveTrackedBridgePid(bridgePid, statusPid, isAlive =
|
|
354
|
+
function resolveTrackedBridgePid(bridgePid, statusPid, instanceLockPid, isAlive = isProcessAlive2) {
|
|
290
355
|
if (isAlive(bridgePid)) return bridgePid;
|
|
291
356
|
if (isAlive(statusPid)) return statusPid;
|
|
292
|
-
|
|
357
|
+
if (isAlive(instanceLockPid)) return instanceLockPid;
|
|
358
|
+
return bridgePid ?? statusPid ?? instanceLockPid;
|
|
293
359
|
}
|
|
294
360
|
function getTrackedBridgePids(status) {
|
|
295
|
-
const resolvedStatus = status ??
|
|
296
|
-
return collectTrackedBridgePids(
|
|
361
|
+
const resolvedStatus = status ?? readJsonFile2(bridgeStatusFile, { running: false });
|
|
362
|
+
return collectTrackedBridgePids(
|
|
363
|
+
readPid(bridgePidFile),
|
|
364
|
+
resolvedStatus.pid,
|
|
365
|
+
readBridgeInstanceLock()?.pid
|
|
366
|
+
);
|
|
297
367
|
}
|
|
298
368
|
function clearBridgePidFile() {
|
|
299
369
|
try {
|
|
300
|
-
|
|
370
|
+
fs3.unlinkSync(bridgePidFile);
|
|
301
371
|
} catch {
|
|
302
372
|
}
|
|
303
373
|
}
|
|
304
374
|
function readBridgeStartLock(filePath = bridgeStartLockFile) {
|
|
305
|
-
const parsed =
|
|
375
|
+
const parsed = readJsonFile2(filePath, null);
|
|
306
376
|
const pid = Number(parsed?.pid);
|
|
307
377
|
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
308
378
|
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
@@ -312,7 +382,7 @@ function isBridgeStartLockStale(lock, options = {}) {
|
|
|
312
382
|
if (!lock) return true;
|
|
313
383
|
const nowMs = options.nowMs ?? Date.now();
|
|
314
384
|
const staleMs = options.staleMs ?? BRIDGE_START_LOCK_STALE_MS;
|
|
315
|
-
const isAlive = options.isAlive ??
|
|
385
|
+
const isAlive = options.isAlive ?? isProcessAlive2;
|
|
316
386
|
const createdAtMs = Date.parse(lock.createdAt);
|
|
317
387
|
if (!Number.isFinite(createdAtMs)) return true;
|
|
318
388
|
if (!isAlive(lock.pid)) return true;
|
|
@@ -328,7 +398,7 @@ function tryAcquireBridgeStartLock(options = {}) {
|
|
|
328
398
|
};
|
|
329
399
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
330
400
|
try {
|
|
331
|
-
|
|
401
|
+
fs3.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
332
402
|
return { acquired: true };
|
|
333
403
|
} catch (error) {
|
|
334
404
|
const code = error.code;
|
|
@@ -342,7 +412,7 @@ function tryAcquireBridgeStartLock(options = {}) {
|
|
|
342
412
|
return { acquired: false, holderPid: existing2?.pid };
|
|
343
413
|
}
|
|
344
414
|
try {
|
|
345
|
-
|
|
415
|
+
fs3.unlinkSync(filePath);
|
|
346
416
|
} catch {
|
|
347
417
|
}
|
|
348
418
|
}
|
|
@@ -354,14 +424,14 @@ function releaseBridgeStartLock(filePath = bridgeStartLockFile, ownerPid = proce
|
|
|
354
424
|
const existing = readBridgeStartLock(filePath);
|
|
355
425
|
if (!existing) {
|
|
356
426
|
try {
|
|
357
|
-
|
|
427
|
+
fs3.unlinkSync(filePath);
|
|
358
428
|
} catch {
|
|
359
429
|
}
|
|
360
430
|
return;
|
|
361
431
|
}
|
|
362
432
|
if (existing.pid !== ownerPid) return;
|
|
363
433
|
try {
|
|
364
|
-
|
|
434
|
+
fs3.unlinkSync(filePath);
|
|
365
435
|
} catch {
|
|
366
436
|
}
|
|
367
437
|
}
|
|
@@ -443,11 +513,11 @@ function ensureBridgeAutostartLauncher() {
|
|
|
443
513
|
" }",
|
|
444
514
|
" } catch { }",
|
|
445
515
|
"}",
|
|
446
|
-
`& $node '${escapePowerShellSingleQuoted(
|
|
516
|
+
`& $node '${escapePowerShellSingleQuoted(path3.join(packageRoot, "dist", "cli.mjs"))}' start`,
|
|
447
517
|
"exit $LASTEXITCODE",
|
|
448
518
|
""
|
|
449
519
|
].join("\r\n");
|
|
450
|
-
|
|
520
|
+
fs3.writeFileSync(bridgeAutostartLauncherFile, content, "utf-8");
|
|
451
521
|
return bridgeAutostartLauncherFile;
|
|
452
522
|
}
|
|
453
523
|
function parsePowerShellJson(raw) {
|
|
@@ -491,7 +561,7 @@ function buildDeferredGlobalNpmUninstallLaunch(options = {}) {
|
|
|
491
561
|
async function launchDeferredGlobalNpmUninstall() {
|
|
492
562
|
ensureDirs();
|
|
493
563
|
const launch = buildDeferredGlobalNpmUninstallLaunch();
|
|
494
|
-
|
|
564
|
+
fs3.writeFileSync(
|
|
495
565
|
launch.logPath,
|
|
496
566
|
[
|
|
497
567
|
`[${(/* @__PURE__ */ new Date()).toISOString()}] Scheduling global uninstall.`,
|
|
@@ -519,14 +589,18 @@ function getUiServerUrl(port = uiPort) {
|
|
|
519
589
|
return `http://127.0.0.1:${port}`;
|
|
520
590
|
}
|
|
521
591
|
function getCurrentUiServerUrl() {
|
|
522
|
-
const status =
|
|
592
|
+
const status = readJsonFile2(uiStatusFile, null);
|
|
523
593
|
if (!status?.port) return void 0;
|
|
524
594
|
return getUiServerUrl(status.port);
|
|
525
595
|
}
|
|
526
596
|
function getBridgeStatus() {
|
|
527
|
-
const status =
|
|
528
|
-
const pid = resolveTrackedBridgePid(
|
|
529
|
-
|
|
597
|
+
const status = readJsonFile2(bridgeStatusFile, { running: false });
|
|
598
|
+
const pid = resolveTrackedBridgePid(
|
|
599
|
+
readPid(bridgePidFile),
|
|
600
|
+
status.pid,
|
|
601
|
+
readBridgeInstanceLock()?.pid
|
|
602
|
+
);
|
|
603
|
+
if (!isProcessAlive2(pid)) {
|
|
530
604
|
return {
|
|
531
605
|
...status,
|
|
532
606
|
pid,
|
|
@@ -540,8 +614,8 @@ function getBridgeStatus() {
|
|
|
540
614
|
};
|
|
541
615
|
}
|
|
542
616
|
function getUiServerStatus() {
|
|
543
|
-
const status =
|
|
544
|
-
if (!
|
|
617
|
+
const status = readJsonFile2(uiStatusFile, { running: false, port: uiPort });
|
|
618
|
+
if (!isProcessAlive2(status.pid)) {
|
|
545
619
|
return {
|
|
546
620
|
...status,
|
|
547
621
|
running: false,
|
|
@@ -626,7 +700,7 @@ async function waitForUiServer(timeoutMs = 15e3) {
|
|
|
626
700
|
async function startBridge() {
|
|
627
701
|
ensureDirs();
|
|
628
702
|
const current = getBridgeStatus();
|
|
629
|
-
const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid &&
|
|
703
|
+
const extraAlivePids = getTrackedBridgePids(current).filter((pid) => pid !== current.pid && isProcessAlive2(pid));
|
|
630
704
|
if (current.running && extraAlivePids.length === 0) return current;
|
|
631
705
|
if (current.running && extraAlivePids.length > 0) {
|
|
632
706
|
await stopBridge();
|
|
@@ -651,17 +725,17 @@ async function startBridge() {
|
|
|
651
725
|
startLockHeld = true;
|
|
652
726
|
try {
|
|
653
727
|
const currentAfterLock = getBridgeStatus();
|
|
654
|
-
const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid &&
|
|
728
|
+
const extraAlivePidsAfterLock = getTrackedBridgePids(currentAfterLock).filter((pid) => pid !== currentAfterLock.pid && isProcessAlive2(pid));
|
|
655
729
|
if (currentAfterLock.running && extraAlivePidsAfterLock.length === 0) return currentAfterLock;
|
|
656
730
|
if (currentAfterLock.running && extraAlivePidsAfterLock.length > 0) {
|
|
657
731
|
await stopBridge();
|
|
658
732
|
}
|
|
659
|
-
const daemonEntry =
|
|
660
|
-
if (!
|
|
733
|
+
const daemonEntry = path3.join(packageRoot, "dist", "daemon.mjs");
|
|
734
|
+
if (!fs3.existsSync(daemonEntry)) {
|
|
661
735
|
throw new Error(`Daemon bundle not found at ${daemonEntry}. Run npm run build first.`);
|
|
662
736
|
}
|
|
663
|
-
const stdoutFd =
|
|
664
|
-
const stderrFd =
|
|
737
|
+
const stdoutFd = fs3.openSync(path3.join(logsDir, "bridge-launcher.out.log"), "a");
|
|
738
|
+
const stderrFd = fs3.openSync(path3.join(logsDir, "bridge-launcher.err.log"), "a");
|
|
665
739
|
const child = spawn(process.execPath, [daemonEntry], {
|
|
666
740
|
cwd: packageRoot,
|
|
667
741
|
detached: true,
|
|
@@ -684,10 +758,11 @@ async function startBridge() {
|
|
|
684
758
|
}
|
|
685
759
|
}
|
|
686
760
|
async function stopBridge() {
|
|
687
|
-
const status =
|
|
688
|
-
const pids = getTrackedBridgePids(status).filter((pid) =>
|
|
761
|
+
const status = readJsonFile2(bridgeStatusFile, { running: false });
|
|
762
|
+
const pids = getTrackedBridgePids(status).filter((pid) => isProcessAlive2(pid));
|
|
689
763
|
if (pids.length === 0) {
|
|
690
764
|
clearBridgePidFile();
|
|
765
|
+
clearStaleBridgeInstanceLock();
|
|
691
766
|
return { ...getBridgeStatus(), running: false };
|
|
692
767
|
}
|
|
693
768
|
for (const pid of pids) {
|
|
@@ -709,13 +784,15 @@ async function stopBridge() {
|
|
|
709
784
|
}
|
|
710
785
|
const startedAt = Date.now();
|
|
711
786
|
while (Date.now() - startedAt < 1e4) {
|
|
712
|
-
if (pids.every((pid) => !
|
|
787
|
+
if (pids.every((pid) => !isProcessAlive2(pid))) {
|
|
713
788
|
clearBridgePidFile();
|
|
789
|
+
clearStaleBridgeInstanceLock();
|
|
714
790
|
return getBridgeStatus();
|
|
715
791
|
}
|
|
716
792
|
await sleep(300);
|
|
717
793
|
}
|
|
718
794
|
clearBridgePidFile();
|
|
795
|
+
clearStaleBridgeInstanceLock();
|
|
719
796
|
return getBridgeStatus();
|
|
720
797
|
}
|
|
721
798
|
async function getBridgeAutostartStatus() {
|
|
@@ -804,8 +881,8 @@ async function uninstallBridgeAutostart() {
|
|
|
804
881
|
].join("; ");
|
|
805
882
|
await runPowerShell(script);
|
|
806
883
|
try {
|
|
807
|
-
if (
|
|
808
|
-
|
|
884
|
+
if (fs3.existsSync(bridgeAutostartLauncherFile)) {
|
|
885
|
+
fs3.unlinkSync(bridgeAutostartLauncherFile);
|
|
809
886
|
}
|
|
810
887
|
} catch {
|
|
811
888
|
}
|
|
@@ -836,12 +913,12 @@ async function ensureUiServerRunning() {
|
|
|
836
913
|
ensureDirs();
|
|
837
914
|
const current = getUiServerStatus();
|
|
838
915
|
if (current.running) return current;
|
|
839
|
-
const serverEntry =
|
|
840
|
-
if (!
|
|
916
|
+
const serverEntry = path3.join(packageRoot, "dist", "ui-server.mjs");
|
|
917
|
+
if (!fs3.existsSync(serverEntry)) {
|
|
841
918
|
throw new Error(`UI server bundle not found at ${serverEntry}. Run npm run build first.`);
|
|
842
919
|
}
|
|
843
|
-
const stdoutFd =
|
|
844
|
-
const stderrFd =
|
|
920
|
+
const stdoutFd = fs3.openSync(path3.join(logsDir, "ui-server.out.log"), "a");
|
|
921
|
+
const stderrFd = fs3.openSync(path3.join(logsDir, "ui-server.err.log"), "a");
|
|
845
922
|
const child = spawn(process.execPath, [serverEntry], {
|
|
846
923
|
cwd: packageRoot,
|
|
847
924
|
detached: true,
|
|
@@ -861,7 +938,7 @@ async function ensureUiServerRunning() {
|
|
|
861
938
|
}
|
|
862
939
|
async function stopUiServer() {
|
|
863
940
|
const status = getUiServerStatus();
|
|
864
|
-
if (!status.pid || !
|
|
941
|
+
if (!status.pid || !isProcessAlive2(status.pid)) {
|
|
865
942
|
const next2 = { ...status, running: false };
|
|
866
943
|
writeUiServerStatus(next2);
|
|
867
944
|
return next2;
|
|
@@ -898,7 +975,7 @@ async function stopUiServer() {
|
|
|
898
975
|
}
|
|
899
976
|
function writeUiServerStatus(status) {
|
|
900
977
|
ensureDirs();
|
|
901
|
-
|
|
978
|
+
fs3.writeFileSync(uiStatusFile, JSON.stringify(status, null, 2), "utf-8");
|
|
902
979
|
}
|
|
903
980
|
function openBrowser(url) {
|
|
904
981
|
if (process.platform === "win32") {
|