@raysonmeng/agentbridge 0.1.10 → 0.1.11
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/.claude-plugin/marketplace.json +1 -1
- package/dist/cli.js +181 -210
- package/dist/daemon.js +41 -32
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +41 -32
- package/plugins/agentbridge/server/daemon.js +41 -32
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"name": "agentbridge",
|
|
14
14
|
"description": "Bridge Claude Code and Codex through a shared daemon, push channel delivery, and reply/get_messages tools.",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.11",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "AgentBridge Contributors",
|
|
18
18
|
"email": "raysonmeng@qq.com"
|
package/dist/cli.js
CHANGED
|
@@ -120,7 +120,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
120
120
|
var require_package = __commonJS((exports, module) => {
|
|
121
121
|
module.exports = {
|
|
122
122
|
name: "@raysonmeng/agentbridge",
|
|
123
|
-
version: "0.1.
|
|
123
|
+
version: "0.1.11",
|
|
124
124
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
125
125
|
type: "module",
|
|
126
126
|
packageManager: "bun@1.3.11",
|
|
@@ -1165,15 +1165,173 @@ function formatBuildInfo(build) {
|
|
|
1165
1165
|
var BUILD_INFO;
|
|
1166
1166
|
var init_build_info = __esm(() => {
|
|
1167
1167
|
BUILD_INFO = Object.freeze({
|
|
1168
|
-
version: defineString("0.1.
|
|
1169
|
-
commit: defineString("
|
|
1168
|
+
version: defineString("0.1.11", "0.0.0-source"),
|
|
1169
|
+
commit: defineString("48eb0ed", "source"),
|
|
1170
1170
|
bundle: defineBundle("dist"),
|
|
1171
1171
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
1172
1172
|
});
|
|
1173
1173
|
});
|
|
1174
1174
|
|
|
1175
|
+
// src/process-lifecycle.ts
|
|
1176
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
1177
|
+
import { basename } from "path";
|
|
1178
|
+
function parsePsProcessList(output) {
|
|
1179
|
+
const entries = [];
|
|
1180
|
+
for (const line of output.split(/\r?\n/)) {
|
|
1181
|
+
const match = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
|
|
1182
|
+
if (!match)
|
|
1183
|
+
continue;
|
|
1184
|
+
const pid = Number.parseInt(match[1], 10);
|
|
1185
|
+
if (!Number.isFinite(pid))
|
|
1186
|
+
continue;
|
|
1187
|
+
entries.push({ pid, command: match[2] });
|
|
1188
|
+
}
|
|
1189
|
+
return entries;
|
|
1190
|
+
}
|
|
1191
|
+
function invokesCodexBinary(command) {
|
|
1192
|
+
const tokens = command.trim().split(/\s+/);
|
|
1193
|
+
const exe = tokens[0] ? basename(tokens[0]) : "";
|
|
1194
|
+
if (exe === "codex")
|
|
1195
|
+
return true;
|
|
1196
|
+
if ((exe === "node" || exe === "bun") && tokens[1]) {
|
|
1197
|
+
return basename(tokens[1]) === "codex";
|
|
1198
|
+
}
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
function commandMatchesManagedCodexTui(command, proxyUrl) {
|
|
1202
|
+
if (!invokesCodexBinary(command))
|
|
1203
|
+
return false;
|
|
1204
|
+
if (!command.includes("tui_app_server"))
|
|
1205
|
+
return false;
|
|
1206
|
+
const remoteUrl = extractRemoteUrl(command);
|
|
1207
|
+
if (!remoteUrl)
|
|
1208
|
+
return false;
|
|
1209
|
+
if (!proxyUrl)
|
|
1210
|
+
return true;
|
|
1211
|
+
return remoteTargetsProxy(remoteUrl, proxyUrl);
|
|
1212
|
+
}
|
|
1213
|
+
function findManagedCodexTuiProcessesFromList(processes, proxyUrl) {
|
|
1214
|
+
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command, proxyUrl));
|
|
1215
|
+
}
|
|
1216
|
+
function findManagedCodexTuiProcesses(proxyUrl) {
|
|
1217
|
+
try {
|
|
1218
|
+
const output = execFileSync4("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
1219
|
+
return findManagedCodexTuiProcessesFromList(parsePsProcessList(output), proxyUrl).filter((entry) => entry.pid !== process.pid);
|
|
1220
|
+
} catch {
|
|
1221
|
+
return [];
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function listManagedCodexTuiProcessesFromList(processes) {
|
|
1225
|
+
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command)).map((entry) => ({ ...entry, remoteUrl: extractRemoteUrl(entry.command) }));
|
|
1226
|
+
}
|
|
1227
|
+
function listManagedCodexTuiProcesses() {
|
|
1228
|
+
try {
|
|
1229
|
+
const output = execFileSync4("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
1230
|
+
return listManagedCodexTuiProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
1231
|
+
} catch {
|
|
1232
|
+
return [];
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function listBridgeFrontendProcessesFromList(processes) {
|
|
1236
|
+
return processes.filter((entry) => /(?:^|[\s/\\])bridge-server\.js(?:\s|$)/.test(entry.command) && (entry.command.includes("agentbridge") || entry.command.includes("agent_bridge")));
|
|
1237
|
+
}
|
|
1238
|
+
function listBridgeFrontendProcesses() {
|
|
1239
|
+
try {
|
|
1240
|
+
const output = execFileSync4("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
1241
|
+
return listBridgeFrontendProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
1242
|
+
} catch {
|
|
1243
|
+
return [];
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
function commandForPid(pid) {
|
|
1247
|
+
try {
|
|
1248
|
+
return execFileSync4("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
1249
|
+
} catch {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function pidLooksAlive(pid) {
|
|
1254
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
1255
|
+
return false;
|
|
1256
|
+
try {
|
|
1257
|
+
process.kill(pid, 0);
|
|
1258
|
+
return true;
|
|
1259
|
+
} catch (err) {
|
|
1260
|
+
return err?.code === "EPERM";
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function isAgentBridgeDaemon(pid, lookup = commandForPid) {
|
|
1264
|
+
const cmd = lookup(pid);
|
|
1265
|
+
if (cmd === null)
|
|
1266
|
+
return false;
|
|
1267
|
+
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
1268
|
+
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
1269
|
+
return hasDaemonEntry && hasAgentbridge;
|
|
1270
|
+
}
|
|
1271
|
+
function isAgentBridgeProcess(pid, lookup = commandForPid) {
|
|
1272
|
+
const cmd = lookup(pid);
|
|
1273
|
+
if (cmd === null)
|
|
1274
|
+
return false;
|
|
1275
|
+
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
1276
|
+
}
|
|
1277
|
+
function terminateProcessSync(pid, options = {}) {
|
|
1278
|
+
const gracefulTimeoutMs = options.gracefulTimeoutMs ?? 2000;
|
|
1279
|
+
const target = options.processGroup && process.platform !== "win32" ? -pid : pid;
|
|
1280
|
+
const label = options.processGroup && process.platform !== "win32" ? `process group ${pid}` : `pid ${pid}`;
|
|
1281
|
+
try {
|
|
1282
|
+
process.kill(target, "SIGTERM");
|
|
1283
|
+
options.log?.(`Sent SIGTERM to ${label}`);
|
|
1284
|
+
} catch {
|
|
1285
|
+
return !isProcessAlive(pid);
|
|
1286
|
+
}
|
|
1287
|
+
if (waitForExitSync(pid, gracefulTimeoutMs))
|
|
1288
|
+
return true;
|
|
1289
|
+
try {
|
|
1290
|
+
process.kill(target, "SIGKILL");
|
|
1291
|
+
options.log?.(`Sent SIGKILL to ${label}`);
|
|
1292
|
+
} catch {}
|
|
1293
|
+
return waitForExitSync(pid, 500);
|
|
1294
|
+
}
|
|
1295
|
+
function waitForExitSync(pid, timeoutMs) {
|
|
1296
|
+
const deadline = Date.now() + timeoutMs;
|
|
1297
|
+
while (Date.now() < deadline) {
|
|
1298
|
+
if (!isProcessAlive(pid))
|
|
1299
|
+
return true;
|
|
1300
|
+
sleepSync(50);
|
|
1301
|
+
}
|
|
1302
|
+
return !isProcessAlive(pid);
|
|
1303
|
+
}
|
|
1304
|
+
function sleepSync(ms) {
|
|
1305
|
+
const buffer = new SharedArrayBuffer(4);
|
|
1306
|
+
const view = new Int32Array(buffer);
|
|
1307
|
+
Atomics.wait(view, 0, 0, ms);
|
|
1308
|
+
}
|
|
1309
|
+
function extractRemoteUrl(command) {
|
|
1310
|
+
const equals = command.match(/(?:^|\s)--remote=([^\s]+)/);
|
|
1311
|
+
if (equals)
|
|
1312
|
+
return equals[1];
|
|
1313
|
+
const separate = command.match(/(?:^|\s)--remote\s+([^\s]+)/);
|
|
1314
|
+
return separate?.[1] ?? null;
|
|
1315
|
+
}
|
|
1316
|
+
function remoteTargetsProxy(remoteUrl, proxyUrl) {
|
|
1317
|
+
try {
|
|
1318
|
+
const remote = new URL(remoteUrl);
|
|
1319
|
+
const proxy = new URL(proxyUrl);
|
|
1320
|
+
return remote.protocol === proxy.protocol && remote.hostname === proxy.hostname && remote.port === proxy.port && normalizePath(remote.pathname) === normalizePath(proxy.pathname);
|
|
1321
|
+
} catch {
|
|
1322
|
+
return remoteUrl === proxyUrl;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function normalizePath(pathname) {
|
|
1326
|
+
return pathname === "" ? "/" : pathname;
|
|
1327
|
+
}
|
|
1328
|
+
var isProcessAlive;
|
|
1329
|
+
var init_process_lifecycle = __esm(() => {
|
|
1330
|
+
isProcessAlive = pidLooksAlive;
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1175
1333
|
// src/daemon-lifecycle.ts
|
|
1176
|
-
import { spawn
|
|
1334
|
+
import { spawn } from "child_process";
|
|
1177
1335
|
import { existsSync as existsSync5, readFileSync as readFileSync4, statSync, unlinkSync, writeFileSync as writeFileSync4, openSync, closeSync, constants } from "fs";
|
|
1178
1336
|
import { fileURLToPath } from "url";
|
|
1179
1337
|
|
|
@@ -1267,7 +1425,7 @@ class DaemonLifecycle {
|
|
|
1267
1425
|
const existingPid = this.readPid();
|
|
1268
1426
|
if (existingPid) {
|
|
1269
1427
|
if (isProcessAlive(existingPid)) {
|
|
1270
|
-
if (
|
|
1428
|
+
if (isAgentBridgeDaemon(existingPid)) {
|
|
1271
1429
|
try {
|
|
1272
1430
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
1273
1431
|
return;
|
|
@@ -1478,7 +1636,7 @@ class DaemonLifecycle {
|
|
|
1478
1636
|
this.releaseLock();
|
|
1479
1637
|
return this.acquireLockStrict(true);
|
|
1480
1638
|
}
|
|
1481
|
-
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !
|
|
1639
|
+
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !isAgentBridgeProcess(holderPid)) {
|
|
1482
1640
|
this.log(`Startup lock is ${Math.round(this.lockAgeMs() / 1000)}s old and holder pid ${holderPid} ` + `is an unrelated process (pid recycled), reclaiming`);
|
|
1483
1641
|
this.releaseLock();
|
|
1484
1642
|
return this.acquireLockStrict(true);
|
|
@@ -1499,14 +1657,6 @@ class DaemonLifecycle {
|
|
|
1499
1657
|
return 0;
|
|
1500
1658
|
}
|
|
1501
1659
|
}
|
|
1502
|
-
isAgentBridgeProcess(pid) {
|
|
1503
|
-
try {
|
|
1504
|
-
const cmd = execFileSync4("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
1505
|
-
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
1506
|
-
} catch {
|
|
1507
|
-
return false;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
1660
|
releaseLock() {
|
|
1511
1661
|
try {
|
|
1512
1662
|
unlinkSync(this.stateDir.lockFile);
|
|
@@ -1524,7 +1674,7 @@ class DaemonLifecycle {
|
|
|
1524
1674
|
this.cleanup();
|
|
1525
1675
|
return false;
|
|
1526
1676
|
}
|
|
1527
|
-
if (!
|
|
1677
|
+
if (!isAgentBridgeDaemon(pid)) {
|
|
1528
1678
|
this.log(`Pid ${pid} is alive but is NOT an AgentBridge daemon \u2014 refusing to kill. Cleaning up stale pid file.`);
|
|
1529
1679
|
this.cleanup();
|
|
1530
1680
|
return false;
|
|
@@ -1552,16 +1702,6 @@ class DaemonLifecycle {
|
|
|
1552
1702
|
this.cleanup();
|
|
1553
1703
|
return true;
|
|
1554
1704
|
}
|
|
1555
|
-
isDaemonProcess(pid) {
|
|
1556
|
-
try {
|
|
1557
|
-
const cmd = execFileSync4("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
1558
|
-
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
1559
|
-
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
1560
|
-
return hasDaemonEntry && hasAgentbridge;
|
|
1561
|
-
} catch {
|
|
1562
|
-
return false;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
1705
|
cleanup() {
|
|
1566
1706
|
this.removePidFile();
|
|
1567
1707
|
this.removeStatusFile();
|
|
@@ -1576,17 +1716,10 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
1576
1716
|
clearTimeout(timer);
|
|
1577
1717
|
}
|
|
1578
1718
|
}
|
|
1579
|
-
function isProcessAlive(pid) {
|
|
1580
|
-
try {
|
|
1581
|
-
process.kill(pid, 0);
|
|
1582
|
-
return true;
|
|
1583
|
-
} catch {
|
|
1584
|
-
return false;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
1719
|
var DEFAULT_DAEMON_ENTRY, DAEMON_ENTRY, DAEMON_PATH, REUSE_READY_RETRIES, REUSE_READY_DELAY_MS = 250, HEALTH_FETCH_TIMEOUT_MS = 500, LOCK_IDENTITY_GRACE_MS;
|
|
1588
1720
|
var init_daemon_lifecycle = __esm(() => {
|
|
1589
1721
|
init_build_info();
|
|
1722
|
+
init_process_lifecycle();
|
|
1590
1723
|
DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
1591
1724
|
DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
1592
1725
|
DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
|
|
@@ -1616,7 +1749,7 @@ import {
|
|
|
1616
1749
|
import { createServer } from "net";
|
|
1617
1750
|
import { createHash, randomUUID } from "crypto";
|
|
1618
1751
|
import { hostname, userInfo } from "os";
|
|
1619
|
-
import { basename, join as join5, resolve as resolve2, sep } from "path";
|
|
1752
|
+
import { basename as basename2, join as join5, resolve as resolve2, sep } from "path";
|
|
1620
1753
|
function portsForSlot(slot) {
|
|
1621
1754
|
if (!Number.isInteger(slot) || slot < 0) {
|
|
1622
1755
|
throw new PairError("PAIR_ID_INVALID", `Invalid slot: ${slot}`);
|
|
@@ -1723,16 +1856,6 @@ function readLockOwner(lockFile) {
|
|
|
1723
1856
|
return null;
|
|
1724
1857
|
}
|
|
1725
1858
|
}
|
|
1726
|
-
function pidLooksAlive(pid) {
|
|
1727
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
1728
|
-
return false;
|
|
1729
|
-
try {
|
|
1730
|
-
process.kill(pid, 0);
|
|
1731
|
-
return true;
|
|
1732
|
-
} catch (err) {
|
|
1733
|
-
return err?.code === "EPERM";
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
1859
|
function lockFileAgeMs(lockFile) {
|
|
1737
1860
|
try {
|
|
1738
1861
|
return Date.now() - statSync2(lockFile).mtimeMs;
|
|
@@ -1866,14 +1989,6 @@ async function withRegistryLock(base, fn) {
|
|
|
1866
1989
|
await sleep(25 + Math.floor(Math.random() * 50));
|
|
1867
1990
|
}
|
|
1868
1991
|
}
|
|
1869
|
-
function isDaemonProcess(pid) {
|
|
1870
|
-
try {
|
|
1871
|
-
const cmd = execFileSync5("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
1872
|
-
return cmd.includes("daemon") && (cmd.includes("agentbridge") || cmd.includes("agent_bridge"));
|
|
1873
|
-
} catch {
|
|
1874
|
-
return false;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
1992
|
function detectLegacyRootDaemon(base) {
|
|
1878
1993
|
const rootPidFile = join5(base, "daemon.pid");
|
|
1879
1994
|
if (!existsSync6(rootPidFile))
|
|
@@ -1885,7 +2000,7 @@ function detectLegacyRootDaemon(base) {
|
|
|
1885
2000
|
} catch {
|
|
1886
2001
|
return null;
|
|
1887
2002
|
}
|
|
1888
|
-
if (!Number.isFinite(pid) || !pidLooksAlive(pid) || !
|
|
2003
|
+
if (!Number.isFinite(pid) || !pidLooksAlive(pid) || !isAgentBridgeDaemon(pid))
|
|
1889
2004
|
return null;
|
|
1890
2005
|
return { pid, controlPort: LEGACY_ROOT_CONTROL_PORT };
|
|
1891
2006
|
}
|
|
@@ -2071,6 +2186,7 @@ async function removeUnregisteredPairDir(base, pairId) {
|
|
|
2071
2186
|
}
|
|
2072
2187
|
var PAIR_BASE_PORT = 4500, PAIR_SLOT_STRIDE = 10, PAIR_ID_REGEX, DEFAULT_PAIR_NAME = "main", LOCK_FILE_NAME = ".registry.lock", REGISTRY_FILE_NAME = "registry.json", LOCK_DEADLINE_MS = 1e4, ORPHAN_GRACE_MS = 3000, LEGACY_ROOT_CONTROL_PORT = 4502, WINDOWS_RESERVED_RE, PairError, MAX_PAIR_SLOT;
|
|
2073
2188
|
var init_pair_registry = __esm(() => {
|
|
2189
|
+
init_process_lifecycle();
|
|
2074
2190
|
PAIR_ID_REGEX = /^[A-Za-z0-9._-]{1,64}$/;
|
|
2075
2191
|
WINDOWS_RESERVED_RE = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
2076
2192
|
PairError = class PairError extends Error {
|
|
@@ -2697,16 +2813,6 @@ function readTurnInProgress(statusFilePath, read = (p) => readFileSync7(p, "utf-
|
|
|
2697
2813
|
return null;
|
|
2698
2814
|
}
|
|
2699
2815
|
}
|
|
2700
|
-
function defaultIsPidAlive(pid) {
|
|
2701
|
-
if (pid <= 0)
|
|
2702
|
-
return false;
|
|
2703
|
-
try {
|
|
2704
|
-
process.kill(pid, 0);
|
|
2705
|
-
return true;
|
|
2706
|
-
} catch (err) {
|
|
2707
|
-
return err.code === "EPERM";
|
|
2708
|
-
}
|
|
2709
|
-
}
|
|
2710
2816
|
function refineCleanExitClassification(turnInProgress) {
|
|
2711
2817
|
if (turnInProgress === true)
|
|
2712
2818
|
return "exit_0_during_turn";
|
|
@@ -2753,7 +2859,11 @@ function captureTuiLogTail(options) {
|
|
|
2753
2859
|
return `(tui log tail capture failed: ${err instanceof Error ? err.message : String(err)})`;
|
|
2754
2860
|
}
|
|
2755
2861
|
}
|
|
2756
|
-
var
|
|
2862
|
+
var defaultIsPidAlive;
|
|
2863
|
+
var init_wrapper_exit_observability = __esm(() => {
|
|
2864
|
+
init_process_lifecycle();
|
|
2865
|
+
defaultIsPidAlive = pidLooksAlive;
|
|
2866
|
+
});
|
|
2757
2867
|
|
|
2758
2868
|
// src/pair-command.ts
|
|
2759
2869
|
function pairScopedCommand(cmd) {
|
|
@@ -2876,7 +2986,7 @@ import {
|
|
|
2876
2986
|
writeFileSync as writeFileSync6
|
|
2877
2987
|
} from "fs";
|
|
2878
2988
|
import { homedir as homedir3 } from "os";
|
|
2879
|
-
import { basename as
|
|
2989
|
+
import { basename as basename3, dirname as dirname3, join as join10 } from "path";
|
|
2880
2990
|
function nowIso() {
|
|
2881
2991
|
return new Date().toISOString();
|
|
2882
2992
|
}
|
|
@@ -2923,7 +3033,7 @@ function findCodexRolloutFile(threadId, env = process.env, maxEntries = 20000) {
|
|
|
2923
3033
|
}
|
|
2924
3034
|
if (!entry.isFile())
|
|
2925
3035
|
continue;
|
|
2926
|
-
const name =
|
|
3036
|
+
const name = basename3(entry.name);
|
|
2927
3037
|
if (name === exactName || name.startsWith("rollout-") && name.endsWith(".jsonl") && name.includes(threadId)) {
|
|
2928
3038
|
return path;
|
|
2929
3039
|
}
|
|
@@ -2957,145 +3067,6 @@ function readUsableCurrentThread(identity, env = process.env) {
|
|
|
2957
3067
|
}
|
|
2958
3068
|
var init_thread_state = () => {};
|
|
2959
3069
|
|
|
2960
|
-
// src/process-lifecycle.ts
|
|
2961
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
2962
|
-
import { basename as basename3 } from "path";
|
|
2963
|
-
function parsePsProcessList(output) {
|
|
2964
|
-
const entries = [];
|
|
2965
|
-
for (const line of output.split(/\r?\n/)) {
|
|
2966
|
-
const match = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
|
|
2967
|
-
if (!match)
|
|
2968
|
-
continue;
|
|
2969
|
-
const pid = Number.parseInt(match[1], 10);
|
|
2970
|
-
if (!Number.isFinite(pid))
|
|
2971
|
-
continue;
|
|
2972
|
-
entries.push({ pid, command: match[2] });
|
|
2973
|
-
}
|
|
2974
|
-
return entries;
|
|
2975
|
-
}
|
|
2976
|
-
function invokesCodexBinary(command) {
|
|
2977
|
-
const tokens = command.trim().split(/\s+/);
|
|
2978
|
-
const exe = tokens[0] ? basename3(tokens[0]) : "";
|
|
2979
|
-
if (exe === "codex")
|
|
2980
|
-
return true;
|
|
2981
|
-
if ((exe === "node" || exe === "bun") && tokens[1]) {
|
|
2982
|
-
return basename3(tokens[1]) === "codex";
|
|
2983
|
-
}
|
|
2984
|
-
return false;
|
|
2985
|
-
}
|
|
2986
|
-
function commandMatchesManagedCodexTui(command, proxyUrl) {
|
|
2987
|
-
if (!invokesCodexBinary(command))
|
|
2988
|
-
return false;
|
|
2989
|
-
if (!command.includes("tui_app_server"))
|
|
2990
|
-
return false;
|
|
2991
|
-
const remoteUrl = extractRemoteUrl(command);
|
|
2992
|
-
if (!remoteUrl)
|
|
2993
|
-
return false;
|
|
2994
|
-
if (!proxyUrl)
|
|
2995
|
-
return true;
|
|
2996
|
-
return remoteTargetsProxy(remoteUrl, proxyUrl);
|
|
2997
|
-
}
|
|
2998
|
-
function findManagedCodexTuiProcessesFromList(processes, proxyUrl) {
|
|
2999
|
-
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command, proxyUrl));
|
|
3000
|
-
}
|
|
3001
|
-
function findManagedCodexTuiProcesses(proxyUrl) {
|
|
3002
|
-
try {
|
|
3003
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
3004
|
-
return findManagedCodexTuiProcessesFromList(parsePsProcessList(output), proxyUrl).filter((entry) => entry.pid !== process.pid);
|
|
3005
|
-
} catch {
|
|
3006
|
-
return [];
|
|
3007
|
-
}
|
|
3008
|
-
}
|
|
3009
|
-
function listManagedCodexTuiProcessesFromList(processes) {
|
|
3010
|
-
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command)).map((entry) => ({ ...entry, remoteUrl: extractRemoteUrl(entry.command) }));
|
|
3011
|
-
}
|
|
3012
|
-
function listManagedCodexTuiProcesses() {
|
|
3013
|
-
try {
|
|
3014
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
3015
|
-
return listManagedCodexTuiProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
3016
|
-
} catch {
|
|
3017
|
-
return [];
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3020
|
-
function listBridgeFrontendProcessesFromList(processes) {
|
|
3021
|
-
return processes.filter((entry) => /(?:^|[\s/\\])bridge-server\.js(?:\s|$)/.test(entry.command) && (entry.command.includes("agentbridge") || entry.command.includes("agent_bridge")));
|
|
3022
|
-
}
|
|
3023
|
-
function listBridgeFrontendProcesses() {
|
|
3024
|
-
try {
|
|
3025
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
3026
|
-
return listBridgeFrontendProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
3027
|
-
} catch {
|
|
3028
|
-
return [];
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
function isProcessAlive2(pid) {
|
|
3032
|
-
try {
|
|
3033
|
-
process.kill(pid, 0);
|
|
3034
|
-
return true;
|
|
3035
|
-
} catch {
|
|
3036
|
-
return false;
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
function commandForPid(pid) {
|
|
3040
|
-
try {
|
|
3041
|
-
return execFileSync6("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
3042
|
-
} catch {
|
|
3043
|
-
return null;
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
function terminateProcessSync(pid, options = {}) {
|
|
3047
|
-
const gracefulTimeoutMs = options.gracefulTimeoutMs ?? 2000;
|
|
3048
|
-
const target = options.processGroup && process.platform !== "win32" ? -pid : pid;
|
|
3049
|
-
const label = options.processGroup && process.platform !== "win32" ? `process group ${pid}` : `pid ${pid}`;
|
|
3050
|
-
try {
|
|
3051
|
-
process.kill(target, "SIGTERM");
|
|
3052
|
-
options.log?.(`Sent SIGTERM to ${label}`);
|
|
3053
|
-
} catch {
|
|
3054
|
-
return !isProcessAlive2(pid);
|
|
3055
|
-
}
|
|
3056
|
-
if (waitForExitSync(pid, gracefulTimeoutMs))
|
|
3057
|
-
return true;
|
|
3058
|
-
try {
|
|
3059
|
-
process.kill(target, "SIGKILL");
|
|
3060
|
-
options.log?.(`Sent SIGKILL to ${label}`);
|
|
3061
|
-
} catch {}
|
|
3062
|
-
return waitForExitSync(pid, 500);
|
|
3063
|
-
}
|
|
3064
|
-
function waitForExitSync(pid, timeoutMs) {
|
|
3065
|
-
const deadline = Date.now() + timeoutMs;
|
|
3066
|
-
while (Date.now() < deadline) {
|
|
3067
|
-
if (!isProcessAlive2(pid))
|
|
3068
|
-
return true;
|
|
3069
|
-
sleepSync(50);
|
|
3070
|
-
}
|
|
3071
|
-
return !isProcessAlive2(pid);
|
|
3072
|
-
}
|
|
3073
|
-
function sleepSync(ms) {
|
|
3074
|
-
const buffer = new SharedArrayBuffer(4);
|
|
3075
|
-
const view = new Int32Array(buffer);
|
|
3076
|
-
Atomics.wait(view, 0, 0, ms);
|
|
3077
|
-
}
|
|
3078
|
-
function extractRemoteUrl(command) {
|
|
3079
|
-
const equals = command.match(/(?:^|\s)--remote=([^\s]+)/);
|
|
3080
|
-
if (equals)
|
|
3081
|
-
return equals[1];
|
|
3082
|
-
const separate = command.match(/(?:^|\s)--remote\s+([^\s]+)/);
|
|
3083
|
-
return separate?.[1] ?? null;
|
|
3084
|
-
}
|
|
3085
|
-
function remoteTargetsProxy(remoteUrl, proxyUrl) {
|
|
3086
|
-
try {
|
|
3087
|
-
const remote = new URL(remoteUrl);
|
|
3088
|
-
const proxy = new URL(proxyUrl);
|
|
3089
|
-
return remote.protocol === proxy.protocol && remote.hostname === proxy.hostname && remote.port === proxy.port && normalizePath(remote.pathname) === normalizePath(proxy.pathname);
|
|
3090
|
-
} catch {
|
|
3091
|
-
return remoteUrl === proxyUrl;
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
function normalizePath(pathname) {
|
|
3095
|
-
return pathname === "" ? "/" : pathname;
|
|
3096
|
-
}
|
|
3097
|
-
var init_process_lifecycle = () => {};
|
|
3098
|
-
|
|
3099
3070
|
// src/cli/codex.ts
|
|
3100
3071
|
var exports_codex = {};
|
|
3101
3072
|
__export(exports_codex, {
|
|
@@ -3104,7 +3075,7 @@ __export(exports_codex, {
|
|
|
3104
3075
|
parseAgentBridgeCodexArgs: () => parseAgentBridgeCodexArgs,
|
|
3105
3076
|
buildCodexArgs: () => buildCodexArgs
|
|
3106
3077
|
});
|
|
3107
|
-
import { spawn as spawn3, execSync as execSync2, execFileSync as
|
|
3078
|
+
import { spawn as spawn3, execSync as execSync2, execFileSync as execFileSync6 } from "child_process";
|
|
3108
3079
|
import {
|
|
3109
3080
|
openSync as openSync3,
|
|
3110
3081
|
writeSync,
|
|
@@ -3377,7 +3348,7 @@ async function runCodex(args) {
|
|
|
3377
3348
|
let attempts = 0;
|
|
3378
3349
|
const discover = () => {
|
|
3379
3350
|
attempts += 1;
|
|
3380
|
-
nativeChildPid = discoverNativeChildPid(launcherPid, (cmd, args2) =>
|
|
3351
|
+
nativeChildPid = discoverNativeChildPid(launcherPid, (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf-8", timeout: 2000 }));
|
|
3381
3352
|
if (nativeChildPid !== null) {
|
|
3382
3353
|
appendWrapperLog(wrapperLogPath, `native child pid=${nativeChildPid} (launcher pid=${launcherPid})`);
|
|
3383
3354
|
return;
|
|
@@ -3483,7 +3454,7 @@ async function runCodex(args) {
|
|
|
3483
3454
|
const tuiLogTail = captureTuiLogTail({
|
|
3484
3455
|
codexHome: join11(homedir4(), ".codex"),
|
|
3485
3456
|
nativePid: nativeChildPid,
|
|
3486
|
-
run: (cmd, args2) =>
|
|
3457
|
+
run: (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf-8", timeout: 2000 })
|
|
3487
3458
|
});
|
|
3488
3459
|
appendWrapperLog(wrapperLogPath, [
|
|
3489
3460
|
`exit: code=${code ?? "null"} signal=${signal ?? "null"} runtime_ms=${runtimeMs} pid=${child.pid ?? "unknown"} native_pid=${nativeChildPid ?? "unknown"} classification=${classification}`,
|
|
@@ -3535,7 +3506,7 @@ function traceCliStart2(event, args, originalEnv, envGuardAction, pair) {
|
|
|
3535
3506
|
function guardNoLiveManagedTui(stateDir, proxyUrl) {
|
|
3536
3507
|
const pid = readTuiPid(stateDir);
|
|
3537
3508
|
if (pid) {
|
|
3538
|
-
if (!
|
|
3509
|
+
if (!isProcessAlive(pid)) {
|
|
3539
3510
|
try {
|
|
3540
3511
|
unlinkSync4(stateDir.tuiPidFile);
|
|
3541
3512
|
} catch {}
|
|
@@ -4041,7 +4012,7 @@ async function killManagedCodexTui(stateDir, proxyUrl, log, gracefulTimeoutMs =
|
|
|
4041
4012
|
if (!pid) {
|
|
4042
4013
|
log("No Codex TUI pid file found");
|
|
4043
4014
|
removeTuiPidFile(stateDir);
|
|
4044
|
-
} else if (!
|
|
4015
|
+
} else if (!isProcessAlive(pid)) {
|
|
4045
4016
|
log(`Codex TUI pid ${pid} is not alive, cleaning up stale pid file`);
|
|
4046
4017
|
removeTuiPidFile(stateDir);
|
|
4047
4018
|
} else if (!isManagedCodexTuiProcess2(pid, proxyUrl)) {
|
package/dist/daemon.js
CHANGED
|
@@ -17,8 +17,8 @@ function defineNumber(value, fallback) {
|
|
|
17
17
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
18
18
|
}
|
|
19
19
|
var BUILD_INFO = Object.freeze({
|
|
20
|
-
version: defineString("0.1.
|
|
21
|
-
commit: defineString("
|
|
20
|
+
version: defineString("0.1.11", "0.0.0-source"),
|
|
21
|
+
commit: defineString("48eb0ed", "source"),
|
|
22
22
|
bundle: defineBundle("dist"),
|
|
23
23
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
24
24
|
});
|
|
@@ -2225,7 +2225,7 @@ class TuiConnectionState {
|
|
|
2225
2225
|
}
|
|
2226
2226
|
|
|
2227
2227
|
// src/daemon-lifecycle.ts
|
|
2228
|
-
import { spawn as spawn2
|
|
2228
|
+
import { spawn as spawn2 } from "child_process";
|
|
2229
2229
|
import { existsSync as existsSync3, readFileSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
2230
2230
|
import { fileURLToPath } from "url";
|
|
2231
2231
|
|
|
@@ -2242,6 +2242,41 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
2242
2242
|
return parsed;
|
|
2243
2243
|
}
|
|
2244
2244
|
|
|
2245
|
+
// src/process-lifecycle.ts
|
|
2246
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2247
|
+
function commandForPid(pid) {
|
|
2248
|
+
try {
|
|
2249
|
+
return execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2250
|
+
} catch {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function pidLooksAlive(pid) {
|
|
2255
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
2256
|
+
return false;
|
|
2257
|
+
try {
|
|
2258
|
+
process.kill(pid, 0);
|
|
2259
|
+
return true;
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
return err?.code === "EPERM";
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
var isProcessAlive = pidLooksAlive;
|
|
2265
|
+
function isAgentBridgeDaemon(pid, lookup = commandForPid) {
|
|
2266
|
+
const cmd = lookup(pid);
|
|
2267
|
+
if (cmd === null)
|
|
2268
|
+
return false;
|
|
2269
|
+
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
2270
|
+
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2271
|
+
return hasDaemonEntry && hasAgentbridge;
|
|
2272
|
+
}
|
|
2273
|
+
function isAgentBridgeProcess(pid, lookup = commandForPid) {
|
|
2274
|
+
const cmd = lookup(pid);
|
|
2275
|
+
if (cmd === null)
|
|
2276
|
+
return false;
|
|
2277
|
+
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2245
2280
|
// src/daemon-lifecycle.ts
|
|
2246
2281
|
var DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
2247
2282
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
@@ -2341,7 +2376,7 @@ class DaemonLifecycle {
|
|
|
2341
2376
|
const existingPid = this.readPid();
|
|
2342
2377
|
if (existingPid) {
|
|
2343
2378
|
if (isProcessAlive(existingPid)) {
|
|
2344
|
-
if (
|
|
2379
|
+
if (isAgentBridgeDaemon(existingPid)) {
|
|
2345
2380
|
try {
|
|
2346
2381
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
2347
2382
|
return;
|
|
@@ -2552,7 +2587,7 @@ class DaemonLifecycle {
|
|
|
2552
2587
|
this.releaseLock();
|
|
2553
2588
|
return this.acquireLockStrict(true);
|
|
2554
2589
|
}
|
|
2555
|
-
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !
|
|
2590
|
+
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !isAgentBridgeProcess(holderPid)) {
|
|
2556
2591
|
this.log(`Startup lock is ${Math.round(this.lockAgeMs() / 1000)}s old and holder pid ${holderPid} ` + `is an unrelated process (pid recycled), reclaiming`);
|
|
2557
2592
|
this.releaseLock();
|
|
2558
2593
|
return this.acquireLockStrict(true);
|
|
@@ -2573,14 +2608,6 @@ class DaemonLifecycle {
|
|
|
2573
2608
|
return 0;
|
|
2574
2609
|
}
|
|
2575
2610
|
}
|
|
2576
|
-
isAgentBridgeProcess(pid) {
|
|
2577
|
-
try {
|
|
2578
|
-
const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2579
|
-
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2580
|
-
} catch {
|
|
2581
|
-
return false;
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
2611
|
releaseLock() {
|
|
2585
2612
|
try {
|
|
2586
2613
|
unlinkSync2(this.stateDir.lockFile);
|
|
@@ -2598,7 +2625,7 @@ class DaemonLifecycle {
|
|
|
2598
2625
|
this.cleanup();
|
|
2599
2626
|
return false;
|
|
2600
2627
|
}
|
|
2601
|
-
if (!
|
|
2628
|
+
if (!isAgentBridgeDaemon(pid)) {
|
|
2602
2629
|
this.log(`Pid ${pid} is alive but is NOT an AgentBridge daemon \u2014 refusing to kill. Cleaning up stale pid file.`);
|
|
2603
2630
|
this.cleanup();
|
|
2604
2631
|
return false;
|
|
@@ -2626,16 +2653,6 @@ class DaemonLifecycle {
|
|
|
2626
2653
|
this.cleanup();
|
|
2627
2654
|
return true;
|
|
2628
2655
|
}
|
|
2629
|
-
isDaemonProcess(pid) {
|
|
2630
|
-
try {
|
|
2631
|
-
const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2632
|
-
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
2633
|
-
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2634
|
-
return hasDaemonEntry && hasAgentbridge;
|
|
2635
|
-
} catch {
|
|
2636
|
-
return false;
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
2656
|
cleanup() {
|
|
2640
2657
|
this.removePidFile();
|
|
2641
2658
|
this.removeStatusFile();
|
|
@@ -2650,14 +2667,6 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
2650
2667
|
clearTimeout(timer);
|
|
2651
2668
|
}
|
|
2652
2669
|
}
|
|
2653
|
-
function isProcessAlive(pid) {
|
|
2654
|
-
try {
|
|
2655
|
-
process.kill(pid, 0);
|
|
2656
|
-
return true;
|
|
2657
|
-
} catch {
|
|
2658
|
-
return false;
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
2670
|
|
|
2662
2671
|
// src/config-service.ts
|
|
2663
2672
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
package/package.json
CHANGED
|
@@ -14227,8 +14227,8 @@ function defineNumber(value, fallback) {
|
|
|
14227
14227
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14228
14228
|
}
|
|
14229
14229
|
var BUILD_INFO = Object.freeze({
|
|
14230
|
-
version: defineString("0.1.
|
|
14231
|
-
commit: defineString("
|
|
14230
|
+
version: defineString("0.1.11", "0.0.0-source"),
|
|
14231
|
+
commit: defineString("48eb0ed", "source"),
|
|
14232
14232
|
bundle: defineBundle("plugin"),
|
|
14233
14233
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
14234
14234
|
});
|
|
@@ -14485,7 +14485,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14485
14485
|
}
|
|
14486
14486
|
|
|
14487
14487
|
// src/daemon-lifecycle.ts
|
|
14488
|
-
import { spawn
|
|
14488
|
+
import { spawn } from "child_process";
|
|
14489
14489
|
import { existsSync as existsSync3, readFileSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
14490
14490
|
import { fileURLToPath } from "url";
|
|
14491
14491
|
|
|
@@ -14502,6 +14502,41 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
14502
14502
|
return parsed;
|
|
14503
14503
|
}
|
|
14504
14504
|
|
|
14505
|
+
// src/process-lifecycle.ts
|
|
14506
|
+
import { execFileSync } from "child_process";
|
|
14507
|
+
function commandForPid(pid) {
|
|
14508
|
+
try {
|
|
14509
|
+
return execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
14510
|
+
} catch {
|
|
14511
|
+
return null;
|
|
14512
|
+
}
|
|
14513
|
+
}
|
|
14514
|
+
function pidLooksAlive(pid) {
|
|
14515
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
14516
|
+
return false;
|
|
14517
|
+
try {
|
|
14518
|
+
process.kill(pid, 0);
|
|
14519
|
+
return true;
|
|
14520
|
+
} catch (err) {
|
|
14521
|
+
return err?.code === "EPERM";
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
var isProcessAlive = pidLooksAlive;
|
|
14525
|
+
function isAgentBridgeDaemon(pid, lookup = commandForPid) {
|
|
14526
|
+
const cmd = lookup(pid);
|
|
14527
|
+
if (cmd === null)
|
|
14528
|
+
return false;
|
|
14529
|
+
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
14530
|
+
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
14531
|
+
return hasDaemonEntry && hasAgentbridge;
|
|
14532
|
+
}
|
|
14533
|
+
function isAgentBridgeProcess(pid, lookup = commandForPid) {
|
|
14534
|
+
const cmd = lookup(pid);
|
|
14535
|
+
if (cmd === null)
|
|
14536
|
+
return false;
|
|
14537
|
+
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
14538
|
+
}
|
|
14539
|
+
|
|
14505
14540
|
// src/daemon-lifecycle.ts
|
|
14506
14541
|
var DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
14507
14542
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
@@ -14601,7 +14636,7 @@ class DaemonLifecycle {
|
|
|
14601
14636
|
const existingPid = this.readPid();
|
|
14602
14637
|
if (existingPid) {
|
|
14603
14638
|
if (isProcessAlive(existingPid)) {
|
|
14604
|
-
if (
|
|
14639
|
+
if (isAgentBridgeDaemon(existingPid)) {
|
|
14605
14640
|
try {
|
|
14606
14641
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
14607
14642
|
return;
|
|
@@ -14812,7 +14847,7 @@ class DaemonLifecycle {
|
|
|
14812
14847
|
this.releaseLock();
|
|
14813
14848
|
return this.acquireLockStrict(true);
|
|
14814
14849
|
}
|
|
14815
|
-
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !
|
|
14850
|
+
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !isAgentBridgeProcess(holderPid)) {
|
|
14816
14851
|
this.log(`Startup lock is ${Math.round(this.lockAgeMs() / 1000)}s old and holder pid ${holderPid} ` + `is an unrelated process (pid recycled), reclaiming`);
|
|
14817
14852
|
this.releaseLock();
|
|
14818
14853
|
return this.acquireLockStrict(true);
|
|
@@ -14833,14 +14868,6 @@ class DaemonLifecycle {
|
|
|
14833
14868
|
return 0;
|
|
14834
14869
|
}
|
|
14835
14870
|
}
|
|
14836
|
-
isAgentBridgeProcess(pid) {
|
|
14837
|
-
try {
|
|
14838
|
-
const cmd = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
14839
|
-
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
14840
|
-
} catch {
|
|
14841
|
-
return false;
|
|
14842
|
-
}
|
|
14843
|
-
}
|
|
14844
14871
|
releaseLock() {
|
|
14845
14872
|
try {
|
|
14846
14873
|
unlinkSync2(this.stateDir.lockFile);
|
|
@@ -14858,7 +14885,7 @@ class DaemonLifecycle {
|
|
|
14858
14885
|
this.cleanup();
|
|
14859
14886
|
return false;
|
|
14860
14887
|
}
|
|
14861
|
-
if (!
|
|
14888
|
+
if (!isAgentBridgeDaemon(pid)) {
|
|
14862
14889
|
this.log(`Pid ${pid} is alive but is NOT an AgentBridge daemon \u2014 refusing to kill. Cleaning up stale pid file.`);
|
|
14863
14890
|
this.cleanup();
|
|
14864
14891
|
return false;
|
|
@@ -14886,16 +14913,6 @@ class DaemonLifecycle {
|
|
|
14886
14913
|
this.cleanup();
|
|
14887
14914
|
return true;
|
|
14888
14915
|
}
|
|
14889
|
-
isDaemonProcess(pid) {
|
|
14890
|
-
try {
|
|
14891
|
-
const cmd = execFileSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
14892
|
-
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
14893
|
-
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
14894
|
-
return hasDaemonEntry && hasAgentbridge;
|
|
14895
|
-
} catch {
|
|
14896
|
-
return false;
|
|
14897
|
-
}
|
|
14898
|
-
}
|
|
14899
14916
|
cleanup() {
|
|
14900
14917
|
this.removePidFile();
|
|
14901
14918
|
this.removeStatusFile();
|
|
@@ -14910,14 +14927,6 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
14910
14927
|
clearTimeout(timer);
|
|
14911
14928
|
}
|
|
14912
14929
|
}
|
|
14913
|
-
function isProcessAlive(pid) {
|
|
14914
|
-
try {
|
|
14915
|
-
process.kill(pid, 0);
|
|
14916
|
-
return true;
|
|
14917
|
-
} catch {
|
|
14918
|
-
return false;
|
|
14919
|
-
}
|
|
14920
|
-
}
|
|
14921
14930
|
|
|
14922
14931
|
// src/config-service.ts
|
|
14923
14932
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
@@ -17,8 +17,8 @@ function defineNumber(value, fallback) {
|
|
|
17
17
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
18
18
|
}
|
|
19
19
|
var BUILD_INFO = Object.freeze({
|
|
20
|
-
version: defineString("0.1.
|
|
21
|
-
commit: defineString("
|
|
20
|
+
version: defineString("0.1.11", "0.0.0-source"),
|
|
21
|
+
commit: defineString("48eb0ed", "source"),
|
|
22
22
|
bundle: defineBundle("plugin"),
|
|
23
23
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
24
24
|
});
|
|
@@ -2225,7 +2225,7 @@ class TuiConnectionState {
|
|
|
2225
2225
|
}
|
|
2226
2226
|
|
|
2227
2227
|
// src/daemon-lifecycle.ts
|
|
2228
|
-
import { spawn as spawn2
|
|
2228
|
+
import { spawn as spawn2 } from "child_process";
|
|
2229
2229
|
import { existsSync as existsSync3, readFileSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
2230
2230
|
import { fileURLToPath } from "url";
|
|
2231
2231
|
|
|
@@ -2242,6 +2242,41 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
2242
2242
|
return parsed;
|
|
2243
2243
|
}
|
|
2244
2244
|
|
|
2245
|
+
// src/process-lifecycle.ts
|
|
2246
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2247
|
+
function commandForPid(pid) {
|
|
2248
|
+
try {
|
|
2249
|
+
return execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2250
|
+
} catch {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function pidLooksAlive(pid) {
|
|
2255
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
2256
|
+
return false;
|
|
2257
|
+
try {
|
|
2258
|
+
process.kill(pid, 0);
|
|
2259
|
+
return true;
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
return err?.code === "EPERM";
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
var isProcessAlive = pidLooksAlive;
|
|
2265
|
+
function isAgentBridgeDaemon(pid, lookup = commandForPid) {
|
|
2266
|
+
const cmd = lookup(pid);
|
|
2267
|
+
if (cmd === null)
|
|
2268
|
+
return false;
|
|
2269
|
+
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
2270
|
+
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2271
|
+
return hasDaemonEntry && hasAgentbridge;
|
|
2272
|
+
}
|
|
2273
|
+
function isAgentBridgeProcess(pid, lookup = commandForPid) {
|
|
2274
|
+
const cmd = lookup(pid);
|
|
2275
|
+
if (cmd === null)
|
|
2276
|
+
return false;
|
|
2277
|
+
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2245
2280
|
// src/daemon-lifecycle.ts
|
|
2246
2281
|
var DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
2247
2282
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
@@ -2341,7 +2376,7 @@ class DaemonLifecycle {
|
|
|
2341
2376
|
const existingPid = this.readPid();
|
|
2342
2377
|
if (existingPid) {
|
|
2343
2378
|
if (isProcessAlive(existingPid)) {
|
|
2344
|
-
if (
|
|
2379
|
+
if (isAgentBridgeDaemon(existingPid)) {
|
|
2345
2380
|
try {
|
|
2346
2381
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
2347
2382
|
return;
|
|
@@ -2552,7 +2587,7 @@ class DaemonLifecycle {
|
|
|
2552
2587
|
this.releaseLock();
|
|
2553
2588
|
return this.acquireLockStrict(true);
|
|
2554
2589
|
}
|
|
2555
|
-
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !
|
|
2590
|
+
if (Number.isFinite(holderPid) && this.lockAgeMs() > LOCK_IDENTITY_GRACE_MS && !isAgentBridgeProcess(holderPid)) {
|
|
2556
2591
|
this.log(`Startup lock is ${Math.round(this.lockAgeMs() / 1000)}s old and holder pid ${holderPid} ` + `is an unrelated process (pid recycled), reclaiming`);
|
|
2557
2592
|
this.releaseLock();
|
|
2558
2593
|
return this.acquireLockStrict(true);
|
|
@@ -2573,14 +2608,6 @@ class DaemonLifecycle {
|
|
|
2573
2608
|
return 0;
|
|
2574
2609
|
}
|
|
2575
2610
|
}
|
|
2576
|
-
isAgentBridgeProcess(pid) {
|
|
2577
|
-
try {
|
|
2578
|
-
const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2579
|
-
return cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2580
|
-
} catch {
|
|
2581
|
-
return false;
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
2611
|
releaseLock() {
|
|
2585
2612
|
try {
|
|
2586
2613
|
unlinkSync2(this.stateDir.lockFile);
|
|
@@ -2598,7 +2625,7 @@ class DaemonLifecycle {
|
|
|
2598
2625
|
this.cleanup();
|
|
2599
2626
|
return false;
|
|
2600
2627
|
}
|
|
2601
|
-
if (!
|
|
2628
|
+
if (!isAgentBridgeDaemon(pid)) {
|
|
2602
2629
|
this.log(`Pid ${pid} is alive but is NOT an AgentBridge daemon \u2014 refusing to kill. Cleaning up stale pid file.`);
|
|
2603
2630
|
this.cleanup();
|
|
2604
2631
|
return false;
|
|
@@ -2626,16 +2653,6 @@ class DaemonLifecycle {
|
|
|
2626
2653
|
this.cleanup();
|
|
2627
2654
|
return true;
|
|
2628
2655
|
}
|
|
2629
|
-
isDaemonProcess(pid) {
|
|
2630
|
-
try {
|
|
2631
|
-
const cmd = execFileSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2632
|
-
const hasDaemonEntry = /(?:^|[\s/\\])[\w.-]*-?daemon\.(?:ts|js)(?:\s|$)/.test(cmd);
|
|
2633
|
-
const hasAgentbridge = cmd.includes("agentbridge") || cmd.includes("agent_bridge");
|
|
2634
|
-
return hasDaemonEntry && hasAgentbridge;
|
|
2635
|
-
} catch {
|
|
2636
|
-
return false;
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
2656
|
cleanup() {
|
|
2640
2657
|
this.removePidFile();
|
|
2641
2658
|
this.removeStatusFile();
|
|
@@ -2650,14 +2667,6 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
2650
2667
|
clearTimeout(timer);
|
|
2651
2668
|
}
|
|
2652
2669
|
}
|
|
2653
|
-
function isProcessAlive(pid) {
|
|
2654
|
-
try {
|
|
2655
|
-
process.kill(pid, 0);
|
|
2656
|
-
return true;
|
|
2657
|
-
} catch {
|
|
2658
|
-
return false;
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
2670
|
|
|
2662
2671
|
// src/config-service.ts
|
|
2663
2672
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|