@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.
@@ -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.10",
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.10",
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.10", "0.0.0-source"),
1169
- commit: defineString("51a44cb", "source"),
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, execFileSync as execFileSync4 } from "child_process";
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 (this.isDaemonProcess(existingPid)) {
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 && !this.isAgentBridgeProcess(holderPid)) {
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 (!this.isDaemonProcess(pid)) {
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) || !isDaemonProcess(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 init_wrapper_exit_observability = () => {};
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 basename2, dirname as dirname3, join as join10 } from "path";
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 = basename2(entry.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 execFileSync7 } from "child_process";
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) => execFileSync7(cmd, args2, { encoding: "utf-8", timeout: 2000 }));
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) => execFileSync7(cmd, args2, { encoding: "utf-8", timeout: 2000 })
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 (!isProcessAlive2(pid)) {
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 (!isProcessAlive2(pid)) {
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.10", "0.0.0-source"),
21
- commit: defineString("51a44cb", "source"),
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, execFileSync as execFileSync2 } from "child_process";
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 (this.isDaemonProcess(existingPid)) {
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 && !this.isAgentBridgeProcess(holderPid)) {
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 (!this.isDaemonProcess(pid)) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raysonmeng/agentbridge",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Bridge between Claude Code and Codex — bidirectional agent communication via MCP Channel + JSON-RPC",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.11",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbridge",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Bridge Claude Code and Codex with a shared daemon, push channel delivery, and bidirectional reply tooling.",
5
5
  "author": {
6
6
  "name": "AgentBridge Contributors",
@@ -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.10", "0.0.0-source"),
14231
- commit: defineString("51a44cb", "source"),
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, execFileSync } from "child_process";
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 (this.isDaemonProcess(existingPid)) {
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 && !this.isAgentBridgeProcess(holderPid)) {
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 (!this.isDaemonProcess(pid)) {
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.10", "0.0.0-source"),
21
- commit: defineString("51a44cb", "source"),
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, execFileSync as execFileSync2 } from "child_process";
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 (this.isDaemonProcess(existingPid)) {
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 && !this.isAgentBridgeProcess(holderPid)) {
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 (!this.isDaemonProcess(pid)) {
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";