@raysonmeng/agentbridge 0.1.8 → 0.1.10
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 +232 -29
- package/dist/daemon.js +26 -6
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +2 -2
- package/plugins/agentbridge/server/daemon.js +26 -6
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"name": "agentbridge",
|
|
14
14
|
"description": "Bridge Claude Code and Codex through a shared daemon, push channel delivery, and reply/get_messages tools.",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.10",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "AgentBridge Contributors",
|
|
18
18
|
"email": "raysonmeng@qq.com"
|
package/README.md
CHANGED
|
@@ -155,8 +155,9 @@ After modifying AgentBridge source code, re-run `agentbridge dev` to sync change
|
|
|
155
155
|
| Command | Description |
|
|
156
156
|
|---------|-------------|
|
|
157
157
|
| `abg init` | Install plugin, check dependencies (bun/claude/codex), generate `.agentbridge/config.json` |
|
|
158
|
-
| `abg claude [args...]` | Start Claude Code with push channel enabled. Clears any killed sentinel from a previous `kill`. Pass-through args are forwarded to `claude` |
|
|
159
|
-
| `abg codex [args...]` | Start Codex TUI connected to AgentBridge daemon. **Bare `abg codex` auto-resumes the pair's last thread; use `abg codex --new` for a fresh thread
|
|
158
|
+
| `abg claude [args...]` | Start Claude Code with push channel enabled. **Runs with `--dangerously-skip-permissions` by default** (opt out: `--safe` or `AGENTBRIDGE_SAFE=1`). Clears any killed sentinel from a previous `kill`. Pass-through args are forwarded to `claude` |
|
|
159
|
+
| `abg codex [args...]` | Start Codex TUI connected to AgentBridge daemon. **Bare `abg codex` auto-resumes the pair's last thread; use `abg codex --new` for a fresh thread. TUI launches run with `--yolo` by default** (opt out: `--safe` or `AGENTBRIDGE_SAFE=1`; non-TUI subcommands like `exec` are never touched). Manages TUI process lifecycle (pid tracking, cleanup). Pass-through args forwarded to `codex` |
|
|
160
|
+
| `abg resume [claude\|codex]` | No target: print the resume commands for this directory's last Claude Code session and this pair's current Codex thread. With a target: resume that side directly (delegates to `abg claude --resume <id>` / `abg codex resume-current`) |
|
|
160
161
|
| `abg pairs` | List registered pairs; `abg pairs rm <name\|id>` removes one, `abg pairs prune` deletes orphan state dirs |
|
|
161
162
|
| `abg doctor [--json]` | Read-only diagnosis: env, daemon health/readiness, build drift, artifact alignment, TUI attachment, logs |
|
|
162
163
|
| `abg budget [--json]` | Both agents' subscription quota snapshot (5h/weekly windows, drift, pause state) |
|
|
@@ -165,7 +166,7 @@ After modifying AgentBridge source code, re-run `agentbridge dev` to sync change
|
|
|
165
166
|
| `abg --help` | Show help |
|
|
166
167
|
| `abg --version` | Show version |
|
|
167
168
|
|
|
168
|
-
The pair-aware commands (`claude`, `codex`, `kill`, `doctor`, `budget`) accept `--pair <name>` to target a specific pair — one pair per project directory by default, with ports allocated per pair in +10 strides from 4500.
|
|
169
|
+
The pair-aware commands (`claude`, `codex`, `resume`, `kill`, `doctor`, `budget`) accept `--pair <name>` to target a specific pair — one pair per project directory by default, with ports allocated per pair in +10 strides from 4500.
|
|
169
170
|
|
|
170
171
|
### Owned flags
|
|
171
172
|
|
|
@@ -173,6 +174,7 @@ Some flags are automatically injected and cannot be manually specified:
|
|
|
173
174
|
|
|
174
175
|
- `agentbridge claude` owns: `--channels`, `--dangerously-load-development-channels`
|
|
175
176
|
- `agentbridge codex` owns: `--remote`, `--enable tui_app_server`
|
|
177
|
+
- Both launchers consume the wrapper flag `--safe` (it is never forwarded): it disables the max-permission defaults for that launch. The defaults are also auto-suppressed when you pass any explicit permission flag yourself (`-a`/`--ask-for-approval`/`-s`/`--sandbox` for codex; `--permission-mode`/`--allow-dangerously-skip-permissions` for claude) — injecting `--yolo` next to an explicit approval policy is a hard codex CLI conflict.
|
|
176
178
|
|
|
177
179
|
Passing these flags manually will result in a hard error with guidance to use the native command directly.
|
|
178
180
|
|
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.10",
|
|
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,8 +1165,8 @@ 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.10", "0.0.0-source"),
|
|
1169
|
+
commit: defineString("51a44cb", "source"),
|
|
1170
1170
|
bundle: defineBundle("dist"),
|
|
1171
1171
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
1172
1172
|
});
|
|
@@ -2441,6 +2441,46 @@ var init_trace_log = __esm(() => {
|
|
|
2441
2441
|
RELEVANT_ENV_RE = /^(AGENTBRIDGE_|CODEX_)/;
|
|
2442
2442
|
});
|
|
2443
2443
|
|
|
2444
|
+
// src/cli/max-permissions.ts
|
|
2445
|
+
function planMaxPermissions(args, suppressors, env = process.env) {
|
|
2446
|
+
let safeFlag = false;
|
|
2447
|
+
const rest = [];
|
|
2448
|
+
for (const a of args) {
|
|
2449
|
+
if (a === "--safe") {
|
|
2450
|
+
safeFlag = true;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
rest.push(a);
|
|
2454
|
+
}
|
|
2455
|
+
const safeMode = safeFlag || env.AGENTBRIDGE_SAFE === "1";
|
|
2456
|
+
const userExpressedPreference = rest.some((a) => suppressors.some((s) => matchesSuppressor(a, s)));
|
|
2457
|
+
return { args: rest, inject: !safeMode && !userExpressedPreference, safeMode };
|
|
2458
|
+
}
|
|
2459
|
+
function matchesSuppressor(token, suppressor) {
|
|
2460
|
+
if (token === suppressor || token.startsWith(`${suppressor}=`))
|
|
2461
|
+
return true;
|
|
2462
|
+
if (/^-[A-Za-z]$/.test(suppressor) && !token.startsWith("--") && token.startsWith(suppressor) && token.length > suppressor.length) {
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
var CLAUDE_MAX_PERMISSION_FLAG = "--dangerously-skip-permissions", CLAUDE_MAX_PERMISSION_SUPPRESSORS, CODEX_MAX_PERMISSION_FLAG = "--yolo", CODEX_MAX_PERMISSION_SUPPRESSORS;
|
|
2468
|
+
var init_max_permissions = __esm(() => {
|
|
2469
|
+
CLAUDE_MAX_PERMISSION_SUPPRESSORS = [
|
|
2470
|
+
CLAUDE_MAX_PERMISSION_FLAG,
|
|
2471
|
+
"--allow-dangerously-skip-permissions",
|
|
2472
|
+
"--permission-mode"
|
|
2473
|
+
];
|
|
2474
|
+
CODEX_MAX_PERMISSION_SUPPRESSORS = [
|
|
2475
|
+
CODEX_MAX_PERMISSION_FLAG,
|
|
2476
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
2477
|
+
"-a",
|
|
2478
|
+
"--ask-for-approval",
|
|
2479
|
+
"-s",
|
|
2480
|
+
"--sandbox"
|
|
2481
|
+
];
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2444
2484
|
// src/cli/claude.ts
|
|
2445
2485
|
var exports_claude = {};
|
|
2446
2486
|
__export(exports_claude, {
|
|
@@ -2457,7 +2497,9 @@ async function runClaude(args) {
|
|
|
2457
2497
|
allowStrict: true,
|
|
2458
2498
|
log: (msg) => console.error(msg)
|
|
2459
2499
|
});
|
|
2460
|
-
const { pairFlag, rest } = parsePairFlag(args);
|
|
2500
|
+
const { pairFlag, rest: pairRest } = parsePairFlag(args);
|
|
2501
|
+
const permissionPlan = planMaxPermissions(pairRest, CLAUDE_MAX_PERMISSION_SUPPRESSORS);
|
|
2502
|
+
const rest = permissionPlan.args;
|
|
2461
2503
|
checkOwnedFlagConflicts(rest, "agentbridge claude", OWNED_FLAGS);
|
|
2462
2504
|
let pair;
|
|
2463
2505
|
try {
|
|
@@ -2484,9 +2526,13 @@ async function runClaude(args) {
|
|
|
2484
2526
|
await assertPairNotLive(lifecycle, pair);
|
|
2485
2527
|
lifecycle.clearKilled();
|
|
2486
2528
|
const channelEntry = `plugin:${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2529
|
+
if (permissionPlan.inject) {
|
|
2530
|
+
console.error(`[agentbridge] running with ${CLAUDE_MAX_PERMISSION_FLAG} (default; opt out with --safe or AGENTBRIDGE_SAFE=1)`);
|
|
2531
|
+
}
|
|
2487
2532
|
const fullArgs = [
|
|
2488
2533
|
"--dangerously-load-development-channels",
|
|
2489
2534
|
channelEntry,
|
|
2535
|
+
...permissionPlan.inject ? [CLAUDE_MAX_PERMISSION_FLAG] : [],
|
|
2490
2536
|
...rest
|
|
2491
2537
|
];
|
|
2492
2538
|
const child = spawn2("claude", fullArgs, {
|
|
@@ -2588,6 +2634,7 @@ var init_claude = __esm(() => {
|
|
|
2588
2634
|
init_env_guard();
|
|
2589
2635
|
init_pair_resolver();
|
|
2590
2636
|
init_trace_log();
|
|
2637
|
+
init_max_permissions();
|
|
2591
2638
|
OWNED_FLAGS = ["--channels", "--dangerously-load-development-channels"];
|
|
2592
2639
|
});
|
|
2593
2640
|
|
|
@@ -3145,8 +3192,14 @@ function resolveCodexResumeArgs(parsed, pair, env = process.env) {
|
|
|
3145
3192
|
}
|
|
3146
3193
|
return { rest: parsed.rest, mode: "passthrough" };
|
|
3147
3194
|
}
|
|
3148
|
-
function buildCodexArgs(userArgs, proxyUrl) {
|
|
3149
|
-
const bridgeFlags = [
|
|
3195
|
+
function buildCodexArgs(userArgs, proxyUrl, opts = {}) {
|
|
3196
|
+
const bridgeFlags = [
|
|
3197
|
+
"--enable",
|
|
3198
|
+
"tui_app_server",
|
|
3199
|
+
"--remote",
|
|
3200
|
+
proxyUrl,
|
|
3201
|
+
...opts.yolo ? [CODEX_MAX_PERMISSION_FLAG] : []
|
|
3202
|
+
];
|
|
3150
3203
|
const first = userArgs[0];
|
|
3151
3204
|
if (!first || first.startsWith("-")) {
|
|
3152
3205
|
return { fullArgs: [...bridgeFlags, ...userArgs], injectedBridgeFlags: true };
|
|
@@ -3172,7 +3225,8 @@ async function runCodex(args) {
|
|
|
3172
3225
|
log: (msg) => console.error(msg)
|
|
3173
3226
|
});
|
|
3174
3227
|
const { pairFlag, rest } = parsePairFlag(args);
|
|
3175
|
-
const
|
|
3228
|
+
const permissionPlan = planMaxPermissions(rest, CODEX_MAX_PERMISSION_SUPPRESSORS);
|
|
3229
|
+
const wrapperArgs = parseAgentBridgeCodexArgs(permissionPlan.args);
|
|
3176
3230
|
const agentsContract = checkAgentsMdContract(process.cwd());
|
|
3177
3231
|
if (!agentsContract.fresh) {
|
|
3178
3232
|
console.error(`[agentbridge] ${agentsContract.message}`);
|
|
@@ -3297,7 +3351,12 @@ async function runCodex(args) {
|
|
|
3297
3351
|
if (resumeArgs.mode === "auto-resume" || resumeArgs.mode === "resume-current") {
|
|
3298
3352
|
console.error(`[agentbridge] Resuming current Codex thread ${resumeArgs.thread.threadId}`);
|
|
3299
3353
|
}
|
|
3300
|
-
const { fullArgs } = buildCodexArgs(resumeArgs.rest, proxyUrl
|
|
3354
|
+
const { fullArgs, injectedBridgeFlags } = buildCodexArgs(resumeArgs.rest, proxyUrl, {
|
|
3355
|
+
yolo: permissionPlan.inject
|
|
3356
|
+
});
|
|
3357
|
+
if (permissionPlan.inject && injectedBridgeFlags) {
|
|
3358
|
+
console.error(`[agentbridge] running with ${CODEX_MAX_PERMISSION_FLAG} (default; opt out with --safe or AGENTBRIDGE_SAFE=1)`);
|
|
3359
|
+
}
|
|
3301
3360
|
const stderrTail = new StderrRingBuffer;
|
|
3302
3361
|
const wrapperLogPath = stateDir.codexWrapperLogFile;
|
|
3303
3362
|
const startedAt = Date.now();
|
|
@@ -3550,6 +3609,7 @@ var init_codex = __esm(() => {
|
|
|
3550
3609
|
init_trace_log();
|
|
3551
3610
|
init_process_lifecycle();
|
|
3552
3611
|
init_claude();
|
|
3612
|
+
init_max_permissions();
|
|
3553
3613
|
OWNED_FLAGS2 = ["--remote"];
|
|
3554
3614
|
TUI_SUBCOMMANDS = new Set(["resume", "fork"]);
|
|
3555
3615
|
NON_TUI_SUBCOMMANDS = new Set([
|
|
@@ -3577,6 +3637,130 @@ var init_codex = __esm(() => {
|
|
|
3577
3637
|
]);
|
|
3578
3638
|
});
|
|
3579
3639
|
|
|
3640
|
+
// src/claude-session.ts
|
|
3641
|
+
import { readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3642
|
+
import { homedir as homedir5 } from "os";
|
|
3643
|
+
import { join as join12 } from "path";
|
|
3644
|
+
function encodeClaudeProjectDir(cwd) {
|
|
3645
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
3646
|
+
}
|
|
3647
|
+
function findLatestClaudeSession(cwd, claudeHome = process.env.CLAUDE_CONFIG_DIR || join12(homedir5(), ".claude")) {
|
|
3648
|
+
const dir = join12(claudeHome, "projects", encodeClaudeProjectDir(cwd));
|
|
3649
|
+
let entries;
|
|
3650
|
+
try {
|
|
3651
|
+
entries = readdirSync4(dir);
|
|
3652
|
+
} catch {
|
|
3653
|
+
return null;
|
|
3654
|
+
}
|
|
3655
|
+
let best = null;
|
|
3656
|
+
for (const name of entries) {
|
|
3657
|
+
if (!name.endsWith(".jsonl"))
|
|
3658
|
+
continue;
|
|
3659
|
+
const sessionId = name.slice(0, -".jsonl".length);
|
|
3660
|
+
if (!SESSION_ID_PATTERN.test(sessionId))
|
|
3661
|
+
continue;
|
|
3662
|
+
const file = join12(dir, name);
|
|
3663
|
+
let mtimeMs;
|
|
3664
|
+
try {
|
|
3665
|
+
const st = statSync5(file);
|
|
3666
|
+
if (!st.isFile())
|
|
3667
|
+
continue;
|
|
3668
|
+
mtimeMs = st.mtimeMs;
|
|
3669
|
+
} catch {
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
if (!best || mtimeMs > best.mtimeMs) {
|
|
3673
|
+
best = { sessionId, file, mtimeMs };
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
return best;
|
|
3677
|
+
}
|
|
3678
|
+
var SESSION_ID_PATTERN;
|
|
3679
|
+
var init_claude_session = __esm(() => {
|
|
3680
|
+
SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3681
|
+
});
|
|
3682
|
+
|
|
3683
|
+
// src/cli/resume.ts
|
|
3684
|
+
var exports_resume = {};
|
|
3685
|
+
__export(exports_resume, {
|
|
3686
|
+
runResume: () => runResume,
|
|
3687
|
+
resolveResumeTargets: () => resolveResumeTargets
|
|
3688
|
+
});
|
|
3689
|
+
function resolveResumeTargets(opts = {}) {
|
|
3690
|
+
const cwd = process.cwd();
|
|
3691
|
+
const { pair } = resolvePairReadOnly(opts.pairFlag);
|
|
3692
|
+
const claude = findLatestClaudeSession(cwd, opts.claudeHome);
|
|
3693
|
+
const codex = readUsableCurrentThread({
|
|
3694
|
+
stateDir: pair.stateDir,
|
|
3695
|
+
pairId: pair.manual ? null : pair.pairId,
|
|
3696
|
+
pairName: pair.name,
|
|
3697
|
+
cwd
|
|
3698
|
+
}, opts.env ?? process.env);
|
|
3699
|
+
return {
|
|
3700
|
+
pairName: pair.name,
|
|
3701
|
+
claudeSessionId: claude?.sessionId ?? null,
|
|
3702
|
+
codexThreadId: codex?.threadId ?? null
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
async function runResume(args) {
|
|
3706
|
+
const { pairFlag, rest } = parsePairFlag(args);
|
|
3707
|
+
const target = rest[0];
|
|
3708
|
+
const extra = rest.slice(1);
|
|
3709
|
+
const pairPrefix = pairFlag !== undefined ? ["--pair", pairFlag] : [];
|
|
3710
|
+
if (target === "claude") {
|
|
3711
|
+
const session = findLatestClaudeSession(process.cwd());
|
|
3712
|
+
if (!session) {
|
|
3713
|
+
console.error("[agentbridge] No Claude Code session found for this directory.");
|
|
3714
|
+
console.error("[agentbridge] Start one with: abg claude");
|
|
3715
|
+
process.exit(1);
|
|
3716
|
+
}
|
|
3717
|
+
console.error(`[agentbridge] Resuming Claude Code session ${session.sessionId}`);
|
|
3718
|
+
const { runClaude: runClaude2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
|
|
3719
|
+
await runClaude2([...pairPrefix, "--resume", session.sessionId, ...extra]);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
if (target === "codex") {
|
|
3723
|
+
const { runCodex: runCodex2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
3724
|
+
await runCodex2([...pairPrefix, "resume-current", ...extra]);
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
if (target !== undefined) {
|
|
3728
|
+
console.error(`Error: unknown resume target "${target}".`);
|
|
3729
|
+
console.error("");
|
|
3730
|
+
console.error("Usage:");
|
|
3731
|
+
console.error(" abg resume # print resume commands for this directory");
|
|
3732
|
+
console.error(" abg resume claude # resume the last Claude Code session here");
|
|
3733
|
+
console.error(" abg resume codex # resume this pair's current Codex thread");
|
|
3734
|
+
process.exit(1);
|
|
3735
|
+
}
|
|
3736
|
+
const targets = resolveResumeTargets({ pairFlag });
|
|
3737
|
+
const pairArg = pairFlag ? `--pair ${pairFlag} ` : "";
|
|
3738
|
+
const lines = [];
|
|
3739
|
+
if (targets.claudeSessionId) {
|
|
3740
|
+
lines.push(`abg ${pairArg}claude --resume ${targets.claudeSessionId}`);
|
|
3741
|
+
} else {
|
|
3742
|
+
lines.push(`# no Claude Code session found for this directory (start one: abg ${pairArg}claude)`);
|
|
3743
|
+
}
|
|
3744
|
+
if (targets.codexThreadId) {
|
|
3745
|
+
lines.push(`abg ${pairArg}codex resume ${targets.codexThreadId}`);
|
|
3746
|
+
} else {
|
|
3747
|
+
lines.push(`# no verified Codex thread for pair "${targets.pairName}" (start one: abg ${pairArg}codex --new)`);
|
|
3748
|
+
}
|
|
3749
|
+
console.log(lines.join(`
|
|
3750
|
+
`));
|
|
3751
|
+
console.error("");
|
|
3752
|
+
console.error("Tip: `abg resume claude` / `abg resume codex` runs these directly.");
|
|
3753
|
+
console.error("(max-permission flags are applied by default; opt out with --safe or AGENTBRIDGE_SAFE=1)");
|
|
3754
|
+
if (!targets.claudeSessionId && !targets.codexThreadId) {
|
|
3755
|
+
process.exit(1);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
var init_resume = __esm(() => {
|
|
3759
|
+
init_claude_session();
|
|
3760
|
+
init_pair_resolver();
|
|
3761
|
+
init_thread_state();
|
|
3762
|
+
});
|
|
3763
|
+
|
|
3580
3764
|
// src/cli/kill.ts
|
|
3581
3765
|
var exports_kill = {};
|
|
3582
3766
|
__export(exports_kill, {
|
|
@@ -3585,7 +3769,7 @@ __export(exports_kill, {
|
|
|
3585
3769
|
formatKillReport: () => formatKillReport
|
|
3586
3770
|
});
|
|
3587
3771
|
import { readFileSync as readFileSync10, unlinkSync as unlinkSync5 } from "fs";
|
|
3588
|
-
import { join as
|
|
3772
|
+
import { join as join13 } from "path";
|
|
3589
3773
|
async function runKill(args = []) {
|
|
3590
3774
|
const argError = validateKillArgs(args);
|
|
3591
3775
|
if (argError === "help") {
|
|
@@ -3637,7 +3821,7 @@ async function runKill(args = []) {
|
|
|
3637
3821
|
for (const dirName of listPairDirsSafe(base)) {
|
|
3638
3822
|
if (registeredIds.has(dirName))
|
|
3639
3823
|
continue;
|
|
3640
|
-
const stateDir = new StateDirResolver(
|
|
3824
|
+
const stateDir = new StateDirResolver(join13(base, "pairs", dirName));
|
|
3641
3825
|
results.push(await stopStateDir(`${dirName} (unregistered)`, stateDir, portsFromStateDir(stateDir)));
|
|
3642
3826
|
}
|
|
3643
3827
|
const legacy = detectLegacyRootDaemon(base);
|
|
@@ -3722,7 +3906,7 @@ No arguments stop this directory's registered pairs and any legacy-root daemon.
|
|
|
3722
3906
|
}
|
|
3723
3907
|
async function stopPairEntry(base, pair) {
|
|
3724
3908
|
const ports = portsForEntry(pair);
|
|
3725
|
-
const stateDir = new StateDirResolver(
|
|
3909
|
+
const stateDir = new StateDirResolver(join13(base, "pairs", pair.pairId));
|
|
3726
3910
|
return stopStateDir(pair.pairId, stateDir, ports);
|
|
3727
3911
|
}
|
|
3728
3912
|
function listPairDirsSafe(base) {
|
|
@@ -3911,7 +4095,7 @@ var exports_pairs = {};
|
|
|
3911
4095
|
__export(exports_pairs, {
|
|
3912
4096
|
runPairs: () => runPairs
|
|
3913
4097
|
});
|
|
3914
|
-
import { join as
|
|
4098
|
+
import { join as join14 } from "path";
|
|
3915
4099
|
async function runPairs(args = []) {
|
|
3916
4100
|
const [command, ...rest] = args;
|
|
3917
4101
|
if (command === "rm") {
|
|
@@ -4077,7 +4261,7 @@ async function collectRows() {
|
|
|
4077
4261
|
}
|
|
4078
4262
|
async function rowForPair(base, pair) {
|
|
4079
4263
|
const ports = portsForEntry(pair);
|
|
4080
|
-
const stateDir = new StateDirResolver(
|
|
4264
|
+
const stateDir = new StateDirResolver(join14(base, "pairs", pair.pairId));
|
|
4081
4265
|
const lifecycle = new DaemonLifecycle({
|
|
4082
4266
|
stateDir,
|
|
4083
4267
|
controlPort: ports.controlPort,
|
|
@@ -4188,7 +4372,7 @@ import {
|
|
|
4188
4372
|
mkdirSync as mkdirSync7,
|
|
4189
4373
|
readFileSync as readFileSync11
|
|
4190
4374
|
} from "fs";
|
|
4191
|
-
import { dirname as dirname5, join as
|
|
4375
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
4192
4376
|
function isKickoffText(text) {
|
|
4193
4377
|
if (!text)
|
|
4194
4378
|
return false;
|
|
@@ -4219,7 +4403,7 @@ function extractFirstRealUserMessage(rolloutPath) {
|
|
|
4219
4403
|
}
|
|
4220
4404
|
function scanResumePollution(options = {}) {
|
|
4221
4405
|
const codexHome2 = options.codexHome ?? codexHome();
|
|
4222
|
-
const dbPath = options.dbPath ??
|
|
4406
|
+
const dbPath = options.dbPath ?? join15(codexHome2, "state_5.sqlite");
|
|
4223
4407
|
if (!existsSync11(dbPath)) {
|
|
4224
4408
|
return { codexHome: codexHome2, dbPath, scanned: 0, candidates: [], applied: 0, renamed: 0, deleted: 0 };
|
|
4225
4409
|
}
|
|
@@ -4313,12 +4497,12 @@ function scanResumePollution(options = {}) {
|
|
|
4313
4497
|
}
|
|
4314
4498
|
function backupCodexStateFiles(dbPath, now = new Date().toISOString()) {
|
|
4315
4499
|
const safeStamp = now.replace(/[:.]/g, "-");
|
|
4316
|
-
const base =
|
|
4500
|
+
const base = join15(dirname5(dbPath), "agentbridge-backups", `resume-pollution-${safeStamp}`);
|
|
4317
4501
|
mkdirSync7(base, { recursive: true });
|
|
4318
4502
|
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
4319
4503
|
if (!existsSync11(path))
|
|
4320
4504
|
continue;
|
|
4321
|
-
const target =
|
|
4505
|
+
const target = join15(base, path.split("/").pop());
|
|
4322
4506
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
4323
4507
|
copyFileSync(path, target);
|
|
4324
4508
|
}
|
|
@@ -4366,9 +4550,9 @@ __export(exports_doctor, {
|
|
|
4366
4550
|
runDoctor: () => runDoctor,
|
|
4367
4551
|
formatDoctorReport: () => formatDoctorReport
|
|
4368
4552
|
});
|
|
4369
|
-
import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as
|
|
4370
|
-
import { homedir as
|
|
4371
|
-
import { join as
|
|
4553
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4554
|
+
import { homedir as homedir6 } from "os";
|
|
4555
|
+
import { join as join16 } from "path";
|
|
4372
4556
|
async function runDoctor(args = []) {
|
|
4373
4557
|
if (args[0] === "resume-pollution") {
|
|
4374
4558
|
runResumePollution(args.slice(1));
|
|
@@ -4557,15 +4741,15 @@ function artifactAlignmentCheck() {
|
|
|
4557
4741
|
stamps.push({ label: "global-cli", commit });
|
|
4558
4742
|
} catch {}
|
|
4559
4743
|
}
|
|
4560
|
-
const cacheRoot =
|
|
4744
|
+
const cacheRoot = join16(homedir6(), ".claude", "plugins", "cache", "agentbridge", "agentbridge");
|
|
4561
4745
|
try {
|
|
4562
|
-
for (const version of
|
|
4563
|
-
const commit = extractBundleCommit(
|
|
4746
|
+
for (const version of readdirSync5(cacheRoot)) {
|
|
4747
|
+
const commit = extractBundleCommit(join16(cacheRoot, version, "server", "daemon.js"));
|
|
4564
4748
|
if (commit)
|
|
4565
4749
|
stamps.push({ label: `plugin-cache@${version}`, commit });
|
|
4566
4750
|
}
|
|
4567
4751
|
} catch {}
|
|
4568
|
-
const repoBundle =
|
|
4752
|
+
const repoBundle = join16(process.cwd(), "plugins", "agentbridge", "server", "daemon.js");
|
|
4569
4753
|
if (existsSync12(repoBundle)) {
|
|
4570
4754
|
const commit = extractBundleCommit(repoBundle);
|
|
4571
4755
|
if (commit)
|
|
@@ -4607,7 +4791,7 @@ function logCheck(name, path) {
|
|
|
4607
4791
|
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
4792
|
};
|
|
4609
4793
|
}
|
|
4610
|
-
const stat =
|
|
4794
|
+
const stat = statSync6(path);
|
|
4611
4795
|
if (stat.size > LARGE_LOG_WARN_BYTES) {
|
|
4612
4796
|
return {
|
|
4613
4797
|
name,
|
|
@@ -4844,6 +5028,10 @@ async function main(command, restArgs) {
|
|
|
4844
5028
|
const { runCodex: runCodex2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
4845
5029
|
await runCodex2(restArgs);
|
|
4846
5030
|
break;
|
|
5031
|
+
case "resume":
|
|
5032
|
+
const { runResume: runResume2 } = await Promise.resolve().then(() => (init_resume(), exports_resume));
|
|
5033
|
+
await runResume2(restArgs);
|
|
5034
|
+
break;
|
|
4847
5035
|
case "kill":
|
|
4848
5036
|
const { runKill: runKill2 } = await Promise.resolve().then(() => (init_kill(), exports_kill));
|
|
4849
5037
|
await runKill2(restArgs);
|
|
@@ -4889,6 +5077,10 @@ Commands:
|
|
|
4889
5077
|
claude [args...] Start Claude Code with push channel enabled
|
|
4890
5078
|
codex [args...] Start Codex TUI connected to AgentBridge daemon
|
|
4891
5079
|
(bare command auto-resumes the last thread; --new starts fresh)
|
|
5080
|
+
resume [claude|codex]
|
|
5081
|
+
No target: print resume commands for this directory's last
|
|
5082
|
+
Claude session + this pair's current Codex thread.
|
|
5083
|
+
With target: resume that side directly.
|
|
4892
5084
|
pairs [rm <name|id> | prune [--dry-run]]
|
|
4893
5085
|
List pairs; remove one (rm), or delete orphan state dirs (prune)
|
|
4894
5086
|
doctor [--json] Diagnose env, daemon, build drift, logs, and current thread
|
|
@@ -4898,10 +5090,17 @@ Commands:
|
|
|
4898
5090
|
Stop this directory's pairs (default), every pair (all/--all), or one (--pair)
|
|
4899
5091
|
|
|
4900
5092
|
Options:
|
|
4901
|
-
--pair <name> Run claude/codex/kill in a named pair. The name is scoped to
|
|
5093
|
+
--pair <name> Run claude/codex/resume/kill/doctor/budget in a named pair. The name is scoped to
|
|
4902
5094
|
the current directory, so the same name in another directory
|
|
4903
5095
|
is a separate pair. Goes BEFORE the command. Without it, the
|
|
4904
5096
|
pair name defaults to "main" for the current directory.
|
|
5097
|
+
--safe Disable the max-permission defaults for this launch.
|
|
5098
|
+
Goes AFTER the command (abg claude --safe); also auto-
|
|
5099
|
+
suppressed when you pass any explicit permission flag
|
|
5100
|
+
(-a/--sandbox for codex, --permission-mode for claude).
|
|
5101
|
+
(abg claude runs with --dangerously-skip-permissions and
|
|
5102
|
+
abg codex with --yolo by default; AGENTBRIDGE_SAFE=1 also
|
|
5103
|
+
disables both.)
|
|
4905
5104
|
--help, -h Show this help message
|
|
4906
5105
|
--version, -v Show version
|
|
4907
5106
|
|
|
@@ -4915,6 +5114,10 @@ Examples:
|
|
|
4915
5114
|
abg init # First-time setup
|
|
4916
5115
|
abg claude # Start the "main" pair for this directory
|
|
4917
5116
|
abg codex # Connect Codex to this directory's "main" pair
|
|
5117
|
+
abg resume # Print resume commands for both sides
|
|
5118
|
+
abg resume claude # Resume the last Claude Code session here
|
|
5119
|
+
abg resume codex # Resume this pair's current Codex thread
|
|
5120
|
+
abg claude --safe # One launch without the max-permission default
|
|
4918
5121
|
abg --pair work claude # Start a named pair "work" (this directory)
|
|
4919
5122
|
abg --pair work codex # Connect Codex to the "work" pair
|
|
4920
5123
|
abg --pair review claude # A second, parallel pair
|
|
@@ -4940,9 +5143,9 @@ function printVersion() {
|
|
|
4940
5143
|
}
|
|
4941
5144
|
var MARKETPLACE_NAME = "agentbridge", PLUGIN_NAME = "agentbridge", REFRESH_COMMANDS, NOTIFY_COMMANDS, PAIR_AWARE_COMMANDS;
|
|
4942
5145
|
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"]);
|
|
5146
|
+
REFRESH_COMMANDS = new Set(["claude", "codex", "resume"]);
|
|
5147
|
+
NOTIFY_COMMANDS = new Set(["claude", "codex", "init", "dev", "resume"]);
|
|
5148
|
+
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget", "resume"]);
|
|
4946
5149
|
if (import.meta.main) {
|
|
4947
5150
|
const { command, restArgs } = parseTopLevel(process.argv.slice(2));
|
|
4948
5151
|
main(command, restArgs).catch((err) => {
|
package/dist/daemon.js
CHANGED
|
@@ -17,8 +17,8 @@ function defineNumber(value, fallback) {
|
|
|
17
17
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
18
18
|
}
|
|
19
19
|
var BUILD_INFO = Object.freeze({
|
|
20
|
-
version: defineString("0.1.
|
|
21
|
-
commit: defineString("
|
|
20
|
+
version: defineString("0.1.10", "0.0.0-source"),
|
|
21
|
+
commit: defineString("51a44cb", "source"),
|
|
22
22
|
bundle: defineBundle("dist"),
|
|
23
23
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
24
24
|
});
|
|
@@ -811,10 +811,19 @@ class CodexAdapter extends EventEmitter {
|
|
|
811
811
|
this.log("Cannot steer: no turn in progress (use injectMessage)");
|
|
812
812
|
return false;
|
|
813
813
|
}
|
|
814
|
-
|
|
814
|
+
const expectedTurnId = this.currentSteerableTurnId();
|
|
815
|
+
if (!expectedTurnId) {
|
|
816
|
+
this.log("Cannot steer: no addressable active turn id (turn/started carried no id)");
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
this.log(`Steering message into active Codex turn ${expectedTurnId} (${text.length} chars)`);
|
|
815
820
|
const requestId = this.nextInjectionId--;
|
|
816
821
|
this.trackBridgeRequestId(requestId, "steer");
|
|
817
|
-
const params = {
|
|
822
|
+
const params = {
|
|
823
|
+
threadId: this.threadId,
|
|
824
|
+
expectedTurnId,
|
|
825
|
+
input: [{ type: "text", text }]
|
|
826
|
+
};
|
|
818
827
|
try {
|
|
819
828
|
this.appServerWs.send(JSON.stringify({
|
|
820
829
|
method: "turn/steer",
|
|
@@ -1758,6 +1767,14 @@ class CodexAdapter extends EventEmitter {
|
|
|
1758
1767
|
this.log(`Thread detected: ${threadId} (${reason})`);
|
|
1759
1768
|
this.emit("ready", threadId);
|
|
1760
1769
|
}
|
|
1770
|
+
currentSteerableTurnId() {
|
|
1771
|
+
let newest = null;
|
|
1772
|
+
for (const id of this.activeTurnIds) {
|
|
1773
|
+
if (!id.startsWith("unknown:"))
|
|
1774
|
+
newest = id;
|
|
1775
|
+
}
|
|
1776
|
+
return newest;
|
|
1777
|
+
}
|
|
1761
1778
|
get turnPhase() {
|
|
1762
1779
|
if (this.activeTurnIds.size > 0) {
|
|
1763
1780
|
const allStalled = [...this.activeTurnIds].every((id) => this.currentlyStalledTurnIds.has(id));
|
|
@@ -1776,6 +1793,7 @@ class CodexAdapter extends EventEmitter {
|
|
|
1776
1793
|
markTurnStarted(turnId) {
|
|
1777
1794
|
const wasInProgress = this.turnInProgress;
|
|
1778
1795
|
const turnKey = typeof turnId === "string" && turnId.length > 0 ? turnId : `unknown:${Date.now()}`;
|
|
1796
|
+
this.activeTurnIds.delete(turnKey);
|
|
1779
1797
|
this.activeTurnIds.add(turnKey);
|
|
1780
1798
|
this.stalledTurnIds.delete(turnKey);
|
|
1781
1799
|
this.currentlyStalledTurnIds.delete(turnKey);
|
|
@@ -3994,7 +4012,8 @@ codex.on("turnPhaseChanged", ({ phase, previous }) => {
|
|
|
3994
4012
|
});
|
|
3995
4013
|
codex.on("steerFailed", (reason) => {
|
|
3996
4014
|
log(`Steer rejected by app-server: ${reason}`);
|
|
3997
|
-
|
|
4015
|
+
const advice = codex.turnInProgress ? "wait for it to finish (\u2705), then send normally" : "the turn has ended \u2014 resend as a normal reply";
|
|
4016
|
+
emitToClaude(systemMessage("system_steer_failed", `\u26A0\uFE0F Your steer message did NOT reach Codex (${reason}). The original turn continues unaffected \u2014 ${advice}.`));
|
|
3998
4017
|
});
|
|
3999
4018
|
codex.on("steerAccepted", () => {
|
|
4000
4019
|
log("Steer accepted by app-server");
|
|
@@ -4255,11 +4274,12 @@ function handleControlMessage(ws, raw) {
|
|
|
4255
4274
|
if (steered) {
|
|
4256
4275
|
clearAttentionWindow();
|
|
4257
4276
|
}
|
|
4277
|
+
const steerFailureAdvice = codex.turnInProgress ? "Steer failed: the running turn cannot be steered right now \u2014 wait for it to finish (\u2705), then send normally." : "Steer failed: the turn may have just ended or the connection dropped \u2014 retry as a normal reply.";
|
|
4258
4278
|
sendProtocolMessage(ws, {
|
|
4259
4279
|
type: "claude_to_codex_result",
|
|
4260
4280
|
requestId: message.requestId,
|
|
4261
4281
|
success: steered,
|
|
4262
|
-
error: steered ? undefined :
|
|
4282
|
+
error: steered ? undefined : steerFailureAdvice
|
|
4263
4283
|
});
|
|
4264
4284
|
return;
|
|
4265
4285
|
}
|
package/package.json
CHANGED
|
@@ -14227,8 +14227,8 @@ function defineNumber(value, fallback) {
|
|
|
14227
14227
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14228
14228
|
}
|
|
14229
14229
|
var BUILD_INFO = Object.freeze({
|
|
14230
|
-
version: defineString("0.1.
|
|
14231
|
-
commit: defineString("
|
|
14230
|
+
version: defineString("0.1.10", "0.0.0-source"),
|
|
14231
|
+
commit: defineString("51a44cb", "source"),
|
|
14232
14232
|
bundle: defineBundle("plugin"),
|
|
14233
14233
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
14234
14234
|
});
|
|
@@ -17,8 +17,8 @@ function defineNumber(value, fallback) {
|
|
|
17
17
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
18
18
|
}
|
|
19
19
|
var BUILD_INFO = Object.freeze({
|
|
20
|
-
version: defineString("0.1.
|
|
21
|
-
commit: defineString("
|
|
20
|
+
version: defineString("0.1.10", "0.0.0-source"),
|
|
21
|
+
commit: defineString("51a44cb", "source"),
|
|
22
22
|
bundle: defineBundle("plugin"),
|
|
23
23
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
24
24
|
});
|
|
@@ -811,10 +811,19 @@ class CodexAdapter extends EventEmitter {
|
|
|
811
811
|
this.log("Cannot steer: no turn in progress (use injectMessage)");
|
|
812
812
|
return false;
|
|
813
813
|
}
|
|
814
|
-
|
|
814
|
+
const expectedTurnId = this.currentSteerableTurnId();
|
|
815
|
+
if (!expectedTurnId) {
|
|
816
|
+
this.log("Cannot steer: no addressable active turn id (turn/started carried no id)");
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
this.log(`Steering message into active Codex turn ${expectedTurnId} (${text.length} chars)`);
|
|
815
820
|
const requestId = this.nextInjectionId--;
|
|
816
821
|
this.trackBridgeRequestId(requestId, "steer");
|
|
817
|
-
const params = {
|
|
822
|
+
const params = {
|
|
823
|
+
threadId: this.threadId,
|
|
824
|
+
expectedTurnId,
|
|
825
|
+
input: [{ type: "text", text }]
|
|
826
|
+
};
|
|
818
827
|
try {
|
|
819
828
|
this.appServerWs.send(JSON.stringify({
|
|
820
829
|
method: "turn/steer",
|
|
@@ -1758,6 +1767,14 @@ class CodexAdapter extends EventEmitter {
|
|
|
1758
1767
|
this.log(`Thread detected: ${threadId} (${reason})`);
|
|
1759
1768
|
this.emit("ready", threadId);
|
|
1760
1769
|
}
|
|
1770
|
+
currentSteerableTurnId() {
|
|
1771
|
+
let newest = null;
|
|
1772
|
+
for (const id of this.activeTurnIds) {
|
|
1773
|
+
if (!id.startsWith("unknown:"))
|
|
1774
|
+
newest = id;
|
|
1775
|
+
}
|
|
1776
|
+
return newest;
|
|
1777
|
+
}
|
|
1761
1778
|
get turnPhase() {
|
|
1762
1779
|
if (this.activeTurnIds.size > 0) {
|
|
1763
1780
|
const allStalled = [...this.activeTurnIds].every((id) => this.currentlyStalledTurnIds.has(id));
|
|
@@ -1776,6 +1793,7 @@ class CodexAdapter extends EventEmitter {
|
|
|
1776
1793
|
markTurnStarted(turnId) {
|
|
1777
1794
|
const wasInProgress = this.turnInProgress;
|
|
1778
1795
|
const turnKey = typeof turnId === "string" && turnId.length > 0 ? turnId : `unknown:${Date.now()}`;
|
|
1796
|
+
this.activeTurnIds.delete(turnKey);
|
|
1779
1797
|
this.activeTurnIds.add(turnKey);
|
|
1780
1798
|
this.stalledTurnIds.delete(turnKey);
|
|
1781
1799
|
this.currentlyStalledTurnIds.delete(turnKey);
|
|
@@ -3994,7 +4012,8 @@ codex.on("turnPhaseChanged", ({ phase, previous }) => {
|
|
|
3994
4012
|
});
|
|
3995
4013
|
codex.on("steerFailed", (reason) => {
|
|
3996
4014
|
log(`Steer rejected by app-server: ${reason}`);
|
|
3997
|
-
|
|
4015
|
+
const advice = codex.turnInProgress ? "wait for it to finish (\u2705), then send normally" : "the turn has ended \u2014 resend as a normal reply";
|
|
4016
|
+
emitToClaude(systemMessage("system_steer_failed", `\u26A0\uFE0F Your steer message did NOT reach Codex (${reason}). The original turn continues unaffected \u2014 ${advice}.`));
|
|
3998
4017
|
});
|
|
3999
4018
|
codex.on("steerAccepted", () => {
|
|
4000
4019
|
log("Steer accepted by app-server");
|
|
@@ -4255,11 +4274,12 @@ function handleControlMessage(ws, raw) {
|
|
|
4255
4274
|
if (steered) {
|
|
4256
4275
|
clearAttentionWindow();
|
|
4257
4276
|
}
|
|
4277
|
+
const steerFailureAdvice = codex.turnInProgress ? "Steer failed: the running turn cannot be steered right now \u2014 wait for it to finish (\u2705), then send normally." : "Steer failed: the turn may have just ended or the connection dropped \u2014 retry as a normal reply.";
|
|
4258
4278
|
sendProtocolMessage(ws, {
|
|
4259
4279
|
type: "claude_to_codex_result",
|
|
4260
4280
|
requestId: message.requestId,
|
|
4261
4281
|
success: steered,
|
|
4262
|
-
error: steered ? undefined :
|
|
4282
|
+
error: steered ? undefined : steerFailureAdvice
|
|
4263
4283
|
});
|
|
4264
4284
|
return;
|
|
4265
4285
|
}
|