@raysonmeng/agentbridge 0.1.12 → 0.1.13
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 +1 -1
- package/dist/cli.js +951 -412
- package/dist/daemon.js +1117 -422
- package/package.json +3 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +596 -180
- package/plugins/agentbridge/server/daemon.js +1117 -422
package/dist/cli.js
CHANGED
|
@@ -17,10 +17,67 @@ var __export = (target, all) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
19
19
|
|
|
20
|
+
// src/cli-invocation.ts
|
|
21
|
+
import { basename } from "path";
|
|
22
|
+
function cliInvocationName(argv = process.argv) {
|
|
23
|
+
const raw = argv[1];
|
|
24
|
+
if (typeof raw !== "string" || raw.length === 0)
|
|
25
|
+
return DEFAULT_CLI_NAME;
|
|
26
|
+
const name = basename(raw).replace(/\.(ts|js|mjs|cjs)$/, "");
|
|
27
|
+
return isCliName(name) ? name : DEFAULT_CLI_NAME;
|
|
28
|
+
}
|
|
29
|
+
function isCliName(value) {
|
|
30
|
+
return CLI_NAMES.includes(value);
|
|
31
|
+
}
|
|
32
|
+
var CLI_NAMES, DEFAULT_CLI_NAME = "abg";
|
|
33
|
+
var init_cli_invocation = __esm(() => {
|
|
34
|
+
CLI_NAMES = ["abg", "agentbridge"];
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// src/atomic-json.ts
|
|
38
|
+
import * as fs from "fs";
|
|
39
|
+
import { randomUUID } from "crypto";
|
|
40
|
+
import { dirname } from "path";
|
|
41
|
+
function tmpPathFor(targetPath) {
|
|
42
|
+
return `${targetPath}.tmp.${process.pid}.${randomUUID()}`;
|
|
43
|
+
}
|
|
44
|
+
function atomicWriteText(path, content, options = {}) {
|
|
45
|
+
fs.mkdirSync(dirname(path), { recursive: true });
|
|
46
|
+
const tmp = tmpPathFor(path);
|
|
47
|
+
let renamed = false;
|
|
48
|
+
const fd = fs.openSync(tmp, "w", options.mode ?? 438);
|
|
49
|
+
try {
|
|
50
|
+
try {
|
|
51
|
+
fs.writeFileSync(fd, content, "utf-8");
|
|
52
|
+
if (options.fsync)
|
|
53
|
+
fs.fsyncSync(fd);
|
|
54
|
+
} finally {
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
fs.renameSync(tmp, path);
|
|
58
|
+
renamed = true;
|
|
59
|
+
} finally {
|
|
60
|
+
if (!renamed) {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(tmp);
|
|
63
|
+
} catch {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function atomicWriteJson(path, value, options = {}) {
|
|
68
|
+
atomicWriteText(path, JSON.stringify(value, null, 2) + `
|
|
69
|
+
`, options);
|
|
70
|
+
}
|
|
71
|
+
var init_atomic_json = () => {};
|
|
72
|
+
|
|
20
73
|
// src/state-dir.ts
|
|
21
|
-
import { mkdirSync, existsSync } from "fs";
|
|
74
|
+
import { mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
22
75
|
import { join } from "path";
|
|
23
76
|
import { homedir, platform } from "os";
|
|
77
|
+
function resolveXdgStateBase(rawXdg = process.env.XDG_STATE_HOME) {
|
|
78
|
+
const xdgState = rawXdg && rawXdg.length > 0 ? rawXdg : join(homedir(), ".local", "state");
|
|
79
|
+
return join(xdgState, "agentbridge");
|
|
80
|
+
}
|
|
24
81
|
|
|
25
82
|
class StateDirResolver {
|
|
26
83
|
stateDir;
|
|
@@ -28,8 +85,7 @@ class StateDirResolver {
|
|
|
28
85
|
if (platform() === "darwin") {
|
|
29
86
|
return join(homedir(), "Library", "Application Support", "AgentBridge");
|
|
30
87
|
}
|
|
31
|
-
|
|
32
|
-
return join(xdgState, "agentbridge");
|
|
88
|
+
return resolveXdgStateBase(process.env.XDG_STATE_HOME);
|
|
33
89
|
}
|
|
34
90
|
constructor(envOverride) {
|
|
35
91
|
const override = envOverride ?? process.env.AGENTBRIDGE_STATE_DIR;
|
|
@@ -37,7 +93,7 @@ class StateDirResolver {
|
|
|
37
93
|
}
|
|
38
94
|
ensure() {
|
|
39
95
|
if (!existsSync(this.stateDir)) {
|
|
40
|
-
|
|
96
|
+
mkdirSync2(this.stateDir, { recursive: true });
|
|
41
97
|
}
|
|
42
98
|
}
|
|
43
99
|
get dir() {
|
|
@@ -55,8 +111,8 @@ class StateDirResolver {
|
|
|
55
111
|
get statusFile() {
|
|
56
112
|
return join(this.stateDir, "status.json");
|
|
57
113
|
}
|
|
58
|
-
get
|
|
59
|
-
return join(this.stateDir, "
|
|
114
|
+
get daemonRecordFile() {
|
|
115
|
+
return join(this.stateDir, "daemon.json");
|
|
60
116
|
}
|
|
61
117
|
get currentThreadFile() {
|
|
62
118
|
return join(this.stateDir, "current-thread.json");
|
|
@@ -120,7 +176,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
120
176
|
var require_package = __commonJS((exports, module) => {
|
|
121
177
|
module.exports = {
|
|
122
178
|
name: "@raysonmeng/agentbridge",
|
|
123
|
-
version: "0.1.
|
|
179
|
+
version: "0.1.13",
|
|
124
180
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
125
181
|
type: "module",
|
|
126
182
|
packageManager: "bun@1.3.11",
|
|
@@ -151,6 +207,8 @@ var require_package = __commonJS((exports, module) => {
|
|
|
151
207
|
prepublishOnly: "bun run build:cli && bun run build:plugin && bun run verify:plugin-sync && bun scripts/check-plugin-versions.js",
|
|
152
208
|
"validate:plugin": "claude plugin validate plugins/agentbridge && claude plugin validate .claude-plugin/marketplace.json",
|
|
153
209
|
test: "bun test src",
|
|
210
|
+
"test:unit": "bun test src/unit-test",
|
|
211
|
+
"test:integration": "bun test src/integration-test",
|
|
154
212
|
"e2e:transport": "bun scripts/e2e-codex-transport.mjs",
|
|
155
213
|
"install:global": "node scripts/install-global.mjs local",
|
|
156
214
|
"install:global:local": "node scripts/install-global.mjs local",
|
|
@@ -199,7 +257,7 @@ __export(exports_update_notifier, {
|
|
|
199
257
|
buildUpdateNotice: () => buildUpdateNotice,
|
|
200
258
|
PACKAGE_NAME: () => PACKAGE_NAME
|
|
201
259
|
});
|
|
202
|
-
import { readFileSync
|
|
260
|
+
import { readFileSync } from "fs";
|
|
203
261
|
function getCurrentVersion() {
|
|
204
262
|
try {
|
|
205
263
|
return require_package().version;
|
|
@@ -235,9 +293,7 @@ function readCache(stateDir) {
|
|
|
235
293
|
}
|
|
236
294
|
function writeCache(stateDir, cache) {
|
|
237
295
|
try {
|
|
238
|
-
stateDir.
|
|
239
|
-
writeFileSync(stateDir.updateCheckFile, JSON.stringify(cache, null, 2) + `
|
|
240
|
-
`, "utf-8");
|
|
296
|
+
atomicWriteJson(stateDir.updateCheckFile, cache);
|
|
241
297
|
} catch {}
|
|
242
298
|
}
|
|
243
299
|
function parseLatestFromRegistry(body) {
|
|
@@ -311,6 +367,7 @@ function maybeNotifyUpdate(deps = {}) {
|
|
|
311
367
|
}
|
|
312
368
|
var PACKAGE_NAME = "@raysonmeng/agentbridge", REGISTRY_URL, ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json", DEFAULT_CHECK_INTERVAL_MS, FETCH_TIMEOUT_MS = 2500, CHECK_INTERVAL_ENV = "AGENTBRIDGE_UPDATE_CHECK_INTERVAL_MS";
|
|
313
369
|
var init_update_notifier = __esm(() => {
|
|
370
|
+
init_atomic_json();
|
|
314
371
|
init_state_dir();
|
|
315
372
|
init_version_utils();
|
|
316
373
|
REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}`;
|
|
@@ -318,7 +375,7 @@ var init_update_notifier = __esm(() => {
|
|
|
318
375
|
});
|
|
319
376
|
|
|
320
377
|
// src/config-service.ts
|
|
321
|
-
import { readFileSync as readFileSync2,
|
|
378
|
+
import { readFileSync as readFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
322
379
|
import { join as join2 } from "path";
|
|
323
380
|
function isRecord(value) {
|
|
324
381
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -442,13 +499,13 @@ function normalizeConfig(raw) {
|
|
|
442
499
|
return {
|
|
443
500
|
version: typeof config.version === "string" ? config.version : DEFAULT_CONFIG.version,
|
|
444
501
|
codex: {
|
|
445
|
-
appPort:
|
|
446
|
-
proxyPort:
|
|
502
|
+
appPort: normalizeBoundedInteger(codex.appPort ?? daemon.port, DEFAULT_CONFIG.codex.appPort, 1, 65535),
|
|
503
|
+
proxyPort: normalizeBoundedInteger(codex.proxyPort ?? daemon.proxyPort, DEFAULT_CONFIG.codex.proxyPort, 1, 65535)
|
|
447
504
|
},
|
|
448
505
|
turnCoordination: {
|
|
449
|
-
attentionWindowSeconds:
|
|
506
|
+
attentionWindowSeconds: normalizeBoundedInteger(turnCoordination.attentionWindowSeconds, DEFAULT_CONFIG.turnCoordination.attentionWindowSeconds, 0, Number.MAX_SAFE_INTEGER)
|
|
450
507
|
},
|
|
451
|
-
idleShutdownSeconds:
|
|
508
|
+
idleShutdownSeconds: normalizeBoundedInteger(config.idleShutdownSeconds, DEFAULT_CONFIG.idleShutdownSeconds, 1, Number.MAX_SAFE_INTEGER),
|
|
452
509
|
budget: normalizeBudgetConfig(config.budget)
|
|
453
510
|
};
|
|
454
511
|
}
|
|
@@ -520,9 +577,7 @@ class ConfigService {
|
|
|
520
577
|
};
|
|
521
578
|
}
|
|
522
579
|
save(config) {
|
|
523
|
-
this.
|
|
524
|
-
writeFileSync2(this.configPath, JSON.stringify(config, null, 2) + `
|
|
525
|
-
`, "utf-8");
|
|
580
|
+
atomicWriteJson(this.configPath, config);
|
|
526
581
|
}
|
|
527
582
|
initDefaults() {
|
|
528
583
|
this.ensureConfigDir();
|
|
@@ -538,12 +593,13 @@ class ConfigService {
|
|
|
538
593
|
}
|
|
539
594
|
ensureConfigDir() {
|
|
540
595
|
if (!existsSync2(this.configDir)) {
|
|
541
|
-
|
|
596
|
+
mkdirSync3(this.configDir, { recursive: true });
|
|
542
597
|
}
|
|
543
598
|
}
|
|
544
599
|
}
|
|
545
600
|
var DEFAULT_BUDGET_CONFIG, DEFAULT_CONFIG, CONFIG_DIR = ".agentbridge", CONFIG_FILE = "config.json", NOOP_LOGGER = () => {};
|
|
546
601
|
var init_config_service = __esm(() => {
|
|
602
|
+
init_atomic_json();
|
|
547
603
|
DEFAULT_BUDGET_CONFIG = {
|
|
548
604
|
enabled: true,
|
|
549
605
|
pollSeconds: 300,
|
|
@@ -576,7 +632,7 @@ var init_config_service = __esm(() => {
|
|
|
576
632
|
});
|
|
577
633
|
|
|
578
634
|
// src/cli/pkg-root.ts
|
|
579
|
-
import { dirname, join as join3 } from "path";
|
|
635
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
580
636
|
import { existsSync as existsSync3 } from "fs";
|
|
581
637
|
import { execFileSync } from "child_process";
|
|
582
638
|
function findPackageRoot() {
|
|
@@ -585,7 +641,7 @@ function findPackageRoot() {
|
|
|
585
641
|
if (existsSync3(join3(dir, "package.json"))) {
|
|
586
642
|
return dir;
|
|
587
643
|
}
|
|
588
|
-
const parent =
|
|
644
|
+
const parent = dirname2(dir);
|
|
589
645
|
if (parent === dir) {
|
|
590
646
|
throw new Error("Could not find package.json in any parent directory");
|
|
591
647
|
}
|
|
@@ -760,18 +816,24 @@ __export(exports_init, {
|
|
|
760
816
|
writeCollaborationSections: () => writeCollaborationSections,
|
|
761
817
|
runInit: () => runInit,
|
|
762
818
|
pluginInstallFallbackGuidance: () => pluginInstallFallbackGuidance,
|
|
819
|
+
formatDepChecks: () => formatDepChecks,
|
|
763
820
|
compareVersions: () => compareVersions
|
|
764
821
|
});
|
|
765
822
|
import { execSync, execFileSync as execFileSync2 } from "child_process";
|
|
766
|
-
import { readFileSync as readFileSync3, writeFileSync as
|
|
823
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
767
824
|
import { join as join5 } from "path";
|
|
768
825
|
async function runInit() {
|
|
769
826
|
console.log(`AgentBridge Init
|
|
770
827
|
`);
|
|
828
|
+
const cli = cliInvocationName();
|
|
771
829
|
console.log("Checking dependencies...");
|
|
772
|
-
checkBun();
|
|
773
|
-
|
|
774
|
-
|
|
830
|
+
const depChecks = [checkBun(), checkClaude(), checkCodex()];
|
|
831
|
+
for (const line of formatDepChecks(depChecks, cli)) {
|
|
832
|
+
console.log(line);
|
|
833
|
+
}
|
|
834
|
+
if (depChecks.some((check) => check.status === "fail")) {
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
775
837
|
console.log("");
|
|
776
838
|
console.log("Generating project config...");
|
|
777
839
|
const configService = new ConfigService;
|
|
@@ -803,7 +865,7 @@ async function runInit() {
|
|
|
803
865
|
pluginInstalled = true;
|
|
804
866
|
} catch {
|
|
805
867
|
console.log(" Plugin install skipped (marketplace registration or install failed).");
|
|
806
|
-
for (const line of pluginInstallFallbackGuidance(detectRepoCheckout())) {
|
|
868
|
+
for (const line of pluginInstallFallbackGuidance(detectRepoCheckout(), cli)) {
|
|
807
869
|
console.log(line);
|
|
808
870
|
}
|
|
809
871
|
}
|
|
@@ -818,8 +880,8 @@ async function runInit() {
|
|
|
818
880
|
}
|
|
819
881
|
console.log("Next steps:");
|
|
820
882
|
console.log(" 1. If Claude Code is already running, execute /reload-plugins in your session");
|
|
821
|
-
console.log(
|
|
822
|
-
console.log(
|
|
883
|
+
console.log(` 2. Start Claude Code: ${cli} claude`);
|
|
884
|
+
console.log(` 3. Start Codex TUI: ${cli} codex`);
|
|
823
885
|
}
|
|
824
886
|
function detectRepoCheckout() {
|
|
825
887
|
try {
|
|
@@ -828,11 +890,11 @@ function detectRepoCheckout() {
|
|
|
828
890
|
return false;
|
|
829
891
|
}
|
|
830
892
|
}
|
|
831
|
-
function pluginInstallFallbackGuidance(insideRepo) {
|
|
893
|
+
function pluginInstallFallbackGuidance(insideRepo, cli = cliInvocationName()) {
|
|
832
894
|
if (insideRepo) {
|
|
833
895
|
return [
|
|
834
896
|
" You can install it later with:",
|
|
835
|
-
|
|
897
|
+
` ${cli} dev # registers marketplace and installs plugin`
|
|
836
898
|
];
|
|
837
899
|
}
|
|
838
900
|
return [
|
|
@@ -840,46 +902,68 @@ function pluginInstallFallbackGuidance(insideRepo) {
|
|
|
840
902
|
...MARKETPLACE_STEPS.map((step) => ` ${step}`)
|
|
841
903
|
];
|
|
842
904
|
}
|
|
905
|
+
function formatDepChecks(checks, cli) {
|
|
906
|
+
const lines = [];
|
|
907
|
+
for (const check of checks) {
|
|
908
|
+
lines.push(` ${check.status.toUpperCase().padEnd(4)} ${check.name}: ${check.detail}`);
|
|
909
|
+
if ((check.status === "warn" || check.status === "fail") && check.hint) {
|
|
910
|
+
lines.push(` \u21B3 ${check.hint}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
lines.push(` \u9A8C\u8BC1\u5B89\u88C5: ${cli} doctor`);
|
|
914
|
+
return lines;
|
|
915
|
+
}
|
|
843
916
|
function checkBun() {
|
|
844
917
|
try {
|
|
845
918
|
const version = execSync("bun --version", { encoding: "utf-8" }).trim();
|
|
846
|
-
|
|
919
|
+
return { name: "bun", status: "ok", detail: version };
|
|
847
920
|
} catch {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
921
|
+
return {
|
|
922
|
+
name: "bun",
|
|
923
|
+
status: "fail",
|
|
924
|
+
detail: "not found in PATH",
|
|
925
|
+
hint: "Install Bun: https://bun.sh"
|
|
926
|
+
};
|
|
851
927
|
}
|
|
852
928
|
}
|
|
853
929
|
function checkClaude() {
|
|
930
|
+
let versionOutput;
|
|
854
931
|
try {
|
|
855
|
-
|
|
856
|
-
const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
857
|
-
if (match) {
|
|
858
|
-
const version = match[1];
|
|
859
|
-
console.log(` claude: ${version}`);
|
|
860
|
-
if (compareVersions(version, MIN_CLAUDE_VERSION) < 0) {
|
|
861
|
-
console.error(` ERROR: Claude Code version ${version} is too old.`);
|
|
862
|
-
console.error(` Channels require >= ${MIN_CLAUDE_VERSION}.`);
|
|
863
|
-
console.error(" Update: npm update -g @anthropic-ai/claude-code");
|
|
864
|
-
process.exit(1);
|
|
865
|
-
}
|
|
866
|
-
} else {
|
|
867
|
-
console.log(` claude: ${versionOutput} (version check skipped)`);
|
|
868
|
-
}
|
|
932
|
+
versionOutput = execSync("claude --version", { encoding: "utf-8" }).trim();
|
|
869
933
|
} catch {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
934
|
+
return {
|
|
935
|
+
name: "claude",
|
|
936
|
+
status: "fail",
|
|
937
|
+
detail: "not found in PATH",
|
|
938
|
+
hint: "Install Claude Code: npm install -g @anthropic-ai/claude-code"
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const match = versionOutput.match(/(\d+\.\d+\.\d+)/);
|
|
942
|
+
if (!match) {
|
|
943
|
+
return { name: "claude", status: "ok", detail: `${versionOutput} (version check skipped)` };
|
|
944
|
+
}
|
|
945
|
+
const version = match[1];
|
|
946
|
+
if (compareVersions(version, MIN_CLAUDE_VERSION) < 0) {
|
|
947
|
+
return {
|
|
948
|
+
name: "claude",
|
|
949
|
+
status: "fail",
|
|
950
|
+
detail: `${version} is too old (channels require >= ${MIN_CLAUDE_VERSION})`,
|
|
951
|
+
hint: "Update: npm update -g @anthropic-ai/claude-code"
|
|
952
|
+
};
|
|
873
953
|
}
|
|
954
|
+
return { name: "claude", status: "ok", detail: version };
|
|
874
955
|
}
|
|
875
956
|
function checkCodex() {
|
|
876
957
|
try {
|
|
877
958
|
const version = execSync("codex --version", { encoding: "utf-8" }).trim();
|
|
878
|
-
|
|
959
|
+
return { name: "codex", status: "ok", detail: version };
|
|
879
960
|
} catch {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
961
|
+
return {
|
|
962
|
+
name: "codex",
|
|
963
|
+
status: "warn",
|
|
964
|
+
detail: "not found in PATH (the Codex side will be unavailable until installed)",
|
|
965
|
+
hint: "Install Codex when you want to pair: https://github.com/openai/codex"
|
|
966
|
+
};
|
|
883
967
|
}
|
|
884
968
|
}
|
|
885
969
|
function writeCollaborationSections(projectRoot) {
|
|
@@ -905,7 +989,7 @@ function writeCollaborationSections(projectRoot) {
|
|
|
905
989
|
results.push(`${name}: unchanged (section already up to date)`);
|
|
906
990
|
continue;
|
|
907
991
|
}
|
|
908
|
-
|
|
992
|
+
writeFileSync2(path, updated, "utf-8");
|
|
909
993
|
if (existing === "") {
|
|
910
994
|
results.push(`${name}: created with collaboration section`);
|
|
911
995
|
} else if (existing.includes(`<!-- ${MARKER_ID}:start -->`)) {
|
|
@@ -918,6 +1002,7 @@ function writeCollaborationSections(projectRoot) {
|
|
|
918
1002
|
}
|
|
919
1003
|
var MIN_CLAUDE_VERSION = "2.1.80";
|
|
920
1004
|
var init_init = __esm(() => {
|
|
1005
|
+
init_cli_invocation();
|
|
921
1006
|
init_config_service();
|
|
922
1007
|
init_cli();
|
|
923
1008
|
init_pkg_root();
|
|
@@ -1041,7 +1126,7 @@ var init_dev = __esm(() => {
|
|
|
1041
1126
|
});
|
|
1042
1127
|
|
|
1043
1128
|
// src/control-protocol.ts
|
|
1044
|
-
var CLOSE_CODE_REPLACED = 4001, CLOSE_CODE_EVICTED_STALE = 4002, CLOSE_CODE_PROBE_IN_PROGRESS = 4003, CLOSE_CODE_PAIR_MISMATCH = 4004;
|
|
1129
|
+
var CLOSE_CODE_REPLACED = 4001, CLOSE_CODE_EVICTED_STALE = 4002, CLOSE_CODE_PROBE_IN_PROGRESS = 4003, CLOSE_CODE_PAIR_MISMATCH = 4004, CLOSE_CODE_TOKEN_MISMATCH = 4005, CLOSE_CODE_CONTRACT_MISMATCH = 4006;
|
|
1045
1130
|
|
|
1046
1131
|
// src/interrupt-timing.ts
|
|
1047
1132
|
var CLIENT_REPLY_TIMEOUT_MS = 15000, INTERRUPT_CLIENT_MARGIN_MS = 2000, MAX_INTERRUPT_TIMEOUT_MS;
|
|
@@ -1049,6 +1134,76 @@ var init_interrupt_timing = __esm(() => {
|
|
|
1049
1134
|
MAX_INTERRUPT_TIMEOUT_MS = CLIENT_REPLY_TIMEOUT_MS - INTERRUPT_CLIENT_MARGIN_MS;
|
|
1050
1135
|
});
|
|
1051
1136
|
|
|
1137
|
+
// src/pending-request-registry.ts
|
|
1138
|
+
class PendingRequestRegistry {
|
|
1139
|
+
entries = new Map;
|
|
1140
|
+
setTimer;
|
|
1141
|
+
clearTimer;
|
|
1142
|
+
constructor(deps = {}) {
|
|
1143
|
+
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1144
|
+
this.clearTimer = deps.clearTimer ?? ((handle) => clearTimeout(handle));
|
|
1145
|
+
}
|
|
1146
|
+
get size() {
|
|
1147
|
+
return this.entries.size;
|
|
1148
|
+
}
|
|
1149
|
+
has(id) {
|
|
1150
|
+
return this.entries.has(id);
|
|
1151
|
+
}
|
|
1152
|
+
register(id, options) {
|
|
1153
|
+
const existing = this.entries.get(id);
|
|
1154
|
+
if (existing) {
|
|
1155
|
+
this.clearTimer(existing.timer);
|
|
1156
|
+
this.entries.delete(id);
|
|
1157
|
+
}
|
|
1158
|
+
return new Promise((resolve3, reject) => {
|
|
1159
|
+
const timer = this.setTimer(() => {
|
|
1160
|
+
if (!this.entries.has(id))
|
|
1161
|
+
return;
|
|
1162
|
+
this.entries.delete(id);
|
|
1163
|
+
options.onTimeout({ resolve: resolve3, reject });
|
|
1164
|
+
}, options.timeoutMs);
|
|
1165
|
+
if (options.unref) {
|
|
1166
|
+
timer.unref?.();
|
|
1167
|
+
}
|
|
1168
|
+
this.entries.set(id, { resolve: resolve3, reject, timer });
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
settle(id, value) {
|
|
1172
|
+
const entry = this.entries.get(id);
|
|
1173
|
+
if (!entry)
|
|
1174
|
+
return false;
|
|
1175
|
+
this.clearTimer(entry.timer);
|
|
1176
|
+
this.entries.delete(id);
|
|
1177
|
+
entry.resolve(value);
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
1180
|
+
reject(id, error) {
|
|
1181
|
+
const entry = this.entries.get(id);
|
|
1182
|
+
if (!entry)
|
|
1183
|
+
return false;
|
|
1184
|
+
this.clearTimer(entry.timer);
|
|
1185
|
+
this.entries.delete(id);
|
|
1186
|
+
entry.reject(error);
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1189
|
+
settleAll(value) {
|
|
1190
|
+
const make = typeof value === "function" ? value : () => value;
|
|
1191
|
+
for (const [id, entry] of this.entries) {
|
|
1192
|
+
this.clearTimer(entry.timer);
|
|
1193
|
+
this.entries.delete(id);
|
|
1194
|
+
entry.resolve(make(id));
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
rejectAll(error) {
|
|
1198
|
+
const make = typeof error === "function" ? error : () => error;
|
|
1199
|
+
for (const [id, entry] of this.entries) {
|
|
1200
|
+
this.clearTimer(entry.timer);
|
|
1201
|
+
this.entries.delete(id);
|
|
1202
|
+
entry.reject(make(id));
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1052
1207
|
// src/daemon-client.ts
|
|
1053
1208
|
import { EventEmitter } from "events";
|
|
1054
1209
|
var nextSocketId = 0, DaemonClient;
|
|
@@ -1060,7 +1215,8 @@ var init_daemon_client = __esm(() => {
|
|
|
1060
1215
|
ws = null;
|
|
1061
1216
|
wsId = 0;
|
|
1062
1217
|
nextRequestId = 1;
|
|
1063
|
-
pendingReplies = new
|
|
1218
|
+
pendingReplies = new PendingRequestRegistry;
|
|
1219
|
+
pendingEventWaiters = new PendingRequestRegistry;
|
|
1064
1220
|
constructor(url, options = {}) {
|
|
1065
1221
|
super();
|
|
1066
1222
|
this.url = url;
|
|
@@ -1106,82 +1262,73 @@ var init_daemon_client = __esm(() => {
|
|
|
1106
1262
|
});
|
|
1107
1263
|
}
|
|
1108
1264
|
attachClaude() {
|
|
1265
|
+
const identity = this.resolveIdentity();
|
|
1109
1266
|
this.send({
|
|
1110
1267
|
type: "claude_connect",
|
|
1111
|
-
...
|
|
1268
|
+
...identity ? { identity } : {}
|
|
1112
1269
|
});
|
|
1113
1270
|
}
|
|
1271
|
+
resolveIdentity() {
|
|
1272
|
+
const opt = this.options.identity;
|
|
1273
|
+
return typeof opt === "function" ? opt() : opt;
|
|
1274
|
+
}
|
|
1114
1275
|
async attachClaudeAndWaitForStatus(timeoutMs = 1000) {
|
|
1115
1276
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1116
1277
|
return null;
|
|
1117
1278
|
}
|
|
1118
|
-
return
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (timer) {
|
|
1126
|
-
clearTimeout(timer);
|
|
1127
|
-
timer = null;
|
|
1128
|
-
}
|
|
1129
|
-
this.off("status", onStatus);
|
|
1130
|
-
this.off("rejected", onRejected);
|
|
1131
|
-
this.off("disconnect", onDisconnect);
|
|
1132
|
-
};
|
|
1133
|
-
const finish = (value) => {
|
|
1134
|
-
cleanup();
|
|
1135
|
-
resolve3(value);
|
|
1136
|
-
};
|
|
1137
|
-
const onStatus = (status) => finish(status);
|
|
1138
|
-
const onRejected = () => finish(null);
|
|
1139
|
-
const onDisconnect = () => finish(null);
|
|
1140
|
-
this.on("status", onStatus);
|
|
1141
|
-
this.on("rejected", onRejected);
|
|
1142
|
-
this.on("disconnect", onDisconnect);
|
|
1143
|
-
timer = setTimeout(() => {
|
|
1144
|
-
finish(null);
|
|
1145
|
-
}, timeoutMs);
|
|
1146
|
-
try {
|
|
1147
|
-
this.attachClaude();
|
|
1148
|
-
} catch {
|
|
1149
|
-
finish(null);
|
|
1150
|
-
}
|
|
1279
|
+
return this.awaitTypedResponse({
|
|
1280
|
+
key: "status",
|
|
1281
|
+
successEvent: "status",
|
|
1282
|
+
successValue: (status) => status,
|
|
1283
|
+
failValue: null,
|
|
1284
|
+
timeoutMs,
|
|
1285
|
+
send: () => this.attachClaude()
|
|
1151
1286
|
});
|
|
1152
1287
|
}
|
|
1153
1288
|
async probeIncumbent(timeoutMs = 3000) {
|
|
1154
1289
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1155
1290
|
return { connected: false, alive: false };
|
|
1156
1291
|
}
|
|
1157
|
-
return
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
if (timer)
|
|
1165
|
-
clearTimeout(timer);
|
|
1166
|
-
this.off("incumbentStatus", onStatus);
|
|
1167
|
-
this.off("disconnect", onDisconnect);
|
|
1168
|
-
this.off("rejected", onRejected);
|
|
1169
|
-
resolve3(value);
|
|
1170
|
-
};
|
|
1171
|
-
const onStatus = (s) => finish(s);
|
|
1172
|
-
const onDisconnect = () => finish({ connected: false, alive: false });
|
|
1173
|
-
const onRejected = () => finish({ connected: false, alive: false });
|
|
1174
|
-
this.on("incumbentStatus", onStatus);
|
|
1175
|
-
this.on("disconnect", onDisconnect);
|
|
1176
|
-
this.on("rejected", onRejected);
|
|
1177
|
-
timer = setTimeout(() => finish({ connected: false, alive: false }), timeoutMs);
|
|
1178
|
-
try {
|
|
1179
|
-
this.send({ type: "probe_incumbent" });
|
|
1180
|
-
} catch {
|
|
1181
|
-
finish({ connected: false, alive: false });
|
|
1182
|
-
}
|
|
1292
|
+
return this.awaitTypedResponse({
|
|
1293
|
+
key: "incumbent_status",
|
|
1294
|
+
successEvent: "incumbentStatus",
|
|
1295
|
+
successValue: (s) => s,
|
|
1296
|
+
failValue: { connected: false, alive: false },
|
|
1297
|
+
timeoutMs,
|
|
1298
|
+
send: () => this.send({ type: "probe_incumbent" })
|
|
1183
1299
|
});
|
|
1184
1300
|
}
|
|
1301
|
+
awaitTypedResponse(opts) {
|
|
1302
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
1303
|
+
const onSuccess = (payload) => {
|
|
1304
|
+
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
1305
|
+
};
|
|
1306
|
+
const onRejected = () => {
|
|
1307
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
1308
|
+
};
|
|
1309
|
+
const onDisconnect = () => {
|
|
1310
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
1311
|
+
};
|
|
1312
|
+
const pending = this.pendingEventWaiters.register(key, {
|
|
1313
|
+
timeoutMs,
|
|
1314
|
+
onTimeout: ({ resolve: resolve3 }) => resolve3(failValue)
|
|
1315
|
+
});
|
|
1316
|
+
const cleanup = () => {
|
|
1317
|
+
this.off(successEvent, onSuccess);
|
|
1318
|
+
this.off("rejected", onRejected);
|
|
1319
|
+
this.off("disconnect", onDisconnect);
|
|
1320
|
+
};
|
|
1321
|
+
pending.finally(cleanup);
|
|
1322
|
+
this.on(successEvent, onSuccess);
|
|
1323
|
+
this.on("rejected", onRejected);
|
|
1324
|
+
this.on("disconnect", onDisconnect);
|
|
1325
|
+
try {
|
|
1326
|
+
send();
|
|
1327
|
+
} catch {
|
|
1328
|
+
this.pendingEventWaiters.settle(key, failValue);
|
|
1329
|
+
}
|
|
1330
|
+
return pending;
|
|
1331
|
+
}
|
|
1185
1332
|
async disconnect() {
|
|
1186
1333
|
if (!this.ws)
|
|
1187
1334
|
return;
|
|
@@ -1199,21 +1346,19 @@ var init_daemon_client = __esm(() => {
|
|
|
1199
1346
|
return { success: false, error: "AgentBridge daemon is not connected." };
|
|
1200
1347
|
}
|
|
1201
1348
|
const requestId = `reply_${Date.now()}_${this.nextRequestId++}`;
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
resolve3({ success: false, error: "Timed out waiting for AgentBridge daemon reply." });
|
|
1206
|
-
}, CLIENT_REPLY_TIMEOUT_MS);
|
|
1207
|
-
this.pendingReplies.set(requestId, { resolve: resolve3, timer });
|
|
1208
|
-
this.send({
|
|
1209
|
-
type: "claude_to_codex",
|
|
1210
|
-
requestId,
|
|
1211
|
-
message,
|
|
1212
|
-
...requireReply ? { requireReply: true } : {},
|
|
1213
|
-
...onBusy && onBusy !== "reject" ? { onBusy } : {},
|
|
1214
|
-
...idempotencyKey ? { idempotencyKey } : {}
|
|
1215
|
-
});
|
|
1349
|
+
const pending = this.pendingReplies.register(requestId, {
|
|
1350
|
+
timeoutMs: CLIENT_REPLY_TIMEOUT_MS,
|
|
1351
|
+
onTimeout: ({ resolve: resolve3 }) => resolve3({ success: false, error: "Timed out waiting for AgentBridge daemon reply." })
|
|
1216
1352
|
});
|
|
1353
|
+
this.send({
|
|
1354
|
+
type: "claude_to_codex",
|
|
1355
|
+
requestId,
|
|
1356
|
+
message,
|
|
1357
|
+
...requireReply ? { requireReply: true } : {},
|
|
1358
|
+
...onBusy && onBusy !== "reject" ? { onBusy } : {},
|
|
1359
|
+
...idempotencyKey ? { idempotencyKey } : {}
|
|
1360
|
+
});
|
|
1361
|
+
return pending;
|
|
1217
1362
|
}
|
|
1218
1363
|
attachSocketHandlers(ws, socketId) {
|
|
1219
1364
|
ws.onmessage = (event) => {
|
|
@@ -1229,12 +1374,7 @@ var init_daemon_client = __esm(() => {
|
|
|
1229
1374
|
this.emit("codexMessage", message.message);
|
|
1230
1375
|
return;
|
|
1231
1376
|
case "claude_to_codex_result": {
|
|
1232
|
-
|
|
1233
|
-
if (!pending)
|
|
1234
|
-
return;
|
|
1235
|
-
clearTimeout(pending.timer);
|
|
1236
|
-
this.pendingReplies.delete(message.requestId);
|
|
1237
|
-
pending.resolve({
|
|
1377
|
+
this.pendingReplies.settle(message.requestId, {
|
|
1238
1378
|
success: message.success,
|
|
1239
1379
|
error: message.error,
|
|
1240
1380
|
...message.code !== undefined ? { code: message.code } : {},
|
|
@@ -1265,7 +1405,7 @@ var init_daemon_client = __esm(() => {
|
|
|
1265
1405
|
if (isCurrent) {
|
|
1266
1406
|
this.ws = null;
|
|
1267
1407
|
this.rejectPendingReplies("AgentBridge daemon disconnected.");
|
|
1268
|
-
if (event.code === CLOSE_CODE_REPLACED || event.code === CLOSE_CODE_EVICTED_STALE || event.code === CLOSE_CODE_PROBE_IN_PROGRESS || event.code === CLOSE_CODE_PAIR_MISMATCH) {
|
|
1408
|
+
if (event.code === CLOSE_CODE_REPLACED || event.code === CLOSE_CODE_EVICTED_STALE || event.code === CLOSE_CODE_PROBE_IN_PROGRESS || event.code === CLOSE_CODE_PAIR_MISMATCH || event.code === CLOSE_CODE_TOKEN_MISMATCH || event.code === CLOSE_CODE_CONTRACT_MISMATCH) {
|
|
1269
1409
|
this.emit("rejected", event.code);
|
|
1270
1410
|
} else {
|
|
1271
1411
|
this.emit("disconnect");
|
|
@@ -1275,11 +1415,7 @@ var init_daemon_client = __esm(() => {
|
|
|
1275
1415
|
ws.onerror = () => {};
|
|
1276
1416
|
}
|
|
1277
1417
|
rejectPendingReplies(error) {
|
|
1278
|
-
|
|
1279
|
-
clearTimeout(pending.timer);
|
|
1280
|
-
pending.resolve({ success: false, error });
|
|
1281
|
-
this.pendingReplies.delete(requestId);
|
|
1282
|
-
}
|
|
1418
|
+
this.pendingReplies.settleAll(() => ({ success: false, error }));
|
|
1283
1419
|
}
|
|
1284
1420
|
send(message) {
|
|
1285
1421
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -1298,6 +1434,10 @@ var init_daemon_client = __esm(() => {
|
|
|
1298
1434
|
var CONTRACT_VERSION = 1;
|
|
1299
1435
|
|
|
1300
1436
|
// src/build-info.ts
|
|
1437
|
+
function hasValidCodeHash(build) {
|
|
1438
|
+
const hash = build?.codeHash;
|
|
1439
|
+
return typeof hash === "string" && hash.length > 0 && hash !== CODE_HASH_SENTINEL;
|
|
1440
|
+
}
|
|
1301
1441
|
function defineString(value, fallback) {
|
|
1302
1442
|
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
1303
1443
|
}
|
|
@@ -1312,7 +1452,14 @@ function defineNumber(value, fallback) {
|
|
|
1312
1452
|
function sameRuntimeContract(a, b) {
|
|
1313
1453
|
if (!a || !b)
|
|
1314
1454
|
return false;
|
|
1315
|
-
|
|
1455
|
+
if (a.version !== b.version || a.contractVersion !== b.contractVersion)
|
|
1456
|
+
return false;
|
|
1457
|
+
if (hasValidCodeHash(a) && hasValidCodeHash(b))
|
|
1458
|
+
return a.codeHash === b.codeHash;
|
|
1459
|
+
return a.commit === b.commit;
|
|
1460
|
+
}
|
|
1461
|
+
function runtimeContractComparisonBasis(a, b) {
|
|
1462
|
+
return hasValidCodeHash(a) && hasValidCodeHash(b) ? "codeHash" : "commit";
|
|
1316
1463
|
}
|
|
1317
1464
|
function compatibleContractVersion(a, b) {
|
|
1318
1465
|
if (!a || !b)
|
|
@@ -1322,21 +1469,23 @@ function compatibleContractVersion(a, b) {
|
|
|
1322
1469
|
function formatBuildInfo(build) {
|
|
1323
1470
|
if (!build)
|
|
1324
1471
|
return "<unknown>";
|
|
1325
|
-
|
|
1472
|
+
const codeHash = hasValidCodeHash(build) ? `/code-${build.codeHash}` : "";
|
|
1473
|
+
return `${build.version}/${build.commit}/${build.bundle}/contract-v${build.contractVersion}${codeHash}`;
|
|
1326
1474
|
}
|
|
1327
|
-
var BUILD_INFO;
|
|
1475
|
+
var CODE_HASH_SENTINEL = "source", BUILD_INFO;
|
|
1328
1476
|
var init_build_info = __esm(() => {
|
|
1329
1477
|
BUILD_INFO = Object.freeze({
|
|
1330
|
-
version: defineString("0.1.
|
|
1331
|
-
commit: defineString("
|
|
1478
|
+
version: defineString("0.1.13", "0.0.0-source"),
|
|
1479
|
+
commit: defineString("7a71869", "source"),
|
|
1332
1480
|
bundle: defineBundle("dist"),
|
|
1333
|
-
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
1481
|
+
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
1482
|
+
codeHash: defineString("e1fd67d07c62", "source")
|
|
1334
1483
|
});
|
|
1335
1484
|
});
|
|
1336
1485
|
|
|
1337
1486
|
// src/process-lifecycle.ts
|
|
1338
1487
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
1339
|
-
import { basename } from "path";
|
|
1488
|
+
import { basename as basename2 } from "path";
|
|
1340
1489
|
function parsePsProcessList(output) {
|
|
1341
1490
|
const entries = [];
|
|
1342
1491
|
for (const line of output.split(/\r?\n/)) {
|
|
@@ -1352,11 +1501,11 @@ function parsePsProcessList(output) {
|
|
|
1352
1501
|
}
|
|
1353
1502
|
function invokesCodexBinary(command) {
|
|
1354
1503
|
const tokens = command.trim().split(/\s+/);
|
|
1355
|
-
const exe = tokens[0] ?
|
|
1504
|
+
const exe = tokens[0] ? basename2(tokens[0]) : "";
|
|
1356
1505
|
if (exe === "codex")
|
|
1357
1506
|
return true;
|
|
1358
1507
|
if ((exe === "node" || exe === "bun") && tokens[1]) {
|
|
1359
|
-
return
|
|
1508
|
+
return basename2(tokens[1]) === "codex";
|
|
1360
1509
|
}
|
|
1361
1510
|
return false;
|
|
1362
1511
|
}
|
|
@@ -1492,9 +1641,142 @@ var init_process_lifecycle = __esm(() => {
|
|
|
1492
1641
|
isProcessAlive = pidLooksAlive;
|
|
1493
1642
|
});
|
|
1494
1643
|
|
|
1644
|
+
// src/daemon-record.ts
|
|
1645
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1646
|
+
function writeDaemonRecord(path, record) {
|
|
1647
|
+
atomicWriteJson(path, record);
|
|
1648
|
+
}
|
|
1649
|
+
function sanitizePorts(value) {
|
|
1650
|
+
if (typeof value !== "object" || value === null)
|
|
1651
|
+
return;
|
|
1652
|
+
const raw = value;
|
|
1653
|
+
const ports = {};
|
|
1654
|
+
if (typeof raw.appPort === "number")
|
|
1655
|
+
ports.appPort = raw.appPort;
|
|
1656
|
+
if (typeof raw.proxyPort === "number")
|
|
1657
|
+
ports.proxyPort = raw.proxyPort;
|
|
1658
|
+
if (typeof raw.controlPort === "number")
|
|
1659
|
+
ports.controlPort = raw.controlPort;
|
|
1660
|
+
return Object.keys(ports).length > 0 ? ports : undefined;
|
|
1661
|
+
}
|
|
1662
|
+
function readDaemonRecord(path, read = defaultRead) {
|
|
1663
|
+
let parsed;
|
|
1664
|
+
try {
|
|
1665
|
+
parsed = JSON.parse(read(path));
|
|
1666
|
+
} catch {
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
1670
|
+
return null;
|
|
1671
|
+
const obj = parsed;
|
|
1672
|
+
if (typeof obj.pid !== "number" || !Number.isFinite(obj.pid))
|
|
1673
|
+
return null;
|
|
1674
|
+
const phase = obj.phase === "ready" ? "ready" : "booting";
|
|
1675
|
+
const record = { pid: obj.pid, phase };
|
|
1676
|
+
if (typeof obj.startedAt === "number")
|
|
1677
|
+
record.startedAt = obj.startedAt;
|
|
1678
|
+
if (typeof obj.nonce === "string")
|
|
1679
|
+
record.nonce = obj.nonce;
|
|
1680
|
+
if (obj.pairId === null || typeof obj.pairId === "string")
|
|
1681
|
+
record.pairId = obj.pairId;
|
|
1682
|
+
if (obj.cwd === null || typeof obj.cwd === "string")
|
|
1683
|
+
record.cwd = obj.cwd;
|
|
1684
|
+
if (obj.stateDir === null || typeof obj.stateDir === "string")
|
|
1685
|
+
record.stateDir = obj.stateDir;
|
|
1686
|
+
if (typeof obj.proxyUrl === "string")
|
|
1687
|
+
record.proxyUrl = obj.proxyUrl;
|
|
1688
|
+
if (typeof obj.appServerUrl === "string")
|
|
1689
|
+
record.appServerUrl = obj.appServerUrl;
|
|
1690
|
+
const ports = sanitizePorts(obj.ports);
|
|
1691
|
+
if (ports !== undefined)
|
|
1692
|
+
record.ports = ports;
|
|
1693
|
+
if (typeof obj.build === "object" && obj.build !== null) {
|
|
1694
|
+
record.build = obj.build;
|
|
1695
|
+
}
|
|
1696
|
+
if (typeof obj.turnPhase === "string")
|
|
1697
|
+
record.turnPhase = obj.turnPhase;
|
|
1698
|
+
if (typeof obj.turnInProgress === "boolean")
|
|
1699
|
+
record.turnInProgress = obj.turnInProgress;
|
|
1700
|
+
if (typeof obj.attentionWindowActive === "boolean") {
|
|
1701
|
+
record.attentionWindowActive = obj.attentionWindowActive;
|
|
1702
|
+
}
|
|
1703
|
+
return record;
|
|
1704
|
+
}
|
|
1705
|
+
function synthesizeLegacyRecord(pidFilePath, statusFilePath, read = defaultRead) {
|
|
1706
|
+
let pidFromPidFile = null;
|
|
1707
|
+
try {
|
|
1708
|
+
const raw = read(pidFilePath).trim();
|
|
1709
|
+
const n = Number.parseInt(raw, 10);
|
|
1710
|
+
if (Number.isFinite(n))
|
|
1711
|
+
pidFromPidFile = n;
|
|
1712
|
+
} catch {}
|
|
1713
|
+
let status = null;
|
|
1714
|
+
try {
|
|
1715
|
+
const parsed = JSON.parse(read(statusFilePath));
|
|
1716
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
1717
|
+
status = parsed;
|
|
1718
|
+
} catch {}
|
|
1719
|
+
const pidFromStatus = status && typeof status.pid === "number" && Number.isFinite(status.pid) ? status.pid : null;
|
|
1720
|
+
const pid = pidFromPidFile ?? pidFromStatus;
|
|
1721
|
+
if (pid === null)
|
|
1722
|
+
return null;
|
|
1723
|
+
const record = {
|
|
1724
|
+
pid,
|
|
1725
|
+
phase: status ? "ready" : "booting"
|
|
1726
|
+
};
|
|
1727
|
+
if (status) {
|
|
1728
|
+
if (typeof status.proxyUrl === "string")
|
|
1729
|
+
record.proxyUrl = status.proxyUrl;
|
|
1730
|
+
if (typeof status.appServerUrl === "string")
|
|
1731
|
+
record.appServerUrl = status.appServerUrl;
|
|
1732
|
+
const controlPort = typeof status.controlPort === "number" ? status.controlPort : undefined;
|
|
1733
|
+
const proxyPort = portFromUrl(status.proxyUrl);
|
|
1734
|
+
const appPort = portFromUrl(status.appServerUrl);
|
|
1735
|
+
if (controlPort !== undefined || proxyPort !== undefined || appPort !== undefined) {
|
|
1736
|
+
record.ports = {};
|
|
1737
|
+
if (appPort !== undefined)
|
|
1738
|
+
record.ports.appPort = appPort;
|
|
1739
|
+
if (proxyPort !== undefined)
|
|
1740
|
+
record.ports.proxyPort = proxyPort;
|
|
1741
|
+
if (controlPort !== undefined)
|
|
1742
|
+
record.ports.controlPort = controlPort;
|
|
1743
|
+
}
|
|
1744
|
+
if (status.pairId === null || typeof status.pairId === "string")
|
|
1745
|
+
record.pairId = status.pairId;
|
|
1746
|
+
if (status.cwd === null || typeof status.cwd === "string")
|
|
1747
|
+
record.cwd = status.cwd;
|
|
1748
|
+
if (status.stateDir === null || typeof status.stateDir === "string")
|
|
1749
|
+
record.stateDir = status.stateDir;
|
|
1750
|
+
if (typeof status.build === "object" && status.build !== null) {
|
|
1751
|
+
record.build = status.build;
|
|
1752
|
+
}
|
|
1753
|
+
if (typeof status.turnPhase === "string")
|
|
1754
|
+
record.turnPhase = status.turnPhase;
|
|
1755
|
+
if (typeof status.turnInProgress === "boolean")
|
|
1756
|
+
record.turnInProgress = status.turnInProgress;
|
|
1757
|
+
if (typeof status.attentionWindowActive === "boolean") {
|
|
1758
|
+
record.attentionWindowActive = status.attentionWindowActive;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return record;
|
|
1762
|
+
}
|
|
1763
|
+
function readUnifiedDaemonRecord(paths, read = defaultRead) {
|
|
1764
|
+
return readDaemonRecord(paths.daemonRecordFile, read) ?? synthesizeLegacyRecord(paths.pidFile, paths.statusFile, read);
|
|
1765
|
+
}
|
|
1766
|
+
function portFromUrl(url) {
|
|
1767
|
+
if (typeof url !== "string")
|
|
1768
|
+
return;
|
|
1769
|
+
const match = url.match(/:(\d+)(?:[/?]|$)/);
|
|
1770
|
+
return match ? Number.parseInt(match[1], 10) : undefined;
|
|
1771
|
+
}
|
|
1772
|
+
var defaultRead = (path) => readFileSync4(path, "utf-8");
|
|
1773
|
+
var init_daemon_record = __esm(() => {
|
|
1774
|
+
init_atomic_json();
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1495
1777
|
// src/daemon-lifecycle.ts
|
|
1496
1778
|
import { spawn } from "child_process";
|
|
1497
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1779
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3, openSync as openSync2, closeSync as closeSync2, constants } from "fs";
|
|
1498
1780
|
import { fileURLToPath } from "url";
|
|
1499
1781
|
function isReuseVerdict(verdict) {
|
|
1500
1782
|
return verdict === "reuse" || verdict === "reuse-despite-drift";
|
|
@@ -1531,22 +1813,33 @@ function classifyDaemon(expectedPairId, status, buildInfo) {
|
|
|
1531
1813
|
reason: "runtime build drift has a compatible contract and a live Codex TUI is attached"
|
|
1532
1814
|
};
|
|
1533
1815
|
}
|
|
1816
|
+
const basis = runtimeContractComparisonBasis(status.build, buildInfo) === "codeHash" ? "compared by codeHash" : "compared by commit stamp; legacy build without codeHash";
|
|
1534
1817
|
return {
|
|
1535
1818
|
verdict: "replace-drifted",
|
|
1536
|
-
reason: `runtime build ${formatBuildInfo(status.build)} does not match launcher
|
|
1819
|
+
reason: `runtime build ${formatBuildInfo(status.build)} does not match launcher ` + `${formatBuildInfo(buildInfo)} (${basis})`
|
|
1537
1820
|
};
|
|
1538
1821
|
}
|
|
1539
1822
|
return { verdict: "reuse", reason: "daemon pair and runtime contract match" };
|
|
1540
1823
|
}
|
|
1824
|
+
function resolveTiming(timing) {
|
|
1825
|
+
return {
|
|
1826
|
+
reuseReadyRetries: timing?.reuseReadyRetries ?? REUSE_READY_RETRIES,
|
|
1827
|
+
reuseReadyDelayMs: timing?.reuseReadyDelayMs ?? REUSE_READY_DELAY_MS,
|
|
1828
|
+
waitReadyRetries: timing?.waitReadyRetries ?? WAIT_READY_RETRIES,
|
|
1829
|
+
waitReadyDelayMs: timing?.waitReadyDelayMs ?? WAIT_READY_DELAY_MS
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1541
1832
|
|
|
1542
1833
|
class DaemonLifecycle {
|
|
1543
1834
|
stateDir;
|
|
1544
1835
|
controlPort;
|
|
1545
1836
|
log;
|
|
1837
|
+
timing;
|
|
1546
1838
|
constructor(opts) {
|
|
1547
1839
|
this.stateDir = opts.stateDir;
|
|
1548
1840
|
this.controlPort = opts.controlPort;
|
|
1549
1841
|
this.log = opts.log;
|
|
1842
|
+
this.timing = resolveTiming(opts.timing);
|
|
1550
1843
|
}
|
|
1551
1844
|
get healthUrl() {
|
|
1552
1845
|
return `http://127.0.0.1:${this.controlPort}/healthz`;
|
|
@@ -1603,7 +1896,7 @@ class DaemonLifecycle {
|
|
|
1603
1896
|
break;
|
|
1604
1897
|
}
|
|
1605
1898
|
try {
|
|
1606
|
-
await this.waitForReady(
|
|
1899
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
1607
1900
|
return;
|
|
1608
1901
|
} catch {
|
|
1609
1902
|
this.log(`Daemon on control port ${this.controlPort} is healthy but not ready within reuse window \u2014 replacing`);
|
|
@@ -1616,7 +1909,7 @@ class DaemonLifecycle {
|
|
|
1616
1909
|
if (isProcessAlive(existingPid)) {
|
|
1617
1910
|
if (isAgentBridgeDaemon(existingPid)) {
|
|
1618
1911
|
try {
|
|
1619
|
-
await this.waitForReady(
|
|
1912
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
1620
1913
|
return;
|
|
1621
1914
|
} catch {
|
|
1622
1915
|
this.log(`Existing daemon process ${existingPid} never became ready \u2014 replacing`);
|
|
@@ -1644,7 +1937,7 @@ class DaemonLifecycle {
|
|
|
1644
1937
|
await this.kill(3000, status?.pid);
|
|
1645
1938
|
} else {
|
|
1646
1939
|
try {
|
|
1647
|
-
await this.waitForReady(
|
|
1940
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
1648
1941
|
return;
|
|
1649
1942
|
} catch {
|
|
1650
1943
|
this.log(`Daemon on control port ${this.controlPort} is healthy but not ready under startup lock \u2014 replacing`);
|
|
@@ -1653,7 +1946,7 @@ class DaemonLifecycle {
|
|
|
1653
1946
|
}
|
|
1654
1947
|
}
|
|
1655
1948
|
this.launch();
|
|
1656
|
-
await this.waitForReady();
|
|
1949
|
+
await this.waitForReady(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
1657
1950
|
});
|
|
1658
1951
|
}
|
|
1659
1952
|
async isHealthy() {
|
|
@@ -1680,7 +1973,7 @@ class DaemonLifecycle {
|
|
|
1680
1973
|
return false;
|
|
1681
1974
|
}
|
|
1682
1975
|
}
|
|
1683
|
-
async waitForReady(maxRetries =
|
|
1976
|
+
async waitForReady(maxRetries = WAIT_READY_RETRIES, delayMs = WAIT_READY_DELAY_MS) {
|
|
1684
1977
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1685
1978
|
if (await this.isReady())
|
|
1686
1979
|
return;
|
|
@@ -1688,7 +1981,7 @@ class DaemonLifecycle {
|
|
|
1688
1981
|
}
|
|
1689
1982
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness on ${this.readyUrl}`);
|
|
1690
1983
|
}
|
|
1691
|
-
async waitForReadyAndOurs(maxRetries =
|
|
1984
|
+
async waitForReadyAndOurs(maxRetries = WAIT_READY_RETRIES, delayMs = WAIT_READY_DELAY_MS) {
|
|
1692
1985
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1693
1986
|
if (await this.isReady()) {
|
|
1694
1987
|
const status = await this.fetchStatus();
|
|
@@ -1704,22 +1997,35 @@ class DaemonLifecycle {
|
|
|
1704
1997
|
}
|
|
1705
1998
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness+identity on ${this.readyUrl} (control port ${this.controlPort})`);
|
|
1706
1999
|
}
|
|
2000
|
+
readDaemonRecord() {
|
|
2001
|
+
return readUnifiedDaemonRecord({
|
|
2002
|
+
daemonRecordFile: this.stateDir.daemonRecordFile,
|
|
2003
|
+
pidFile: this.stateDir.pidFile,
|
|
2004
|
+
statusFile: this.stateDir.statusFile
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
writeDaemonRecord(record) {
|
|
2008
|
+
writeDaemonRecord(this.stateDir.daemonRecordFile, record);
|
|
2009
|
+
}
|
|
2010
|
+
removeDaemonRecord() {
|
|
2011
|
+
try {
|
|
2012
|
+
unlinkSync2(this.stateDir.daemonRecordFile);
|
|
2013
|
+
} catch {}
|
|
2014
|
+
}
|
|
1707
2015
|
readStatus() {
|
|
1708
2016
|
try {
|
|
1709
|
-
const raw =
|
|
2017
|
+
const raw = readFileSync5(this.stateDir.statusFile, "utf-8");
|
|
1710
2018
|
return JSON.parse(raw);
|
|
1711
2019
|
} catch {
|
|
1712
2020
|
return null;
|
|
1713
2021
|
}
|
|
1714
2022
|
}
|
|
1715
2023
|
writeStatus(status) {
|
|
1716
|
-
this.stateDir.
|
|
1717
|
-
writeFileSync4(this.stateDir.statusFile, JSON.stringify(status, null, 2) + `
|
|
1718
|
-
`, "utf-8");
|
|
2024
|
+
atomicWriteJson(this.stateDir.statusFile, status);
|
|
1719
2025
|
}
|
|
1720
2026
|
readPid() {
|
|
1721
2027
|
try {
|
|
1722
|
-
const raw =
|
|
2028
|
+
const raw = readFileSync5(this.stateDir.pidFile, "utf-8").trim();
|
|
1723
2029
|
if (!raw)
|
|
1724
2030
|
return null;
|
|
1725
2031
|
const pid = Number.parseInt(raw, 10);
|
|
@@ -1729,28 +2035,27 @@ class DaemonLifecycle {
|
|
|
1729
2035
|
}
|
|
1730
2036
|
}
|
|
1731
2037
|
writePid(pid) {
|
|
1732
|
-
this.stateDir.
|
|
1733
|
-
|
|
1734
|
-
`, "utf-8");
|
|
2038
|
+
atomicWriteText(this.stateDir.pidFile, `${pid ?? process.pid}
|
|
2039
|
+
`);
|
|
1735
2040
|
}
|
|
1736
2041
|
removePidFile() {
|
|
1737
2042
|
try {
|
|
1738
|
-
|
|
2043
|
+
unlinkSync2(this.stateDir.pidFile);
|
|
1739
2044
|
} catch {}
|
|
1740
2045
|
}
|
|
1741
2046
|
removeStatusFile() {
|
|
1742
2047
|
try {
|
|
1743
|
-
|
|
2048
|
+
unlinkSync2(this.stateDir.statusFile);
|
|
1744
2049
|
} catch {}
|
|
1745
2050
|
}
|
|
1746
2051
|
markKilled() {
|
|
1747
2052
|
this.stateDir.ensure();
|
|
1748
|
-
|
|
2053
|
+
writeFileSync3(this.stateDir.killedFile, `${Date.now()}
|
|
1749
2054
|
`, "utf-8");
|
|
1750
2055
|
}
|
|
1751
2056
|
clearKilled() {
|
|
1752
2057
|
try {
|
|
1753
|
-
|
|
2058
|
+
unlinkSync2(this.stateDir.killedFile);
|
|
1754
2059
|
} catch {}
|
|
1755
2060
|
}
|
|
1756
2061
|
wasKilled() {
|
|
@@ -1772,8 +2077,10 @@ class DaemonLifecycle {
|
|
|
1772
2077
|
daemonProc.unref();
|
|
1773
2078
|
}
|
|
1774
2079
|
removeStalePidFile() {
|
|
1775
|
-
this.log("Removing stale
|
|
2080
|
+
this.log("Removing stale daemon identity files");
|
|
1776
2081
|
this.removePidFile();
|
|
2082
|
+
this.removeStatusFile();
|
|
2083
|
+
this.removeDaemonRecord();
|
|
1777
2084
|
}
|
|
1778
2085
|
async replaceUnhealthyDaemon(statusPid) {
|
|
1779
2086
|
await this.withStartupLockStrict(async (locked) => {
|
|
@@ -1789,7 +2096,7 @@ class DaemonLifecycle {
|
|
|
1789
2096
|
}
|
|
1790
2097
|
if (isReuseVerdict(classification.verdict)) {
|
|
1791
2098
|
try {
|
|
1792
|
-
await this.waitForReady(
|
|
2099
|
+
await this.waitForReady(this.timing.reuseReadyRetries, this.timing.reuseReadyDelayMs);
|
|
1793
2100
|
return;
|
|
1794
2101
|
} catch {}
|
|
1795
2102
|
}
|
|
@@ -1797,12 +2104,12 @@ class DaemonLifecycle {
|
|
|
1797
2104
|
this.log(`Killing unhealthy daemon on control port ${this.controlPort} and relaunching`);
|
|
1798
2105
|
await this.kill(3000, statusPid);
|
|
1799
2106
|
this.launch();
|
|
1800
|
-
await this.waitForReady();
|
|
2107
|
+
await this.waitForReady(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
1801
2108
|
});
|
|
1802
2109
|
}
|
|
1803
2110
|
async waitForContendedStartupLock() {
|
|
1804
2111
|
this.log("Another process holds the startup lock, waiting for readiness+identity...");
|
|
1805
|
-
await this.waitForReadyAndOurs();
|
|
2112
|
+
await this.waitForReadyAndOurs(this.timing.waitReadyRetries, this.timing.waitReadyDelayMs);
|
|
1806
2113
|
}
|
|
1807
2114
|
async withStartupLockStrict(fn) {
|
|
1808
2115
|
const locked = this.acquireLockStrict();
|
|
@@ -1817,15 +2124,15 @@ class DaemonLifecycle {
|
|
|
1817
2124
|
this.stateDir.ensure();
|
|
1818
2125
|
let fd = null;
|
|
1819
2126
|
try {
|
|
1820
|
-
fd =
|
|
1821
|
-
|
|
2127
|
+
fd = openSync2(this.stateDir.lockFile, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY);
|
|
2128
|
+
writeFileSync3(fd, `${process.pid}
|
|
1822
2129
|
`);
|
|
1823
|
-
|
|
2130
|
+
closeSync2(fd);
|
|
1824
2131
|
return true;
|
|
1825
2132
|
} catch (err) {
|
|
1826
2133
|
if (fd !== null && err.code !== "EEXIST") {
|
|
1827
2134
|
try {
|
|
1828
|
-
|
|
2135
|
+
closeSync2(fd);
|
|
1829
2136
|
} catch {}
|
|
1830
2137
|
this.releaseLock();
|
|
1831
2138
|
}
|
|
@@ -1833,7 +2140,7 @@ class DaemonLifecycle {
|
|
|
1833
2140
|
if (reclaimed)
|
|
1834
2141
|
return false;
|
|
1835
2142
|
try {
|
|
1836
|
-
const holderPid = Number.parseInt(
|
|
2143
|
+
const holderPid = Number.parseInt(readFileSync5(this.stateDir.lockFile, "utf-8").trim(), 10);
|
|
1837
2144
|
if (Number.isFinite(holderPid) && !isProcessAlive(holderPid)) {
|
|
1838
2145
|
this.log(`Stale startup lock from dead process ${holderPid}, reclaiming`);
|
|
1839
2146
|
this.releaseLock();
|
|
@@ -1862,7 +2169,7 @@ class DaemonLifecycle {
|
|
|
1862
2169
|
}
|
|
1863
2170
|
releaseLock() {
|
|
1864
2171
|
try {
|
|
1865
|
-
|
|
2172
|
+
unlinkSync2(this.stateDir.lockFile);
|
|
1866
2173
|
} catch {}
|
|
1867
2174
|
}
|
|
1868
2175
|
async kill(gracefulTimeoutMs = 3000, pidOverride) {
|
|
@@ -1908,6 +2215,7 @@ class DaemonLifecycle {
|
|
|
1908
2215
|
cleanup() {
|
|
1909
2216
|
this.removePidFile();
|
|
1910
2217
|
this.removeStatusFile();
|
|
2218
|
+
this.removeDaemonRecord();
|
|
1911
2219
|
}
|
|
1912
2220
|
}
|
|
1913
2221
|
async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
@@ -1919,10 +2227,12 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
|
|
|
1919
2227
|
clearTimeout(timer);
|
|
1920
2228
|
}
|
|
1921
2229
|
}
|
|
1922
|
-
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;
|
|
2230
|
+
var DEFAULT_DAEMON_ENTRY, DAEMON_ENTRY, DAEMON_PATH, REUSE_READY_RETRIES, REUSE_READY_DELAY_MS = 250, WAIT_READY_RETRIES = 40, WAIT_READY_DELAY_MS = 250, HEALTH_FETCH_TIMEOUT_MS = 500, LOCK_IDENTITY_GRACE_MS;
|
|
1923
2231
|
var init_daemon_lifecycle = __esm(() => {
|
|
2232
|
+
init_atomic_json();
|
|
1924
2233
|
init_build_info();
|
|
1925
2234
|
init_process_lifecycle();
|
|
2235
|
+
init_daemon_record();
|
|
1926
2236
|
DEFAULT_DAEMON_ENTRY = import.meta.url.endsWith(".ts") ? "./daemon.ts" : "./daemon.js";
|
|
1927
2237
|
DAEMON_ENTRY = process.env.AGENTBRIDGE_DAEMON_ENTRY || DEFAULT_DAEMON_ENTRY;
|
|
1928
2238
|
DAEMON_PATH = fileURLToPath(new URL(DAEMON_ENTRY, import.meta.url));
|
|
@@ -1933,26 +2243,22 @@ var init_daemon_lifecycle = __esm(() => {
|
|
|
1933
2243
|
// src/pair-registry.ts
|
|
1934
2244
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
1935
2245
|
import {
|
|
1936
|
-
closeSync as closeSync2,
|
|
1937
2246
|
existsSync as existsSync7,
|
|
1938
|
-
fsyncSync,
|
|
1939
2247
|
linkSync,
|
|
1940
2248
|
lstatSync,
|
|
1941
|
-
mkdirSync as
|
|
1942
|
-
openSync as openSync2,
|
|
2249
|
+
mkdirSync as mkdirSync4,
|
|
1943
2250
|
readdirSync,
|
|
1944
|
-
readFileSync as
|
|
2251
|
+
readFileSync as readFileSync6,
|
|
1945
2252
|
realpathSync,
|
|
1946
|
-
renameSync,
|
|
1947
2253
|
rmSync as rmSync2,
|
|
1948
2254
|
statSync as statSync2,
|
|
1949
|
-
unlinkSync as
|
|
1950
|
-
writeFileSync as
|
|
2255
|
+
unlinkSync as unlinkSync3,
|
|
2256
|
+
writeFileSync as writeFileSync4
|
|
1951
2257
|
} from "fs";
|
|
1952
2258
|
import { createServer } from "net";
|
|
1953
|
-
import { createHash, randomUUID } from "crypto";
|
|
2259
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
1954
2260
|
import { hostname, userInfo } from "os";
|
|
1955
|
-
import { basename as
|
|
2261
|
+
import { basename as basename3, join as join6, resolve as resolve3, sep } from "path";
|
|
1956
2262
|
function portsForSlot(slot) {
|
|
1957
2263
|
if (!Number.isInteger(slot) || slot < 0) {
|
|
1958
2264
|
throw new PairError("PAIR_ID_INVALID", `Invalid slot: ${slot}`);
|
|
@@ -2001,7 +2307,7 @@ function readRegistry(base) {
|
|
|
2001
2307
|
return { version: 1, pairs: [] };
|
|
2002
2308
|
let parsed;
|
|
2003
2309
|
try {
|
|
2004
|
-
parsed = JSON.parse(
|
|
2310
|
+
parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
2005
2311
|
} catch (err) {
|
|
2006
2312
|
throw new PairError("PAIR_REGISTRY_CORRUPT", `Registry JSON is not parseable at ${path}: ${err.message}`, {
|
|
2007
2313
|
path
|
|
@@ -2032,26 +2338,14 @@ function readRegistry(base) {
|
|
|
2032
2338
|
return parsed;
|
|
2033
2339
|
}
|
|
2034
2340
|
function writeRegistry(base, reg) {
|
|
2035
|
-
|
|
2036
|
-
const target = registryPath(base);
|
|
2037
|
-
const tmp = `${target}.tmp.${process.pid}`;
|
|
2038
|
-
const data = JSON.stringify(reg, null, 2) + `
|
|
2039
|
-
`;
|
|
2040
|
-
const fd = openSync2(tmp, "w");
|
|
2041
|
-
try {
|
|
2042
|
-
writeFileSync5(fd, data);
|
|
2043
|
-
fsyncSync(fd);
|
|
2044
|
-
} finally {
|
|
2045
|
-
closeSync2(fd);
|
|
2046
|
-
}
|
|
2047
|
-
renameSync(tmp, target);
|
|
2341
|
+
atomicWriteJson(registryPath(base), reg, { fsync: true });
|
|
2048
2342
|
}
|
|
2049
2343
|
function lockFilePath(base) {
|
|
2050
2344
|
return join6(pairsDir(base), LOCK_FILE_NAME);
|
|
2051
2345
|
}
|
|
2052
2346
|
function readLockOwner(lockFile) {
|
|
2053
2347
|
try {
|
|
2054
|
-
const parsed = JSON.parse(
|
|
2348
|
+
const parsed = JSON.parse(readFileSync6(lockFile, "utf-8"));
|
|
2055
2349
|
if (typeof parsed.pid === "number" && typeof parsed.nonce === "string")
|
|
2056
2350
|
return parsed;
|
|
2057
2351
|
return null;
|
|
@@ -2091,7 +2385,7 @@ function lockIsStale(lockFile) {
|
|
|
2091
2385
|
}
|
|
2092
2386
|
function attemptReclaim(lockFile) {
|
|
2093
2387
|
const reclaimLock = `${lockFile}.reclaim`;
|
|
2094
|
-
const myNonce =
|
|
2388
|
+
const myNonce = randomUUID2();
|
|
2095
2389
|
const ownerJson = JSON.stringify({
|
|
2096
2390
|
pid: process.pid,
|
|
2097
2391
|
createdAt: Date.now(),
|
|
@@ -2099,10 +2393,10 @@ function attemptReclaim(lockFile) {
|
|
|
2099
2393
|
hostname: safeHostname(),
|
|
2100
2394
|
uid: safeUid()
|
|
2101
2395
|
});
|
|
2102
|
-
const tmp = `${reclaimLock}.acq.${process.pid}.${
|
|
2396
|
+
const tmp = `${reclaimLock}.acq.${process.pid}.${randomUUID2()}`;
|
|
2103
2397
|
let held = false;
|
|
2104
2398
|
try {
|
|
2105
|
-
|
|
2399
|
+
writeFileSync4(tmp, ownerJson);
|
|
2106
2400
|
try {
|
|
2107
2401
|
linkSync(tmp, reclaimLock);
|
|
2108
2402
|
held = true;
|
|
@@ -2110,7 +2404,7 @@ function attemptReclaim(lockFile) {
|
|
|
2110
2404
|
if (err?.code === "EEXIST") {
|
|
2111
2405
|
if (lockIsStale(reclaimLock)) {
|
|
2112
2406
|
try {
|
|
2113
|
-
|
|
2407
|
+
unlinkSync3(reclaimLock);
|
|
2114
2408
|
} catch {}
|
|
2115
2409
|
}
|
|
2116
2410
|
return;
|
|
@@ -2119,7 +2413,7 @@ function attemptReclaim(lockFile) {
|
|
|
2119
2413
|
}
|
|
2120
2414
|
} finally {
|
|
2121
2415
|
try {
|
|
2122
|
-
|
|
2416
|
+
unlinkSync3(tmp);
|
|
2123
2417
|
} catch {}
|
|
2124
2418
|
}
|
|
2125
2419
|
if (!held)
|
|
@@ -2129,22 +2423,22 @@ function attemptReclaim(lockFile) {
|
|
|
2129
2423
|
return;
|
|
2130
2424
|
if (lockIsStale(lockFile)) {
|
|
2131
2425
|
try {
|
|
2132
|
-
|
|
2426
|
+
unlinkSync3(lockFile);
|
|
2133
2427
|
} catch {}
|
|
2134
2428
|
}
|
|
2135
2429
|
} finally {
|
|
2136
2430
|
if (readLockOwner(reclaimLock)?.nonce === myNonce) {
|
|
2137
2431
|
try {
|
|
2138
|
-
|
|
2432
|
+
unlinkSync3(reclaimLock);
|
|
2139
2433
|
} catch {}
|
|
2140
2434
|
}
|
|
2141
2435
|
}
|
|
2142
2436
|
}
|
|
2143
2437
|
async function withRegistryLock(base, fn) {
|
|
2144
|
-
|
|
2438
|
+
mkdirSync4(pairsDir(base), { recursive: true });
|
|
2145
2439
|
const lockFile = lockFilePath(base);
|
|
2146
2440
|
const deadline = Date.now() + LOCK_DEADLINE_MS;
|
|
2147
|
-
const myNonce =
|
|
2441
|
+
const myNonce = randomUUID2();
|
|
2148
2442
|
const ownerJson = JSON.stringify({
|
|
2149
2443
|
pid: process.pid,
|
|
2150
2444
|
createdAt: Date.now(),
|
|
@@ -2153,10 +2447,10 @@ async function withRegistryLock(base, fn) {
|
|
|
2153
2447
|
uid: safeUid()
|
|
2154
2448
|
});
|
|
2155
2449
|
for (;; ) {
|
|
2156
|
-
const tmp = `${lockFile}.acq.${process.pid}.${
|
|
2450
|
+
const tmp = `${lockFile}.acq.${process.pid}.${randomUUID2()}`;
|
|
2157
2451
|
let acquired = false;
|
|
2158
2452
|
try {
|
|
2159
|
-
|
|
2453
|
+
writeFileSync4(tmp, ownerJson);
|
|
2160
2454
|
try {
|
|
2161
2455
|
linkSync(tmp, lockFile);
|
|
2162
2456
|
acquired = true;
|
|
@@ -2166,7 +2460,7 @@ async function withRegistryLock(base, fn) {
|
|
|
2166
2460
|
}
|
|
2167
2461
|
} finally {
|
|
2168
2462
|
try {
|
|
2169
|
-
|
|
2463
|
+
unlinkSync3(tmp);
|
|
2170
2464
|
} catch {}
|
|
2171
2465
|
}
|
|
2172
2466
|
if (acquired) {
|
|
@@ -2176,7 +2470,7 @@ async function withRegistryLock(base, fn) {
|
|
|
2176
2470
|
const current = readLockOwner(lockFile);
|
|
2177
2471
|
if (!current || current.nonce === myNonce) {
|
|
2178
2472
|
try {
|
|
2179
|
-
|
|
2473
|
+
unlinkSync3(lockFile);
|
|
2180
2474
|
} catch {}
|
|
2181
2475
|
}
|
|
2182
2476
|
}
|
|
@@ -2198,7 +2492,7 @@ function detectLegacyRootDaemon(base) {
|
|
|
2198
2492
|
return null;
|
|
2199
2493
|
let pid;
|
|
2200
2494
|
try {
|
|
2201
|
-
const raw =
|
|
2495
|
+
const raw = readFileSync6(rootPidFile, "utf-8").trim();
|
|
2202
2496
|
pid = Number.parseInt(raw, 10);
|
|
2203
2497
|
} catch {
|
|
2204
2498
|
return null;
|
|
@@ -2306,6 +2600,9 @@ async function removeAllocatedPairIfUnchanged(base, pairId, slot) {
|
|
|
2306
2600
|
writeRegistry(base, { version: 1, pairs: nextPairs });
|
|
2307
2601
|
});
|
|
2308
2602
|
}
|
|
2603
|
+
function pairsRootDir(base) {
|
|
2604
|
+
return pairsDir(base);
|
|
2605
|
+
}
|
|
2309
2606
|
function pairDirPath(base, pairId) {
|
|
2310
2607
|
const id = validatePairId(pairId);
|
|
2311
2608
|
return join6(pairsDir(base), id);
|
|
@@ -2348,12 +2645,17 @@ function pairDirDaemonAlive(base, pairId) {
|
|
|
2348
2645
|
const dir = join6(pairsDir(base), pairId);
|
|
2349
2646
|
const pids = [];
|
|
2350
2647
|
try {
|
|
2351
|
-
const
|
|
2648
|
+
const record = JSON.parse(readFileSync6(join6(dir, "daemon.json"), "utf-8"));
|
|
2649
|
+
if (typeof record?.pid === "number" && Number.isFinite(record.pid))
|
|
2650
|
+
pids.push(record.pid);
|
|
2651
|
+
} catch {}
|
|
2652
|
+
try {
|
|
2653
|
+
const pid = Number.parseInt(readFileSync6(join6(dir, "daemon.pid"), "utf-8").trim(), 10);
|
|
2352
2654
|
if (Number.isFinite(pid))
|
|
2353
2655
|
pids.push(pid);
|
|
2354
2656
|
} catch {}
|
|
2355
2657
|
try {
|
|
2356
|
-
const status = JSON.parse(
|
|
2658
|
+
const status = JSON.parse(readFileSync6(join6(dir, "status.json"), "utf-8"));
|
|
2357
2659
|
if (typeof status?.pid === "number")
|
|
2358
2660
|
pids.push(status.pid);
|
|
2359
2661
|
} catch {}
|
|
@@ -2387,10 +2689,54 @@ async function removeUnregisteredPairDir(base, pairId) {
|
|
|
2387
2689
|
return { removed: removePairDir(base, pairId) };
|
|
2388
2690
|
});
|
|
2389
2691
|
}
|
|
2390
|
-
|
|
2692
|
+
async function removeOrphanPairDirIgnoringRegistry(base, pairId) {
|
|
2693
|
+
return withRegistryLock(base, () => {
|
|
2694
|
+
if (pairDirDaemonAlive(base, pairId)) {
|
|
2695
|
+
return { removed: false, reason: "live" };
|
|
2696
|
+
}
|
|
2697
|
+
return { removed: removePairDir(base, pairId) };
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
function isEntryReclaimable(signals) {
|
|
2701
|
+
return signals.cwdGone && signals.dead && signals.old;
|
|
2702
|
+
}
|
|
2703
|
+
function cwdMissing(cwd) {
|
|
2704
|
+
try {
|
|
2705
|
+
statSync2(cwd);
|
|
2706
|
+
return false;
|
|
2707
|
+
} catch (err) {
|
|
2708
|
+
return err?.code === "ENOENT";
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function parseCreatedAtMs(createdAt) {
|
|
2712
|
+
if (typeof createdAt !== "string")
|
|
2713
|
+
return null;
|
|
2714
|
+
const ms = Date.parse(createdAt);
|
|
2715
|
+
return Number.isFinite(ms) ? ms : null;
|
|
2716
|
+
}
|
|
2717
|
+
function classifyReclaimableEntries(base, now = Date.now()) {
|
|
2718
|
+
const reg = readRegistry(base);
|
|
2719
|
+
const out = [];
|
|
2720
|
+
for (const entry of reg.pairs) {
|
|
2721
|
+
const createdMs = parseCreatedAtMs(entry.createdAt);
|
|
2722
|
+
const ageMs = createdMs === null ? null : Math.max(0, now - createdMs);
|
|
2723
|
+
const signals = {
|
|
2724
|
+
cwdGone: cwdMissing(entry.cwd),
|
|
2725
|
+
dead: !pairDirDaemonAlive(base, entry.pairId),
|
|
2726
|
+
old: ageMs !== null && ageMs >= RECLAIMABLE_MIN_AGE_MS,
|
|
2727
|
+
ageMs
|
|
2728
|
+
};
|
|
2729
|
+
if (isEntryReclaimable(signals))
|
|
2730
|
+
out.push({ entry, signals });
|
|
2731
|
+
}
|
|
2732
|
+
return out;
|
|
2733
|
+
}
|
|
2734
|
+
var PAIR_BASE_PORT = 4500, PAIR_SLOT_STRIDE = 10, PAIR_ID_REGEX, DEFAULT_PAIR_NAME = "main", RECLAIMABLE_MIN_AGE_MS, 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;
|
|
2391
2735
|
var init_pair_registry = __esm(() => {
|
|
2736
|
+
init_atomic_json();
|
|
2392
2737
|
init_process_lifecycle();
|
|
2393
2738
|
PAIR_ID_REGEX = /^[A-Za-z0-9._-]{1,64}$/;
|
|
2739
|
+
RECLAIMABLE_MIN_AGE_MS = 24 * 60 * 60 * 1000;
|
|
2394
2740
|
WINDOWS_RESERVED_RE = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
2395
2741
|
PairError = class PairError extends Error {
|
|
2396
2742
|
code;
|
|
@@ -2673,7 +3019,7 @@ var init_pair_resolver = __esm(() => {
|
|
|
2673
3019
|
});
|
|
2674
3020
|
|
|
2675
3021
|
// src/trace-log.ts
|
|
2676
|
-
import { appendFileSync, existsSync as existsSync8, mkdirSync as
|
|
3022
|
+
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync4 } from "fs";
|
|
2677
3023
|
import { join as join8 } from "path";
|
|
2678
3024
|
function pickRelevantEnv(env) {
|
|
2679
3025
|
const picked = {};
|
|
@@ -2725,7 +3071,7 @@ function appendTraceEvent(input) {
|
|
|
2725
3071
|
};
|
|
2726
3072
|
const logsDir = join8(input.cwd, ".agentbridge", "logs");
|
|
2727
3073
|
const isNewDayFile = !existsSync8(path);
|
|
2728
|
-
|
|
3074
|
+
mkdirSync5(logsDir, { recursive: true });
|
|
2729
3075
|
if (isNewDayFile) {
|
|
2730
3076
|
pruneOldTraceLogs(logsDir, path, Date.parse(timestamp));
|
|
2731
3077
|
}
|
|
@@ -2751,7 +3097,7 @@ function pruneOldTraceLogs(logsDir, keepPath, nowMs) {
|
|
|
2751
3097
|
continue;
|
|
2752
3098
|
try {
|
|
2753
3099
|
if (statSync3(filePath).mtimeMs < cutoff) {
|
|
2754
|
-
|
|
3100
|
+
unlinkSync4(filePath);
|
|
2755
3101
|
}
|
|
2756
3102
|
} catch {}
|
|
2757
3103
|
}
|
|
@@ -2834,10 +3180,12 @@ var exports_claude = {};
|
|
|
2834
3180
|
__export(exports_claude, {
|
|
2835
3181
|
warnIfPluginCacheMissing: () => warnIfPluginCacheMissing,
|
|
2836
3182
|
runClaude: () => runClaude,
|
|
3183
|
+
mapChildExitCode: () => mapChildExitCode,
|
|
2837
3184
|
checkOwnedFlagConflicts: () => checkOwnedFlagConflicts
|
|
2838
3185
|
});
|
|
2839
3186
|
import { spawn as spawn2 } from "child_process";
|
|
2840
3187
|
import { existsSync as existsSync9 } from "fs";
|
|
3188
|
+
import { constants as osConstants } from "os";
|
|
2841
3189
|
async function runClaude(args) {
|
|
2842
3190
|
const originalEnv = { ...process.env };
|
|
2843
3191
|
const envGuardResult = guardAgentBridgeEnv({
|
|
@@ -2890,8 +3238,8 @@ async function runClaude(args) {
|
|
|
2890
3238
|
stdio: "inherit",
|
|
2891
3239
|
env: process.env
|
|
2892
3240
|
});
|
|
2893
|
-
child.on("exit", (code) => {
|
|
2894
|
-
process.exit(code
|
|
3241
|
+
child.on("exit", (code, signal) => {
|
|
3242
|
+
process.exit(mapChildExitCode(code, signal));
|
|
2895
3243
|
});
|
|
2896
3244
|
child.on("error", (err) => {
|
|
2897
3245
|
if (err.code === "ENOENT") {
|
|
@@ -2903,6 +3251,12 @@ async function runClaude(args) {
|
|
|
2903
3251
|
process.exit(1);
|
|
2904
3252
|
});
|
|
2905
3253
|
}
|
|
3254
|
+
function mapChildExitCode(code, signal) {
|
|
3255
|
+
if (signal) {
|
|
3256
|
+
return 128 + (osConstants.signals[signal] ?? 0);
|
|
3257
|
+
}
|
|
3258
|
+
return code ?? 0;
|
|
3259
|
+
}
|
|
2906
3260
|
function warnIfPluginCacheMissing(cacheRoot = pluginCacheRoot(), log = (msg) => console.error(msg)) {
|
|
2907
3261
|
let cacheExists;
|
|
2908
3262
|
try {
|
|
@@ -3003,7 +3357,7 @@ var init_claude = __esm(() => {
|
|
|
3003
3357
|
});
|
|
3004
3358
|
|
|
3005
3359
|
// src/agents-contract.ts
|
|
3006
|
-
import { existsSync as existsSync10, readFileSync as
|
|
3360
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
3007
3361
|
import { join as join9 } from "path";
|
|
3008
3362
|
function checkAgentsMdContract(cwd) {
|
|
3009
3363
|
const path = join9(cwd, "AGENTS.md");
|
|
@@ -3011,7 +3365,7 @@ function checkAgentsMdContract(cwd) {
|
|
|
3011
3365
|
let content = "";
|
|
3012
3366
|
if (exists) {
|
|
3013
3367
|
try {
|
|
3014
|
-
content =
|
|
3368
|
+
content = readFileSync7(path, "utf-8");
|
|
3015
3369
|
} catch {
|
|
3016
3370
|
return {
|
|
3017
3371
|
fresh: false,
|
|
@@ -3038,7 +3392,7 @@ function isFreshAgentsMdContract(content) {
|
|
|
3038
3392
|
var init_agents_contract = () => {};
|
|
3039
3393
|
|
|
3040
3394
|
// src/wrapper-exit-observability.ts
|
|
3041
|
-
import { readFileSync as
|
|
3395
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
3042
3396
|
import { join as join10 } from "path";
|
|
3043
3397
|
function discoverNativeChildPid(launcherPid, run) {
|
|
3044
3398
|
try {
|
|
@@ -3049,17 +3403,15 @@ function discoverNativeChildPid(launcherPid, run) {
|
|
|
3049
3403
|
return null;
|
|
3050
3404
|
}
|
|
3051
3405
|
}
|
|
3052
|
-
function
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
if (typeof status.turnInProgress !== "boolean")
|
|
3056
|
-
return null;
|
|
3057
|
-
if (typeof status.pid === "number" && !isPidAlive(status.pid))
|
|
3058
|
-
return null;
|
|
3059
|
-
return status.turnInProgress;
|
|
3060
|
-
} catch {
|
|
3406
|
+
function readUnifiedTurnInProgress(paths, read = (p) => readFileSync8(p, "utf-8"), isPidAlive = defaultIsPidAlive) {
|
|
3407
|
+
const record = readUnifiedDaemonRecord(paths, read);
|
|
3408
|
+
if (!record)
|
|
3061
3409
|
return null;
|
|
3062
|
-
|
|
3410
|
+
if (typeof record.turnInProgress !== "boolean")
|
|
3411
|
+
return null;
|
|
3412
|
+
if (typeof record.pid === "number" && !isPidAlive(record.pid))
|
|
3413
|
+
return null;
|
|
3414
|
+
return record.turnInProgress;
|
|
3063
3415
|
}
|
|
3064
3416
|
function refineCleanExitClassification(turnInProgress) {
|
|
3065
3417
|
if (turnInProgress === true)
|
|
@@ -3068,14 +3420,14 @@ function refineCleanExitClassification(turnInProgress) {
|
|
|
3068
3420
|
return "exit_0_idle";
|
|
3069
3421
|
return "exit_0_turn_unknown";
|
|
3070
3422
|
}
|
|
3071
|
-
function findCodexSqliteLog(codexHome,
|
|
3423
|
+
function findCodexSqliteLog(codexHome, fs2 = { readdir: readdirSync3, stat: statSync4 }) {
|
|
3072
3424
|
try {
|
|
3073
|
-
const entries =
|
|
3425
|
+
const entries = fs2.readdir(codexHome).filter((name) => /^logs.*\.sqlite$/.test(String(name)));
|
|
3074
3426
|
let best = null;
|
|
3075
3427
|
for (const name of entries) {
|
|
3076
3428
|
const path = join10(codexHome, String(name));
|
|
3077
3429
|
try {
|
|
3078
|
-
const mtime =
|
|
3430
|
+
const mtime = fs2.stat(path).mtimeMs;
|
|
3079
3431
|
if (!best || mtime > best.mtime)
|
|
3080
3432
|
best = { path, mtime };
|
|
3081
3433
|
} catch {}
|
|
@@ -3110,14 +3462,15 @@ function captureTuiLogTail(options) {
|
|
|
3110
3462
|
var defaultIsPidAlive;
|
|
3111
3463
|
var init_wrapper_exit_observability = __esm(() => {
|
|
3112
3464
|
init_process_lifecycle();
|
|
3465
|
+
init_daemon_record();
|
|
3113
3466
|
defaultIsPidAlive = pidLooksAlive;
|
|
3114
3467
|
});
|
|
3115
3468
|
|
|
3116
3469
|
// src/pair-command.ts
|
|
3117
|
-
function pairScopedCommand(cmd) {
|
|
3470
|
+
function pairScopedCommand(cmd, name = cliInvocationName()) {
|
|
3118
3471
|
const pairId = process.env.AGENTBRIDGE_PAIR_ID;
|
|
3119
3472
|
if (!pairId)
|
|
3120
|
-
return
|
|
3473
|
+
return `${name} ${cmd}`;
|
|
3121
3474
|
let selector = process.env.AGENTBRIDGE_PAIR_NAME;
|
|
3122
3475
|
if (!selector) {
|
|
3123
3476
|
try {
|
|
@@ -3126,19 +3479,20 @@ function pairScopedCommand(cmd) {
|
|
|
3126
3479
|
selector = pairId;
|
|
3127
3480
|
}
|
|
3128
3481
|
}
|
|
3129
|
-
return
|
|
3482
|
+
return `${name} --pair ${selector} ${cmd}`;
|
|
3130
3483
|
}
|
|
3131
3484
|
var init_pair_command = __esm(() => {
|
|
3485
|
+
init_cli_invocation();
|
|
3132
3486
|
init_pair_resolver();
|
|
3133
3487
|
});
|
|
3134
3488
|
|
|
3135
3489
|
// src/rotating-log.ts
|
|
3136
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync11, renameSync as renameSync2, statSync as statSync5, unlinkSync as
|
|
3137
|
-
import { dirname as
|
|
3490
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, renameSync as renameSync2, statSync as statSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
3491
|
+
import { dirname as dirname3 } from "path";
|
|
3138
3492
|
function appendRotatingLog(path, content, options = {}, fsOps = REAL_FS_OPS) {
|
|
3139
3493
|
const maxBytes = options.maxBytes ?? positiveIntFromEnv("AGENTBRIDGE_LOG_MAX_BYTES", DEFAULT_MAX_BYTES);
|
|
3140
3494
|
const keep = options.keep ?? positiveIntFromEnv("AGENTBRIDGE_LOG_ROTATE_KEEP", DEFAULT_KEEP);
|
|
3141
|
-
if (!fsOps.existsSync(
|
|
3495
|
+
if (!fsOps.existsSync(dirname3(path)))
|
|
3142
3496
|
return;
|
|
3143
3497
|
rotateIfNeeded(path, Buffer.byteLength(content), maxBytes, keep, fsOps);
|
|
3144
3498
|
fsOps.appendFileSync(path, content, "utf-8");
|
|
@@ -3196,7 +3550,7 @@ function rotateIfNeeded(path, incomingBytes, maxBytes, keep, fsOps) {
|
|
|
3196
3550
|
var DEFAULT_MAX_BYTES, DEFAULT_KEEP = 3, REAL_FS_OPS;
|
|
3197
3551
|
var init_rotating_log = __esm(() => {
|
|
3198
3552
|
DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
3199
|
-
REAL_FS_OPS = { statSync: statSync5, renameSync: renameSync2, unlinkSync:
|
|
3553
|
+
REAL_FS_OPS = { statSync: statSync5, renameSync: renameSync2, unlinkSync: unlinkSync5, appendFileSync: appendFileSync2, existsSync: existsSync11 };
|
|
3200
3554
|
});
|
|
3201
3555
|
|
|
3202
3556
|
// src/stderr-ring-buffer.ts
|
|
@@ -3250,30 +3604,20 @@ var init_stderr_ring_buffer = __esm(() => {
|
|
|
3250
3604
|
// src/thread-state.ts
|
|
3251
3605
|
import {
|
|
3252
3606
|
existsSync as existsSync12,
|
|
3253
|
-
mkdirSync as mkdirSync5,
|
|
3254
3607
|
readdirSync as readdirSync4,
|
|
3255
|
-
readFileSync as
|
|
3256
|
-
renameSync as renameSync3,
|
|
3257
|
-
writeFileSync as writeFileSync6
|
|
3608
|
+
readFileSync as readFileSync9
|
|
3258
3609
|
} from "fs";
|
|
3259
3610
|
import { homedir as homedir3 } from "os";
|
|
3260
|
-
import { basename as
|
|
3611
|
+
import { basename as basename4, join as join11 } from "path";
|
|
3261
3612
|
function nowIso() {
|
|
3262
3613
|
return new Date().toISOString();
|
|
3263
3614
|
}
|
|
3264
3615
|
function codexHome(env = process.env) {
|
|
3265
3616
|
return env.CODEX_HOME && env.CODEX_HOME.length > 0 ? env.CODEX_HOME : join11(homedir3(), ".codex");
|
|
3266
3617
|
}
|
|
3267
|
-
function atomicWriteJson(path, value) {
|
|
3268
|
-
mkdirSync5(dirname3(path), { recursive: true });
|
|
3269
|
-
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
3270
|
-
writeFileSync6(tmp, JSON.stringify(value, null, 2) + `
|
|
3271
|
-
`, "utf-8");
|
|
3272
|
-
renameSync3(tmp, path);
|
|
3273
|
-
}
|
|
3274
3618
|
function readRawCurrentThread(stateDir) {
|
|
3275
3619
|
try {
|
|
3276
|
-
const parsed = JSON.parse(
|
|
3620
|
+
const parsed = JSON.parse(readFileSync9(stateDir.currentThreadFile, "utf-8"));
|
|
3277
3621
|
if (parsed?.version === 1 && typeof parsed.threadId === "string" && parsed.threadId.length > 0 && (parsed.status === "pending" || parsed.status === "current") && typeof parsed.cwd === "string") {
|
|
3278
3622
|
return parsed;
|
|
3279
3623
|
}
|
|
@@ -3304,7 +3648,7 @@ function findCodexRolloutFile(threadId, env = process.env, maxEntries = 20000) {
|
|
|
3304
3648
|
}
|
|
3305
3649
|
if (!entry.isFile())
|
|
3306
3650
|
continue;
|
|
3307
|
-
const name =
|
|
3651
|
+
const name = basename4(entry.name);
|
|
3308
3652
|
if (name === exactName || name.startsWith("rollout-") && name.endsWith(".jsonl") && name.includes(threadId)) {
|
|
3309
3653
|
return path;
|
|
3310
3654
|
}
|
|
@@ -3336,7 +3680,9 @@ function readUsableCurrentThread(identity, env = process.env) {
|
|
|
3336
3680
|
atomicWriteJson(identity.stateDir.currentThreadFile, repaired);
|
|
3337
3681
|
return repaired;
|
|
3338
3682
|
}
|
|
3339
|
-
var init_thread_state = () => {
|
|
3683
|
+
var init_thread_state = __esm(() => {
|
|
3684
|
+
init_atomic_json();
|
|
3685
|
+
});
|
|
3340
3686
|
|
|
3341
3687
|
// src/cli/codex.ts
|
|
3342
3688
|
var exports_codex = {};
|
|
@@ -3351,9 +3697,9 @@ import {
|
|
|
3351
3697
|
openSync as openSync3,
|
|
3352
3698
|
writeSync,
|
|
3353
3699
|
closeSync as closeSync3,
|
|
3354
|
-
writeFileSync as
|
|
3355
|
-
readFileSync as
|
|
3356
|
-
unlinkSync as
|
|
3700
|
+
writeFileSync as writeFileSync5,
|
|
3701
|
+
readFileSync as readFileSync10,
|
|
3702
|
+
unlinkSync as unlinkSync6,
|
|
3357
3703
|
existsSync as existsSync13,
|
|
3358
3704
|
mkdirSync as mkdirSync6
|
|
3359
3705
|
} from "fs";
|
|
@@ -3525,9 +3871,9 @@ async function runCodex(args) {
|
|
|
3525
3871
|
process.exit(1);
|
|
3526
3872
|
}
|
|
3527
3873
|
let proxyUrl;
|
|
3528
|
-
const
|
|
3529
|
-
if (
|
|
3530
|
-
proxyUrl =
|
|
3874
|
+
const record = lifecycle.readDaemonRecord();
|
|
3875
|
+
if (typeof record?.proxyUrl === "string" && record.proxyUrl.length > 0) {
|
|
3876
|
+
proxyUrl = record.proxyUrl;
|
|
3531
3877
|
} else {
|
|
3532
3878
|
const fallbackProxyPort = process.env.CODEX_PROXY_PORT ?? String(new ConfigService().loadOrDefault((msg) => console.error(`[agentbridge] ${msg}`)).codex.proxyPort);
|
|
3533
3879
|
proxyUrl = `ws://127.0.0.1:${fallbackProxyPort}`;
|
|
@@ -3609,7 +3955,7 @@ async function runCodex(args) {
|
|
|
3609
3955
|
env: buildChildEnv()
|
|
3610
3956
|
});
|
|
3611
3957
|
if (typeof child.pid === "number") {
|
|
3612
|
-
|
|
3958
|
+
writeFileSync5(stateDir.tuiPidFile, `${child.pid}
|
|
3613
3959
|
`, "utf-8");
|
|
3614
3960
|
appendWrapperLog(wrapperLogPath, `child pid=${child.pid}`);
|
|
3615
3961
|
}
|
|
@@ -3649,7 +3995,7 @@ async function runCodex(args) {
|
|
|
3649
3995
|
return;
|
|
3650
3996
|
cleanedTuiPid = true;
|
|
3651
3997
|
try {
|
|
3652
|
-
|
|
3998
|
+
unlinkSync6(stateDir.tuiPidFile);
|
|
3653
3999
|
} catch {}
|
|
3654
4000
|
}
|
|
3655
4001
|
function requestChildTermination(reason) {
|
|
@@ -3720,7 +4066,11 @@ async function runCodex(args) {
|
|
|
3720
4066
|
else if (typeof code === "number" && code !== 0)
|
|
3721
4067
|
classification = `nonzero_exit:${code}`;
|
|
3722
4068
|
else if (code === 0 && tail.trim().length === 0) {
|
|
3723
|
-
classification = refineCleanExitClassification(
|
|
4069
|
+
classification = refineCleanExitClassification(readUnifiedTurnInProgress({
|
|
4070
|
+
daemonRecordFile: stateDir.daemonRecordFile,
|
|
4071
|
+
pidFile: stateDir.pidFile,
|
|
4072
|
+
statusFile: stateDir.statusFile
|
|
4073
|
+
}));
|
|
3724
4074
|
}
|
|
3725
4075
|
const tuiLogTail = captureTuiLogTail({
|
|
3726
4076
|
codexHome: join12(homedir4(), ".codex"),
|
|
@@ -3779,12 +4129,12 @@ function guardNoLiveManagedTui(stateDir, proxyUrl) {
|
|
|
3779
4129
|
if (pid) {
|
|
3780
4130
|
if (!isProcessAlive(pid)) {
|
|
3781
4131
|
try {
|
|
3782
|
-
|
|
4132
|
+
unlinkSync6(stateDir.tuiPidFile);
|
|
3783
4133
|
} catch {}
|
|
3784
4134
|
} else if (!isManagedCodexTuiProcess(pid, proxyUrl)) {
|
|
3785
4135
|
appendWrapperLog(stateDir.codexWrapperLogFile, `stale tui pid file pointed at unmanaged live pid=${pid}; removing`);
|
|
3786
4136
|
try {
|
|
3787
|
-
|
|
4137
|
+
unlinkSync6(stateDir.tuiPidFile);
|
|
3788
4138
|
} catch {}
|
|
3789
4139
|
} else {
|
|
3790
4140
|
console.error(`[agentbridge] This pair already has a managed Codex TUI running (pid ${pid}).`);
|
|
@@ -3801,7 +4151,7 @@ function guardNoLiveManagedTui(stateDir, proxyUrl) {
|
|
|
3801
4151
|
}
|
|
3802
4152
|
function readTuiPid(stateDir) {
|
|
3803
4153
|
try {
|
|
3804
|
-
const raw =
|
|
4154
|
+
const raw = readFileSync10(stateDir.tuiPidFile, "utf-8").trim();
|
|
3805
4155
|
if (!raw)
|
|
3806
4156
|
return null;
|
|
3807
4157
|
const pid = Number.parseInt(raw, 10);
|
|
@@ -3815,7 +4165,12 @@ function isManagedCodexTuiProcess(pid, proxyUrl) {
|
|
|
3815
4165
|
return cmd !== null && commandMatchesManagedCodexTui(cmd, proxyUrl);
|
|
3816
4166
|
}
|
|
3817
4167
|
function proxyHealthUrl(proxyUrl) {
|
|
3818
|
-
|
|
4168
|
+
let url;
|
|
4169
|
+
try {
|
|
4170
|
+
url = new URL(proxyUrl);
|
|
4171
|
+
} catch {
|
|
4172
|
+
throw new Error(`Malformed Codex proxy URL: ${JSON.stringify(proxyUrl)}`);
|
|
4173
|
+
}
|
|
3819
4174
|
url.protocol = url.protocol === "wss:" ? "https:" : "http:";
|
|
3820
4175
|
url.pathname = "/healthz";
|
|
3821
4176
|
url.search = "";
|
|
@@ -4010,7 +4365,7 @@ __export(exports_kill, {
|
|
|
4010
4365
|
runKill: () => runKill,
|
|
4011
4366
|
formatKillReport: () => formatKillReport
|
|
4012
4367
|
});
|
|
4013
|
-
import { readFileSync as
|
|
4368
|
+
import { readFileSync as readFileSync11, unlinkSync as unlinkSync7 } from "fs";
|
|
4014
4369
|
import { join as join14 } from "path";
|
|
4015
4370
|
async function runKill(args = []) {
|
|
4016
4371
|
const argError = validateKillArgs(args);
|
|
@@ -4031,8 +4386,9 @@ async function runKill(args = []) {
|
|
|
4031
4386
|
const base = computeBaseDir();
|
|
4032
4387
|
console.log(`AgentBridge Kill \u2014 stopping AgentBridge pair processes
|
|
4033
4388
|
`);
|
|
4389
|
+
const cli = cliInvocationName();
|
|
4034
4390
|
const results = [];
|
|
4035
|
-
let restartCommand =
|
|
4391
|
+
let restartCommand = `${cli} claude`;
|
|
4036
4392
|
if (parsed.pairFlag !== undefined) {
|
|
4037
4393
|
let pair;
|
|
4038
4394
|
try {
|
|
@@ -4046,7 +4402,7 @@ async function runKill(args = []) {
|
|
|
4046
4402
|
printKnownPairs(base);
|
|
4047
4403
|
return;
|
|
4048
4404
|
}
|
|
4049
|
-
restartCommand =
|
|
4405
|
+
restartCommand = `${cli} --pair ${pair.name ?? parsed.pairFlag} claude`;
|
|
4050
4406
|
results.push(await stopPairEntry(base, pair));
|
|
4051
4407
|
} else if (parsed.all) {
|
|
4052
4408
|
let registered = [];
|
|
@@ -4080,7 +4436,7 @@ async function runKill(args = []) {
|
|
|
4080
4436
|
cwdPairs = listPairsForCwd(base, process.cwd());
|
|
4081
4437
|
} catch (error) {
|
|
4082
4438
|
const message = error instanceof Error ? error.message : String(error);
|
|
4083
|
-
console.log(`\u26A0\uFE0F pair registry \u4E0D\u53EF\u8BFB\uFF08${message}\uFF09\u2014\u2014\u65E0\u6CD5\u6309\u76EE\u5F55\u5B9A\u4F4D pair\u3002` +
|
|
4439
|
+
console.log(`\u26A0\uFE0F pair registry \u4E0D\u53EF\u8BFB\uFF08${message}\uFF09\u2014\u2014\u65E0\u6CD5\u6309\u76EE\u5F55\u5B9A\u4F4D pair\u3002` + `\u8FD0\u884C \`${cli} kill --all\` \u53EF\u964D\u7EA7\u4E3A\u5168\u76D8\u72B6\u6001\u76EE\u5F55\u626B\u63CF\uFF0C\u505C\u6B62\u6240\u6709\u80FD\u627E\u5230\u7684 pair\u3002`);
|
|
4084
4440
|
process.exitCode = 2;
|
|
4085
4441
|
}
|
|
4086
4442
|
for (const pair of cwdPairs) {
|
|
@@ -4096,7 +4452,7 @@ async function runKill(args = []) {
|
|
|
4096
4452
|
}
|
|
4097
4453
|
if (results.length === 0) {
|
|
4098
4454
|
console.log(`No AgentBridge pairs registered for current directory: ${process.cwd()}`);
|
|
4099
|
-
console.log(
|
|
4455
|
+
console.log(`Use \`${cli} kill all\` or \`${cli} kill --all\` to stop pairs from every directory.`);
|
|
4100
4456
|
return;
|
|
4101
4457
|
}
|
|
4102
4458
|
}
|
|
@@ -4159,22 +4515,18 @@ function listPairDirsSafe(base) {
|
|
|
4159
4515
|
}
|
|
4160
4516
|
}
|
|
4161
4517
|
function portsFromStateDir(stateDir) {
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
};
|
|
4169
|
-
} catch {
|
|
4518
|
+
const record = readUnifiedDaemonRecord({
|
|
4519
|
+
daemonRecordFile: stateDir.daemonRecordFile,
|
|
4520
|
+
pidFile: stateDir.pidFile,
|
|
4521
|
+
statusFile: stateDir.statusFile
|
|
4522
|
+
});
|
|
4523
|
+
if (!record)
|
|
4170
4524
|
return { appPort: 0, proxyPort: 0, controlPort: 0 };
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
const match = url.match(/:(\d+)(?:[/?]|$)/);
|
|
4177
|
-
return match ? Number.parseInt(match[1], 10) : null;
|
|
4525
|
+
return {
|
|
4526
|
+
appPort: record.ports?.appPort ?? portFromUrl(record.appServerUrl) ?? 0,
|
|
4527
|
+
proxyPort: record.ports?.proxyPort ?? portFromUrl(record.proxyUrl) ?? 0,
|
|
4528
|
+
controlPort: record.ports?.controlPort ?? 0
|
|
4529
|
+
};
|
|
4178
4530
|
}
|
|
4179
4531
|
async function stopStateDir(label, stateDir, ports) {
|
|
4180
4532
|
const portsLabel = `${ports.appPort}/${ports.proxyPort}/${ports.controlPort}`;
|
|
@@ -4187,8 +4539,8 @@ async function stopStateDir(label, stateDir, ports) {
|
|
|
4187
4539
|
log
|
|
4188
4540
|
});
|
|
4189
4541
|
lifecycle.markKilled();
|
|
4190
|
-
const
|
|
4191
|
-
const proxyUrl = typeof
|
|
4542
|
+
const record = lifecycle.readDaemonRecord();
|
|
4543
|
+
const proxyUrl = typeof record?.proxyUrl === "string" && record.proxyUrl.length > 0 ? record.proxyUrl : `ws://127.0.0.1:${ports.proxyPort}`;
|
|
4192
4544
|
const tuiKilled = await killManagedCodexTui(stateDir, proxyUrl, log);
|
|
4193
4545
|
const daemonKilled = await lifecycle.kill();
|
|
4194
4546
|
return { label, portsLabel, daemonKilled, tuiKilled, details };
|
|
@@ -4253,9 +4605,10 @@ function formatKillReport(results, frontends, restartCommand) {
|
|
|
4253
4605
|
}
|
|
4254
4606
|
lines.push("");
|
|
4255
4607
|
if (stopped.length > 0) {
|
|
4608
|
+
const cliName = restartCommand.split(" ")[0] ?? "abg";
|
|
4256
4609
|
lines.push("AgentBridge stopped.");
|
|
4257
4610
|
lines.push(`Please restart Claude Code (\`${restartCommand}\`), switch to a new conversation, or run \`/resume\` to fully disconnect.`);
|
|
4258
|
-
lines.push("\u2139\uFE0F \u5DF2\u5199\u5165 killed \u54E8\u5175\uFF1A\u88AB\u505C\u6B62\u7684 pair \u4E0D\u4F1A\u88AB\u81EA\u52A8\u590D\u6D3B\uFF1B" + `\u4E0B\u6B21 \`${restartCommand}\` /
|
|
4611
|
+
lines.push("\u2139\uFE0F \u5DF2\u5199\u5165 killed \u54E8\u5175\uFF1A\u88AB\u505C\u6B62\u7684 pair \u4E0D\u4F1A\u88AB\u81EA\u52A8\u590D\u6D3B\uFF1B" + `\u4E0B\u6B21 \`${restartCommand}\` / \`${cliName} codex\` \u4F1A\u6E05\u9664\u54E8\u5175\u5E76\u7528\u5F53\u524D\u5B89\u88C5\u7248\u672C\u542F\u52A8\u5168\u65B0 daemon\u3002`);
|
|
4259
4612
|
} else {
|
|
4260
4613
|
lines.push("No running AgentBridge daemon or managed Codex TUI found.");
|
|
4261
4614
|
lines.push("\u2139\uFE0F \u76EE\u6807 pair \u90FD\u6CA1\u6709\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B\u2014\u2014\u5982\u679C\u4F60\u4ECD\u770B\u5230 AgentBridge \u6D3B\u52A8\uFF0C\u89C1\u4E0B\u65B9\u524D\u7AEF\u63D0\u793A\u3002");
|
|
@@ -4306,7 +4659,7 @@ async function killManagedCodexTui(stateDir, proxyUrl, log, gracefulTimeoutMs =
|
|
|
4306
4659
|
}
|
|
4307
4660
|
function readTuiPid2(stateDir) {
|
|
4308
4661
|
try {
|
|
4309
|
-
const raw =
|
|
4662
|
+
const raw = readFileSync11(stateDir.tuiPidFile, "utf-8").trim();
|
|
4310
4663
|
if (!raw)
|
|
4311
4664
|
return null;
|
|
4312
4665
|
const pid = Number.parseInt(raw, 10);
|
|
@@ -4317,7 +4670,7 @@ function readTuiPid2(stateDir) {
|
|
|
4317
4670
|
}
|
|
4318
4671
|
function removeTuiPidFile(stateDir) {
|
|
4319
4672
|
try {
|
|
4320
|
-
|
|
4673
|
+
unlinkSync7(stateDir.tuiPidFile);
|
|
4321
4674
|
} catch {}
|
|
4322
4675
|
}
|
|
4323
4676
|
function isManagedCodexTuiProcess2(pid, proxyUrl) {
|
|
@@ -4325,7 +4678,9 @@ function isManagedCodexTuiProcess2(pid, proxyUrl) {
|
|
|
4325
4678
|
return cmd !== null && commandMatchesManagedCodexTui(cmd, proxyUrl);
|
|
4326
4679
|
}
|
|
4327
4680
|
var init_kill = __esm(() => {
|
|
4681
|
+
init_cli_invocation();
|
|
4328
4682
|
init_daemon_lifecycle();
|
|
4683
|
+
init_daemon_record();
|
|
4329
4684
|
init_pair_registry();
|
|
4330
4685
|
init_pair_resolver();
|
|
4331
4686
|
init_process_lifecycle();
|
|
@@ -4338,6 +4693,13 @@ __export(exports_pairs, {
|
|
|
4338
4693
|
runPairs: () => runPairs
|
|
4339
4694
|
});
|
|
4340
4695
|
import { join as join15 } from "path";
|
|
4696
|
+
function isRegistryCorruptError(error) {
|
|
4697
|
+
return error instanceof PairError && error.code === "PAIR_REGISTRY_CORRUPT";
|
|
4698
|
+
}
|
|
4699
|
+
function registryPathForNotice(base, error) {
|
|
4700
|
+
const fromDetails = error.details?.path;
|
|
4701
|
+
return typeof fromDetails === "string" && fromDetails.length > 0 ? fromDetails : join15(pairsRootDir(base), "registry.json");
|
|
4702
|
+
}
|
|
4341
4703
|
async function runPairs(args = []) {
|
|
4342
4704
|
const [command, ...rest] = args;
|
|
4343
4705
|
if (command === "rm") {
|
|
@@ -4350,7 +4712,7 @@ async function runPairs(args = []) {
|
|
|
4350
4712
|
}
|
|
4351
4713
|
if (command && command !== "list" && command !== "--json" && command !== "--threads") {
|
|
4352
4714
|
console.error(`Unknown pairs command: ${command}`);
|
|
4353
|
-
console.error("Usage: abg pairs [--json] [--threads] | abg pairs rm <name|id> | abg pairs prune [--
|
|
4715
|
+
console.error("Usage: abg pairs [--json] [--threads] | abg pairs rm <name|id> | abg pairs prune [--apply]");
|
|
4354
4716
|
process.exit(1);
|
|
4355
4717
|
}
|
|
4356
4718
|
const json = command === "--json" || rest.includes("--json");
|
|
@@ -4405,16 +4767,40 @@ async function runRemove(args) {
|
|
|
4405
4767
|
}
|
|
4406
4768
|
}
|
|
4407
4769
|
async function runPrune(args) {
|
|
4408
|
-
const
|
|
4770
|
+
const apply = args.includes("--apply");
|
|
4409
4771
|
for (const arg of args) {
|
|
4410
|
-
if (arg !== "--dry-run") {
|
|
4772
|
+
if (arg !== "--apply" && arg !== "--dry-run") {
|
|
4411
4773
|
console.error(`Unknown prune argument: ${arg}`);
|
|
4412
|
-
console.error("Usage: abg pairs prune [--
|
|
4774
|
+
console.error("Usage: abg pairs prune [--apply]");
|
|
4413
4775
|
process.exit(1);
|
|
4414
4776
|
}
|
|
4415
4777
|
}
|
|
4778
|
+
if (apply && args.includes("--dry-run")) {
|
|
4779
|
+
console.error("Error: --apply and --dry-run are mutually exclusive.");
|
|
4780
|
+
console.error("Usage: abg pairs prune [--apply]");
|
|
4781
|
+
process.exit(1);
|
|
4782
|
+
}
|
|
4416
4783
|
const base = computeBaseDir();
|
|
4417
|
-
|
|
4784
|
+
let reclaimable;
|
|
4785
|
+
let registryReadable = true;
|
|
4786
|
+
try {
|
|
4787
|
+
reclaimable = classifyReclaimableEntries(base);
|
|
4788
|
+
} catch (error) {
|
|
4789
|
+
if (!isRegistryCorruptError(error))
|
|
4790
|
+
throw error;
|
|
4791
|
+
registryReadable = false;
|
|
4792
|
+
reclaimable = [];
|
|
4793
|
+
console.error(`\u26A0\uFE0F pair registry \u4E0D\u53EF\u8BFB\uFF08${error.message}\uFF09\u2014\u2014` + `\u4F4D\u4E8E ${registryPathForNotice(base, error)}\u3002` + `\u8DF3\u8FC7 registry \u6761\u76EE\u56DE\u6536\uFF0C\u964D\u7EA7\u4E3A\u78C1\u76D8\u626B\u63CF\u6E05\u7406\u5B64\u513F\u76EE\u5F55\uFF08\u65E0\u9700\u53EF\u89E3\u6790\u7684 registry\uFF09\u3002` + `\u4FEE\u590D\u6216\u5220\u9664\u8BE5\u6587\u4EF6\u540E\u53EF\u6062\u590D\u5B8C\u6574 prune\u3002`);
|
|
4794
|
+
process.exitCode = 2;
|
|
4795
|
+
}
|
|
4796
|
+
const reclaimableIds = new Set(reclaimable.map((c) => c.entry.pairId.toLowerCase()));
|
|
4797
|
+
const dirResult = pruneOrphanDirs(base, apply, reclaimableIds, registryReadable);
|
|
4798
|
+
const entryResult = await pruneReclaimableEntries(reclaimable, base, apply);
|
|
4799
|
+
const resolvedDirResult = await dirResult;
|
|
4800
|
+
printPruneSummary(resolvedDirResult, entryResult, apply);
|
|
4801
|
+
}
|
|
4802
|
+
async function pruneOrphanDirs(base, apply, reclaimableIds, registryReadable) {
|
|
4803
|
+
const registered = registryReadable ? new Set(listPairs(base).map((pair) => pair.pairId.toLowerCase())) : new Set;
|
|
4418
4804
|
const removed = [];
|
|
4419
4805
|
const kept = [];
|
|
4420
4806
|
for (const name of listPairDirs(base)) {
|
|
@@ -4429,6 +4815,9 @@ async function runPrune(args) {
|
|
|
4429
4815
|
kept.push({ name, reason: "directory name is not a canonical pair id" });
|
|
4430
4816
|
continue;
|
|
4431
4817
|
}
|
|
4818
|
+
if (reclaimableIds.has(id.toLowerCase())) {
|
|
4819
|
+
continue;
|
|
4820
|
+
}
|
|
4432
4821
|
if (registered.has(id.toLowerCase())) {
|
|
4433
4822
|
kept.push({ name, reason: "registered \u2014 use `abg pairs rm`" });
|
|
4434
4823
|
continue;
|
|
@@ -4437,12 +4826,12 @@ async function runPrune(args) {
|
|
|
4437
4826
|
kept.push({ name, reason: "daemon still alive" });
|
|
4438
4827
|
continue;
|
|
4439
4828
|
}
|
|
4440
|
-
if (
|
|
4829
|
+
if (!apply) {
|
|
4441
4830
|
removed.push(name);
|
|
4442
4831
|
continue;
|
|
4443
4832
|
}
|
|
4444
4833
|
try {
|
|
4445
|
-
const outcome = await removeUnregisteredPairDir(base, id);
|
|
4834
|
+
const outcome = registryReadable ? await removeUnregisteredPairDir(base, id) : await removeOrphanPairDirIgnoringRegistry(base, id);
|
|
4446
4835
|
if (outcome.removed) {
|
|
4447
4836
|
removed.push(name);
|
|
4448
4837
|
} else if (outcome.reason === "registered") {
|
|
@@ -4456,33 +4845,90 @@ async function runPrune(args) {
|
|
|
4456
4845
|
kept.push({ name, reason: `error: ${err instanceof Error ? err.message : String(err)}` });
|
|
4457
4846
|
}
|
|
4458
4847
|
}
|
|
4459
|
-
|
|
4848
|
+
return { removed, kept };
|
|
4849
|
+
}
|
|
4850
|
+
async function pruneReclaimableEntries(candidates, base, apply) {
|
|
4851
|
+
const reclaimed = [];
|
|
4852
|
+
const kept = [];
|
|
4853
|
+
for (const candidate of candidates) {
|
|
4854
|
+
const reason = describeReclaimReason(candidate);
|
|
4855
|
+
if (!apply) {
|
|
4856
|
+
reclaimed.push({ pairId: candidate.entry.pairId, slot: candidate.entry.slot, reason });
|
|
4857
|
+
continue;
|
|
4858
|
+
}
|
|
4859
|
+
try {
|
|
4860
|
+
const res = await removePairEntryAndDir(base, candidate.entry.pairId);
|
|
4861
|
+
if (res.keptLive) {
|
|
4862
|
+
kept.push({ pairId: candidate.entry.pairId, reason: "became live during prune" });
|
|
4863
|
+
} else {
|
|
4864
|
+
reclaimed.push({ pairId: candidate.entry.pairId, slot: candidate.entry.slot, reason });
|
|
4865
|
+
}
|
|
4866
|
+
} catch (err) {
|
|
4867
|
+
kept.push({
|
|
4868
|
+
pairId: candidate.entry.pairId,
|
|
4869
|
+
reason: `error: ${err instanceof Error ? err.message : String(err)}`
|
|
4870
|
+
});
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
return { reclaimed, kept };
|
|
4460
4874
|
}
|
|
4461
|
-
function
|
|
4462
|
-
|
|
4463
|
-
|
|
4875
|
+
function describeReclaimReason(candidate) {
|
|
4876
|
+
const { signals } = candidate;
|
|
4877
|
+
const age = signals.ageMs === null ? "age?" : `age ${formatAgeDays(signals.ageMs)}`;
|
|
4878
|
+
return `cwd-gone, dead, ${age}`;
|
|
4879
|
+
}
|
|
4880
|
+
function formatAgeDays(ageMs) {
|
|
4881
|
+
const days = ageMs / (24 * 60 * 60 * 1000);
|
|
4882
|
+
return days >= 10 ? `${Math.round(days)}d` : `${days.toFixed(1)}d`;
|
|
4883
|
+
}
|
|
4884
|
+
function printPruneSummary(dirResult, entryResult, apply) {
|
|
4885
|
+
const { removed: dirsRemoved, kept: dirsKept } = dirResult;
|
|
4886
|
+
const { reclaimed: entriesReclaimed, kept: entriesKept } = entryResult;
|
|
4887
|
+
const nothingFound = dirsRemoved.length === 0 && dirsKept.length === 0 && entriesReclaimed.length === 0 && entriesKept.length === 0;
|
|
4888
|
+
if (nothingFound) {
|
|
4889
|
+
console.log("Nothing to prune: no orphan pair directories or reclaimable entries found.");
|
|
4464
4890
|
return;
|
|
4465
4891
|
}
|
|
4466
|
-
if (
|
|
4467
|
-
console.log(
|
|
4468
|
-
for (const name of
|
|
4892
|
+
if (dirsRemoved.length > 0) {
|
|
4893
|
+
console.log(apply ? "Removed orphan pair directories:" : "Would remove orphan pair directories:");
|
|
4894
|
+
for (const name of dirsRemoved)
|
|
4469
4895
|
console.log(` ${name}`);
|
|
4470
|
-
} else {
|
|
4471
|
-
console.log(dryRun ? "No orphan pair directories to remove." : "No orphan pair directories removed.");
|
|
4472
4896
|
}
|
|
4473
|
-
if (
|
|
4897
|
+
if (entriesReclaimed.length > 0) {
|
|
4898
|
+
console.log(apply ? "Reclaimed registry entries:" : "Would reclaim registry entries:");
|
|
4899
|
+
for (const { pairId, slot, reason } of entriesReclaimed) {
|
|
4900
|
+
console.log(` ${pairId} (slot ${slot}) \u2014 ${reason}`);
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
if (dirsRemoved.length === 0 && entriesReclaimed.length === 0) {
|
|
4904
|
+
console.log(apply ? "Nothing was reclaimed." : "Nothing to reclaim.");
|
|
4905
|
+
}
|
|
4906
|
+
const keptLines = [
|
|
4907
|
+
...dirsKept.map(({ name, reason }) => ` ${name} (${reason})`),
|
|
4908
|
+
...entriesKept.map(({ pairId, reason }) => ` ${pairId} (${reason})`)
|
|
4909
|
+
];
|
|
4910
|
+
if (keptLines.length > 0) {
|
|
4474
4911
|
console.log("Kept:");
|
|
4475
|
-
for (const
|
|
4476
|
-
console.log(
|
|
4912
|
+
for (const line of keptLines)
|
|
4913
|
+
console.log(line);
|
|
4477
4914
|
}
|
|
4478
|
-
if (
|
|
4915
|
+
if (!apply) {
|
|
4479
4916
|
console.log(`
|
|
4480
|
-
(dry run \u2014 nothing was deleted. Re-run
|
|
4917
|
+
(dry run \u2014 nothing was deleted. Re-run with --apply to reclaim.)`);
|
|
4481
4918
|
}
|
|
4482
4919
|
}
|
|
4483
4920
|
async function collectRows() {
|
|
4484
4921
|
const base = computeBaseDir();
|
|
4485
|
-
|
|
4922
|
+
let rows;
|
|
4923
|
+
try {
|
|
4924
|
+
rows = await Promise.all(listPairs(base).map((pair) => rowForPair(base, pair)));
|
|
4925
|
+
} catch (error) {
|
|
4926
|
+
if (!isRegistryCorruptError(error))
|
|
4927
|
+
throw error;
|
|
4928
|
+
console.error(`\u26A0\uFE0F pair registry \u4E0D\u53EF\u8BFB\uFF08${error.message}\uFF09\u2014\u2014` + `\u4F4D\u4E8E ${registryPathForNotice(base, error)}\u3002` + `\u964D\u7EA7\u4E3A\u78C1\u76D8\u626B\u63CF\u5217\u51FA ${pairsRootDir(base)} \u4E0B\u7684 pair \u76EE\u5F55\uFF08slot/name/cwd \u7B49\u9700 registry \u7684\u5B57\u6BB5\u663E\u793A\u4E3A -\uFF09\u3002` + `\u4FEE\u590D\u6216\u5220\u9664\u8BE5\u6587\u4EF6\u540E\u53EF\u6062\u590D\u5B8C\u6574\u5217\u8868\uFF1B\u7528 \`abg pairs prune\` \u6E05\u7406\u5B64\u513F\u76EE\u5F55\u3002`);
|
|
4929
|
+
process.exitCode = 2;
|
|
4930
|
+
rows = await collectDiskScanRows(base);
|
|
4931
|
+
}
|
|
4486
4932
|
const legacy = detectLegacyRootDaemon(base);
|
|
4487
4933
|
if (legacy) {
|
|
4488
4934
|
rows.push({
|
|
@@ -4509,9 +4955,9 @@ async function rowForPair(base, pair) {
|
|
|
4509
4955
|
controlPort: ports.controlPort,
|
|
4510
4956
|
log: () => {}
|
|
4511
4957
|
});
|
|
4512
|
-
const [running,
|
|
4958
|
+
const [running, record] = await Promise.all([
|
|
4513
4959
|
lifecycle.isHealthy(),
|
|
4514
|
-
Promise.resolve(lifecycle.
|
|
4960
|
+
Promise.resolve(lifecycle.readDaemonRecord())
|
|
4515
4961
|
]);
|
|
4516
4962
|
const thread = readRawCurrentThread(stateDir);
|
|
4517
4963
|
return {
|
|
@@ -4522,7 +4968,42 @@ async function rowForPair(base, pair) {
|
|
|
4522
4968
|
source: pair.source,
|
|
4523
4969
|
cwd: pair.cwd,
|
|
4524
4970
|
running,
|
|
4525
|
-
pid: typeof
|
|
4971
|
+
pid: typeof record?.pid === "number" ? record.pid : null,
|
|
4972
|
+
threadId: thread?.threadId ?? null,
|
|
4973
|
+
threadStatus: thread?.status ?? null,
|
|
4974
|
+
threadUpdatedAt: thread?.updatedAt ?? null
|
|
4975
|
+
};
|
|
4976
|
+
}
|
|
4977
|
+
async function collectDiskScanRows(base) {
|
|
4978
|
+
const names = listPairDirsSafe2(base);
|
|
4979
|
+
return Promise.all(names.map((name) => rowForDiskScanDir(base, name)));
|
|
4980
|
+
}
|
|
4981
|
+
function listPairDirsSafe2(base) {
|
|
4982
|
+
try {
|
|
4983
|
+
return listPairDirs(base);
|
|
4984
|
+
} catch {
|
|
4985
|
+
return [];
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
async function rowForDiskScanDir(base, dirName) {
|
|
4989
|
+
const stateDir = new StateDirResolver(join15(base, "pairs", dirName));
|
|
4990
|
+
const record = new DaemonLifecycle({ stateDir, controlPort: 0, log: () => {} }).readDaemonRecord();
|
|
4991
|
+
const ports = {
|
|
4992
|
+
appPort: record?.ports?.appPort ?? 0,
|
|
4993
|
+
proxyPort: record?.ports?.proxyPort ?? 0,
|
|
4994
|
+
controlPort: record?.ports?.controlPort ?? 0
|
|
4995
|
+
};
|
|
4996
|
+
const running = ports.controlPort > 0 ? await new DaemonLifecycle({ stateDir, controlPort: ports.controlPort, log: () => {} }).isHealthy() : false;
|
|
4997
|
+
const thread = readRawCurrentThread(stateDir);
|
|
4998
|
+
return {
|
|
4999
|
+
pairId: dirName,
|
|
5000
|
+
name: "-",
|
|
5001
|
+
slot: null,
|
|
5002
|
+
ports,
|
|
5003
|
+
source: "cwd",
|
|
5004
|
+
cwd: "-",
|
|
5005
|
+
running,
|
|
5006
|
+
pid: typeof record?.pid === "number" ? record.pid : null,
|
|
4526
5007
|
threadId: thread?.threadId ?? null,
|
|
4527
5008
|
threadStatus: thread?.status ?? null,
|
|
4528
5009
|
threadUpdatedAt: thread?.updatedAt ?? null
|
|
@@ -4612,7 +5093,7 @@ import {
|
|
|
4612
5093
|
copyFileSync,
|
|
4613
5094
|
existsSync as existsSync14,
|
|
4614
5095
|
mkdirSync as mkdirSync7,
|
|
4615
|
-
readFileSync as
|
|
5096
|
+
readFileSync as readFileSync12
|
|
4616
5097
|
} from "fs";
|
|
4617
5098
|
import { dirname as dirname5, join as join16 } from "path";
|
|
4618
5099
|
function isKickoffText(text) {
|
|
@@ -4623,7 +5104,7 @@ function isKickoffText(text) {
|
|
|
4623
5104
|
function extractFirstRealUserMessage(rolloutPath) {
|
|
4624
5105
|
if (!existsSync14(rolloutPath))
|
|
4625
5106
|
return null;
|
|
4626
|
-
const raw =
|
|
5107
|
+
const raw = readFileSync12(rolloutPath, "utf-8");
|
|
4627
5108
|
for (const line of raw.split(`
|
|
4628
5109
|
`)) {
|
|
4629
5110
|
if (!line.trim())
|
|
@@ -4790,9 +5271,11 @@ var init_resume_pollution = __esm(() => {
|
|
|
4790
5271
|
var exports_doctor = {};
|
|
4791
5272
|
__export(exports_doctor, {
|
|
4792
5273
|
runDoctor: () => runDoctor,
|
|
4793
|
-
formatDoctorReport: () => formatDoctorReport
|
|
5274
|
+
formatDoctorReport: () => formatDoctorReport,
|
|
5275
|
+
evaluateArtifactAlignment: () => evaluateArtifactAlignment,
|
|
5276
|
+
describeBuildDrift: () => describeBuildDrift
|
|
4794
5277
|
});
|
|
4795
|
-
import { existsSync as existsSync15, readFileSync as
|
|
5278
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
|
|
4796
5279
|
import { join as join17 } from "path";
|
|
4797
5280
|
async function runDoctor(args = []) {
|
|
4798
5281
|
if (args[0] === "resume-pollution") {
|
|
@@ -4869,6 +5352,7 @@ function runResumePollution(args) {
|
|
|
4869
5352
|
}
|
|
4870
5353
|
async function buildDoctorReport(pair, registered) {
|
|
4871
5354
|
const cwd = process.cwd();
|
|
5355
|
+
const cli = cliInvocationName();
|
|
4872
5356
|
const env = inspectAgentBridgeEnv({ cwd, env: process.env });
|
|
4873
5357
|
const [health, ready] = registered ? await Promise.all([
|
|
4874
5358
|
fetchDaemonStatus(pair.ports.controlPort, "/healthz"),
|
|
@@ -4888,20 +5372,20 @@ async function buildDoctorReport(pair, registered) {
|
|
|
4888
5372
|
name: "pair registration",
|
|
4889
5373
|
status: registered ? "ok" : "warn",
|
|
4890
5374
|
detail: registered ? pair.manual ? "manual mode (explicit env)" : `registered as ${pair.pairId}` : `not registered yet \u2014 would be ${pair.pairId} (created on first launch)`,
|
|
4891
|
-
hint: registered ? undefined :
|
|
5375
|
+
hint: registered ? undefined : `\u8BE5\u76EE\u5F55\u8FD8\u6CA1\u6709\u6CE8\u518C\u8FC7 pair\uFF1A\u8FD0\u884C \`${cli} claude\` \u5373\u4F1A\u521B\u5EFA\u3002\u4EE5\u4E0B\u68C0\u67E5\u6309\u672A\u542F\u52A8\u72B6\u6001\u89E3\u8BFB\u3002`
|
|
4892
5376
|
});
|
|
4893
5377
|
checks.push({
|
|
4894
5378
|
name: "env",
|
|
4895
5379
|
status: env.ok ? "ok" : "fail",
|
|
4896
5380
|
detail: env.ok ? "AgentBridge env matches cwd" : env.reasons.join("; "),
|
|
4897
|
-
hint: env.ok ? undefined :
|
|
5381
|
+
hint: env.ok ? undefined : `\u73AF\u5883\u53D8\u91CF\u4E0E\u5F53\u524D\u76EE\u5F55\u4E0D\u5339\u914D\uFF1A\u8BF7\u5728\u6B63\u786E\u7684\u9879\u76EE\u76EE\u5F55\u91CC\u91CD\u65B0\u8FD0\u884C \`${cli} claude\`\uFF0C\u4E0D\u8981\u590D\u7528\u5176\u4ED6\u76EE\u5F55\u7684\u4F1A\u8BDD\u73AF\u5883\u3002`
|
|
4898
5382
|
});
|
|
4899
|
-
checks.push(configParseabilityCheck(cwd));
|
|
5383
|
+
checks.push(configParseabilityCheck(cwd, cli));
|
|
4900
5384
|
checks.push({
|
|
4901
5385
|
name: "daemon health",
|
|
4902
5386
|
status: health ? "ok" : "warn",
|
|
4903
5387
|
detail: health ? `healthz reachable pid=${health.pid}` : registered ? `no daemon reachable on :${pair.ports.controlPort}` : "n/a \u2014 pair not registered",
|
|
4904
|
-
hint: health ? undefined :
|
|
5388
|
+
hint: health ? undefined : `daemon \u672A\u8FD0\u884C\u3002\u8FD0\u884C \`${cli} claude\`\uFF08\u6216 \`${cli} codex\`\uFF09\u4F1A\u81EA\u52A8\u542F\u52A8\u5B83\u3002`
|
|
4905
5389
|
});
|
|
4906
5390
|
checks.push({
|
|
4907
5391
|
name: "daemon readiness",
|
|
@@ -4909,18 +5393,26 @@ async function buildDoctorReport(pair, registered) {
|
|
|
4909
5393
|
detail: ready ? `ready thread=${ready.threadId ?? "none"}` : health ? "readyz is not OK" : "n/a \u2014 daemon not running",
|
|
4910
5394
|
hint: !ready && health ? "daemon \u5728\u8FD0\u884C\u4F46 codex app-server \u5C1A\u672A\u5C31\u7EEA\uFF1B\u7A0D\u5019\u7247\u523B\u91CD\u8BD5\uFF0C\u6301\u7EED\u4E0D\u5C31\u7EEA\u8BF7\u67E5\u770B\u4E0B\u65B9 daemon log\u3002" : undefined
|
|
4911
5395
|
});
|
|
5396
|
+
const appServerInfo = health?.appServerInfo ?? null;
|
|
5397
|
+
checks.push({
|
|
5398
|
+
name: "codex app-server",
|
|
5399
|
+
status: health ? "ok" : "skip",
|
|
5400
|
+
detail: !health ? "n/a \u2014 daemon not running" : appServerInfo ? `version=${appServerInfo.version ?? "unknown"}` + (appServerInfo.platformOs ? ` platform=${appServerInfo.platformOs}` : "") : "not captured yet \u2014 connect Codex (initialize handshake) to populate",
|
|
5401
|
+
hint: health && appServerInfo && appServerInfo.version === null ? "app-server \u672A\u8FD4\u56DE\u53EF\u89E3\u6790\u7684\u7248\u672C\u53F7\uFF08userAgent \u5F02\u5E38\uFF09\u3002\u82E5\u521A\u5347\u7EA7\u8FC7 Codex\uFF0C\u8BF7\u6838\u5BF9 codex-adapter \u7684 version-coupling checklist\u3002" : undefined
|
|
5402
|
+
});
|
|
5403
|
+
const drift = buildDrift === true ? describeBuildDrift(health?.build, BUILD_INFO, cli) : null;
|
|
4912
5404
|
checks.push({
|
|
4913
5405
|
name: "build drift",
|
|
4914
5406
|
status: buildDrift === false ? "ok" : buildDrift === true ? "fail" : "skip",
|
|
4915
|
-
detail: buildDrift === false ? `runtime matches launcher ${formatBuildInfo(BUILD_INFO)}` :
|
|
4916
|
-
hint:
|
|
5407
|
+
detail: buildDrift === false ? `runtime matches launcher ${formatBuildInfo(BUILD_INFO)}` : drift ? drift.detail : launcherStamped ? "n/a \u2014 daemon not running" : "n/a \u2014 launcher running from source (unstamped)",
|
|
5408
|
+
hint: drift?.hint
|
|
4917
5409
|
});
|
|
4918
5410
|
checks.push(artifactAlignmentCheck());
|
|
4919
5411
|
checks.push({
|
|
4920
5412
|
name: "current thread",
|
|
4921
5413
|
status: usableThread ? "ok" : rawThread ? "warn" : registered ? "warn" : "skip",
|
|
4922
5414
|
detail: usableThread ? `current=${usableThread.threadId}` : rawThread ? rawThread.status === "current" ? `stored ${rawThread.threadId} has no rollout file yet` : `stored ${rawThread.threadId} is still ${rawThread.status} (no first response yet)` : registered ? "no current-thread.json for this pair" : "n/a \u2014 pair not registered",
|
|
4923
|
-
hint: usableThread ? undefined : rawThread ? "\u901A\u5E38\u65E0\u5BB3\uFF1A\u7EBF\u7A0B\u8FD8\u6CA1\u6709\u4EA7\u751F\u9996\u6761\u56DE\u5E94\u3001\u6216 rollout \u6587\u4EF6\u5C1A\u672A\u843D\u76D8\u3002" +
|
|
5415
|
+
hint: usableThread ? undefined : rawThread ? "\u901A\u5E38\u65E0\u5BB3\uFF1A\u7EBF\u7A0B\u8FD8\u6CA1\u6709\u4EA7\u751F\u9996\u6761\u56DE\u5E94\u3001\u6216 rollout \u6587\u4EF6\u5C1A\u672A\u843D\u76D8\u3002" + `\u4EC5\u5F53 \`${cli} codex\`\uFF08resume\uFF09\u5931\u8D25\u65F6\u624D\u9700\u8981\u5904\u7406\uFF1A\u7528 \`${cli} codex --new\` \u5F00\u65B0\u7EBF\u7A0B\u3002` : registered ? "\u5C1A\u65E0\u7EBF\u7A0B\u8BB0\u5F55\uFF1A\u8FDE\u63A5 Codex \u540E\u5EFA\u7ACB\u9996\u4E2A\u7EBF\u7A0B\u65F6\u4F1A\u81EA\u52A8\u5199\u5165\uFF0C\u65E0\u9700\u5904\u7406\u3002" : undefined
|
|
4924
5416
|
});
|
|
4925
5417
|
const pairProxyUrl = `ws://127.0.0.1:${pair.ports.proxyPort}`;
|
|
4926
5418
|
const managedTuis = listManagedCodexTuiProcesses();
|
|
@@ -4937,19 +5429,19 @@ async function buildDoctorReport(pair, registered) {
|
|
|
4937
5429
|
name: "codex tui (this pair)",
|
|
4938
5430
|
status: attachedHere.length > 0 ? "ok" : "warn",
|
|
4939
5431
|
detail: attachedHere.length > 0 ? `${attachedHere.length} attached to ${pairProxyUrl} (pid ${attachedHere.map((t) => t.pid).join(", ")})` : `no managed Codex TUI attached to this pair's proxy ${pairProxyUrl}`,
|
|
4940
|
-
hint: attachedHere.length > 0 ? undefined :
|
|
5432
|
+
hint: attachedHere.length > 0 ? undefined : `\u53E6\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u3001\u5728\u540C\u4E00\u76EE\u5F55\u8FD0\u884C \`${cli} codex\` \u8FDE\u63A5\u672C pair\u3002`
|
|
4941
5433
|
});
|
|
4942
5434
|
checks.push({
|
|
4943
5435
|
name: "codex tui (other pairs)",
|
|
4944
5436
|
status: attachedElsewhere.length > 0 ? "warn" : "ok",
|
|
4945
5437
|
detail: attachedElsewhere.length > 0 ? `${attachedElsewhere.length} managed Codex TUI(s) attached to a DIFFERENT pair/proxy \u2014 likely started from another cwd, will not bridge here: ` + attachedElsewhere.map((t) => `pid ${t.pid}\u2192${t.remoteUrl ?? "?"}`).join(", ") : "no managed Codex TUI attached to another pair",
|
|
4946
|
-
hint: attachedElsewhere.length > 0 ?
|
|
5438
|
+
hint: attachedElsewhere.length > 0 ? `\u8FD9\u4E9B TUI \u5C5E\u4E8E\u5176\u4ED6\u76EE\u5F55\u7684 pair\uFF0C\u4E0D\u5F71\u54CD\u672C pair\uFF1B\u5B83\u4EEC\u4E0D\u4F1A\u6865\u63A5\u5230\u8FD9\u91CC\u3002\u5982\u4E0D\u518D\u9700\u8981\uFF0C\u53BB\u5BF9\u5E94\u76EE\u5F55\u8FD0\u884C \`${cli} kill\`\u3002` : undefined
|
|
4947
5439
|
});
|
|
4948
5440
|
for (const [name, path] of [
|
|
4949
5441
|
["daemon log", pair.stateDir.logFile],
|
|
4950
5442
|
["codex wrapper log", pair.stateDir.codexWrapperLogFile]
|
|
4951
5443
|
]) {
|
|
4952
|
-
checks.push(logCheck(name, path));
|
|
5444
|
+
checks.push(logCheck(name, path, cli));
|
|
4953
5445
|
}
|
|
4954
5446
|
return {
|
|
4955
5447
|
cwd,
|
|
@@ -4970,61 +5462,102 @@ async function buildDoctorReport(pair, registered) {
|
|
|
4970
5462
|
checks
|
|
4971
5463
|
};
|
|
4972
5464
|
}
|
|
5465
|
+
function describeBuildDrift(runtime, launcher, cli = "abg") {
|
|
5466
|
+
const basis = runtimeContractComparisonBasis(runtime, launcher);
|
|
5467
|
+
const baseDetail = `runtime ${formatBuildInfo(runtime)} differs from launcher ${formatBuildInfo(launcher)}`;
|
|
5468
|
+
const baseHint = "daemon \u8FD0\u884C\u7684\u662F\u65E7\u6784\u5EFA\uFF08\u901A\u5E38\u7531\u65E7\u7248 CLI \u6216\u672A\u91CD\u5F00\u7684 Claude Code \u7A97\u53E3\u542F\u52A8\uFF09\u3002" + `\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684 Codex \u4F1A\u8BDD\u65F6\uFF0C\u8FD0\u884C \`${cli} kill\` \u540E\u91CD\u65B0 \`${cli} claude\` \u5373\u53EF\u5BF9\u9F50\uFF1B` + "\u6709\u6D3B\u8DC3\u4F1A\u8BDD\u5219\u7B49\u6536\u5C3E\u540E\u518D\u91CD\u542F\u2014\u2014\u7248\u672C\u5DEE\u5F02\u4E0D\u4F1A\u5F3A\u6740\u6D3B\u8DC3\u4F1A\u8BDD\uFF0C\u53EF\u4EE5\u7EE7\u7EED\u7528\u3002";
|
|
5469
|
+
if (basis === "codeHash") {
|
|
5470
|
+
return { detail: `${baseDetail} [compared by codeHash \u2014 real code difference]`, hint: baseHint };
|
|
5471
|
+
}
|
|
5472
|
+
return {
|
|
5473
|
+
detail: `${baseDetail} [compared by commit stamp \u2014 legacy build without codeHash]`,
|
|
5474
|
+
hint: baseHint + "\uFF08\u6CE8\u610F\uFF1A\u672C\u5224\u5B9A\u57FA\u4E8E commit stamp \u53E3\u5F84\u2014\u2014\u6709\u4E00\u4FA7\u662F\u7F3A codeHash \u7684\u65E7\u6784\u5EFA\uFF1Bsquash \u5408\u5E76\u4F1A\u8BA9 stamp \u6EDE\u540E\u4E00\u683C\uFF0C" + "\u6E90\u7801\u4E00\u81F4\u65F6\u4E5F\u53EF\u80FD\u8BEF\u62A5\u3002\u5347\u7EA7\u4E24\u7AEF\u5230\u5E26 codeHash \u7684\u6784\u5EFA\u540E\u5C06\u6309\u4EE3\u7801\u5185\u5BB9\u5224\u5B9A\u3002\uFF09"
|
|
5475
|
+
};
|
|
5476
|
+
}
|
|
5477
|
+
function isUsableCodeHash(hash) {
|
|
5478
|
+
return typeof hash === "string" && hash.length > 0 && hash !== "source";
|
|
5479
|
+
}
|
|
5480
|
+
function evaluateArtifactAlignment(stamps) {
|
|
5481
|
+
if (stamps.length < 2) {
|
|
5482
|
+
return {
|
|
5483
|
+
name: "artifact alignment",
|
|
5484
|
+
status: "skip",
|
|
5485
|
+
detail: "n/a \u2014 fewer than two stamped artifacts found"
|
|
5486
|
+
};
|
|
5487
|
+
}
|
|
5488
|
+
if (stamps.every((stamp) => isUsableCodeHash(stamp.codeHash))) {
|
|
5489
|
+
const rendered2 = stamps.map((stamp) => `${stamp.label}=${stamp.codeHash}`).join(", ");
|
|
5490
|
+
if (new Set(stamps.map((stamp) => stamp.codeHash)).size === 1) {
|
|
5491
|
+
return { name: "artifact alignment", status: "ok", detail: `codeHash basis: ${rendered2}` };
|
|
5492
|
+
}
|
|
5493
|
+
return {
|
|
5494
|
+
name: "artifact alignment",
|
|
5495
|
+
status: "fail",
|
|
5496
|
+
detail: `deployed artifacts contain DIFFERENT code (codeHash basis): ${rendered2}`,
|
|
5497
|
+
hint: "\u90E8\u7F72\u7269\u4EE3\u7801\u5206\u88C2\u4F1A\u5BFC\u81F4\u4E92\u76F8\u66FF\u6362 daemon\uFF08\u6740\u6389\u6D3B\u4F1A\u8BDD\uFF09\u3002\u5728\u4ED3\u5E93\u76EE\u5F55\u8FD0\u884C `bun run install:global` " + "\u4E00\u6B21\u6027\u5BF9\u9F50\u5168\u5C40 CLI \u4E0E\u63D2\u4EF6\u7F13\u5B58\uFF0C\u7136\u540E\u5173\u95ED\u5E76\u91CD\u5F00\u4ECD\u5728\u4F7F\u7528\u65E7\u63D2\u4EF6\u7684 Claude Code \u7A97\u53E3\u3002"
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
const rendered = stamps.map((stamp) => `${stamp.label}=${stamp.commit}`).join(", ");
|
|
5501
|
+
if (new Set(stamps.map((stamp) => stamp.commit)).size === 1) {
|
|
5502
|
+
return {
|
|
5503
|
+
name: "artifact alignment",
|
|
5504
|
+
status: "ok",
|
|
5505
|
+
detail: `legacy commit-stamp basis: ${rendered}`
|
|
5506
|
+
};
|
|
5507
|
+
}
|
|
5508
|
+
return {
|
|
5509
|
+
name: "artifact alignment",
|
|
5510
|
+
status: "fail",
|
|
5511
|
+
detail: `deployed artifacts are at DIFFERENT builds (legacy commit-stamp basis): ${rendered}`,
|
|
5512
|
+
hint: "\uFF08stamp \u53E3\u5F84\uFF1A\u5B58\u5728\u7F3A codeHash \u7684\u65E7\u90E8\u7F72\u7269\uFF0C\u4E14 squash \u5408\u5E76\u4F1A\u8BA9 stamp \u6EDE\u540E\u4E00\u683C\uFF0C\u6E90\u7801\u4E00\u81F4\u65F6\u4E5F\u53EF\u80FD\u8BEF\u62A5\u3002\uFF09" + "\u90E8\u7F72\u7269\u7248\u672C\u5206\u88C2\u4F1A\u5BFC\u81F4\u4E92\u76F8\u66FF\u6362 daemon\uFF08\u6740\u6389\u6D3B\u4F1A\u8BDD\uFF09\u3002\u5728\u4ED3\u5E93\u76EE\u5F55\u8FD0\u884C `bun run install:global` " + "\u4E00\u6B21\u6027\u5BF9\u9F50\u5168\u5C40 CLI \u4E0E\u63D2\u4EF6\u7F13\u5B58\u5E76\u5347\u7EA7\u5230\u5E26 codeHash \u7684\u6784\u5EFA\uFF0C\u7136\u540E\u5173\u95ED\u5E76\u91CD\u5F00\u4ECD\u5728\u4F7F\u7528\u65E7\u63D2\u4EF6\u7684 Claude Code \u7A97\u53E3\uFF1B" + "\u5BF9\u9F50\u540E\u6B64\u68C0\u67E5\u5C06\u6309\u4EE3\u7801\u5185\u5BB9\uFF08codeHash\uFF09\u5224\u5B9A\uFF0Cstamp \u6EDE\u540E\u4E0D\u518D\u8BEF\u62A5\u3002"
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
4973
5515
|
function artifactAlignmentCheck() {
|
|
4974
5516
|
const stamps = [];
|
|
4975
5517
|
if (BUILD_INFO.commit !== "source") {
|
|
4976
|
-
stamps.push({
|
|
5518
|
+
stamps.push({
|
|
5519
|
+
label: `launcher(${BUILD_INFO.bundle})`,
|
|
5520
|
+
commit: BUILD_INFO.commit,
|
|
5521
|
+
codeHash: hasValidCodeHash(BUILD_INFO) ? BUILD_INFO.codeHash ?? null : null
|
|
5522
|
+
});
|
|
4977
5523
|
}
|
|
4978
5524
|
const bin = Bun.which("agentbridge") ?? Bun.which("abg");
|
|
4979
5525
|
if (bin) {
|
|
4980
5526
|
try {
|
|
4981
|
-
const
|
|
4982
|
-
if (
|
|
4983
|
-
stamps.push({ label: "global-cli",
|
|
5527
|
+
const stamp = extractBundleStamp(realpathSync3(bin));
|
|
5528
|
+
if (stamp)
|
|
5529
|
+
stamps.push({ label: "global-cli", ...stamp });
|
|
4984
5530
|
} catch {}
|
|
4985
5531
|
}
|
|
4986
5532
|
const cacheRoot = pluginCacheRoot();
|
|
4987
5533
|
try {
|
|
4988
5534
|
for (const version of readdirSync6(cacheRoot)) {
|
|
4989
|
-
const
|
|
4990
|
-
if (
|
|
4991
|
-
stamps.push({ label: `plugin-cache@${version}`,
|
|
5535
|
+
const stamp = extractBundleStamp(join17(cacheRoot, version, "server", "daemon.js"));
|
|
5536
|
+
if (stamp)
|
|
5537
|
+
stamps.push({ label: `plugin-cache@${version}`, ...stamp });
|
|
4992
5538
|
}
|
|
4993
5539
|
} catch {}
|
|
4994
5540
|
const repoBundle = join17(process.cwd(), "plugins", "agentbridge", "server", "daemon.js");
|
|
4995
5541
|
if (existsSync15(repoBundle)) {
|
|
4996
|
-
const
|
|
4997
|
-
if (
|
|
4998
|
-
stamps.push({ label: "repo-bundle",
|
|
4999
|
-
}
|
|
5000
|
-
if (stamps.length < 2) {
|
|
5001
|
-
return {
|
|
5002
|
-
name: "artifact alignment",
|
|
5003
|
-
status: "skip",
|
|
5004
|
-
detail: "n/a \u2014 fewer than two stamped artifacts found"
|
|
5005
|
-
};
|
|
5542
|
+
const stamp = extractBundleStamp(repoBundle);
|
|
5543
|
+
if (stamp)
|
|
5544
|
+
stamps.push({ label: "repo-bundle", ...stamp });
|
|
5006
5545
|
}
|
|
5007
|
-
|
|
5008
|
-
const rendered = stamps.map((s) => `${s.label}=${s.commit}`).join(", ");
|
|
5009
|
-
if (commits.size === 1) {
|
|
5010
|
-
return { name: "artifact alignment", status: "ok", detail: rendered };
|
|
5011
|
-
}
|
|
5012
|
-
return {
|
|
5013
|
-
name: "artifact alignment",
|
|
5014
|
-
status: "fail",
|
|
5015
|
-
detail: `deployed artifacts are at DIFFERENT builds: ${rendered}`,
|
|
5016
|
-
hint: "\u90E8\u7F72\u7269\u7248\u672C\u5206\u88C2\u4F1A\u5BFC\u81F4\u4E92\u76F8\u66FF\u6362 daemon\uFF08\u6740\u6389\u6D3B\u4F1A\u8BDD\uFF09\u3002\u5728\u4ED3\u5E93\u76EE\u5F55\u8FD0\u884C `bun run install:global` " + "\u4E00\u6B21\u6027\u5BF9\u9F50\u5168\u5C40 CLI \u4E0E\u63D2\u4EF6\u7F13\u5B58\uFF0C\u7136\u540E\u5173\u95ED\u5E76\u91CD\u5F00\u4ECD\u5728\u4F7F\u7528\u65E7\u63D2\u4EF6\u7684 Claude Code \u7A97\u53E3\u3002"
|
|
5017
|
-
};
|
|
5546
|
+
return evaluateArtifactAlignment(stamps);
|
|
5018
5547
|
}
|
|
5019
|
-
function
|
|
5548
|
+
function extractBundleStamp(path) {
|
|
5020
5549
|
try {
|
|
5021
|
-
const
|
|
5022
|
-
|
|
5550
|
+
const text = readFileSync13(path, "utf-8");
|
|
5551
|
+
const commit = text.match(/commit:\s*defineString\("([^"]+)",\s*"source"\)/)?.[1] ?? null;
|
|
5552
|
+
if (!commit)
|
|
5553
|
+
return null;
|
|
5554
|
+
const codeHash = text.match(/codeHash:\s*defineString\("([^"]+)",\s*"source"\)/)?.[1] ?? null;
|
|
5555
|
+
return { commit, codeHash };
|
|
5023
5556
|
} catch {
|
|
5024
5557
|
return null;
|
|
5025
5558
|
}
|
|
5026
5559
|
}
|
|
5027
|
-
function configParseabilityCheck(cwd) {
|
|
5560
|
+
function configParseabilityCheck(cwd, cli) {
|
|
5028
5561
|
const desc = new ConfigService(cwd).describeConfig();
|
|
5029
5562
|
if (desc.state === "absent") {
|
|
5030
5563
|
return {
|
|
@@ -5038,7 +5571,7 @@ function configParseabilityCheck(cwd) {
|
|
|
5038
5571
|
name: "config.json",
|
|
5039
5572
|
status: "warn",
|
|
5040
5573
|
detail: `unparseable at ${desc.path} (${desc.reason}) \u2014 custom thresholds NOT in effect, using defaults`,
|
|
5041
|
-
hint: "config.json \u635F\u574F\u6216\u5B57\u6BB5\u7C7B\u578B\u9519\u8BEF\uFF1Abridge \u5DF2\u56DE\u9000\u5230\u9ED8\u8BA4\u9608\u503C\uFF0C\u4F60\u7684\u81EA\u5B9A\u4E49 budget/idle \u8BBE\u7F6E\u672A\u751F\u6548\u3002" +
|
|
5574
|
+
hint: "config.json \u635F\u574F\u6216\u5B57\u6BB5\u7C7B\u578B\u9519\u8BEF\uFF1Abridge \u5DF2\u56DE\u9000\u5230\u9ED8\u8BA4\u9608\u503C\uFF0C\u4F60\u7684\u81EA\u5B9A\u4E49 budget/idle \u8BBE\u7F6E\u672A\u751F\u6548\u3002" + `\u4FEE\u6B63\u8BE5\u6587\u4EF6\u7684 JSON \u8BED\u6CD5/\u5B57\u6BB5\u7C7B\u578B\u540E\u91CD\u542F \`${cli} claude\` \u5373\u53EF\u91CD\u65B0\u751F\u6548\u3002`
|
|
5042
5575
|
};
|
|
5043
5576
|
}
|
|
5044
5577
|
return {
|
|
@@ -5047,7 +5580,7 @@ function configParseabilityCheck(cwd) {
|
|
|
5047
5580
|
detail: desc.customValues ? `parsed at ${desc.path} \u2014 custom values in effect` : `parsed at ${desc.path} \u2014 all values match defaults`
|
|
5048
5581
|
};
|
|
5049
5582
|
}
|
|
5050
|
-
function logCheck(name, path) {
|
|
5583
|
+
function logCheck(name, path, cli) {
|
|
5051
5584
|
if (!existsSync15(path)) {
|
|
5052
5585
|
return {
|
|
5053
5586
|
name,
|
|
@@ -5062,7 +5595,7 @@ function logCheck(name, path) {
|
|
|
5062
5595
|
name,
|
|
5063
5596
|
status: "warn",
|
|
5064
5597
|
detail: `${path} (${stat.size} bytes, oversized; stop the pair, rebuild/reinstall, then rotate or remove this log)`,
|
|
5065
|
-
hint:
|
|
5598
|
+
hint: `\u65E5\u5FD7\u8FC7\u5927\uFF1A\`${cli} kill\` \u505C\u6B62 pair \u540E\u5220\u9664\u8BE5\u6587\u4EF6\u518D\u91CD\u542F\u5373\u53EF\u3002`
|
|
5066
5599
|
};
|
|
5067
5600
|
}
|
|
5068
5601
|
return { name, status: "ok", detail: `${path} (${stat.size} bytes)` };
|
|
@@ -5100,6 +5633,7 @@ var LARGE_LOG_WARN_BYTES;
|
|
|
5100
5633
|
var init_doctor = __esm(() => {
|
|
5101
5634
|
init_plugin_cache();
|
|
5102
5635
|
init_build_info();
|
|
5636
|
+
init_cli_invocation();
|
|
5103
5637
|
init_config_service();
|
|
5104
5638
|
init_env_guard();
|
|
5105
5639
|
init_pair_resolver();
|
|
@@ -5201,6 +5735,7 @@ __export(exports_budget, {
|
|
|
5201
5735
|
});
|
|
5202
5736
|
async function runBudget(args) {
|
|
5203
5737
|
const json = args.includes("--json");
|
|
5738
|
+
const cli = cliInvocationName();
|
|
5204
5739
|
const { pairFlag } = parsePairFlag(args.filter((arg) => arg !== "--json"));
|
|
5205
5740
|
let resolution;
|
|
5206
5741
|
try {
|
|
@@ -5220,7 +5755,7 @@ async function runBudget(args) {
|
|
|
5220
5755
|
if (json) {
|
|
5221
5756
|
console.log(JSON.stringify({ ok: false, error: "pair_not_registered" }));
|
|
5222
5757
|
} else {
|
|
5223
|
-
console.error(
|
|
5758
|
+
console.error(`\u8BE5\u76EE\u5F55\u5C1A\u65E0 pair\uFF0C\u5148\u8FD0\u884C ${cli} claude`);
|
|
5224
5759
|
}
|
|
5225
5760
|
process.exit(1);
|
|
5226
5761
|
return;
|
|
@@ -5230,7 +5765,7 @@ async function runBudget(args) {
|
|
|
5230
5765
|
if (json) {
|
|
5231
5766
|
console.log(JSON.stringify({ ok: false, pairId: pair.pairId, error: "daemon_unreachable" }));
|
|
5232
5767
|
} else {
|
|
5233
|
-
console.error(`AgentBridge daemon \u672A\u8FD0\u884C\uFF08pair ${pair.pairId}\uFF0C\u63A7\u5236\u7AEF\u53E3 ${pair.ports.controlPort}\uFF09\u3002` +
|
|
5768
|
+
console.error(`AgentBridge daemon \u672A\u8FD0\u884C\uFF08pair ${pair.pairId}\uFF0C\u63A7\u5236\u7AEF\u53E3 ${pair.ports.controlPort}\uFF09\u3002` + `\u5148\u8FD0\u884C \`${cli} claude\` \u542F\u52A8\u4F1A\u8BDD\u3002`);
|
|
5234
5769
|
}
|
|
5235
5770
|
process.exit(1);
|
|
5236
5771
|
}
|
|
@@ -5242,6 +5777,7 @@ async function runBudget(args) {
|
|
|
5242
5777
|
console.log(status.budget ? renderBudgetSnapshot(status.budget) : BUDGET_UNAVAILABLE_TEXT);
|
|
5243
5778
|
}
|
|
5244
5779
|
var init_budget = __esm(() => {
|
|
5780
|
+
init_cli_invocation();
|
|
5245
5781
|
init_pair_resolver();
|
|
5246
5782
|
init_render();
|
|
5247
5783
|
});
|
|
@@ -5254,7 +5790,7 @@ __export(exports_logs, {
|
|
|
5254
5790
|
parseLogsArgs: () => parseLogsArgs,
|
|
5255
5791
|
followLog: () => followLog
|
|
5256
5792
|
});
|
|
5257
|
-
import { existsSync as existsSync16, readFileSync as
|
|
5793
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
5258
5794
|
import { spawn as spawn4 } from "child_process";
|
|
5259
5795
|
function parseLogsArgs(args) {
|
|
5260
5796
|
let codex = false;
|
|
@@ -5347,7 +5883,7 @@ async function runLogs(args) {
|
|
|
5347
5883
|
function printTail(logPath, count, label, pairName) {
|
|
5348
5884
|
let text;
|
|
5349
5885
|
try {
|
|
5350
|
-
text =
|
|
5886
|
+
text = readFileSync14(logPath, "utf8");
|
|
5351
5887
|
} catch (err) {
|
|
5352
5888
|
console.error(`[agentbridge] failed to read ${label} for pair ${pairName}: ` + `${err instanceof Error ? err.message : String(err)} (${logPath})`);
|
|
5353
5889
|
process.exit(1);
|
|
@@ -5492,6 +6028,7 @@ async function main(command, restArgs) {
|
|
|
5492
6028
|
}
|
|
5493
6029
|
}
|
|
5494
6030
|
function printHelp() {
|
|
6031
|
+
const cli = cliInvocationName();
|
|
5495
6032
|
console.log(`
|
|
5496
6033
|
AgentBridge \u2014 Multi-agent collaboration bridge
|
|
5497
6034
|
|
|
@@ -5509,8 +6046,9 @@ Commands:
|
|
|
5509
6046
|
No target: print resume commands for this directory's last
|
|
5510
6047
|
Claude session + this pair's current Codex thread.
|
|
5511
6048
|
With target: resume that side directly.
|
|
5512
|
-
pairs [rm <name|id> | prune [--
|
|
5513
|
-
List pairs; remove one (rm), or
|
|
6049
|
+
pairs [rm <name|id> | prune [--apply]]
|
|
6050
|
+
List pairs; remove one (rm), or reclaim orphan dirs + stranded
|
|
6051
|
+
entries (prune previews by default; --apply to delete)
|
|
5514
6052
|
doctor [--json] Diagnose env, daemon, build drift, logs, and current thread
|
|
5515
6053
|
doctor resume-pollution [--apply] Find/fix old AgentBridge kickoff metadata
|
|
5516
6054
|
budget [--json] Show both agents' subscription quota snapshot (5h/weekly, drift, pause state)
|
|
@@ -5542,30 +6080,30 @@ Multi-pair:
|
|
|
5542
6080
|
contesting it \u2014 pick another --pair name (or kill the live one first).
|
|
5543
6081
|
|
|
5544
6082
|
Examples:
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
6083
|
+
${cli} init # First-time setup
|
|
6084
|
+
${cli} claude # Start the "main" pair for this directory
|
|
6085
|
+
${cli} codex # Connect Codex to this directory's "main" pair
|
|
6086
|
+
${cli} resume # Print resume commands for both sides
|
|
6087
|
+
${cli} resume claude # Resume the last Claude Code session here
|
|
6088
|
+
${cli} resume codex # Resume this pair's current Codex thread
|
|
6089
|
+
${cli} claude --safe # One launch without the max-permission default
|
|
6090
|
+
${cli} --pair work claude # Start a named pair "work" (this directory)
|
|
6091
|
+
${cli} --pair work codex # Connect Codex to the "work" pair
|
|
6092
|
+
${cli} --pair review claude # A second, parallel pair
|
|
6093
|
+
${cli} pairs # List all pairs and their ports/status
|
|
6094
|
+
${cli} pairs --threads # Include current thread mapping
|
|
6095
|
+
${cli} doctor --json # Emit a structured diagnostics report
|
|
6096
|
+
${cli} logs # Tail the last 100 lines of this pair's daemon log
|
|
6097
|
+
${cli} logs -f -n 200 # Follow the log, starting from the last 200 lines
|
|
6098
|
+
${cli} logs --codex # Tail the codex wrapper log instead
|
|
6099
|
+
${cli} --pair work logs # Tail the "work" pair's daemon log
|
|
6100
|
+
${cli} pairs rm work # Stop this directory's "work" pair and free its slot
|
|
6101
|
+
${cli} pairs rm work-1a2b3c4d # ...or by its full id (from that pair's directory)
|
|
6102
|
+
${cli} pairs prune # Preview reclaimable: orphan dirs + stranded entries (cwd-gone, dead, >1d)
|
|
6103
|
+
${cli} pairs prune --apply # ...actually delete the previewed dirs + entries
|
|
6104
|
+
${cli} --pair work kill # Stop only this directory's "work" pair
|
|
6105
|
+
${cli} kill # Stop this directory's pairs (+ any legacy-root daemon)
|
|
6106
|
+
${cli} kill all # Stop every pair in every directory (+ legacy-root)
|
|
5569
6107
|
`.trim());
|
|
5570
6108
|
}
|
|
5571
6109
|
function printVersion() {
|
|
@@ -5578,6 +6116,7 @@ function printVersion() {
|
|
|
5578
6116
|
}
|
|
5579
6117
|
var MARKETPLACE_NAME = "agentbridge", PLUGIN_NAME = "agentbridge", REFRESH_COMMANDS, NOTIFY_COMMANDS, PAIR_AWARE_COMMANDS;
|
|
5580
6118
|
var init_cli = __esm(() => {
|
|
6119
|
+
init_cli_invocation();
|
|
5581
6120
|
REFRESH_COMMANDS = new Set(["claude", "codex", "resume"]);
|
|
5582
6121
|
NOTIFY_COMMANDS = new Set(["claude", "codex", "init", "dev", "resume"]);
|
|
5583
6122
|
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget", "resume", "logs"]);
|