@raysonmeng/agentbridge 0.1.9 → 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/README.md +5 -3
- package/dist/cli.js +410 -236
- 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
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 {
|
|
@@ -2441,6 +2557,46 @@ var init_trace_log = __esm(() => {
|
|
|
2441
2557
|
RELEVANT_ENV_RE = /^(AGENTBRIDGE_|CODEX_)/;
|
|
2442
2558
|
});
|
|
2443
2559
|
|
|
2560
|
+
// src/cli/max-permissions.ts
|
|
2561
|
+
function planMaxPermissions(args, suppressors, env = process.env) {
|
|
2562
|
+
let safeFlag = false;
|
|
2563
|
+
const rest = [];
|
|
2564
|
+
for (const a of args) {
|
|
2565
|
+
if (a === "--safe") {
|
|
2566
|
+
safeFlag = true;
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
rest.push(a);
|
|
2570
|
+
}
|
|
2571
|
+
const safeMode = safeFlag || env.AGENTBRIDGE_SAFE === "1";
|
|
2572
|
+
const userExpressedPreference = rest.some((a) => suppressors.some((s) => matchesSuppressor(a, s)));
|
|
2573
|
+
return { args: rest, inject: !safeMode && !userExpressedPreference, safeMode };
|
|
2574
|
+
}
|
|
2575
|
+
function matchesSuppressor(token, suppressor) {
|
|
2576
|
+
if (token === suppressor || token.startsWith(`${suppressor}=`))
|
|
2577
|
+
return true;
|
|
2578
|
+
if (/^-[A-Za-z]$/.test(suppressor) && !token.startsWith("--") && token.startsWith(suppressor) && token.length > suppressor.length) {
|
|
2579
|
+
return true;
|
|
2580
|
+
}
|
|
2581
|
+
return false;
|
|
2582
|
+
}
|
|
2583
|
+
var CLAUDE_MAX_PERMISSION_FLAG = "--dangerously-skip-permissions", CLAUDE_MAX_PERMISSION_SUPPRESSORS, CODEX_MAX_PERMISSION_FLAG = "--yolo", CODEX_MAX_PERMISSION_SUPPRESSORS;
|
|
2584
|
+
var init_max_permissions = __esm(() => {
|
|
2585
|
+
CLAUDE_MAX_PERMISSION_SUPPRESSORS = [
|
|
2586
|
+
CLAUDE_MAX_PERMISSION_FLAG,
|
|
2587
|
+
"--allow-dangerously-skip-permissions",
|
|
2588
|
+
"--permission-mode"
|
|
2589
|
+
];
|
|
2590
|
+
CODEX_MAX_PERMISSION_SUPPRESSORS = [
|
|
2591
|
+
CODEX_MAX_PERMISSION_FLAG,
|
|
2592
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
2593
|
+
"-a",
|
|
2594
|
+
"--ask-for-approval",
|
|
2595
|
+
"-s",
|
|
2596
|
+
"--sandbox"
|
|
2597
|
+
];
|
|
2598
|
+
});
|
|
2599
|
+
|
|
2444
2600
|
// src/cli/claude.ts
|
|
2445
2601
|
var exports_claude = {};
|
|
2446
2602
|
__export(exports_claude, {
|
|
@@ -2457,7 +2613,9 @@ async function runClaude(args) {
|
|
|
2457
2613
|
allowStrict: true,
|
|
2458
2614
|
log: (msg) => console.error(msg)
|
|
2459
2615
|
});
|
|
2460
|
-
const { pairFlag, rest } = parsePairFlag(args);
|
|
2616
|
+
const { pairFlag, rest: pairRest } = parsePairFlag(args);
|
|
2617
|
+
const permissionPlan = planMaxPermissions(pairRest, CLAUDE_MAX_PERMISSION_SUPPRESSORS);
|
|
2618
|
+
const rest = permissionPlan.args;
|
|
2461
2619
|
checkOwnedFlagConflicts(rest, "agentbridge claude", OWNED_FLAGS);
|
|
2462
2620
|
let pair;
|
|
2463
2621
|
try {
|
|
@@ -2484,9 +2642,13 @@ async function runClaude(args) {
|
|
|
2484
2642
|
await assertPairNotLive(lifecycle, pair);
|
|
2485
2643
|
lifecycle.clearKilled();
|
|
2486
2644
|
const channelEntry = `plugin:${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2645
|
+
if (permissionPlan.inject) {
|
|
2646
|
+
console.error(`[agentbridge] running with ${CLAUDE_MAX_PERMISSION_FLAG} (default; opt out with --safe or AGENTBRIDGE_SAFE=1)`);
|
|
2647
|
+
}
|
|
2487
2648
|
const fullArgs = [
|
|
2488
2649
|
"--dangerously-load-development-channels",
|
|
2489
2650
|
channelEntry,
|
|
2651
|
+
...permissionPlan.inject ? [CLAUDE_MAX_PERMISSION_FLAG] : [],
|
|
2490
2652
|
...rest
|
|
2491
2653
|
];
|
|
2492
2654
|
const child = spawn2("claude", fullArgs, {
|
|
@@ -2588,6 +2750,7 @@ var init_claude = __esm(() => {
|
|
|
2588
2750
|
init_env_guard();
|
|
2589
2751
|
init_pair_resolver();
|
|
2590
2752
|
init_trace_log();
|
|
2753
|
+
init_max_permissions();
|
|
2591
2754
|
OWNED_FLAGS = ["--channels", "--dangerously-load-development-channels"];
|
|
2592
2755
|
});
|
|
2593
2756
|
|
|
@@ -2650,16 +2813,6 @@ function readTurnInProgress(statusFilePath, read = (p) => readFileSync7(p, "utf-
|
|
|
2650
2813
|
return null;
|
|
2651
2814
|
}
|
|
2652
2815
|
}
|
|
2653
|
-
function defaultIsPidAlive(pid) {
|
|
2654
|
-
if (pid <= 0)
|
|
2655
|
-
return false;
|
|
2656
|
-
try {
|
|
2657
|
-
process.kill(pid, 0);
|
|
2658
|
-
return true;
|
|
2659
|
-
} catch (err) {
|
|
2660
|
-
return err.code === "EPERM";
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
2816
|
function refineCleanExitClassification(turnInProgress) {
|
|
2664
2817
|
if (turnInProgress === true)
|
|
2665
2818
|
return "exit_0_during_turn";
|
|
@@ -2706,7 +2859,11 @@ function captureTuiLogTail(options) {
|
|
|
2706
2859
|
return `(tui log tail capture failed: ${err instanceof Error ? err.message : String(err)})`;
|
|
2707
2860
|
}
|
|
2708
2861
|
}
|
|
2709
|
-
var
|
|
2862
|
+
var defaultIsPidAlive;
|
|
2863
|
+
var init_wrapper_exit_observability = __esm(() => {
|
|
2864
|
+
init_process_lifecycle();
|
|
2865
|
+
defaultIsPidAlive = pidLooksAlive;
|
|
2866
|
+
});
|
|
2710
2867
|
|
|
2711
2868
|
// src/pair-command.ts
|
|
2712
2869
|
function pairScopedCommand(cmd) {
|
|
@@ -2829,7 +2986,7 @@ import {
|
|
|
2829
2986
|
writeFileSync as writeFileSync6
|
|
2830
2987
|
} from "fs";
|
|
2831
2988
|
import { homedir as homedir3 } from "os";
|
|
2832
|
-
import { basename as
|
|
2989
|
+
import { basename as basename3, dirname as dirname3, join as join10 } from "path";
|
|
2833
2990
|
function nowIso() {
|
|
2834
2991
|
return new Date().toISOString();
|
|
2835
2992
|
}
|
|
@@ -2876,7 +3033,7 @@ function findCodexRolloutFile(threadId, env = process.env, maxEntries = 20000) {
|
|
|
2876
3033
|
}
|
|
2877
3034
|
if (!entry.isFile())
|
|
2878
3035
|
continue;
|
|
2879
|
-
const name =
|
|
3036
|
+
const name = basename3(entry.name);
|
|
2880
3037
|
if (name === exactName || name.startsWith("rollout-") && name.endsWith(".jsonl") && name.includes(threadId)) {
|
|
2881
3038
|
return path;
|
|
2882
3039
|
}
|
|
@@ -2910,145 +3067,6 @@ function readUsableCurrentThread(identity, env = process.env) {
|
|
|
2910
3067
|
}
|
|
2911
3068
|
var init_thread_state = () => {};
|
|
2912
3069
|
|
|
2913
|
-
// src/process-lifecycle.ts
|
|
2914
|
-
import { execFileSync as execFileSync6 } from "child_process";
|
|
2915
|
-
import { basename as basename3 } from "path";
|
|
2916
|
-
function parsePsProcessList(output) {
|
|
2917
|
-
const entries = [];
|
|
2918
|
-
for (const line of output.split(/\r?\n/)) {
|
|
2919
|
-
const match = line.match(/^\s*(\d+)\s+(.+?)\s*$/);
|
|
2920
|
-
if (!match)
|
|
2921
|
-
continue;
|
|
2922
|
-
const pid = Number.parseInt(match[1], 10);
|
|
2923
|
-
if (!Number.isFinite(pid))
|
|
2924
|
-
continue;
|
|
2925
|
-
entries.push({ pid, command: match[2] });
|
|
2926
|
-
}
|
|
2927
|
-
return entries;
|
|
2928
|
-
}
|
|
2929
|
-
function invokesCodexBinary(command) {
|
|
2930
|
-
const tokens = command.trim().split(/\s+/);
|
|
2931
|
-
const exe = tokens[0] ? basename3(tokens[0]) : "";
|
|
2932
|
-
if (exe === "codex")
|
|
2933
|
-
return true;
|
|
2934
|
-
if ((exe === "node" || exe === "bun") && tokens[1]) {
|
|
2935
|
-
return basename3(tokens[1]) === "codex";
|
|
2936
|
-
}
|
|
2937
|
-
return false;
|
|
2938
|
-
}
|
|
2939
|
-
function commandMatchesManagedCodexTui(command, proxyUrl) {
|
|
2940
|
-
if (!invokesCodexBinary(command))
|
|
2941
|
-
return false;
|
|
2942
|
-
if (!command.includes("tui_app_server"))
|
|
2943
|
-
return false;
|
|
2944
|
-
const remoteUrl = extractRemoteUrl(command);
|
|
2945
|
-
if (!remoteUrl)
|
|
2946
|
-
return false;
|
|
2947
|
-
if (!proxyUrl)
|
|
2948
|
-
return true;
|
|
2949
|
-
return remoteTargetsProxy(remoteUrl, proxyUrl);
|
|
2950
|
-
}
|
|
2951
|
-
function findManagedCodexTuiProcessesFromList(processes, proxyUrl) {
|
|
2952
|
-
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command, proxyUrl));
|
|
2953
|
-
}
|
|
2954
|
-
function findManagedCodexTuiProcesses(proxyUrl) {
|
|
2955
|
-
try {
|
|
2956
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
2957
|
-
return findManagedCodexTuiProcessesFromList(parsePsProcessList(output), proxyUrl).filter((entry) => entry.pid !== process.pid);
|
|
2958
|
-
} catch {
|
|
2959
|
-
return [];
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
function listManagedCodexTuiProcessesFromList(processes) {
|
|
2963
|
-
return processes.filter((entry) => commandMatchesManagedCodexTui(entry.command)).map((entry) => ({ ...entry, remoteUrl: extractRemoteUrl(entry.command) }));
|
|
2964
|
-
}
|
|
2965
|
-
function listManagedCodexTuiProcesses() {
|
|
2966
|
-
try {
|
|
2967
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
2968
|
-
return listManagedCodexTuiProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
2969
|
-
} catch {
|
|
2970
|
-
return [];
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
function listBridgeFrontendProcessesFromList(processes) {
|
|
2974
|
-
return processes.filter((entry) => /(?:^|[\s/\\])bridge-server\.js(?:\s|$)/.test(entry.command) && (entry.command.includes("agentbridge") || entry.command.includes("agent_bridge")));
|
|
2975
|
-
}
|
|
2976
|
-
function listBridgeFrontendProcesses() {
|
|
2977
|
-
try {
|
|
2978
|
-
const output = execFileSync6("ps", ["-axo", "pid=,command="], { encoding: "utf-8" });
|
|
2979
|
-
return listBridgeFrontendProcessesFromList(parsePsProcessList(output)).filter((entry) => entry.pid !== process.pid);
|
|
2980
|
-
} catch {
|
|
2981
|
-
return [];
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
function isProcessAlive2(pid) {
|
|
2985
|
-
try {
|
|
2986
|
-
process.kill(pid, 0);
|
|
2987
|
-
return true;
|
|
2988
|
-
} catch {
|
|
2989
|
-
return false;
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
function commandForPid(pid) {
|
|
2993
|
-
try {
|
|
2994
|
-
return execFileSync6("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf-8" }).trim();
|
|
2995
|
-
} catch {
|
|
2996
|
-
return null;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
function terminateProcessSync(pid, options = {}) {
|
|
3000
|
-
const gracefulTimeoutMs = options.gracefulTimeoutMs ?? 2000;
|
|
3001
|
-
const target = options.processGroup && process.platform !== "win32" ? -pid : pid;
|
|
3002
|
-
const label = options.processGroup && process.platform !== "win32" ? `process group ${pid}` : `pid ${pid}`;
|
|
3003
|
-
try {
|
|
3004
|
-
process.kill(target, "SIGTERM");
|
|
3005
|
-
options.log?.(`Sent SIGTERM to ${label}`);
|
|
3006
|
-
} catch {
|
|
3007
|
-
return !isProcessAlive2(pid);
|
|
3008
|
-
}
|
|
3009
|
-
if (waitForExitSync(pid, gracefulTimeoutMs))
|
|
3010
|
-
return true;
|
|
3011
|
-
try {
|
|
3012
|
-
process.kill(target, "SIGKILL");
|
|
3013
|
-
options.log?.(`Sent SIGKILL to ${label}`);
|
|
3014
|
-
} catch {}
|
|
3015
|
-
return waitForExitSync(pid, 500);
|
|
3016
|
-
}
|
|
3017
|
-
function waitForExitSync(pid, timeoutMs) {
|
|
3018
|
-
const deadline = Date.now() + timeoutMs;
|
|
3019
|
-
while (Date.now() < deadline) {
|
|
3020
|
-
if (!isProcessAlive2(pid))
|
|
3021
|
-
return true;
|
|
3022
|
-
sleepSync(50);
|
|
3023
|
-
}
|
|
3024
|
-
return !isProcessAlive2(pid);
|
|
3025
|
-
}
|
|
3026
|
-
function sleepSync(ms) {
|
|
3027
|
-
const buffer = new SharedArrayBuffer(4);
|
|
3028
|
-
const view = new Int32Array(buffer);
|
|
3029
|
-
Atomics.wait(view, 0, 0, ms);
|
|
3030
|
-
}
|
|
3031
|
-
function extractRemoteUrl(command) {
|
|
3032
|
-
const equals = command.match(/(?:^|\s)--remote=([^\s]+)/);
|
|
3033
|
-
if (equals)
|
|
3034
|
-
return equals[1];
|
|
3035
|
-
const separate = command.match(/(?:^|\s)--remote\s+([^\s]+)/);
|
|
3036
|
-
return separate?.[1] ?? null;
|
|
3037
|
-
}
|
|
3038
|
-
function remoteTargetsProxy(remoteUrl, proxyUrl) {
|
|
3039
|
-
try {
|
|
3040
|
-
const remote = new URL(remoteUrl);
|
|
3041
|
-
const proxy = new URL(proxyUrl);
|
|
3042
|
-
return remote.protocol === proxy.protocol && remote.hostname === proxy.hostname && remote.port === proxy.port && normalizePath(remote.pathname) === normalizePath(proxy.pathname);
|
|
3043
|
-
} catch {
|
|
3044
|
-
return remoteUrl === proxyUrl;
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
function normalizePath(pathname) {
|
|
3048
|
-
return pathname === "" ? "/" : pathname;
|
|
3049
|
-
}
|
|
3050
|
-
var init_process_lifecycle = () => {};
|
|
3051
|
-
|
|
3052
3070
|
// src/cli/codex.ts
|
|
3053
3071
|
var exports_codex = {};
|
|
3054
3072
|
__export(exports_codex, {
|
|
@@ -3057,7 +3075,7 @@ __export(exports_codex, {
|
|
|
3057
3075
|
parseAgentBridgeCodexArgs: () => parseAgentBridgeCodexArgs,
|
|
3058
3076
|
buildCodexArgs: () => buildCodexArgs
|
|
3059
3077
|
});
|
|
3060
|
-
import { spawn as spawn3, execSync as execSync2, execFileSync as
|
|
3078
|
+
import { spawn as spawn3, execSync as execSync2, execFileSync as execFileSync6 } from "child_process";
|
|
3061
3079
|
import {
|
|
3062
3080
|
openSync as openSync3,
|
|
3063
3081
|
writeSync,
|
|
@@ -3145,8 +3163,14 @@ function resolveCodexResumeArgs(parsed, pair, env = process.env) {
|
|
|
3145
3163
|
}
|
|
3146
3164
|
return { rest: parsed.rest, mode: "passthrough" };
|
|
3147
3165
|
}
|
|
3148
|
-
function buildCodexArgs(userArgs, proxyUrl) {
|
|
3149
|
-
const bridgeFlags = [
|
|
3166
|
+
function buildCodexArgs(userArgs, proxyUrl, opts = {}) {
|
|
3167
|
+
const bridgeFlags = [
|
|
3168
|
+
"--enable",
|
|
3169
|
+
"tui_app_server",
|
|
3170
|
+
"--remote",
|
|
3171
|
+
proxyUrl,
|
|
3172
|
+
...opts.yolo ? [CODEX_MAX_PERMISSION_FLAG] : []
|
|
3173
|
+
];
|
|
3150
3174
|
const first = userArgs[0];
|
|
3151
3175
|
if (!first || first.startsWith("-")) {
|
|
3152
3176
|
return { fullArgs: [...bridgeFlags, ...userArgs], injectedBridgeFlags: true };
|
|
@@ -3172,7 +3196,8 @@ async function runCodex(args) {
|
|
|
3172
3196
|
log: (msg) => console.error(msg)
|
|
3173
3197
|
});
|
|
3174
3198
|
const { pairFlag, rest } = parsePairFlag(args);
|
|
3175
|
-
const
|
|
3199
|
+
const permissionPlan = planMaxPermissions(rest, CODEX_MAX_PERMISSION_SUPPRESSORS);
|
|
3200
|
+
const wrapperArgs = parseAgentBridgeCodexArgs(permissionPlan.args);
|
|
3176
3201
|
const agentsContract = checkAgentsMdContract(process.cwd());
|
|
3177
3202
|
if (!agentsContract.fresh) {
|
|
3178
3203
|
console.error(`[agentbridge] ${agentsContract.message}`);
|
|
@@ -3297,7 +3322,12 @@ async function runCodex(args) {
|
|
|
3297
3322
|
if (resumeArgs.mode === "auto-resume" || resumeArgs.mode === "resume-current") {
|
|
3298
3323
|
console.error(`[agentbridge] Resuming current Codex thread ${resumeArgs.thread.threadId}`);
|
|
3299
3324
|
}
|
|
3300
|
-
const { fullArgs } = buildCodexArgs(resumeArgs.rest, proxyUrl
|
|
3325
|
+
const { fullArgs, injectedBridgeFlags } = buildCodexArgs(resumeArgs.rest, proxyUrl, {
|
|
3326
|
+
yolo: permissionPlan.inject
|
|
3327
|
+
});
|
|
3328
|
+
if (permissionPlan.inject && injectedBridgeFlags) {
|
|
3329
|
+
console.error(`[agentbridge] running with ${CODEX_MAX_PERMISSION_FLAG} (default; opt out with --safe or AGENTBRIDGE_SAFE=1)`);
|
|
3330
|
+
}
|
|
3301
3331
|
const stderrTail = new StderrRingBuffer;
|
|
3302
3332
|
const wrapperLogPath = stateDir.codexWrapperLogFile;
|
|
3303
3333
|
const startedAt = Date.now();
|
|
@@ -3318,7 +3348,7 @@ async function runCodex(args) {
|
|
|
3318
3348
|
let attempts = 0;
|
|
3319
3349
|
const discover = () => {
|
|
3320
3350
|
attempts += 1;
|
|
3321
|
-
nativeChildPid = discoverNativeChildPid(launcherPid, (cmd, args2) =>
|
|
3351
|
+
nativeChildPid = discoverNativeChildPid(launcherPid, (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf-8", timeout: 2000 }));
|
|
3322
3352
|
if (nativeChildPid !== null) {
|
|
3323
3353
|
appendWrapperLog(wrapperLogPath, `native child pid=${nativeChildPid} (launcher pid=${launcherPid})`);
|
|
3324
3354
|
return;
|
|
@@ -3424,7 +3454,7 @@ async function runCodex(args) {
|
|
|
3424
3454
|
const tuiLogTail = captureTuiLogTail({
|
|
3425
3455
|
codexHome: join11(homedir4(), ".codex"),
|
|
3426
3456
|
nativePid: nativeChildPid,
|
|
3427
|
-
run: (cmd, args2) =>
|
|
3457
|
+
run: (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf-8", timeout: 2000 })
|
|
3428
3458
|
});
|
|
3429
3459
|
appendWrapperLog(wrapperLogPath, [
|
|
3430
3460
|
`exit: code=${code ?? "null"} signal=${signal ?? "null"} runtime_ms=${runtimeMs} pid=${child.pid ?? "unknown"} native_pid=${nativeChildPid ?? "unknown"} classification=${classification}`,
|
|
@@ -3476,7 +3506,7 @@ function traceCliStart2(event, args, originalEnv, envGuardAction, pair) {
|
|
|
3476
3506
|
function guardNoLiveManagedTui(stateDir, proxyUrl) {
|
|
3477
3507
|
const pid = readTuiPid(stateDir);
|
|
3478
3508
|
if (pid) {
|
|
3479
|
-
if (!
|
|
3509
|
+
if (!isProcessAlive(pid)) {
|
|
3480
3510
|
try {
|
|
3481
3511
|
unlinkSync4(stateDir.tuiPidFile);
|
|
3482
3512
|
} catch {}
|
|
@@ -3550,6 +3580,7 @@ var init_codex = __esm(() => {
|
|
|
3550
3580
|
init_trace_log();
|
|
3551
3581
|
init_process_lifecycle();
|
|
3552
3582
|
init_claude();
|
|
3583
|
+
init_max_permissions();
|
|
3553
3584
|
OWNED_FLAGS2 = ["--remote"];
|
|
3554
3585
|
TUI_SUBCOMMANDS = new Set(["resume", "fork"]);
|
|
3555
3586
|
NON_TUI_SUBCOMMANDS = new Set([
|
|
@@ -3577,6 +3608,130 @@ var init_codex = __esm(() => {
|
|
|
3577
3608
|
]);
|
|
3578
3609
|
});
|
|
3579
3610
|
|
|
3611
|
+
// src/claude-session.ts
|
|
3612
|
+
import { readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3613
|
+
import { homedir as homedir5 } from "os";
|
|
3614
|
+
import { join as join12 } from "path";
|
|
3615
|
+
function encodeClaudeProjectDir(cwd) {
|
|
3616
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
3617
|
+
}
|
|
3618
|
+
function findLatestClaudeSession(cwd, claudeHome = process.env.CLAUDE_CONFIG_DIR || join12(homedir5(), ".claude")) {
|
|
3619
|
+
const dir = join12(claudeHome, "projects", encodeClaudeProjectDir(cwd));
|
|
3620
|
+
let entries;
|
|
3621
|
+
try {
|
|
3622
|
+
entries = readdirSync4(dir);
|
|
3623
|
+
} catch {
|
|
3624
|
+
return null;
|
|
3625
|
+
}
|
|
3626
|
+
let best = null;
|
|
3627
|
+
for (const name of entries) {
|
|
3628
|
+
if (!name.endsWith(".jsonl"))
|
|
3629
|
+
continue;
|
|
3630
|
+
const sessionId = name.slice(0, -".jsonl".length);
|
|
3631
|
+
if (!SESSION_ID_PATTERN.test(sessionId))
|
|
3632
|
+
continue;
|
|
3633
|
+
const file = join12(dir, name);
|
|
3634
|
+
let mtimeMs;
|
|
3635
|
+
try {
|
|
3636
|
+
const st = statSync5(file);
|
|
3637
|
+
if (!st.isFile())
|
|
3638
|
+
continue;
|
|
3639
|
+
mtimeMs = st.mtimeMs;
|
|
3640
|
+
} catch {
|
|
3641
|
+
continue;
|
|
3642
|
+
}
|
|
3643
|
+
if (!best || mtimeMs > best.mtimeMs) {
|
|
3644
|
+
best = { sessionId, file, mtimeMs };
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
return best;
|
|
3648
|
+
}
|
|
3649
|
+
var SESSION_ID_PATTERN;
|
|
3650
|
+
var init_claude_session = __esm(() => {
|
|
3651
|
+
SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
// src/cli/resume.ts
|
|
3655
|
+
var exports_resume = {};
|
|
3656
|
+
__export(exports_resume, {
|
|
3657
|
+
runResume: () => runResume,
|
|
3658
|
+
resolveResumeTargets: () => resolveResumeTargets
|
|
3659
|
+
});
|
|
3660
|
+
function resolveResumeTargets(opts = {}) {
|
|
3661
|
+
const cwd = process.cwd();
|
|
3662
|
+
const { pair } = resolvePairReadOnly(opts.pairFlag);
|
|
3663
|
+
const claude = findLatestClaudeSession(cwd, opts.claudeHome);
|
|
3664
|
+
const codex = readUsableCurrentThread({
|
|
3665
|
+
stateDir: pair.stateDir,
|
|
3666
|
+
pairId: pair.manual ? null : pair.pairId,
|
|
3667
|
+
pairName: pair.name,
|
|
3668
|
+
cwd
|
|
3669
|
+
}, opts.env ?? process.env);
|
|
3670
|
+
return {
|
|
3671
|
+
pairName: pair.name,
|
|
3672
|
+
claudeSessionId: claude?.sessionId ?? null,
|
|
3673
|
+
codexThreadId: codex?.threadId ?? null
|
|
3674
|
+
};
|
|
3675
|
+
}
|
|
3676
|
+
async function runResume(args) {
|
|
3677
|
+
const { pairFlag, rest } = parsePairFlag(args);
|
|
3678
|
+
const target = rest[0];
|
|
3679
|
+
const extra = rest.slice(1);
|
|
3680
|
+
const pairPrefix = pairFlag !== undefined ? ["--pair", pairFlag] : [];
|
|
3681
|
+
if (target === "claude") {
|
|
3682
|
+
const session = findLatestClaudeSession(process.cwd());
|
|
3683
|
+
if (!session) {
|
|
3684
|
+
console.error("[agentbridge] No Claude Code session found for this directory.");
|
|
3685
|
+
console.error("[agentbridge] Start one with: abg claude");
|
|
3686
|
+
process.exit(1);
|
|
3687
|
+
}
|
|
3688
|
+
console.error(`[agentbridge] Resuming Claude Code session ${session.sessionId}`);
|
|
3689
|
+
const { runClaude: runClaude2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
|
|
3690
|
+
await runClaude2([...pairPrefix, "--resume", session.sessionId, ...extra]);
|
|
3691
|
+
return;
|
|
3692
|
+
}
|
|
3693
|
+
if (target === "codex") {
|
|
3694
|
+
const { runCodex: runCodex2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
3695
|
+
await runCodex2([...pairPrefix, "resume-current", ...extra]);
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
if (target !== undefined) {
|
|
3699
|
+
console.error(`Error: unknown resume target "${target}".`);
|
|
3700
|
+
console.error("");
|
|
3701
|
+
console.error("Usage:");
|
|
3702
|
+
console.error(" abg resume # print resume commands for this directory");
|
|
3703
|
+
console.error(" abg resume claude # resume the last Claude Code session here");
|
|
3704
|
+
console.error(" abg resume codex # resume this pair's current Codex thread");
|
|
3705
|
+
process.exit(1);
|
|
3706
|
+
}
|
|
3707
|
+
const targets = resolveResumeTargets({ pairFlag });
|
|
3708
|
+
const pairArg = pairFlag ? `--pair ${pairFlag} ` : "";
|
|
3709
|
+
const lines = [];
|
|
3710
|
+
if (targets.claudeSessionId) {
|
|
3711
|
+
lines.push(`abg ${pairArg}claude --resume ${targets.claudeSessionId}`);
|
|
3712
|
+
} else {
|
|
3713
|
+
lines.push(`# no Claude Code session found for this directory (start one: abg ${pairArg}claude)`);
|
|
3714
|
+
}
|
|
3715
|
+
if (targets.codexThreadId) {
|
|
3716
|
+
lines.push(`abg ${pairArg}codex resume ${targets.codexThreadId}`);
|
|
3717
|
+
} else {
|
|
3718
|
+
lines.push(`# no verified Codex thread for pair "${targets.pairName}" (start one: abg ${pairArg}codex --new)`);
|
|
3719
|
+
}
|
|
3720
|
+
console.log(lines.join(`
|
|
3721
|
+
`));
|
|
3722
|
+
console.error("");
|
|
3723
|
+
console.error("Tip: `abg resume claude` / `abg resume codex` runs these directly.");
|
|
3724
|
+
console.error("(max-permission flags are applied by default; opt out with --safe or AGENTBRIDGE_SAFE=1)");
|
|
3725
|
+
if (!targets.claudeSessionId && !targets.codexThreadId) {
|
|
3726
|
+
process.exit(1);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
var init_resume = __esm(() => {
|
|
3730
|
+
init_claude_session();
|
|
3731
|
+
init_pair_resolver();
|
|
3732
|
+
init_thread_state();
|
|
3733
|
+
});
|
|
3734
|
+
|
|
3580
3735
|
// src/cli/kill.ts
|
|
3581
3736
|
var exports_kill = {};
|
|
3582
3737
|
__export(exports_kill, {
|
|
@@ -3585,7 +3740,7 @@ __export(exports_kill, {
|
|
|
3585
3740
|
formatKillReport: () => formatKillReport
|
|
3586
3741
|
});
|
|
3587
3742
|
import { readFileSync as readFileSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
3588
|
-
import { join as
|
|
3743
|
+
import { join as join13 } from "path";
|
|
3589
3744
|
async function runKill(args = []) {
|
|
3590
3745
|
const argError = validateKillArgs(args);
|
|
3591
3746
|
if (argError === "help") {
|
|
@@ -3637,7 +3792,7 @@ async function runKill(args = []) {
|
|
|
3637
3792
|
for (const dirName of listPairDirsSafe(base)) {
|
|
3638
3793
|
if (registeredIds.has(dirName))
|
|
3639
3794
|
continue;
|
|
3640
|
-
const stateDir = new StateDirResolver(
|
|
3795
|
+
const stateDir = new StateDirResolver(join13(base, "pairs", dirName));
|
|
3641
3796
|
results.push(await stopStateDir(`${dirName} (unregistered)`, stateDir, portsFromStateDir(stateDir)));
|
|
3642
3797
|
}
|
|
3643
3798
|
const legacy = detectLegacyRootDaemon(base);
|
|
@@ -3722,7 +3877,7 @@ No arguments stop this directory's registered pairs and any legacy-root daemon.
|
|
|
3722
3877
|
}
|
|
3723
3878
|
async function stopPairEntry(base, pair) {
|
|
3724
3879
|
const ports = portsForEntry(pair);
|
|
3725
|
-
const stateDir = new StateDirResolver(
|
|
3880
|
+
const stateDir = new StateDirResolver(join13(base, "pairs", pair.pairId));
|
|
3726
3881
|
return stopStateDir(pair.pairId, stateDir, ports);
|
|
3727
3882
|
}
|
|
3728
3883
|
function listPairDirsSafe(base) {
|
|
@@ -3857,7 +4012,7 @@ async function killManagedCodexTui(stateDir, proxyUrl, log, gracefulTimeoutMs =
|
|
|
3857
4012
|
if (!pid) {
|
|
3858
4013
|
log("No Codex TUI pid file found");
|
|
3859
4014
|
removeTuiPidFile(stateDir);
|
|
3860
|
-
} else if (!
|
|
4015
|
+
} else if (!isProcessAlive(pid)) {
|
|
3861
4016
|
log(`Codex TUI pid ${pid} is not alive, cleaning up stale pid file`);
|
|
3862
4017
|
removeTuiPidFile(stateDir);
|
|
3863
4018
|
} else if (!isManagedCodexTuiProcess2(pid, proxyUrl)) {
|
|
@@ -3911,7 +4066,7 @@ var exports_pairs = {};
|
|
|
3911
4066
|
__export(exports_pairs, {
|
|
3912
4067
|
runPairs: () => runPairs
|
|
3913
4068
|
});
|
|
3914
|
-
import { join as
|
|
4069
|
+
import { join as join14 } from "path";
|
|
3915
4070
|
async function runPairs(args = []) {
|
|
3916
4071
|
const [command, ...rest] = args;
|
|
3917
4072
|
if (command === "rm") {
|
|
@@ -4077,7 +4232,7 @@ async function collectRows() {
|
|
|
4077
4232
|
}
|
|
4078
4233
|
async function rowForPair(base, pair) {
|
|
4079
4234
|
const ports = portsForEntry(pair);
|
|
4080
|
-
const stateDir = new StateDirResolver(
|
|
4235
|
+
const stateDir = new StateDirResolver(join14(base, "pairs", pair.pairId));
|
|
4081
4236
|
const lifecycle = new DaemonLifecycle({
|
|
4082
4237
|
stateDir,
|
|
4083
4238
|
controlPort: ports.controlPort,
|
|
@@ -4188,7 +4343,7 @@ import {
|
|
|
4188
4343
|
mkdirSync as mkdirSync7,
|
|
4189
4344
|
readFileSync as readFileSync11
|
|
4190
4345
|
} from "fs";
|
|
4191
|
-
import { dirname as dirname5, join as
|
|
4346
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
4192
4347
|
function isKickoffText(text) {
|
|
4193
4348
|
if (!text)
|
|
4194
4349
|
return false;
|
|
@@ -4219,7 +4374,7 @@ function extractFirstRealUserMessage(rolloutPath) {
|
|
|
4219
4374
|
}
|
|
4220
4375
|
function scanResumePollution(options = {}) {
|
|
4221
4376
|
const codexHome2 = options.codexHome ?? codexHome();
|
|
4222
|
-
const dbPath = options.dbPath ??
|
|
4377
|
+
const dbPath = options.dbPath ?? join15(codexHome2, "state_5.sqlite");
|
|
4223
4378
|
if (!existsSync11(dbPath)) {
|
|
4224
4379
|
return { codexHome: codexHome2, dbPath, scanned: 0, candidates: [], applied: 0, renamed: 0, deleted: 0 };
|
|
4225
4380
|
}
|
|
@@ -4313,12 +4468,12 @@ function scanResumePollution(options = {}) {
|
|
|
4313
4468
|
}
|
|
4314
4469
|
function backupCodexStateFiles(dbPath, now = new Date().toISOString()) {
|
|
4315
4470
|
const safeStamp = now.replace(/[:.]/g, "-");
|
|
4316
|
-
const base =
|
|
4471
|
+
const base = join15(dirname5(dbPath), "agentbridge-backups", `resume-pollution-${safeStamp}`);
|
|
4317
4472
|
mkdirSync7(base, { recursive: true });
|
|
4318
4473
|
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
4319
4474
|
if (!existsSync11(path))
|
|
4320
4475
|
continue;
|
|
4321
|
-
const target =
|
|
4476
|
+
const target = join15(base, path.split("/").pop());
|
|
4322
4477
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
4323
4478
|
copyFileSync(path, target);
|
|
4324
4479
|
}
|
|
@@ -4366,9 +4521,9 @@ __export(exports_doctor, {
|
|
|
4366
4521
|
runDoctor: () => runDoctor,
|
|
4367
4522
|
formatDoctorReport: () => formatDoctorReport
|
|
4368
4523
|
});
|
|
4369
|
-
import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as
|
|
4370
|
-
import { homedir as
|
|
4371
|
-
import { join as
|
|
4524
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4525
|
+
import { homedir as homedir6 } from "os";
|
|
4526
|
+
import { join as join16 } from "path";
|
|
4372
4527
|
async function runDoctor(args = []) {
|
|
4373
4528
|
if (args[0] === "resume-pollution") {
|
|
4374
4529
|
runResumePollution(args.slice(1));
|
|
@@ -4557,15 +4712,15 @@ function artifactAlignmentCheck() {
|
|
|
4557
4712
|
stamps.push({ label: "global-cli", commit });
|
|
4558
4713
|
} catch {}
|
|
4559
4714
|
}
|
|
4560
|
-
const cacheRoot =
|
|
4715
|
+
const cacheRoot = join16(homedir6(), ".claude", "plugins", "cache", "agentbridge", "agentbridge");
|
|
4561
4716
|
try {
|
|
4562
|
-
for (const version of
|
|
4563
|
-
const commit = extractBundleCommit(
|
|
4717
|
+
for (const version of readdirSync5(cacheRoot)) {
|
|
4718
|
+
const commit = extractBundleCommit(join16(cacheRoot, version, "server", "daemon.js"));
|
|
4564
4719
|
if (commit)
|
|
4565
4720
|
stamps.push({ label: `plugin-cache@${version}`, commit });
|
|
4566
4721
|
}
|
|
4567
4722
|
} catch {}
|
|
4568
|
-
const repoBundle =
|
|
4723
|
+
const repoBundle = join16(process.cwd(), "plugins", "agentbridge", "server", "daemon.js");
|
|
4569
4724
|
if (existsSync12(repoBundle)) {
|
|
4570
4725
|
const commit = extractBundleCommit(repoBundle);
|
|
4571
4726
|
if (commit)
|
|
@@ -4607,7 +4762,7 @@ function logCheck(name, path) {
|
|
|
4607
4762
|
hint: "\u65E5\u5FD7\u4F1A\u5728\u76F8\u5E94\u8FDB\u7A0B\u9996\u6B21\u542F\u52A8\u65F6\u521B\u5EFA\uFF1B\u8FDB\u7A0B\u4ECE\u672A\u542F\u52A8\u8FC7\u65F6\u8FD9\u662F\u6B63\u5E38\u7684\u3002"
|
|
4608
4763
|
};
|
|
4609
4764
|
}
|
|
4610
|
-
const stat =
|
|
4765
|
+
const stat = statSync6(path);
|
|
4611
4766
|
if (stat.size > LARGE_LOG_WARN_BYTES) {
|
|
4612
4767
|
return {
|
|
4613
4768
|
name,
|
|
@@ -4844,6 +4999,10 @@ async function main(command, restArgs) {
|
|
|
4844
4999
|
const { runCodex: runCodex2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
4845
5000
|
await runCodex2(restArgs);
|
|
4846
5001
|
break;
|
|
5002
|
+
case "resume":
|
|
5003
|
+
const { runResume: runResume2 } = await Promise.resolve().then(() => (init_resume(), exports_resume));
|
|
5004
|
+
await runResume2(restArgs);
|
|
5005
|
+
break;
|
|
4847
5006
|
case "kill":
|
|
4848
5007
|
const { runKill: runKill2 } = await Promise.resolve().then(() => (init_kill(), exports_kill));
|
|
4849
5008
|
await runKill2(restArgs);
|
|
@@ -4889,6 +5048,10 @@ Commands:
|
|
|
4889
5048
|
claude [args...] Start Claude Code with push channel enabled
|
|
4890
5049
|
codex [args...] Start Codex TUI connected to AgentBridge daemon
|
|
4891
5050
|
(bare command auto-resumes the last thread; --new starts fresh)
|
|
5051
|
+
resume [claude|codex]
|
|
5052
|
+
No target: print resume commands for this directory's last
|
|
5053
|
+
Claude session + this pair's current Codex thread.
|
|
5054
|
+
With target: resume that side directly.
|
|
4892
5055
|
pairs [rm <name|id> | prune [--dry-run]]
|
|
4893
5056
|
List pairs; remove one (rm), or delete orphan state dirs (prune)
|
|
4894
5057
|
doctor [--json] Diagnose env, daemon, build drift, logs, and current thread
|
|
@@ -4898,10 +5061,17 @@ Commands:
|
|
|
4898
5061
|
Stop this directory's pairs (default), every pair (all/--all), or one (--pair)
|
|
4899
5062
|
|
|
4900
5063
|
Options:
|
|
4901
|
-
--pair <name> Run claude/codex/kill in a named pair. The name is scoped to
|
|
5064
|
+
--pair <name> Run claude/codex/resume/kill/doctor/budget in a named pair. The name is scoped to
|
|
4902
5065
|
the current directory, so the same name in another directory
|
|
4903
5066
|
is a separate pair. Goes BEFORE the command. Without it, the
|
|
4904
5067
|
pair name defaults to "main" for the current directory.
|
|
5068
|
+
--safe Disable the max-permission defaults for this launch.
|
|
5069
|
+
Goes AFTER the command (abg claude --safe); also auto-
|
|
5070
|
+
suppressed when you pass any explicit permission flag
|
|
5071
|
+
(-a/--sandbox for codex, --permission-mode for claude).
|
|
5072
|
+
(abg claude runs with --dangerously-skip-permissions and
|
|
5073
|
+
abg codex with --yolo by default; AGENTBRIDGE_SAFE=1 also
|
|
5074
|
+
disables both.)
|
|
4905
5075
|
--help, -h Show this help message
|
|
4906
5076
|
--version, -v Show version
|
|
4907
5077
|
|
|
@@ -4915,6 +5085,10 @@ Examples:
|
|
|
4915
5085
|
abg init # First-time setup
|
|
4916
5086
|
abg claude # Start the "main" pair for this directory
|
|
4917
5087
|
abg codex # Connect Codex to this directory's "main" pair
|
|
5088
|
+
abg resume # Print resume commands for both sides
|
|
5089
|
+
abg resume claude # Resume the last Claude Code session here
|
|
5090
|
+
abg resume codex # Resume this pair's current Codex thread
|
|
5091
|
+
abg claude --safe # One launch without the max-permission default
|
|
4918
5092
|
abg --pair work claude # Start a named pair "work" (this directory)
|
|
4919
5093
|
abg --pair work codex # Connect Codex to the "work" pair
|
|
4920
5094
|
abg --pair review claude # A second, parallel pair
|
|
@@ -4940,9 +5114,9 @@ function printVersion() {
|
|
|
4940
5114
|
}
|
|
4941
5115
|
var MARKETPLACE_NAME = "agentbridge", PLUGIN_NAME = "agentbridge", REFRESH_COMMANDS, NOTIFY_COMMANDS, PAIR_AWARE_COMMANDS;
|
|
4942
5116
|
var init_cli = __esm(() => {
|
|
4943
|
-
REFRESH_COMMANDS = new Set(["claude", "codex"]);
|
|
4944
|
-
NOTIFY_COMMANDS = new Set(["claude", "codex", "init", "dev"]);
|
|
4945
|
-
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget"]);
|
|
5117
|
+
REFRESH_COMMANDS = new Set(["claude", "codex", "resume"]);
|
|
5118
|
+
NOTIFY_COMMANDS = new Set(["claude", "codex", "init", "dev", "resume"]);
|
|
5119
|
+
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget", "resume"]);
|
|
4946
5120
|
if (import.meta.main) {
|
|
4947
5121
|
const { command, restArgs } = parseTopLevel(process.argv.slice(2));
|
|
4948
5122
|
main(command, restArgs).catch((err) => {
|