@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/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.9",
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.9", "0.0.0-source"),
1169
- commit: defineString("10dfd58", "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 {
@@ -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 init_wrapper_exit_observability = () => {};
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 basename2, dirname as dirname3, join as join10 } from "path";
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 = basename2(entry.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 execFileSync7 } from "child_process";
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 = ["--enable", "tui_app_server", "--remote", proxyUrl];
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 wrapperArgs = parseAgentBridgeCodexArgs(rest);
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) => execFileSync7(cmd, args2, { encoding: "utf-8", timeout: 2000 }));
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) => execFileSync7(cmd, args2, { encoding: "utf-8", timeout: 2000 })
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 (!isProcessAlive2(pid)) {
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 join12 } from "path";
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(join12(base, "pairs", dirName));
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(join12(base, "pairs", pair.pairId));
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 (!isProcessAlive2(pid)) {
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 join13 } from "path";
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(join13(base, "pairs", pair.pairId));
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 join14 } from "path";
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 ?? join14(codexHome2, "state_5.sqlite");
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 = join14(dirname5(dbPath), "agentbridge-backups", `resume-pollution-${safeStamp}`);
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 = join14(base, path.split("/").pop());
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 readdirSync4, realpathSync as realpathSync3, statSync as statSync5 } from "fs";
4370
- import { homedir as homedir5 } from "os";
4371
- import { join as join15 } from "path";
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 = join15(homedir5(), ".claude", "plugins", "cache", "agentbridge", "agentbridge");
4715
+ const cacheRoot = join16(homedir6(), ".claude", "plugins", "cache", "agentbridge", "agentbridge");
4561
4716
  try {
4562
- for (const version of readdirSync4(cacheRoot)) {
4563
- const commit = extractBundleCommit(join15(cacheRoot, version, "server", "daemon.js"));
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 = join15(process.cwd(), "plugins", "agentbridge", "server", "daemon.js");
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 = statSync5(path);
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) => {