@raysonmeng/agentbridge 0.1.4 → 0.1.6
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 +2 -3
- package/README.zh-CN.md +2 -3
- package/dist/cli.js +349 -90
- package/package.json +3 -2
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +156 -118
- package/plugins/agentbridge/server/daemon.js +639 -176
- package/scripts/postinstall.cjs +44 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raysonmeng/agentbridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Bridge between Claude Code and Codex — bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
"start": "bun run src/bridge.ts",
|
|
20
20
|
"build:cli": "mkdir -p dist && bun build src/cli.ts --outfile dist/cli.js --target bun && chmod +x dist/cli.js",
|
|
21
21
|
"build:plugin": "mkdir -p plugins/agentbridge/server && bun build src/bridge.ts --outfile plugins/agentbridge/server/bridge-server.js --target bun && bun build src/daemon.ts --outfile plugins/agentbridge/server/daemon.js --target bun",
|
|
22
|
+
"verify:plugin-sync": "node scripts/verify-plugin-sync.cjs",
|
|
22
23
|
"postinstall": "node scripts/postinstall.cjs",
|
|
23
24
|
"prepublishOnly": "bun run build:cli && bun run build:plugin",
|
|
24
25
|
"validate:plugin": "claude plugin validate plugins/agentbridge && claude plugin validate .claude-plugin/marketplace.json",
|
|
25
26
|
"test": "bun test src",
|
|
26
27
|
"typecheck": "tsc --noEmit",
|
|
27
28
|
"validate:plugin-versions": "bun scripts/check-plugin-versions.js",
|
|
28
|
-
"check": "tsc --noEmit && bun test src && bun run
|
|
29
|
+
"check": "tsc --noEmit && bun test src && bun run verify:plugin-sync && bun scripts/check-plugin-versions.js"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -13663,6 +13663,60 @@ class StdioServerTransport {
|
|
|
13663
13663
|
import { EventEmitter } from "events";
|
|
13664
13664
|
import { randomUUID } from "crypto";
|
|
13665
13665
|
import { appendFileSync } from "fs";
|
|
13666
|
+
|
|
13667
|
+
// src/state-dir.ts
|
|
13668
|
+
import { mkdirSync, existsSync } from "fs";
|
|
13669
|
+
import { join } from "path";
|
|
13670
|
+
import { homedir, platform } from "os";
|
|
13671
|
+
|
|
13672
|
+
class StateDirResolver {
|
|
13673
|
+
stateDir;
|
|
13674
|
+
constructor(envOverride) {
|
|
13675
|
+
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
13676
|
+
if (override) {
|
|
13677
|
+
this.stateDir = override;
|
|
13678
|
+
} else if (platform() === "darwin") {
|
|
13679
|
+
this.stateDir = join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
13680
|
+
} else {
|
|
13681
|
+
const xdgState = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
|
|
13682
|
+
this.stateDir = join(xdgState, "agentbridge");
|
|
13683
|
+
}
|
|
13684
|
+
}
|
|
13685
|
+
ensure() {
|
|
13686
|
+
if (!existsSync(this.stateDir)) {
|
|
13687
|
+
mkdirSync(this.stateDir, { recursive: true });
|
|
13688
|
+
}
|
|
13689
|
+
}
|
|
13690
|
+
get dir() {
|
|
13691
|
+
return this.stateDir;
|
|
13692
|
+
}
|
|
13693
|
+
get pidFile() {
|
|
13694
|
+
return join(this.stateDir, "daemon.pid");
|
|
13695
|
+
}
|
|
13696
|
+
get tuiPidFile() {
|
|
13697
|
+
return join(this.stateDir, "codex-tui.pid");
|
|
13698
|
+
}
|
|
13699
|
+
get lockFile() {
|
|
13700
|
+
return join(this.stateDir, "daemon.lock");
|
|
13701
|
+
}
|
|
13702
|
+
get statusFile() {
|
|
13703
|
+
return join(this.stateDir, "status.json");
|
|
13704
|
+
}
|
|
13705
|
+
get portsFile() {
|
|
13706
|
+
return join(this.stateDir, "ports.json");
|
|
13707
|
+
}
|
|
13708
|
+
get logFile() {
|
|
13709
|
+
return join(this.stateDir, "agentbridge.log");
|
|
13710
|
+
}
|
|
13711
|
+
get codexWrapperLogFile() {
|
|
13712
|
+
return join(this.stateDir, "codex-wrapper.log");
|
|
13713
|
+
}
|
|
13714
|
+
get killedFile() {
|
|
13715
|
+
return join(this.stateDir, "killed");
|
|
13716
|
+
}
|
|
13717
|
+
}
|
|
13718
|
+
|
|
13719
|
+
// src/claude-adapter.ts
|
|
13666
13720
|
var CLAUDE_INSTRUCTIONS = [
|
|
13667
13721
|
"Codex is an AI coding agent (OpenAI) running in a separate session on the same machine.",
|
|
13668
13722
|
"",
|
|
@@ -13697,23 +13751,27 @@ var CLAUDE_INSTRUCTIONS = [
|
|
|
13697
13751
|
"- If the reply tool returns a busy error, Codex is still executing \u2014 wait and try again later."
|
|
13698
13752
|
].join(`
|
|
13699
13753
|
`);
|
|
13700
|
-
var LOG_FILE = "/tmp/agentbridge.log";
|
|
13701
13754
|
|
|
13702
13755
|
class ClaudeAdapter extends EventEmitter {
|
|
13703
13756
|
server;
|
|
13704
13757
|
notificationSeq = 0;
|
|
13705
13758
|
sessionId;
|
|
13706
13759
|
notificationIdPrefix;
|
|
13760
|
+
instanceId;
|
|
13707
13761
|
replySender = null;
|
|
13762
|
+
logFile;
|
|
13708
13763
|
configuredMode;
|
|
13709
13764
|
resolvedMode = null;
|
|
13710
13765
|
pendingMessages = [];
|
|
13711
13766
|
maxBufferedMessages;
|
|
13712
13767
|
droppedMessageCount = 0;
|
|
13713
|
-
constructor() {
|
|
13768
|
+
constructor(logFile = new StateDirResolver().logFile) {
|
|
13714
13769
|
super();
|
|
13770
|
+
this.logFile = logFile;
|
|
13771
|
+
this.instanceId = randomUUID().slice(0, 8);
|
|
13715
13772
|
this.sessionId = `codex_${Date.now()}`;
|
|
13716
13773
|
this.notificationIdPrefix = randomUUID().replace(/-/g, "").slice(0, 12);
|
|
13774
|
+
this.log(`ClaudeAdapter created (instance=${this.instanceId})`);
|
|
13717
13775
|
const envMode = process.env.AGENTBRIDGE_MODE;
|
|
13718
13776
|
this.configuredMode = envMode && ["push", "pull", "auto"].includes(envMode) ? envMode : "auto";
|
|
13719
13777
|
this.maxBufferedMessages = parseInt(process.env.AGENTBRIDGE_MAX_BUFFERED_MESSAGES ?? "100", 10);
|
|
@@ -13750,10 +13808,11 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13750
13808
|
this.log(`Delivery mode set by AGENTBRIDGE_MODE: ${this.resolvedMode}`);
|
|
13751
13809
|
} else {
|
|
13752
13810
|
this.resolvedMode = "push";
|
|
13753
|
-
this.log("Delivery mode defaulting to push (set AGENTBRIDGE_MODE=pull
|
|
13811
|
+
this.log("Delivery mode defaulting to push (set AGENTBRIDGE_MODE=pull to use polling instead)");
|
|
13754
13812
|
}
|
|
13755
13813
|
}
|
|
13756
13814
|
async pushNotification(message) {
|
|
13815
|
+
this.log(`pushNotification (instance=${this.instanceId}, mode=${this.resolvedMode}, msgId=${message.id}, len=${message.content.length})`);
|
|
13757
13816
|
if (this.resolvedMode === "push") {
|
|
13758
13817
|
await this.pushViaChannel(message);
|
|
13759
13818
|
} else {
|
|
@@ -13781,6 +13840,7 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13781
13840
|
this.log(`Pushed notification: ${msgId}`);
|
|
13782
13841
|
} catch (e) {
|
|
13783
13842
|
this.log(`Push notification failed: ${e.message}`);
|
|
13843
|
+
this.queueForPull(message);
|
|
13784
13844
|
}
|
|
13785
13845
|
}
|
|
13786
13846
|
queueForPull(message) {
|
|
@@ -13790,9 +13850,10 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
13790
13850
|
this.log(`Message queue full, dropped oldest message (total dropped: ${this.droppedMessageCount})`);
|
|
13791
13851
|
}
|
|
13792
13852
|
this.pendingMessages.push(message);
|
|
13793
|
-
this.log(`Queued message for pull (${this.pendingMessages.length} pending)`);
|
|
13853
|
+
this.log(`Queued message for pull (${this.pendingMessages.length} pending, instance=${this.instanceId})`);
|
|
13794
13854
|
}
|
|
13795
13855
|
drainMessages() {
|
|
13856
|
+
this.log(`get_messages called (instance=${this.instanceId}, pending=${this.pendingMessages.length}, dropped=${this.droppedMessageCount})`);
|
|
13796
13857
|
if (this.pendingMessages.length === 0 && this.droppedMessageCount === 0) {
|
|
13797
13858
|
return {
|
|
13798
13859
|
content: [{ type: "text", text: "No new messages from Codex." }]
|
|
@@ -13817,6 +13878,7 @@ Codex: ${msg.content}`;
|
|
|
13817
13878
|
}).join(`
|
|
13818
13879
|
|
|
13819
13880
|
`);
|
|
13881
|
+
this.log(`get_messages returning ${count} message(s) (instance=${this.instanceId}, dropped=${dropped})`);
|
|
13820
13882
|
return {
|
|
13821
13883
|
content: [
|
|
13822
13884
|
{
|
|
@@ -13922,13 +13984,18 @@ ${formatted}`
|
|
|
13922
13984
|
`;
|
|
13923
13985
|
process.stderr.write(line);
|
|
13924
13986
|
try {
|
|
13925
|
-
appendFileSync(
|
|
13987
|
+
appendFileSync(this.logFile, line);
|
|
13926
13988
|
} catch {}
|
|
13927
13989
|
}
|
|
13928
13990
|
}
|
|
13929
13991
|
|
|
13930
13992
|
// src/daemon-client.ts
|
|
13931
13993
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
13994
|
+
|
|
13995
|
+
// src/control-protocol.ts
|
|
13996
|
+
var CLOSE_CODE_REPLACED = 4001;
|
|
13997
|
+
|
|
13998
|
+
// src/daemon-client.ts
|
|
13932
13999
|
var nextSocketId = 0;
|
|
13933
14000
|
|
|
13934
14001
|
class DaemonClient extends EventEmitter2 {
|
|
@@ -14047,7 +14114,11 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14047
14114
|
if (isCurrent) {
|
|
14048
14115
|
this.ws = null;
|
|
14049
14116
|
this.rejectPendingReplies("AgentBridge daemon disconnected.");
|
|
14050
|
-
|
|
14117
|
+
if (event.code === CLOSE_CODE_REPLACED) {
|
|
14118
|
+
this.emit("rejected");
|
|
14119
|
+
} else {
|
|
14120
|
+
this.emit("disconnect");
|
|
14121
|
+
}
|
|
14051
14122
|
}
|
|
14052
14123
|
};
|
|
14053
14124
|
ws.onerror = () => {};
|
|
@@ -14073,7 +14144,7 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14073
14144
|
|
|
14074
14145
|
// src/daemon-lifecycle.ts
|
|
14075
14146
|
import { spawn, execFileSync } from "child_process";
|
|
14076
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
14147
|
+
import { existsSync as existsSync2, readFileSync, unlinkSync, writeFileSync, openSync, closeSync, constants } from "fs";
|
|
14077
14148
|
import { fileURLToPath } from "url";
|
|
14078
14149
|
var DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY ?? "./daemon.ts";
|
|
14079
14150
|
var DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
|
|
@@ -14211,7 +14282,7 @@ class DaemonLifecycle {
|
|
|
14211
14282
|
} catch {}
|
|
14212
14283
|
}
|
|
14213
14284
|
wasKilled() {
|
|
14214
|
-
return
|
|
14285
|
+
return existsSync2(this.stateDir.killedFile);
|
|
14215
14286
|
}
|
|
14216
14287
|
launch() {
|
|
14217
14288
|
this.stateDir.ensure();
|
|
@@ -14332,116 +14403,62 @@ function isProcessAlive(pid) {
|
|
|
14332
14403
|
}
|
|
14333
14404
|
}
|
|
14334
14405
|
|
|
14335
|
-
// src/state-dir.ts
|
|
14336
|
-
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
14337
|
-
import { join } from "path";
|
|
14338
|
-
import { homedir, platform } from "os";
|
|
14339
|
-
|
|
14340
|
-
class StateDirResolver {
|
|
14341
|
-
stateDir;
|
|
14342
|
-
constructor(envOverride) {
|
|
14343
|
-
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
14344
|
-
if (override) {
|
|
14345
|
-
this.stateDir = override;
|
|
14346
|
-
} else if (platform() === "darwin") {
|
|
14347
|
-
this.stateDir = join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
14348
|
-
} else {
|
|
14349
|
-
const xdgState = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
|
|
14350
|
-
this.stateDir = join(xdgState, "agentbridge");
|
|
14351
|
-
}
|
|
14352
|
-
}
|
|
14353
|
-
ensure() {
|
|
14354
|
-
if (!existsSync2(this.stateDir)) {
|
|
14355
|
-
mkdirSync(this.stateDir, { recursive: true });
|
|
14356
|
-
}
|
|
14357
|
-
}
|
|
14358
|
-
get dir() {
|
|
14359
|
-
return this.stateDir;
|
|
14360
|
-
}
|
|
14361
|
-
get pidFile() {
|
|
14362
|
-
return join(this.stateDir, "daemon.pid");
|
|
14363
|
-
}
|
|
14364
|
-
get tuiPidFile() {
|
|
14365
|
-
return join(this.stateDir, "codex-tui.pid");
|
|
14366
|
-
}
|
|
14367
|
-
get lockFile() {
|
|
14368
|
-
return join(this.stateDir, "daemon.lock");
|
|
14369
|
-
}
|
|
14370
|
-
get statusFile() {
|
|
14371
|
-
return join(this.stateDir, "status.json");
|
|
14372
|
-
}
|
|
14373
|
-
get portsFile() {
|
|
14374
|
-
return join(this.stateDir, "ports.json");
|
|
14375
|
-
}
|
|
14376
|
-
get logFile() {
|
|
14377
|
-
return join(this.stateDir, "agentbridge.log");
|
|
14378
|
-
}
|
|
14379
|
-
get killedFile() {
|
|
14380
|
-
return join(this.stateDir, "killed");
|
|
14381
|
-
}
|
|
14382
|
-
}
|
|
14383
|
-
|
|
14384
14406
|
// src/config-service.ts
|
|
14385
14407
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
14386
14408
|
import { join as join2 } from "path";
|
|
14387
14409
|
var DEFAULT_CONFIG = {
|
|
14388
14410
|
version: "1.0",
|
|
14389
|
-
|
|
14390
|
-
|
|
14411
|
+
codex: {
|
|
14412
|
+
appPort: 4500,
|
|
14391
14413
|
proxyPort: 4501
|
|
14392
14414
|
},
|
|
14393
|
-
agents: {
|
|
14394
|
-
claude: {
|
|
14395
|
-
role: "Reviewer, Planner",
|
|
14396
|
-
mode: "push"
|
|
14397
|
-
},
|
|
14398
|
-
codex: {
|
|
14399
|
-
role: "Implementer, Executor"
|
|
14400
|
-
}
|
|
14401
|
-
},
|
|
14402
|
-
markers: ["IMPORTANT", "STATUS", "FYI"],
|
|
14403
14415
|
turnCoordination: {
|
|
14404
|
-
attentionWindowSeconds: 15
|
|
14405
|
-
busyGuard: true
|
|
14416
|
+
attentionWindowSeconds: 15
|
|
14406
14417
|
},
|
|
14407
14418
|
idleShutdownSeconds: 30
|
|
14408
14419
|
};
|
|
14409
|
-
var DEFAULT_COLLABORATION_MD = `# Collaboration Rules
|
|
14410
|
-
|
|
14411
|
-
## Roles
|
|
14412
|
-
- Claude: Reviewer, Planner, Hypothesis Challenger
|
|
14413
|
-
- Codex: Implementer, Executor, Reproducer/Verifier
|
|
14414
|
-
|
|
14415
|
-
## Thinking Patterns
|
|
14416
|
-
- Analytical/review tasks: Independent Analysis & Convergence
|
|
14417
|
-
- Implementation tasks: Architect -> Builder -> Critic
|
|
14418
|
-
- Debugging tasks: Hypothesis -> Experiment -> Interpretation
|
|
14419
|
-
|
|
14420
|
-
## Communication
|
|
14421
|
-
- Use explicit phrases: "My independent view is:", "I agree on:", "I disagree on:", "Current consensus:"
|
|
14422
|
-
- Tag messages with [IMPORTANT], [STATUS], or [FYI]
|
|
14423
|
-
|
|
14424
|
-
## Review Process
|
|
14425
|
-
- Cross-review: author never reviews their own code
|
|
14426
|
-
- All changes go through feature/fix branches + PR
|
|
14427
|
-
- Merge via squash merge
|
|
14428
|
-
|
|
14429
|
-
## Custom Rules
|
|
14430
|
-
<!-- Add your project-specific collaboration rules here -->
|
|
14431
|
-
`;
|
|
14432
14420
|
var CONFIG_DIR = ".agentbridge";
|
|
14433
14421
|
var CONFIG_FILE = "config.json";
|
|
14434
|
-
|
|
14422
|
+
function isRecord(value) {
|
|
14423
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14424
|
+
}
|
|
14425
|
+
function normalizeInteger(value, fallback) {
|
|
14426
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
14427
|
+
return value;
|
|
14428
|
+
if (typeof value === "string") {
|
|
14429
|
+
const parsed = Number(value);
|
|
14430
|
+
if (Number.isFinite(parsed))
|
|
14431
|
+
return parsed;
|
|
14432
|
+
}
|
|
14433
|
+
return fallback;
|
|
14434
|
+
}
|
|
14435
|
+
function normalizeConfig(raw) {
|
|
14436
|
+
if (!isRecord(raw))
|
|
14437
|
+
return null;
|
|
14438
|
+
const config2 = raw;
|
|
14439
|
+
const codex = isRecord(config2.codex) ? config2.codex : {};
|
|
14440
|
+
const daemon = isRecord(config2.daemon) ? config2.daemon : {};
|
|
14441
|
+
const turnCoordination = isRecord(config2.turnCoordination) ? config2.turnCoordination : {};
|
|
14442
|
+
return {
|
|
14443
|
+
version: typeof config2.version === "string" ? config2.version : DEFAULT_CONFIG.version,
|
|
14444
|
+
codex: {
|
|
14445
|
+
appPort: normalizeInteger(codex.appPort ?? daemon.port, DEFAULT_CONFIG.codex.appPort),
|
|
14446
|
+
proxyPort: normalizeInteger(codex.proxyPort ?? daemon.proxyPort, DEFAULT_CONFIG.codex.proxyPort)
|
|
14447
|
+
},
|
|
14448
|
+
turnCoordination: {
|
|
14449
|
+
attentionWindowSeconds: normalizeInteger(turnCoordination.attentionWindowSeconds, DEFAULT_CONFIG.turnCoordination.attentionWindowSeconds)
|
|
14450
|
+
},
|
|
14451
|
+
idleShutdownSeconds: normalizeInteger(config2.idleShutdownSeconds, DEFAULT_CONFIG.idleShutdownSeconds)
|
|
14452
|
+
};
|
|
14453
|
+
}
|
|
14435
14454
|
|
|
14436
14455
|
class ConfigService {
|
|
14437
14456
|
configDir;
|
|
14438
14457
|
configPath;
|
|
14439
|
-
collaborationPath;
|
|
14440
14458
|
constructor(projectRoot) {
|
|
14441
14459
|
const root = projectRoot ?? process.cwd();
|
|
14442
14460
|
this.configDir = join2(root, CONFIG_DIR);
|
|
14443
14461
|
this.configPath = join2(this.configDir, CONFIG_FILE);
|
|
14444
|
-
this.collaborationPath = join2(this.configDir, COLLABORATION_FILE);
|
|
14445
14462
|
}
|
|
14446
14463
|
hasConfig() {
|
|
14447
14464
|
return existsSync3(this.configPath);
|
|
@@ -14449,7 +14466,7 @@ class ConfigService {
|
|
|
14449
14466
|
load() {
|
|
14450
14467
|
try {
|
|
14451
14468
|
const raw = readFileSync2(this.configPath, "utf-8");
|
|
14452
|
-
return JSON.parse(raw);
|
|
14469
|
+
return normalizeConfig(JSON.parse(raw));
|
|
14453
14470
|
} catch {
|
|
14454
14471
|
return null;
|
|
14455
14472
|
}
|
|
@@ -14462,17 +14479,6 @@ class ConfigService {
|
|
|
14462
14479
|
writeFileSync2(this.configPath, JSON.stringify(config2, null, 2) + `
|
|
14463
14480
|
`, "utf-8");
|
|
14464
14481
|
}
|
|
14465
|
-
loadCollaboration() {
|
|
14466
|
-
try {
|
|
14467
|
-
return readFileSync2(this.collaborationPath, "utf-8");
|
|
14468
|
-
} catch {
|
|
14469
|
-
return null;
|
|
14470
|
-
}
|
|
14471
|
-
}
|
|
14472
|
-
saveCollaboration(content) {
|
|
14473
|
-
this.ensureConfigDir();
|
|
14474
|
-
writeFileSync2(this.collaborationPath, content, "utf-8");
|
|
14475
|
-
}
|
|
14476
14482
|
initDefaults() {
|
|
14477
14483
|
this.ensureConfigDir();
|
|
14478
14484
|
const created = [];
|
|
@@ -14480,18 +14486,11 @@ class ConfigService {
|
|
|
14480
14486
|
this.save(DEFAULT_CONFIG);
|
|
14481
14487
|
created.push(this.configPath);
|
|
14482
14488
|
}
|
|
14483
|
-
if (!existsSync3(this.collaborationPath)) {
|
|
14484
|
-
this.saveCollaboration(DEFAULT_COLLABORATION_MD);
|
|
14485
|
-
created.push(this.collaborationPath);
|
|
14486
|
-
}
|
|
14487
14489
|
return created;
|
|
14488
14490
|
}
|
|
14489
14491
|
get configFilePath() {
|
|
14490
14492
|
return this.configPath;
|
|
14491
14493
|
}
|
|
14492
|
-
get collaborationFilePath() {
|
|
14493
|
-
return this.collaborationPath;
|
|
14494
|
-
}
|
|
14495
14494
|
ensureConfigDir() {
|
|
14496
14495
|
if (!existsSync3(this.configDir)) {
|
|
14497
14496
|
mkdirSync2(this.configDir, { recursive: true });
|
|
@@ -14499,17 +14498,31 @@ class ConfigService {
|
|
|
14499
14498
|
}
|
|
14500
14499
|
}
|
|
14501
14500
|
|
|
14501
|
+
// src/bridge-disabled-state.ts
|
|
14502
|
+
function disabledReplyError(reason) {
|
|
14503
|
+
switch (reason) {
|
|
14504
|
+
case "rejected":
|
|
14505
|
+
return "AgentBridge rejected this session \u2014 another Claude Code session is already connected. Close the other session first, or run `agentbridge kill` to reset.";
|
|
14506
|
+
case "killed":
|
|
14507
|
+
return "AgentBridge is disabled by `agentbridge kill`. Restart Claude Code (`agentbridge claude`), switch to a new conversation, or run `/resume` to reconnect.";
|
|
14508
|
+
}
|
|
14509
|
+
}
|
|
14510
|
+
|
|
14502
14511
|
// src/bridge.ts
|
|
14503
14512
|
var stateDir = new StateDirResolver;
|
|
14513
|
+
stateDir.ensure();
|
|
14504
14514
|
var configService = new ConfigService;
|
|
14505
14515
|
var config2 = configService.loadOrDefault();
|
|
14506
14516
|
var CONTROL_PORT = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
|
|
14507
14517
|
var daemonLifecycle = new DaemonLifecycle({ stateDir, controlPort: CONTROL_PORT, log });
|
|
14508
14518
|
var CONTROL_WS_URL = daemonLifecycle.controlWsUrl;
|
|
14509
|
-
var claude = new ClaudeAdapter;
|
|
14519
|
+
var claude = new ClaudeAdapter(stateDir.logFile);
|
|
14510
14520
|
var daemonClient = new DaemonClient(CONTROL_WS_URL);
|
|
14511
14521
|
var shuttingDown = false;
|
|
14512
14522
|
var daemonDisabled = false;
|
|
14523
|
+
var daemonDisabledReason = null;
|
|
14524
|
+
var hasSeenTuiConnect = false;
|
|
14525
|
+
var previousTuiConnected = false;
|
|
14513
14526
|
var RECONNECT_NOTIFY_COOLDOWN_MS = 30000;
|
|
14514
14527
|
var DISABLED_RECOVERY_INTERVAL_MS = 5000;
|
|
14515
14528
|
var lastDisconnectNotifyTs = 0;
|
|
@@ -14523,7 +14536,7 @@ claude.setReplySender(async (msg, requireReply) => {
|
|
|
14523
14536
|
if (daemonDisabled) {
|
|
14524
14537
|
return {
|
|
14525
14538
|
success: false,
|
|
14526
|
-
error:
|
|
14539
|
+
error: disabledReplyError(daemonDisabledReason ?? "killed")
|
|
14527
14540
|
};
|
|
14528
14541
|
}
|
|
14529
14542
|
return daemonClient.sendReply(msg, requireReply);
|
|
@@ -14534,6 +14547,18 @@ daemonClient.on("codexMessage", (message) => {
|
|
|
14534
14547
|
});
|
|
14535
14548
|
daemonClient.on("status", (status) => {
|
|
14536
14549
|
log(`Daemon status: ready=${status.bridgeReady} tui=${status.tuiConnected} thread=${status.threadId ?? "none"} queued=${status.queuedMessageCount}`);
|
|
14550
|
+
if (!hasSeenTuiConnect && status.tuiConnected && !previousTuiConnected) {
|
|
14551
|
+
hasSeenTuiConnect = true;
|
|
14552
|
+
log("First TUI connect detected \u2014 sending kickoff message to Claude");
|
|
14553
|
+
claude.pushNotification(systemMessage("system_tui_kickoff", [
|
|
14554
|
+
"\uD83E\uDD1D Codex has connected via AgentBridge.",
|
|
14555
|
+
"You are now in a multi-agent collaboration session.",
|
|
14556
|
+
"When you receive a complex task, propose a division of labor to Codex.",
|
|
14557
|
+
"Use `reply` to send messages and `get_messages` to check for responses."
|
|
14558
|
+
].join(`
|
|
14559
|
+
`)));
|
|
14560
|
+
}
|
|
14561
|
+
previousTuiConnected = status.tuiConnected;
|
|
14537
14562
|
});
|
|
14538
14563
|
daemonClient.on("disconnect", () => {
|
|
14539
14564
|
if (shuttingDown || daemonDisabled)
|
|
@@ -14548,6 +14573,15 @@ daemonClient.on("disconnect", () => {
|
|
|
14548
14573
|
}
|
|
14549
14574
|
reconnectToDaemon();
|
|
14550
14575
|
});
|
|
14576
|
+
daemonClient.on("rejected", async () => {
|
|
14577
|
+
if (shuttingDown || daemonDisabled)
|
|
14578
|
+
return;
|
|
14579
|
+
log("Daemon rejected this session (close code 4001) \u2014 another Claude session is already connected");
|
|
14580
|
+
daemonDisabled = true;
|
|
14581
|
+
daemonDisabledReason = "rejected";
|
|
14582
|
+
await claude.pushNotification(systemMessage("system_bridge_replaced", "\u26A0\uFE0F AgentBridge daemon rejected this session \u2014 another Claude Code session is already connected. Close the other session first, or run `agentbridge kill` to reset. AgentBridge \u5B88\u62A4\u8FDB\u7A0B\u62D2\u7EDD\u4E86\u6B64\u4F1A\u8BDD\u2014\u2014\u53E6\u4E00\u4E2A Claude Code \u4F1A\u8BDD\u5DF2\u5728\u8FDE\u63A5\u4E2D\u3002\u8BF7\u5148\u5173\u95ED\u53E6\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u6216\u8FD0\u884C `agentbridge kill` \u91CD\u7F6E\u3002"));
|
|
14583
|
+
await daemonClient.disconnect();
|
|
14584
|
+
});
|
|
14551
14585
|
claude.on("ready", async () => {
|
|
14552
14586
|
log(`MCP server ready (delivery mode: ${claude.getDeliveryMode()}) \u2014 ensuring AgentBridge daemon...`);
|
|
14553
14587
|
if (daemonLifecycle.wasKilled()) {
|
|
@@ -14565,6 +14599,7 @@ async function connectToDaemon(isReconnect = false) {
|
|
|
14565
14599
|
await daemonLifecycle.ensureRunning();
|
|
14566
14600
|
await daemonClient.connect();
|
|
14567
14601
|
daemonClient.attachClaude();
|
|
14602
|
+
daemonDisabledReason = null;
|
|
14568
14603
|
if (!isReconnect) {
|
|
14569
14604
|
claude.pushNotification(systemMessage("system_bridge_ready", "\u2705 AgentBridge bridge is ready. Daemon connected. Start Codex in another terminal with: agentbridge codex"));
|
|
14570
14605
|
}
|
|
@@ -14578,6 +14613,7 @@ async function enterDisabledState(logMessage, notificationContent) {
|
|
|
14578
14613
|
if (daemonDisabled)
|
|
14579
14614
|
return;
|
|
14580
14615
|
daemonDisabled = true;
|
|
14616
|
+
daemonDisabledReason = "killed";
|
|
14581
14617
|
log(logMessage);
|
|
14582
14618
|
await claude.pushNotification(systemMessage("system_bridge_disabled", notificationContent));
|
|
14583
14619
|
await daemonClient.disconnect();
|
|
@@ -14666,11 +14702,13 @@ async function pollDisabledRecovery() {
|
|
|
14666
14702
|
await daemonClient.connect();
|
|
14667
14703
|
daemonClient.attachClaude();
|
|
14668
14704
|
daemonDisabled = false;
|
|
14705
|
+
daemonDisabledReason = null;
|
|
14669
14706
|
stopDisabledRecoveryPoller();
|
|
14670
14707
|
claude.pushNotification(systemMessage("system_bridge_recovered", "\u2705 AgentBridge recovered after the killed sentinel was cleared. Daemon reconnected."));
|
|
14671
14708
|
} catch (err) {
|
|
14672
14709
|
log(`Disabled-state direct reconnect failed: ${err.message}`);
|
|
14673
14710
|
daemonDisabled = false;
|
|
14711
|
+
daemonDisabledReason = null;
|
|
14674
14712
|
stopDisabledRecoveryPoller();
|
|
14675
14713
|
reconnectToDaemon();
|
|
14676
14714
|
}
|