@raysonmeng/agentbridge 0.1.11 → 0.1.12
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/dist/cli.js +683 -216
- package/dist/daemon.js +932 -212
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +336 -109
- package/plugins/agentbridge/server/daemon.js +932 -212
package/dist/cli.js
CHANGED
|
@@ -120,7 +120,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
120
120
|
var require_package = __commonJS((exports, module) => {
|
|
121
121
|
module.exports = {
|
|
122
122
|
name: "@raysonmeng/agentbridge",
|
|
123
|
-
version: "0.1.
|
|
123
|
+
version: "0.1.12",
|
|
124
124
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
125
125
|
type: "module",
|
|
126
126
|
packageManager: "bun@1.3.11",
|
|
@@ -323,6 +323,48 @@ import { join as join2 } from "path";
|
|
|
323
323
|
function isRecord(value) {
|
|
324
324
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
325
325
|
}
|
|
326
|
+
function isCoercibleNumber(value) {
|
|
327
|
+
if (typeof value === "number")
|
|
328
|
+
return Number.isFinite(value);
|
|
329
|
+
if (typeof value === "string")
|
|
330
|
+
return Number.isFinite(Number(value));
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
function findShapeViolation(raw) {
|
|
334
|
+
if ("idleShutdownSeconds" in raw && !isCoercibleNumber(raw.idleShutdownSeconds)) {
|
|
335
|
+
return "idleShutdownSeconds is present but not a number";
|
|
336
|
+
}
|
|
337
|
+
if ("budget" in raw) {
|
|
338
|
+
const budget = raw.budget;
|
|
339
|
+
if (!isRecord(budget)) {
|
|
340
|
+
return "budget is present but not an object";
|
|
341
|
+
}
|
|
342
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
343
|
+
for (const key of numericKeys) {
|
|
344
|
+
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
345
|
+
return `budget.${key} is present but not a number`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if ("parallel" in budget) {
|
|
349
|
+
const parallel = budget.parallel;
|
|
350
|
+
if (!isRecord(parallel)) {
|
|
351
|
+
return "budget.parallel is present but not an object";
|
|
352
|
+
}
|
|
353
|
+
for (const key of ["minRemainingPct", "timeWindowSec"]) {
|
|
354
|
+
if (key in parallel && !isCoercibleNumber(parallel[key])) {
|
|
355
|
+
return `budget.parallel.${key} is present but not a number`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
function hasCustomDecisionValues(config) {
|
|
363
|
+
const d = DEFAULT_CONFIG;
|
|
364
|
+
const b = config.budget;
|
|
365
|
+
const db = d.budget;
|
|
366
|
+
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl;
|
|
367
|
+
}
|
|
326
368
|
function normalizeInteger(value, fallback) {
|
|
327
369
|
if (typeof value === "number" && Number.isFinite(value))
|
|
328
370
|
return value;
|
|
@@ -358,35 +400,35 @@ function normalizeCodexOverride(raw) {
|
|
|
358
400
|
override.effort = raw.effort.trim();
|
|
359
401
|
return Object.keys(override).length > 0 ? override : null;
|
|
360
402
|
}
|
|
361
|
-
function normalizeCodexTiers(raw) {
|
|
403
|
+
function normalizeCodexTiers(raw, fallback = DEFAULT_BUDGET_CONFIG.codexTiers) {
|
|
362
404
|
const tiers = isRecord(raw) ? raw : {};
|
|
363
405
|
return {
|
|
364
406
|
full: normalizeCodexOverride(tiers.full),
|
|
365
|
-
balanced: normalizeCodexOverride(tiers.balanced) ??
|
|
366
|
-
eco: normalizeCodexOverride(tiers.eco) ??
|
|
407
|
+
balanced: normalizeCodexOverride(tiers.balanced) ?? fallback.balanced,
|
|
408
|
+
eco: normalizeCodexOverride(tiers.eco) ?? fallback.eco
|
|
367
409
|
};
|
|
368
410
|
}
|
|
369
|
-
function normalizeBudgetConfig(raw) {
|
|
411
|
+
function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
370
412
|
const budget = isRecord(raw) ? raw : {};
|
|
371
413
|
const parallel = isRecord(budget.parallel) ? budget.parallel : {};
|
|
372
|
-
const codexTiers = normalizeCodexTiers(budget.codexTiers);
|
|
373
|
-
let pauseAt = normalizeBoundedInteger(budget.pauseAt,
|
|
374
|
-
let resumeBelow = normalizeBoundedInteger(budget.resumeBelow,
|
|
414
|
+
const codexTiers = normalizeCodexTiers(budget.codexTiers, fallback.codexTiers);
|
|
415
|
+
let pauseAt = normalizeBoundedInteger(budget.pauseAt, fallback.pauseAt, 1, 100);
|
|
416
|
+
let resumeBelow = normalizeBoundedInteger(budget.resumeBelow, fallback.resumeBelow, 0, 99);
|
|
375
417
|
if (pauseAt <= resumeBelow) {
|
|
376
418
|
pauseAt = DEFAULT_BUDGET_CONFIG.pauseAt;
|
|
377
419
|
resumeBelow = DEFAULT_BUDGET_CONFIG.resumeBelow;
|
|
378
420
|
}
|
|
379
421
|
return {
|
|
380
|
-
enabled: normalizeBoolean(budget.enabled,
|
|
381
|
-
pollSeconds: normalizeBoundedInteger(budget.pollSeconds,
|
|
422
|
+
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
423
|
+
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
382
424
|
pauseAt,
|
|
383
425
|
resumeBelow,
|
|
384
|
-
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct,
|
|
426
|
+
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
385
427
|
parallel: {
|
|
386
|
-
minRemainingPct: normalizeBoundedInteger(parallel.minRemainingPct,
|
|
387
|
-
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec,
|
|
428
|
+
minRemainingPct: normalizeBoundedInteger(parallel.minRemainingPct, fallback.parallel.minRemainingPct, 1, 100),
|
|
429
|
+
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
388
430
|
},
|
|
389
|
-
codexTierControl: normalizeBoolean(budget.codexTierControl,
|
|
431
|
+
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
390
432
|
codexTiers
|
|
391
433
|
};
|
|
392
434
|
}
|
|
@@ -423,15 +465,59 @@ class ConfigService {
|
|
|
423
465
|
return existsSync2(this.configPath);
|
|
424
466
|
}
|
|
425
467
|
load() {
|
|
468
|
+
let raw;
|
|
426
469
|
try {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
470
|
+
raw = readFileSync2(this.configPath, "utf-8");
|
|
471
|
+
} catch (err) {
|
|
472
|
+
if (err?.code === "ENOENT") {
|
|
473
|
+
return { state: "absent" };
|
|
474
|
+
}
|
|
475
|
+
return { state: "corrupt", reason: `config.json is unreadable: ${err.message}` };
|
|
476
|
+
}
|
|
477
|
+
let parsed;
|
|
478
|
+
try {
|
|
479
|
+
parsed = JSON.parse(raw);
|
|
480
|
+
} catch (err) {
|
|
481
|
+
return {
|
|
482
|
+
state: "corrupt",
|
|
483
|
+
reason: `config.json is not valid JSON: ${err.message}`
|
|
484
|
+
};
|
|
431
485
|
}
|
|
486
|
+
if (!isRecord(parsed)) {
|
|
487
|
+
return { state: "corrupt", reason: "config.json is not a JSON object" };
|
|
488
|
+
}
|
|
489
|
+
const violation = findShapeViolation(parsed);
|
|
490
|
+
if (violation) {
|
|
491
|
+
return { state: "corrupt", reason: `config.json is shape-invalid: ${violation}` };
|
|
492
|
+
}
|
|
493
|
+
const config = normalizeConfig(parsed);
|
|
494
|
+
if (!config) {
|
|
495
|
+
return { state: "corrupt", reason: "config.json could not be normalized" };
|
|
496
|
+
}
|
|
497
|
+
return { state: "parsed", config };
|
|
432
498
|
}
|
|
433
|
-
loadOrDefault() {
|
|
434
|
-
|
|
499
|
+
loadOrDefault(log = NOOP_LOGGER) {
|
|
500
|
+
const result = this.load();
|
|
501
|
+
if (result.state === "parsed")
|
|
502
|
+
return result.config;
|
|
503
|
+
if (result.state === "corrupt") {
|
|
504
|
+
log(`config.json at ${this.configPath} is unusable (${result.reason}); ` + "falling back to defaults \u2014 your custom budget thresholds / idle-shutdown settings are NOT in effect. " + "Fix the file and restart to re-apply them.");
|
|
505
|
+
}
|
|
506
|
+
return structuredClone(DEFAULT_CONFIG);
|
|
507
|
+
}
|
|
508
|
+
describeConfig() {
|
|
509
|
+
const result = this.load();
|
|
510
|
+
if (result.state === "absent") {
|
|
511
|
+
return { state: "absent", path: this.configPath, customValues: false };
|
|
512
|
+
}
|
|
513
|
+
if (result.state === "corrupt") {
|
|
514
|
+
return { state: "corrupt", path: this.configPath, reason: result.reason, customValues: false };
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
state: "parsed",
|
|
518
|
+
path: this.configPath,
|
|
519
|
+
customValues: hasCustomDecisionValues(result.config)
|
|
520
|
+
};
|
|
435
521
|
}
|
|
436
522
|
save(config) {
|
|
437
523
|
this.ensureConfigDir();
|
|
@@ -456,11 +542,11 @@ class ConfigService {
|
|
|
456
542
|
}
|
|
457
543
|
}
|
|
458
544
|
}
|
|
459
|
-
var DEFAULT_BUDGET_CONFIG, DEFAULT_CONFIG, CONFIG_DIR = ".agentbridge", CONFIG_FILE = "config.json";
|
|
545
|
+
var DEFAULT_BUDGET_CONFIG, DEFAULT_CONFIG, CONFIG_DIR = ".agentbridge", CONFIG_FILE = "config.json", NOOP_LOGGER = () => {};
|
|
460
546
|
var init_config_service = __esm(() => {
|
|
461
547
|
DEFAULT_BUDGET_CONFIG = {
|
|
462
548
|
enabled: true,
|
|
463
|
-
pollSeconds:
|
|
549
|
+
pollSeconds: 300,
|
|
464
550
|
pauseAt: 90,
|
|
465
551
|
resumeBelow: 30,
|
|
466
552
|
syncDriftPct: 10,
|
|
@@ -513,6 +599,30 @@ function registerMarketplace(marketplaceRoot) {
|
|
|
513
599
|
}
|
|
514
600
|
var init_pkg_root = () => {};
|
|
515
601
|
|
|
602
|
+
// src/cli/plugin-cache.ts
|
|
603
|
+
import { existsSync as existsSync4 } from "fs";
|
|
604
|
+
import { homedir as homedir2 } from "os";
|
|
605
|
+
import { join as join4, resolve } from "path";
|
|
606
|
+
function pluginCacheRoot(home = homedir2()) {
|
|
607
|
+
return join4(home, ".claude", "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME);
|
|
608
|
+
}
|
|
609
|
+
function isInsideRepoCheckout(projectRoot) {
|
|
610
|
+
const buildScript = resolve(projectRoot, "scripts", "build-bundles.mjs");
|
|
611
|
+
return existsSync4(buildScript);
|
|
612
|
+
}
|
|
613
|
+
function shouldWarnMissingPluginCache(cacheExists) {
|
|
614
|
+
return !cacheExists;
|
|
615
|
+
}
|
|
616
|
+
var MARKETPLACE_STEPS;
|
|
617
|
+
var init_plugin_cache = __esm(() => {
|
|
618
|
+
init_cli();
|
|
619
|
+
MARKETPLACE_STEPS = [
|
|
620
|
+
`/plugin marketplace add raysonmeng/agent-bridge`,
|
|
621
|
+
`/plugin install ${PLUGIN_NAME}@${MARKETPLACE_NAME}`,
|
|
622
|
+
`/reload-plugins`
|
|
623
|
+
];
|
|
624
|
+
});
|
|
625
|
+
|
|
516
626
|
// src/marker-section.ts
|
|
517
627
|
function upsertMarkedSection(content, sectionId, section) {
|
|
518
628
|
const startMarker = MARKER_START(sectionId);
|
|
@@ -649,11 +759,12 @@ var exports_init = {};
|
|
|
649
759
|
__export(exports_init, {
|
|
650
760
|
writeCollaborationSections: () => writeCollaborationSections,
|
|
651
761
|
runInit: () => runInit,
|
|
762
|
+
pluginInstallFallbackGuidance: () => pluginInstallFallbackGuidance,
|
|
652
763
|
compareVersions: () => compareVersions
|
|
653
764
|
});
|
|
654
765
|
import { execSync, execFileSync as execFileSync2 } from "child_process";
|
|
655
766
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
656
|
-
import { join as
|
|
767
|
+
import { join as join5 } from "path";
|
|
657
768
|
async function runInit() {
|
|
658
769
|
console.log(`AgentBridge Init
|
|
659
770
|
`);
|
|
@@ -681,25 +792,54 @@ async function runInit() {
|
|
|
681
792
|
}
|
|
682
793
|
console.log("");
|
|
683
794
|
console.log("Installing AgentBridge plugin...");
|
|
795
|
+
let pluginInstalled = false;
|
|
684
796
|
try {
|
|
685
|
-
|
|
797
|
+
const packageRoot = findPackageRoot();
|
|
798
|
+
registerMarketplace(packageRoot);
|
|
686
799
|
execFileSync2("claude", ["plugin", "install", `${PLUGIN_NAME}@${MARKETPLACE_NAME}`], {
|
|
687
800
|
stdio: "inherit"
|
|
688
801
|
});
|
|
689
802
|
console.log(" Plugin installed successfully.");
|
|
803
|
+
pluginInstalled = true;
|
|
690
804
|
} catch {
|
|
691
805
|
console.log(" Plugin install skipped (marketplace registration or install failed).");
|
|
692
|
-
|
|
693
|
-
|
|
806
|
+
for (const line of pluginInstallFallbackGuidance(detectRepoCheckout())) {
|
|
807
|
+
console.log(line);
|
|
808
|
+
}
|
|
694
809
|
}
|
|
695
810
|
console.log("");
|
|
696
|
-
|
|
811
|
+
if (pluginInstalled) {
|
|
812
|
+
console.log(`Setup complete!
|
|
697
813
|
`);
|
|
814
|
+
} else {
|
|
815
|
+
console.log(`Setup incomplete \u2014 plugin not installed.
|
|
816
|
+
`);
|
|
817
|
+
process.exitCode = 1;
|
|
818
|
+
}
|
|
698
819
|
console.log("Next steps:");
|
|
699
820
|
console.log(" 1. If Claude Code is already running, execute /reload-plugins in your session");
|
|
700
821
|
console.log(" 2. Start Claude Code: agentbridge claude");
|
|
701
822
|
console.log(" 3. Start Codex TUI: agentbridge codex");
|
|
702
823
|
}
|
|
824
|
+
function detectRepoCheckout() {
|
|
825
|
+
try {
|
|
826
|
+
return isInsideRepoCheckout(findPackageRoot());
|
|
827
|
+
} catch {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function pluginInstallFallbackGuidance(insideRepo) {
|
|
832
|
+
if (insideRepo) {
|
|
833
|
+
return [
|
|
834
|
+
" You can install it later with:",
|
|
835
|
+
" abg dev # registers marketplace and installs plugin"
|
|
836
|
+
];
|
|
837
|
+
}
|
|
838
|
+
return [
|
|
839
|
+
" Install the plugin from Claude Code with these steps:",
|
|
840
|
+
...MARKETPLACE_STEPS.map((step) => ` ${step}`)
|
|
841
|
+
];
|
|
842
|
+
}
|
|
703
843
|
function checkBun() {
|
|
704
844
|
try {
|
|
705
845
|
const version = execSync("bun --version", { encoding: "utf-8" }).trim();
|
|
@@ -745,8 +885,8 @@ function checkCodex() {
|
|
|
745
885
|
function writeCollaborationSections(projectRoot) {
|
|
746
886
|
const results = [];
|
|
747
887
|
const files = [
|
|
748
|
-
{ name: "CLAUDE.md", path:
|
|
749
|
-
{ name: "AGENTS.md", path:
|
|
888
|
+
{ name: "CLAUDE.md", path: join5(projectRoot, "CLAUDE.md"), section: CLAUDE_MD_SECTION },
|
|
889
|
+
{ name: "AGENTS.md", path: join5(projectRoot, "AGENTS.md"), section: AGENTS_MD_SECTION }
|
|
750
890
|
];
|
|
751
891
|
for (const { name, path, section } of files) {
|
|
752
892
|
let existing = "";
|
|
@@ -781,6 +921,7 @@ var init_init = __esm(() => {
|
|
|
781
921
|
init_config_service();
|
|
782
922
|
init_cli();
|
|
783
923
|
init_pkg_root();
|
|
924
|
+
init_plugin_cache();
|
|
784
925
|
init_version_utils();
|
|
785
926
|
});
|
|
786
927
|
|
|
@@ -790,19 +931,17 @@ __export(exports_dev, {
|
|
|
790
931
|
runDev: () => runDev
|
|
791
932
|
});
|
|
792
933
|
import { execFileSync as execFileSync3, spawnSync } from "child_process";
|
|
793
|
-
import { resolve } from "path";
|
|
794
|
-
import { existsSync as
|
|
795
|
-
import { homedir as homedir2 } from "os";
|
|
934
|
+
import { resolve as resolve2 } from "path";
|
|
935
|
+
import { existsSync as existsSync5, cpSync, rmSync } from "fs";
|
|
796
936
|
async function runDev(args = []) {
|
|
797
937
|
console.log(`AgentBridge Dev Setup
|
|
798
938
|
`);
|
|
799
939
|
const skipBuild = args.includes("--skip-build");
|
|
800
940
|
const projectRoot = findPackageRoot();
|
|
801
|
-
const marketplacePath =
|
|
802
|
-
const pluginDir =
|
|
803
|
-
const pluginManifest =
|
|
804
|
-
|
|
805
|
-
if (!existsSync4(buildScript)) {
|
|
941
|
+
const marketplacePath = resolve2(projectRoot, ".claude-plugin", "marketplace.json");
|
|
942
|
+
const pluginDir = resolve2(projectRoot, "plugins", "agentbridge");
|
|
943
|
+
const pluginManifest = resolve2(pluginDir, ".claude-plugin", "plugin.json");
|
|
944
|
+
if (!isInsideRepoCheckout(projectRoot)) {
|
|
806
945
|
console.error(" ERROR: 'agentbridge dev' must run inside an AgentBridge repository checkout \u2014");
|
|
807
946
|
console.error(" the published package does not ship the build scripts.");
|
|
808
947
|
console.error("");
|
|
@@ -839,12 +978,12 @@ async function runDev(args = []) {
|
|
|
839
978
|
console.log(` \u2713 Plugin built successfully
|
|
840
979
|
`);
|
|
841
980
|
}
|
|
842
|
-
if (!
|
|
981
|
+
if (!existsSync5(pluginManifest)) {
|
|
843
982
|
console.error(` ERROR: Plugin manifest not found at ${pluginManifest}`);
|
|
844
983
|
console.error(" Run 'bun run build:plugin' first, or check your working tree.");
|
|
845
984
|
process.exit(1);
|
|
846
985
|
}
|
|
847
|
-
if (!
|
|
986
|
+
if (!existsSync5(marketplacePath)) {
|
|
848
987
|
console.error(` ERROR: Marketplace manifest not found at ${marketplacePath}`);
|
|
849
988
|
process.exit(1);
|
|
850
989
|
}
|
|
@@ -873,12 +1012,12 @@ Installing plugin...`);
|
|
|
873
1012
|
}
|
|
874
1013
|
console.log(`
|
|
875
1014
|
Syncing local plugin to cache...`);
|
|
876
|
-
const cacheDir =
|
|
877
|
-
if (
|
|
1015
|
+
const cacheDir = pluginCacheRoot();
|
|
1016
|
+
if (existsSync5(cacheDir)) {
|
|
878
1017
|
const versionDirs = Bun.spawnSync(["ls", cacheDir]).stdout.toString().trim().split(`
|
|
879
1018
|
`).filter(Boolean);
|
|
880
1019
|
for (const ver of versionDirs) {
|
|
881
|
-
const targetDir =
|
|
1020
|
+
const targetDir = resolve2(cacheDir, ver);
|
|
882
1021
|
rmSync(targetDir, { recursive: true, force: true });
|
|
883
1022
|
cpSync(pluginDir, targetDir, { recursive: true });
|
|
884
1023
|
console.log(` Synced to ${targetDir}`);
|
|
@@ -898,15 +1037,23 @@ Syncing local plugin to cache...`);
|
|
|
898
1037
|
var init_dev = __esm(() => {
|
|
899
1038
|
init_cli();
|
|
900
1039
|
init_pkg_root();
|
|
1040
|
+
init_plugin_cache();
|
|
901
1041
|
});
|
|
902
1042
|
|
|
903
1043
|
// src/control-protocol.ts
|
|
904
1044
|
var CLOSE_CODE_REPLACED = 4001, CLOSE_CODE_EVICTED_STALE = 4002, CLOSE_CODE_PROBE_IN_PROGRESS = 4003, CLOSE_CODE_PAIR_MISMATCH = 4004;
|
|
905
1045
|
|
|
1046
|
+
// src/interrupt-timing.ts
|
|
1047
|
+
var CLIENT_REPLY_TIMEOUT_MS = 15000, INTERRUPT_CLIENT_MARGIN_MS = 2000, MAX_INTERRUPT_TIMEOUT_MS;
|
|
1048
|
+
var init_interrupt_timing = __esm(() => {
|
|
1049
|
+
MAX_INTERRUPT_TIMEOUT_MS = CLIENT_REPLY_TIMEOUT_MS - INTERRUPT_CLIENT_MARGIN_MS;
|
|
1050
|
+
});
|
|
1051
|
+
|
|
906
1052
|
// src/daemon-client.ts
|
|
907
1053
|
import { EventEmitter } from "events";
|
|
908
1054
|
var nextSocketId = 0, DaemonClient;
|
|
909
1055
|
var init_daemon_client = __esm(() => {
|
|
1056
|
+
init_interrupt_timing();
|
|
910
1057
|
DaemonClient = class DaemonClient extends EventEmitter {
|
|
911
1058
|
url;
|
|
912
1059
|
options;
|
|
@@ -933,7 +1080,7 @@ var init_daemon_client = __esm(() => {
|
|
|
933
1080
|
this.ws = null;
|
|
934
1081
|
}
|
|
935
1082
|
const socketId = ++nextSocketId;
|
|
936
|
-
await new Promise((
|
|
1083
|
+
await new Promise((resolve3, reject) => {
|
|
937
1084
|
const ws = new WebSocket(this.url);
|
|
938
1085
|
let settled = false;
|
|
939
1086
|
ws.onopen = () => {
|
|
@@ -942,7 +1089,7 @@ var init_daemon_client = __esm(() => {
|
|
|
942
1089
|
this.wsId = socketId;
|
|
943
1090
|
this.attachSocketHandlers(ws, socketId);
|
|
944
1091
|
this.log(`ws#${socketId} opened and attached`);
|
|
945
|
-
|
|
1092
|
+
resolve3();
|
|
946
1093
|
};
|
|
947
1094
|
ws.onerror = () => {
|
|
948
1095
|
if (settled)
|
|
@@ -968,7 +1115,7 @@ var init_daemon_client = __esm(() => {
|
|
|
968
1115
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
969
1116
|
return null;
|
|
970
1117
|
}
|
|
971
|
-
return await new Promise((
|
|
1118
|
+
return await new Promise((resolve3) => {
|
|
972
1119
|
let settled = false;
|
|
973
1120
|
let timer = null;
|
|
974
1121
|
const cleanup = () => {
|
|
@@ -985,7 +1132,7 @@ var init_daemon_client = __esm(() => {
|
|
|
985
1132
|
};
|
|
986
1133
|
const finish = (value) => {
|
|
987
1134
|
cleanup();
|
|
988
|
-
|
|
1135
|
+
resolve3(value);
|
|
989
1136
|
};
|
|
990
1137
|
const onStatus = (status) => finish(status);
|
|
991
1138
|
const onRejected = () => finish(null);
|
|
@@ -1007,7 +1154,7 @@ var init_daemon_client = __esm(() => {
|
|
|
1007
1154
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1008
1155
|
return { connected: false, alive: false };
|
|
1009
1156
|
}
|
|
1010
|
-
return await new Promise((
|
|
1157
|
+
return await new Promise((resolve3) => {
|
|
1011
1158
|
let settled = false;
|
|
1012
1159
|
let timer = null;
|
|
1013
1160
|
const finish = (value) => {
|
|
@@ -1019,7 +1166,7 @@ var init_daemon_client = __esm(() => {
|
|
|
1019
1166
|
this.off("incumbentStatus", onStatus);
|
|
1020
1167
|
this.off("disconnect", onDisconnect);
|
|
1021
1168
|
this.off("rejected", onRejected);
|
|
1022
|
-
|
|
1169
|
+
resolve3(value);
|
|
1023
1170
|
};
|
|
1024
1171
|
const onStatus = (s) => finish(s);
|
|
1025
1172
|
const onDisconnect = () => finish({ connected: false, alive: false });
|
|
@@ -1047,23 +1194,24 @@ var init_daemon_client = __esm(() => {
|
|
|
1047
1194
|
this.ws = null;
|
|
1048
1195
|
this.rejectPendingReplies("Daemon connection closed");
|
|
1049
1196
|
}
|
|
1050
|
-
async sendReply(message, requireReply, onBusy) {
|
|
1197
|
+
async sendReply(message, requireReply, onBusy, idempotencyKey) {
|
|
1051
1198
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1052
1199
|
return { success: false, error: "AgentBridge daemon is not connected." };
|
|
1053
1200
|
}
|
|
1054
1201
|
const requestId = `reply_${Date.now()}_${this.nextRequestId++}`;
|
|
1055
|
-
return new Promise((
|
|
1202
|
+
return new Promise((resolve3) => {
|
|
1056
1203
|
const timer = setTimeout(() => {
|
|
1057
1204
|
this.pendingReplies.delete(requestId);
|
|
1058
|
-
|
|
1059
|
-
},
|
|
1060
|
-
this.pendingReplies.set(requestId, { resolve:
|
|
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 });
|
|
1061
1208
|
this.send({
|
|
1062
1209
|
type: "claude_to_codex",
|
|
1063
1210
|
requestId,
|
|
1064
1211
|
message,
|
|
1065
1212
|
...requireReply ? { requireReply: true } : {},
|
|
1066
|
-
...onBusy && onBusy !== "reject" ? { onBusy } : {}
|
|
1213
|
+
...onBusy && onBusy !== "reject" ? { onBusy } : {},
|
|
1214
|
+
...idempotencyKey ? { idempotencyKey } : {}
|
|
1067
1215
|
});
|
|
1068
1216
|
});
|
|
1069
1217
|
}
|
|
@@ -1086,9 +1234,23 @@ var init_daemon_client = __esm(() => {
|
|
|
1086
1234
|
return;
|
|
1087
1235
|
clearTimeout(pending.timer);
|
|
1088
1236
|
this.pendingReplies.delete(message.requestId);
|
|
1089
|
-
pending.resolve({
|
|
1237
|
+
pending.resolve({
|
|
1238
|
+
success: message.success,
|
|
1239
|
+
error: message.error,
|
|
1240
|
+
...message.code !== undefined ? { code: message.code } : {},
|
|
1241
|
+
...message.phase !== undefined ? { phase: message.phase } : {},
|
|
1242
|
+
...message.retryAfterMs !== undefined ? { retryAfterMs: message.retryAfterMs } : {}
|
|
1243
|
+
});
|
|
1090
1244
|
return;
|
|
1091
1245
|
}
|
|
1246
|
+
case "turn_started":
|
|
1247
|
+
this.emit("turnStarted", {
|
|
1248
|
+
requestId: message.requestId,
|
|
1249
|
+
...message.idempotencyKey !== undefined ? { idempotencyKey: message.idempotencyKey } : {},
|
|
1250
|
+
threadId: message.threadId,
|
|
1251
|
+
turnId: message.turnId
|
|
1252
|
+
});
|
|
1253
|
+
return;
|
|
1092
1254
|
case "status":
|
|
1093
1255
|
this.emit("status", message.status);
|
|
1094
1256
|
return;
|
|
@@ -1165,8 +1327,8 @@ function formatBuildInfo(build) {
|
|
|
1165
1327
|
var BUILD_INFO;
|
|
1166
1328
|
var init_build_info = __esm(() => {
|
|
1167
1329
|
BUILD_INFO = Object.freeze({
|
|
1168
|
-
version: defineString("0.1.
|
|
1169
|
-
commit: defineString("
|
|
1330
|
+
version: defineString("0.1.12", "0.0.0-source"),
|
|
1331
|
+
commit: defineString("eec6018", "source"),
|
|
1170
1332
|
bundle: defineBundle("dist"),
|
|
1171
1333
|
contractVersion: defineNumber(1, CONTRACT_VERSION)
|
|
1172
1334
|
});
|
|
@@ -1332,8 +1494,50 @@ var init_process_lifecycle = __esm(() => {
|
|
|
1332
1494
|
|
|
1333
1495
|
// src/daemon-lifecycle.ts
|
|
1334
1496
|
import { spawn } from "child_process";
|
|
1335
|
-
import { existsSync as
|
|
1497
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync, unlinkSync, writeFileSync as writeFileSync4, openSync, closeSync, constants } from "fs";
|
|
1336
1498
|
import { fileURLToPath } from "url";
|
|
1499
|
+
function isReuseVerdict(verdict) {
|
|
1500
|
+
return verdict === "reuse" || verdict === "reuse-despite-drift";
|
|
1501
|
+
}
|
|
1502
|
+
function classifyDaemon(expectedPairId, status, buildInfo) {
|
|
1503
|
+
if (!status) {
|
|
1504
|
+
return { verdict: "unreachable", reason: "daemon status is unavailable or unparseable" };
|
|
1505
|
+
}
|
|
1506
|
+
const reportedPairId = status.pairId;
|
|
1507
|
+
if (!expectedPairId && reportedPairId != null) {
|
|
1508
|
+
return {
|
|
1509
|
+
verdict: "manual-conflict",
|
|
1510
|
+
reason: `manual mode must not adopt registered pair ${reportedPairId}`
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
if (expectedPairId) {
|
|
1514
|
+
if (reportedPairId == null) {
|
|
1515
|
+
return {
|
|
1516
|
+
verdict: "replace-foreign",
|
|
1517
|
+
reason: `pair ${expectedPairId} found daemon without pair identity`
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
if (reportedPairId !== expectedPairId) {
|
|
1521
|
+
return {
|
|
1522
|
+
verdict: "replace-foreign",
|
|
1523
|
+
reason: `pair ${expectedPairId} found daemon for pair ${reportedPairId}`
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (!sameRuntimeContract(status.build, buildInfo)) {
|
|
1528
|
+
if (compatibleContractVersion(status.build, buildInfo) && status.tuiConnected === true) {
|
|
1529
|
+
return {
|
|
1530
|
+
verdict: "reuse-despite-drift",
|
|
1531
|
+
reason: "runtime build drift has a compatible contract and a live Codex TUI is attached"
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
return {
|
|
1535
|
+
verdict: "replace-drifted",
|
|
1536
|
+
reason: `runtime build ${formatBuildInfo(status.build)} does not match launcher ${formatBuildInfo(buildInfo)}`
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
return { verdict: "reuse", reason: "daemon pair and runtime contract match" };
|
|
1540
|
+
}
|
|
1337
1541
|
|
|
1338
1542
|
class DaemonLifecycle {
|
|
1339
1543
|
stateDir;
|
|
@@ -1366,52 +1570,37 @@ class DaemonLifecycle {
|
|
|
1366
1570
|
return null;
|
|
1367
1571
|
}
|
|
1368
1572
|
}
|
|
1369
|
-
|
|
1370
|
-
const
|
|
1371
|
-
if (
|
|
1372
|
-
return
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
const reported = status.pairId;
|
|
1376
|
-
if (reported == null)
|
|
1377
|
-
return true;
|
|
1378
|
-
return reported !== expected;
|
|
1379
|
-
}
|
|
1380
|
-
isRegisteredPairDaemonInManualMode(status) {
|
|
1381
|
-
return !this.expectedPairId && status?.pairId != null;
|
|
1382
|
-
}
|
|
1383
|
-
isBuildDrifted(status) {
|
|
1384
|
-
if (process.env.AGENTBRIDGE_ALLOW_BUILD_DRIFT === "1")
|
|
1385
|
-
return false;
|
|
1386
|
-
const runtime = status?.build;
|
|
1387
|
-
if (!runtime)
|
|
1388
|
-
return true;
|
|
1389
|
-
return !sameRuntimeContract(runtime, BUILD_INFO);
|
|
1573
|
+
classifyDaemon(status) {
|
|
1574
|
+
const classification = classifyDaemon(this.expectedPairId, status, BUILD_INFO);
|
|
1575
|
+
if (process.env.AGENTBRIDGE_ALLOW_BUILD_DRIFT === "1" && (classification.verdict === "replace-drifted" || classification.verdict === "unreachable")) {
|
|
1576
|
+
return { verdict: "reuse", reason: "build drift replacement disabled by AGENTBRIDGE_ALLOW_BUILD_DRIFT" };
|
|
1577
|
+
}
|
|
1578
|
+
return classification;
|
|
1390
1579
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
return false;
|
|
1394
|
-
return status?.tuiConnected === true;
|
|
1580
|
+
manualConflictError(status) {
|
|
1581
|
+
return new Error(`Control port ${this.controlPort} is owned by registered pair ${status?.pairId}. ` + `This session has no pair identity (manual mode) and will not reuse or replace it \u2014 ` + `start with \`agentbridge claude\` from that pair's directory, or set AGENTBRIDGE_CONTROL_PORT to a free port.`);
|
|
1395
1582
|
}
|
|
1396
1583
|
async ensureRunning() {
|
|
1397
1584
|
if (await this.isHealthy()) {
|
|
1398
1585
|
const status = await this.fetchStatus();
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
this.log(`Daemon on control port ${this.controlPort} is running build ${formatBuildInfo(status?.build)} ` + `(launcher ${formatBuildInfo(BUILD_INFO)}) but a live Codex TUI is attached \u2014 reusing instead of ` + `replacing; the new build is picked up at the next restart (abg kill, then relaunch)`);
|
|
1410
|
-
} else {
|
|
1586
|
+
const classification = this.classifyDaemon(status);
|
|
1587
|
+
switch (classification.verdict) {
|
|
1588
|
+
case "manual-conflict":
|
|
1589
|
+
throw this.manualConflictError(status);
|
|
1590
|
+
case "replace-foreign":
|
|
1591
|
+
this.log(`Control port ${this.controlPort} held by a daemon for pair ${status?.pairId ?? "<none>"}, ` + `but this pair is ${this.expectedPairId} \u2014 replacing foreign daemon`);
|
|
1592
|
+
await this.replaceUnhealthyDaemon(status?.pid);
|
|
1593
|
+
return;
|
|
1594
|
+
case "replace-drifted":
|
|
1595
|
+
case "unreachable":
|
|
1411
1596
|
this.log(`Daemon on control port ${this.controlPort} is running build ${formatBuildInfo(status?.build)} ` + `but launcher is ${formatBuildInfo(BUILD_INFO)} \u2014 replacing drifted daemon`);
|
|
1412
1597
|
await this.replaceUnhealthyDaemon(status?.pid);
|
|
1413
1598
|
return;
|
|
1414
|
-
|
|
1599
|
+
case "reuse-despite-drift":
|
|
1600
|
+
this.log(`Daemon on control port ${this.controlPort} is running build ${formatBuildInfo(status?.build)} ` + `(launcher ${formatBuildInfo(BUILD_INFO)}) but a live Codex TUI is attached \u2014 reusing instead of ` + `replacing; the new build is picked up at the next restart (abg kill, then relaunch)`);
|
|
1601
|
+
break;
|
|
1602
|
+
case "reuse":
|
|
1603
|
+
break;
|
|
1415
1604
|
}
|
|
1416
1605
|
try {
|
|
1417
1606
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
@@ -1441,14 +1630,17 @@ class DaemonLifecycle {
|
|
|
1441
1630
|
}
|
|
1442
1631
|
await this.withStartupLockStrict(async (locked) => {
|
|
1443
1632
|
if (!locked) {
|
|
1444
|
-
this.
|
|
1445
|
-
await this.waitForReadyAndOurs();
|
|
1633
|
+
await this.waitForContendedStartupLock();
|
|
1446
1634
|
return;
|
|
1447
1635
|
}
|
|
1448
1636
|
if (await this.isHealthy()) {
|
|
1449
1637
|
const status = await this.fetchStatus();
|
|
1450
|
-
|
|
1451
|
-
|
|
1638
|
+
const classification = this.classifyDaemon(status);
|
|
1639
|
+
if (classification.verdict === "manual-conflict") {
|
|
1640
|
+
throw this.manualConflictError(status);
|
|
1641
|
+
}
|
|
1642
|
+
if (!isReuseVerdict(classification.verdict)) {
|
|
1643
|
+
this.log(`Daemon on control port ${this.controlPort} is not reusable under startup lock ` + `(pair=${status?.pairId ?? "<none>"}, build=${formatBuildInfo(status?.build)}, ` + `reason=${classification.reason}) \u2014 replacing`);
|
|
1452
1644
|
await this.kill(3000, status?.pid);
|
|
1453
1645
|
} else {
|
|
1454
1646
|
try {
|
|
@@ -1476,7 +1668,7 @@ class DaemonLifecycle {
|
|
|
1476
1668
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1477
1669
|
if (await this.isHealthy())
|
|
1478
1670
|
return;
|
|
1479
|
-
await new Promise((
|
|
1671
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
1480
1672
|
}
|
|
1481
1673
|
throw new Error(`Timed out waiting for AgentBridge daemon health on ${this.healthUrl}`);
|
|
1482
1674
|
}
|
|
@@ -1492,7 +1684,7 @@ class DaemonLifecycle {
|
|
|
1492
1684
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1493
1685
|
if (await this.isReady())
|
|
1494
1686
|
return;
|
|
1495
|
-
await new Promise((
|
|
1687
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
1496
1688
|
}
|
|
1497
1689
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness on ${this.readyUrl}`);
|
|
1498
1690
|
}
|
|
@@ -1500,11 +1692,15 @@ class DaemonLifecycle {
|
|
|
1500
1692
|
for (let attempt = 0;attempt < maxRetries; attempt++) {
|
|
1501
1693
|
if (await this.isReady()) {
|
|
1502
1694
|
const status = await this.fetchStatus();
|
|
1503
|
-
|
|
1695
|
+
const classification = this.classifyDaemon(status);
|
|
1696
|
+
if (classification.verdict === "manual-conflict") {
|
|
1697
|
+
throw this.manualConflictError(status);
|
|
1698
|
+
}
|
|
1699
|
+
if (isReuseVerdict(classification.verdict)) {
|
|
1504
1700
|
return;
|
|
1505
1701
|
}
|
|
1506
1702
|
}
|
|
1507
|
-
await new Promise((
|
|
1703
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
1508
1704
|
}
|
|
1509
1705
|
throw new Error(`Timed out waiting for AgentBridge daemon readiness+identity on ${this.readyUrl} (control port ${this.controlPort})`);
|
|
1510
1706
|
}
|
|
@@ -1558,7 +1754,7 @@ class DaemonLifecycle {
|
|
|
1558
1754
|
} catch {}
|
|
1559
1755
|
}
|
|
1560
1756
|
wasKilled() {
|
|
1561
|
-
return
|
|
1757
|
+
return existsSync6(this.stateDir.killedFile);
|
|
1562
1758
|
}
|
|
1563
1759
|
launch() {
|
|
1564
1760
|
this.stateDir.ensure();
|
|
@@ -1582,13 +1778,16 @@ class DaemonLifecycle {
|
|
|
1582
1778
|
async replaceUnhealthyDaemon(statusPid) {
|
|
1583
1779
|
await this.withStartupLockStrict(async (locked) => {
|
|
1584
1780
|
if (!locked) {
|
|
1585
|
-
this.
|
|
1586
|
-
await this.waitForReadyAndOurs();
|
|
1781
|
+
await this.waitForContendedStartupLock();
|
|
1587
1782
|
return;
|
|
1588
1783
|
}
|
|
1589
1784
|
if (await this.isHealthy()) {
|
|
1590
1785
|
const status = await this.fetchStatus();
|
|
1591
|
-
|
|
1786
|
+
const classification = this.classifyDaemon(status);
|
|
1787
|
+
if (classification.verdict === "manual-conflict") {
|
|
1788
|
+
throw this.manualConflictError(status);
|
|
1789
|
+
}
|
|
1790
|
+
if (isReuseVerdict(classification.verdict)) {
|
|
1592
1791
|
try {
|
|
1593
1792
|
await this.waitForReady(REUSE_READY_RETRIES, REUSE_READY_DELAY_MS);
|
|
1594
1793
|
return;
|
|
@@ -1601,6 +1800,10 @@ class DaemonLifecycle {
|
|
|
1601
1800
|
await this.waitForReady();
|
|
1602
1801
|
});
|
|
1603
1802
|
}
|
|
1803
|
+
async waitForContendedStartupLock() {
|
|
1804
|
+
this.log("Another process holds the startup lock, waiting for readiness+identity...");
|
|
1805
|
+
await this.waitForReadyAndOurs();
|
|
1806
|
+
}
|
|
1604
1807
|
async withStartupLockStrict(fn) {
|
|
1605
1808
|
const locked = this.acquireLockStrict();
|
|
1606
1809
|
try {
|
|
@@ -1693,7 +1896,7 @@ class DaemonLifecycle {
|
|
|
1693
1896
|
this.cleanup();
|
|
1694
1897
|
return true;
|
|
1695
1898
|
}
|
|
1696
|
-
await new Promise((
|
|
1899
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
1697
1900
|
}
|
|
1698
1901
|
this.log(`Daemon pid ${pid} did not stop gracefully, sending SIGKILL`);
|
|
1699
1902
|
try {
|
|
@@ -1731,7 +1934,7 @@ var init_daemon_lifecycle = __esm(() => {
|
|
|
1731
1934
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
1732
1935
|
import {
|
|
1733
1936
|
closeSync as closeSync2,
|
|
1734
|
-
existsSync as
|
|
1937
|
+
existsSync as existsSync7,
|
|
1735
1938
|
fsyncSync,
|
|
1736
1939
|
linkSync,
|
|
1737
1940
|
lstatSync,
|
|
@@ -1749,7 +1952,7 @@ import {
|
|
|
1749
1952
|
import { createServer } from "net";
|
|
1750
1953
|
import { createHash, randomUUID } from "crypto";
|
|
1751
1954
|
import { hostname, userInfo } from "os";
|
|
1752
|
-
import { basename as basename2, join as
|
|
1955
|
+
import { basename as basename2, join as join6, resolve as resolve3, sep } from "path";
|
|
1753
1956
|
function portsForSlot(slot) {
|
|
1754
1957
|
if (!Number.isInteger(slot) || slot < 0) {
|
|
1755
1958
|
throw new PairError("PAIR_ID_INVALID", `Invalid slot: ${slot}`);
|
|
@@ -1787,14 +1990,14 @@ function pickLowestFreeSlot(entries) {
|
|
|
1787
1990
|
return slot;
|
|
1788
1991
|
}
|
|
1789
1992
|
function pairsDir(base) {
|
|
1790
|
-
return
|
|
1993
|
+
return join6(base, "pairs");
|
|
1791
1994
|
}
|
|
1792
1995
|
function registryPath(base) {
|
|
1793
|
-
return
|
|
1996
|
+
return join6(pairsDir(base), REGISTRY_FILE_NAME);
|
|
1794
1997
|
}
|
|
1795
1998
|
function readRegistry(base) {
|
|
1796
1999
|
const path = registryPath(base);
|
|
1797
|
-
if (!
|
|
2000
|
+
if (!existsSync7(path))
|
|
1798
2001
|
return { version: 1, pairs: [] };
|
|
1799
2002
|
let parsed;
|
|
1800
2003
|
try {
|
|
@@ -1844,7 +2047,7 @@ function writeRegistry(base, reg) {
|
|
|
1844
2047
|
renameSync(tmp, target);
|
|
1845
2048
|
}
|
|
1846
2049
|
function lockFilePath(base) {
|
|
1847
|
-
return
|
|
2050
|
+
return join6(pairsDir(base), LOCK_FILE_NAME);
|
|
1848
2051
|
}
|
|
1849
2052
|
function readLockOwner(lockFile) {
|
|
1850
2053
|
try {
|
|
@@ -1878,7 +2081,7 @@ function safeUid() {
|
|
|
1878
2081
|
}
|
|
1879
2082
|
}
|
|
1880
2083
|
function sleep(ms) {
|
|
1881
|
-
return new Promise((
|
|
2084
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1882
2085
|
}
|
|
1883
2086
|
function lockIsStale(lockFile) {
|
|
1884
2087
|
const owner = readLockOwner(lockFile);
|
|
@@ -1990,8 +2193,8 @@ async function withRegistryLock(base, fn) {
|
|
|
1990
2193
|
}
|
|
1991
2194
|
}
|
|
1992
2195
|
function detectLegacyRootDaemon(base) {
|
|
1993
|
-
const rootPidFile =
|
|
1994
|
-
if (!
|
|
2196
|
+
const rootPidFile = join6(base, "daemon.pid");
|
|
2197
|
+
if (!existsSync7(rootPidFile))
|
|
1995
2198
|
return null;
|
|
1996
2199
|
let pid;
|
|
1997
2200
|
try {
|
|
@@ -2005,11 +2208,11 @@ function detectLegacyRootDaemon(base) {
|
|
|
2005
2208
|
return { pid, controlPort: LEGACY_ROOT_CONTROL_PORT };
|
|
2006
2209
|
}
|
|
2007
2210
|
function probePortFree(port) {
|
|
2008
|
-
return new Promise((
|
|
2211
|
+
return new Promise((resolve4) => {
|
|
2009
2212
|
const server = createServer();
|
|
2010
|
-
server.once("error", () =>
|
|
2213
|
+
server.once("error", () => resolve4(false));
|
|
2011
2214
|
server.once("listening", () => {
|
|
2012
|
-
server.close(() =>
|
|
2215
|
+
server.close(() => resolve4(true));
|
|
2013
2216
|
});
|
|
2014
2217
|
server.listen(port, "127.0.0.1");
|
|
2015
2218
|
});
|
|
@@ -2086,7 +2289,7 @@ async function resolvePair(base, opts) {
|
|
|
2086
2289
|
pairId: entry.pairId,
|
|
2087
2290
|
slot,
|
|
2088
2291
|
ports,
|
|
2089
|
-
stateDir:
|
|
2292
|
+
stateDir: join6(pairsDir(base), entry.pairId),
|
|
2090
2293
|
name: entry.name ?? name,
|
|
2091
2294
|
entry,
|
|
2092
2295
|
warning
|
|
@@ -2094,7 +2297,7 @@ async function resolvePair(base, opts) {
|
|
|
2094
2297
|
}
|
|
2095
2298
|
async function removeAllocatedPairIfUnchanged(base, pairId, slot) {
|
|
2096
2299
|
await withRegistryLock(base, () => {
|
|
2097
|
-
if (
|
|
2300
|
+
if (existsSync7(pairDirPath(base, pairId)) || pairDirDaemonAlive(base, pairId))
|
|
2098
2301
|
return;
|
|
2099
2302
|
const reg = readRegistry(base);
|
|
2100
2303
|
const nextPairs = reg.pairs.filter((pair) => !(pair.pairId === pairId && pair.slot === slot));
|
|
@@ -2105,19 +2308,19 @@ async function removeAllocatedPairIfUnchanged(base, pairId, slot) {
|
|
|
2105
2308
|
}
|
|
2106
2309
|
function pairDirPath(base, pairId) {
|
|
2107
2310
|
const id = validatePairId(pairId);
|
|
2108
|
-
return
|
|
2311
|
+
return join6(pairsDir(base), id);
|
|
2109
2312
|
}
|
|
2110
2313
|
function removePairDir(base, pairId) {
|
|
2111
2314
|
const id = validatePairId(pairId);
|
|
2112
2315
|
const root = pairsDir(base);
|
|
2113
|
-
const dir =
|
|
2114
|
-
const canonicalRoot =
|
|
2115
|
-
const canonicalDir =
|
|
2316
|
+
const dir = join6(root, id);
|
|
2317
|
+
const canonicalRoot = resolve3(root);
|
|
2318
|
+
const canonicalDir = resolve3(dir);
|
|
2116
2319
|
if (canonicalDir === canonicalRoot || !canonicalDir.startsWith(canonicalRoot + sep)) {
|
|
2117
2320
|
throw new PairError("PAIR_ID_INVALID", `Refusing to remove a pair dir outside ${canonicalRoot}: ${canonicalDir}`, { pairId });
|
|
2118
2321
|
}
|
|
2119
2322
|
assertPairsRootNotSymlinked(root);
|
|
2120
|
-
if (!
|
|
2323
|
+
if (!existsSync7(canonicalDir))
|
|
2121
2324
|
return false;
|
|
2122
2325
|
rmSync2(canonicalDir, { recursive: true, force: true });
|
|
2123
2326
|
return true;
|
|
@@ -2135,22 +2338,22 @@ function assertPairsRootNotSymlinked(root) {
|
|
|
2135
2338
|
}
|
|
2136
2339
|
function listPairDirs(base) {
|
|
2137
2340
|
const root = pairsDir(base);
|
|
2138
|
-
if (!
|
|
2341
|
+
if (!existsSync7(root))
|
|
2139
2342
|
return [];
|
|
2140
2343
|
if (lstatSync(root).isSymbolicLink())
|
|
2141
2344
|
return [];
|
|
2142
2345
|
return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2143
2346
|
}
|
|
2144
2347
|
function pairDirDaemonAlive(base, pairId) {
|
|
2145
|
-
const dir =
|
|
2348
|
+
const dir = join6(pairsDir(base), pairId);
|
|
2146
2349
|
const pids = [];
|
|
2147
2350
|
try {
|
|
2148
|
-
const pid = Number.parseInt(readFileSync5(
|
|
2351
|
+
const pid = Number.parseInt(readFileSync5(join6(dir, "daemon.pid"), "utf-8").trim(), 10);
|
|
2149
2352
|
if (Number.isFinite(pid))
|
|
2150
2353
|
pids.push(pid);
|
|
2151
2354
|
} catch {}
|
|
2152
2355
|
try {
|
|
2153
|
-
const status = JSON.parse(readFileSync5(
|
|
2356
|
+
const status = JSON.parse(readFileSync5(join6(dir, "status.json"), "utf-8"));
|
|
2154
2357
|
if (typeof status?.pid === "number")
|
|
2155
2358
|
pids.push(status.pid);
|
|
2156
2359
|
} catch {}
|
|
@@ -2283,7 +2486,7 @@ var init_env_guard = __esm(() => {
|
|
|
2283
2486
|
|
|
2284
2487
|
// src/pair-resolver.ts
|
|
2285
2488
|
import { realpathSync as realpathSync2 } from "fs";
|
|
2286
|
-
import { join as
|
|
2489
|
+
import { join as join7, resolve as resolve4 } from "path";
|
|
2287
2490
|
function computeBaseDir() {
|
|
2288
2491
|
return process.env.AGENTBRIDGE_BASE_DIR || process.env.AGENTBRIDGE_STATE_DIR || StateDirResolver.platformBaseDir();
|
|
2289
2492
|
}
|
|
@@ -2410,7 +2613,7 @@ function resolvePairReadOnly(pairFlag) {
|
|
|
2410
2613
|
pairId: entry.pairId,
|
|
2411
2614
|
slot: entry.slot,
|
|
2412
2615
|
ports: portsForEntry(entry),
|
|
2413
|
-
stateDir: new StateDirResolver(
|
|
2616
|
+
stateDir: new StateDirResolver(join7(base, "pairs", entry.pairId)),
|
|
2414
2617
|
name: entry.name ?? name,
|
|
2415
2618
|
manual: false
|
|
2416
2619
|
}
|
|
@@ -2423,7 +2626,7 @@ function resolvePairReadOnly(pairFlag) {
|
|
|
2423
2626
|
pairId,
|
|
2424
2627
|
slot: null,
|
|
2425
2628
|
ports: { appPort: 0, proxyPort: 0, controlPort: 0 },
|
|
2426
|
-
stateDir: new StateDirResolver(
|
|
2629
|
+
stateDir: new StateDirResolver(join7(base, "pairs", pairId)),
|
|
2427
2630
|
name,
|
|
2428
2631
|
manual: false
|
|
2429
2632
|
}
|
|
@@ -2453,7 +2656,7 @@ function portsForEntry(entry) {
|
|
|
2453
2656
|
return portsForSlot(entry.slot);
|
|
2454
2657
|
}
|
|
2455
2658
|
function canonicalizeCwd(cwd) {
|
|
2456
|
-
const absolute =
|
|
2659
|
+
const absolute = resolve4(cwd);
|
|
2457
2660
|
try {
|
|
2458
2661
|
return realpathSync2.native(absolute);
|
|
2459
2662
|
} catch {
|
|
@@ -2470,8 +2673,8 @@ var init_pair_resolver = __esm(() => {
|
|
|
2470
2673
|
});
|
|
2471
2674
|
|
|
2472
2675
|
// src/trace-log.ts
|
|
2473
|
-
import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
|
|
2474
|
-
import { join as
|
|
2676
|
+
import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2677
|
+
import { join as join8 } from "path";
|
|
2475
2678
|
function pickRelevantEnv(env) {
|
|
2476
2679
|
const picked = {};
|
|
2477
2680
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -2506,7 +2709,7 @@ function redactArgv(argv) {
|
|
|
2506
2709
|
}
|
|
2507
2710
|
function traceLogPath(cwd, timestamp) {
|
|
2508
2711
|
const day = timestamp.slice(0, 10);
|
|
2509
|
-
return
|
|
2712
|
+
return join8(cwd, ".agentbridge", "logs", `trace-${day}.jsonl`);
|
|
2510
2713
|
}
|
|
2511
2714
|
function appendTraceEvent(input) {
|
|
2512
2715
|
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
@@ -2520,11 +2723,39 @@ function appendTraceEvent(input) {
|
|
|
2520
2723
|
...input.env ? { env: pickRelevantEnv(input.env) } : {},
|
|
2521
2724
|
...input.data ? { data: redactData(input.data) } : {}
|
|
2522
2725
|
};
|
|
2523
|
-
|
|
2726
|
+
const logsDir = join8(input.cwd, ".agentbridge", "logs");
|
|
2727
|
+
const isNewDayFile = !existsSync8(path);
|
|
2728
|
+
mkdirSync4(logsDir, { recursive: true });
|
|
2729
|
+
if (isNewDayFile) {
|
|
2730
|
+
pruneOldTraceLogs(logsDir, path, Date.parse(timestamp));
|
|
2731
|
+
}
|
|
2524
2732
|
appendFileSync(path, JSON.stringify(event) + `
|
|
2525
2733
|
`, "utf-8");
|
|
2526
2734
|
return path;
|
|
2527
2735
|
}
|
|
2736
|
+
function pruneOldTraceLogs(logsDir, keepPath, nowMs) {
|
|
2737
|
+
if (!Number.isFinite(nowMs))
|
|
2738
|
+
return;
|
|
2739
|
+
const cutoff = nowMs - TRACE_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
2740
|
+
let entries;
|
|
2741
|
+
try {
|
|
2742
|
+
entries = readdirSync2(logsDir);
|
|
2743
|
+
} catch {
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
for (const name of entries) {
|
|
2747
|
+
if (!TRACE_FILE_RE.test(name))
|
|
2748
|
+
continue;
|
|
2749
|
+
const filePath = join8(logsDir, name);
|
|
2750
|
+
if (filePath === keepPath)
|
|
2751
|
+
continue;
|
|
2752
|
+
try {
|
|
2753
|
+
if (statSync3(filePath).mtimeMs < cutoff) {
|
|
2754
|
+
unlinkSync3(filePath);
|
|
2755
|
+
}
|
|
2756
|
+
} catch {}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2528
2759
|
function isEnvSnapshot(key, value) {
|
|
2529
2760
|
return /env$/i.test(key) && !!value && typeof value === "object" && !Array.isArray(value);
|
|
2530
2761
|
}
|
|
@@ -2550,8 +2781,9 @@ function redactData(value, key = "") {
|
|
|
2550
2781
|
}
|
|
2551
2782
|
return value;
|
|
2552
2783
|
}
|
|
2553
|
-
var SECRET_KEY_RE, SECRET_ARG_RE, RELEVANT_ENV_RE;
|
|
2784
|
+
var TRACE_RETENTION_DAYS = 7, TRACE_FILE_RE, SECRET_KEY_RE, SECRET_ARG_RE, RELEVANT_ENV_RE;
|
|
2554
2785
|
var init_trace_log = __esm(() => {
|
|
2786
|
+
TRACE_FILE_RE = /^trace-\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
2555
2787
|
SECRET_KEY_RE = /(token|secret|password|passwd|api[_-]?key|auth|cookie|session)/i;
|
|
2556
2788
|
SECRET_ARG_RE = /^--?(?:token|secret|password|passwd|apikey|api-key|api_key|auth|cookie|session)(?:=.*)?$/i;
|
|
2557
2789
|
RELEVANT_ENV_RE = /^(AGENTBRIDGE_|CODEX_)/;
|
|
@@ -2600,10 +2832,12 @@ var init_max_permissions = __esm(() => {
|
|
|
2600
2832
|
// src/cli/claude.ts
|
|
2601
2833
|
var exports_claude = {};
|
|
2602
2834
|
__export(exports_claude, {
|
|
2835
|
+
warnIfPluginCacheMissing: () => warnIfPluginCacheMissing,
|
|
2603
2836
|
runClaude: () => runClaude,
|
|
2604
2837
|
checkOwnedFlagConflicts: () => checkOwnedFlagConflicts
|
|
2605
2838
|
});
|
|
2606
2839
|
import { spawn as spawn2 } from "child_process";
|
|
2840
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2607
2841
|
async function runClaude(args) {
|
|
2608
2842
|
const originalEnv = { ...process.env };
|
|
2609
2843
|
const envGuardResult = guardAgentBridgeEnv({
|
|
@@ -2641,6 +2875,7 @@ async function runClaude(args) {
|
|
|
2641
2875
|
}
|
|
2642
2876
|
await assertPairNotLive(lifecycle, pair);
|
|
2643
2877
|
lifecycle.clearKilled();
|
|
2878
|
+
warnIfPluginCacheMissing();
|
|
2644
2879
|
const channelEntry = `plugin:${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
|
|
2645
2880
|
if (permissionPlan.inject) {
|
|
2646
2881
|
console.error(`[agentbridge] running with ${CLAUDE_MAX_PERMISSION_FLAG} (default; opt out with --safe or AGENTBRIDGE_SAFE=1)`);
|
|
@@ -2668,6 +2903,18 @@ async function runClaude(args) {
|
|
|
2668
2903
|
process.exit(1);
|
|
2669
2904
|
});
|
|
2670
2905
|
}
|
|
2906
|
+
function warnIfPluginCacheMissing(cacheRoot = pluginCacheRoot(), log = (msg) => console.error(msg)) {
|
|
2907
|
+
let cacheExists;
|
|
2908
|
+
try {
|
|
2909
|
+
cacheExists = existsSync9(cacheRoot);
|
|
2910
|
+
} catch {
|
|
2911
|
+
return false;
|
|
2912
|
+
}
|
|
2913
|
+
if (!shouldWarnMissingPluginCache(cacheExists))
|
|
2914
|
+
return false;
|
|
2915
|
+
log("[agentbridge] \u26A0\uFE0F Plugin not installed (no plugin cache found). Run `abg init`, " + `or in Claude Code: ${MARKETPLACE_STEPS.join(" \u2192 ")}. Launching anyway\u2026`);
|
|
2916
|
+
return true;
|
|
2917
|
+
}
|
|
2671
2918
|
function traceCliStart(event, args, originalEnv, envGuardAction, pair) {
|
|
2672
2919
|
try {
|
|
2673
2920
|
appendTraceEvent({
|
|
@@ -2744,6 +2991,7 @@ function checkOwnedFlagConflicts(args, commandName, ownedFlags) {
|
|
|
2744
2991
|
var OWNED_FLAGS;
|
|
2745
2992
|
var init_claude = __esm(() => {
|
|
2746
2993
|
init_cli();
|
|
2994
|
+
init_plugin_cache();
|
|
2747
2995
|
init_daemon_client();
|
|
2748
2996
|
init_daemon_lifecycle();
|
|
2749
2997
|
init_build_info();
|
|
@@ -2755,11 +3003,11 @@ var init_claude = __esm(() => {
|
|
|
2755
3003
|
});
|
|
2756
3004
|
|
|
2757
3005
|
// src/agents-contract.ts
|
|
2758
|
-
import { existsSync as
|
|
2759
|
-
import { join as
|
|
3006
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
3007
|
+
import { join as join9 } from "path";
|
|
2760
3008
|
function checkAgentsMdContract(cwd) {
|
|
2761
|
-
const path =
|
|
2762
|
-
const exists =
|
|
3009
|
+
const path = join9(cwd, "AGENTS.md");
|
|
3010
|
+
const exists = existsSync10(path);
|
|
2763
3011
|
let content = "";
|
|
2764
3012
|
if (exists) {
|
|
2765
3013
|
try {
|
|
@@ -2790,8 +3038,8 @@ function isFreshAgentsMdContract(content) {
|
|
|
2790
3038
|
var init_agents_contract = () => {};
|
|
2791
3039
|
|
|
2792
3040
|
// src/wrapper-exit-observability.ts
|
|
2793
|
-
import { readFileSync as readFileSync7, readdirSync as
|
|
2794
|
-
import { join as
|
|
3041
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
3042
|
+
import { join as join10 } from "path";
|
|
2795
3043
|
function discoverNativeChildPid(launcherPid, run) {
|
|
2796
3044
|
try {
|
|
2797
3045
|
const out = run("pgrep", ["-P", String(launcherPid)]);
|
|
@@ -2820,12 +3068,12 @@ function refineCleanExitClassification(turnInProgress) {
|
|
|
2820
3068
|
return "exit_0_idle";
|
|
2821
3069
|
return "exit_0_turn_unknown";
|
|
2822
3070
|
}
|
|
2823
|
-
function findCodexSqliteLog(codexHome, fs = { readdir:
|
|
3071
|
+
function findCodexSqliteLog(codexHome, fs = { readdir: readdirSync3, stat: statSync4 }) {
|
|
2824
3072
|
try {
|
|
2825
3073
|
const entries = fs.readdir(codexHome).filter((name) => /^logs.*\.sqlite$/.test(String(name)));
|
|
2826
3074
|
let best = null;
|
|
2827
3075
|
for (const name of entries) {
|
|
2828
|
-
const path =
|
|
3076
|
+
const path = join10(codexHome, String(name));
|
|
2829
3077
|
try {
|
|
2830
3078
|
const mtime = fs.stat(path).mtimeMs;
|
|
2831
3079
|
if (!best || mtime > best.mtime)
|
|
@@ -2885,15 +3133,15 @@ var init_pair_command = __esm(() => {
|
|
|
2885
3133
|
});
|
|
2886
3134
|
|
|
2887
3135
|
// src/rotating-log.ts
|
|
2888
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
3136
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, renameSync as renameSync2, statSync as statSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
2889
3137
|
import { dirname as dirname2 } from "path";
|
|
2890
|
-
function appendRotatingLog(path, content, options = {}) {
|
|
3138
|
+
function appendRotatingLog(path, content, options = {}, fsOps = REAL_FS_OPS) {
|
|
2891
3139
|
const maxBytes = options.maxBytes ?? positiveIntFromEnv("AGENTBRIDGE_LOG_MAX_BYTES", DEFAULT_MAX_BYTES);
|
|
2892
3140
|
const keep = options.keep ?? positiveIntFromEnv("AGENTBRIDGE_LOG_ROTATE_KEEP", DEFAULT_KEEP);
|
|
2893
|
-
if (!
|
|
3141
|
+
if (!fsOps.existsSync(dirname2(path)))
|
|
2894
3142
|
return;
|
|
2895
|
-
rotateIfNeeded(path, Buffer.byteLength(content), maxBytes, keep);
|
|
2896
|
-
|
|
3143
|
+
rotateIfNeeded(path, Buffer.byteLength(content), maxBytes, keep, fsOps);
|
|
3144
|
+
fsOps.appendFileSync(path, content, "utf-8");
|
|
2897
3145
|
}
|
|
2898
3146
|
function positiveIntFromEnv(name, fallback) {
|
|
2899
3147
|
const value = process.env[name];
|
|
@@ -2902,30 +3150,53 @@ function positiveIntFromEnv(name, fallback) {
|
|
|
2902
3150
|
const parsed = Number(value);
|
|
2903
3151
|
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
2904
3152
|
}
|
|
2905
|
-
function
|
|
3153
|
+
function isEnoent(error) {
|
|
3154
|
+
return !!error && error.code === "ENOENT";
|
|
3155
|
+
}
|
|
3156
|
+
function renameIfPresent(from, to, fsOps) {
|
|
3157
|
+
try {
|
|
3158
|
+
fsOps.renameSync(from, to);
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
if (!isEnoent(error))
|
|
3161
|
+
throw error;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function unlinkIfPresent(path, fsOps) {
|
|
3165
|
+
try {
|
|
3166
|
+
fsOps.unlinkSync(path);
|
|
3167
|
+
} catch (error) {
|
|
3168
|
+
if (!isEnoent(error))
|
|
3169
|
+
throw error;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function rotateIfNeeded(path, incomingBytes, maxBytes, keep, fsOps) {
|
|
2906
3173
|
if (!Number.isFinite(maxBytes) || maxBytes <= 0 || keep <= 0)
|
|
2907
3174
|
return;
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3175
|
+
let size;
|
|
3176
|
+
try {
|
|
3177
|
+
size = fsOps.statSync(path).size;
|
|
3178
|
+
} catch (error) {
|
|
3179
|
+
if (isEnoent(error))
|
|
3180
|
+
return;
|
|
3181
|
+
throw error;
|
|
3182
|
+
}
|
|
2911
3183
|
if (size + incomingBytes <= maxBytes)
|
|
2912
3184
|
return;
|
|
2913
3185
|
for (let index = keep;index >= 1; index--) {
|
|
2914
3186
|
const current = `${path}.${index}`;
|
|
2915
3187
|
const next = `${path}.${index + 1}`;
|
|
2916
|
-
if (!existsSync8(current))
|
|
2917
|
-
continue;
|
|
2918
3188
|
if (index === keep) {
|
|
2919
|
-
|
|
3189
|
+
unlinkIfPresent(current, fsOps);
|
|
2920
3190
|
} else {
|
|
2921
|
-
|
|
3191
|
+
renameIfPresent(current, next, fsOps);
|
|
2922
3192
|
}
|
|
2923
3193
|
}
|
|
2924
|
-
|
|
3194
|
+
renameIfPresent(path, `${path}.1`, fsOps);
|
|
2925
3195
|
}
|
|
2926
|
-
var DEFAULT_MAX_BYTES, DEFAULT_KEEP = 3;
|
|
3196
|
+
var DEFAULT_MAX_BYTES, DEFAULT_KEEP = 3, REAL_FS_OPS;
|
|
2927
3197
|
var init_rotating_log = __esm(() => {
|
|
2928
3198
|
DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
3199
|
+
REAL_FS_OPS = { statSync: statSync5, renameSync: renameSync2, unlinkSync: unlinkSync4, appendFileSync: appendFileSync2, existsSync: existsSync11 };
|
|
2929
3200
|
});
|
|
2930
3201
|
|
|
2931
3202
|
// src/stderr-ring-buffer.ts
|
|
@@ -2978,20 +3249,20 @@ var init_stderr_ring_buffer = __esm(() => {
|
|
|
2978
3249
|
|
|
2979
3250
|
// src/thread-state.ts
|
|
2980
3251
|
import {
|
|
2981
|
-
existsSync as
|
|
3252
|
+
existsSync as existsSync12,
|
|
2982
3253
|
mkdirSync as mkdirSync5,
|
|
2983
|
-
readdirSync as
|
|
3254
|
+
readdirSync as readdirSync4,
|
|
2984
3255
|
readFileSync as readFileSync8,
|
|
2985
3256
|
renameSync as renameSync3,
|
|
2986
3257
|
writeFileSync as writeFileSync6
|
|
2987
3258
|
} from "fs";
|
|
2988
3259
|
import { homedir as homedir3 } from "os";
|
|
2989
|
-
import { basename as basename3, dirname as dirname3, join as
|
|
3260
|
+
import { basename as basename3, dirname as dirname3, join as join11 } from "path";
|
|
2990
3261
|
function nowIso() {
|
|
2991
3262
|
return new Date().toISOString();
|
|
2992
3263
|
}
|
|
2993
3264
|
function codexHome(env = process.env) {
|
|
2994
|
-
return env.CODEX_HOME && env.CODEX_HOME.length > 0 ? env.CODEX_HOME :
|
|
3265
|
+
return env.CODEX_HOME && env.CODEX_HOME.length > 0 ? env.CODEX_HOME : join11(homedir3(), ".codex");
|
|
2995
3266
|
}
|
|
2996
3267
|
function atomicWriteJson(path, value) {
|
|
2997
3268
|
mkdirSync5(dirname3(path), { recursive: true });
|
|
@@ -3010,8 +3281,8 @@ function readRawCurrentThread(stateDir) {
|
|
|
3010
3281
|
return null;
|
|
3011
3282
|
}
|
|
3012
3283
|
function findCodexRolloutFile(threadId, env = process.env, maxEntries = 20000) {
|
|
3013
|
-
const sessionsDir =
|
|
3014
|
-
if (!threadId || !
|
|
3284
|
+
const sessionsDir = join11(codexHome(env), "sessions");
|
|
3285
|
+
if (!threadId || !existsSync12(sessionsDir))
|
|
3015
3286
|
return null;
|
|
3016
3287
|
const exactName = `rollout-${threadId}.jsonl`;
|
|
3017
3288
|
const stack = [sessionsDir];
|
|
@@ -3020,13 +3291,13 @@ function findCodexRolloutFile(threadId, env = process.env, maxEntries = 20000) {
|
|
|
3020
3291
|
const dir = stack.pop();
|
|
3021
3292
|
let entries;
|
|
3022
3293
|
try {
|
|
3023
|
-
entries =
|
|
3294
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
3024
3295
|
} catch {
|
|
3025
3296
|
continue;
|
|
3026
3297
|
}
|
|
3027
3298
|
for (const entry of entries) {
|
|
3028
3299
|
visited++;
|
|
3029
|
-
const path =
|
|
3300
|
+
const path = join11(dir, entry.name);
|
|
3030
3301
|
if (entry.isDirectory()) {
|
|
3031
3302
|
stack.push(path);
|
|
3032
3303
|
continue;
|
|
@@ -3051,7 +3322,7 @@ function readUsableCurrentThread(identity, env = process.env) {
|
|
|
3051
3322
|
return null;
|
|
3052
3323
|
if (state.cwd !== identity.cwd)
|
|
3053
3324
|
return null;
|
|
3054
|
-
if (state.rolloutPath &&
|
|
3325
|
+
if (state.rolloutPath && existsSync12(state.rolloutPath))
|
|
3055
3326
|
return state;
|
|
3056
3327
|
const rolloutPath = findCodexRolloutFile(state.threadId, env);
|
|
3057
3328
|
if (!rolloutPath)
|
|
@@ -3082,16 +3353,16 @@ import {
|
|
|
3082
3353
|
closeSync as closeSync3,
|
|
3083
3354
|
writeFileSync as writeFileSync7,
|
|
3084
3355
|
readFileSync as readFileSync9,
|
|
3085
|
-
unlinkSync as
|
|
3086
|
-
existsSync as
|
|
3356
|
+
unlinkSync as unlinkSync5,
|
|
3357
|
+
existsSync as existsSync13,
|
|
3087
3358
|
mkdirSync as mkdirSync6
|
|
3088
3359
|
} from "fs";
|
|
3089
3360
|
import { homedir as homedir4 } from "os";
|
|
3090
|
-
import { dirname as dirname4, join as
|
|
3361
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
3091
3362
|
function appendWrapperLog(path, entry) {
|
|
3092
3363
|
try {
|
|
3093
3364
|
const dir = dirname4(path);
|
|
3094
|
-
if (!
|
|
3365
|
+
if (!existsSync13(dir)) {
|
|
3095
3366
|
mkdirSync6(dir, { recursive: true });
|
|
3096
3367
|
}
|
|
3097
3368
|
appendRotatingLog(path, `[${new Date().toISOString()}] ${entry}
|
|
@@ -3258,7 +3529,7 @@ async function runCodex(args) {
|
|
|
3258
3529
|
if (status?.proxyUrl) {
|
|
3259
3530
|
proxyUrl = status.proxyUrl;
|
|
3260
3531
|
} else {
|
|
3261
|
-
const fallbackProxyPort = process.env.CODEX_PROXY_PORT ?? String(new ConfigService().loadOrDefault().codex.proxyPort);
|
|
3532
|
+
const fallbackProxyPort = process.env.CODEX_PROXY_PORT ?? String(new ConfigService().loadOrDefault((msg) => console.error(`[agentbridge] ${msg}`)).codex.proxyPort);
|
|
3262
3533
|
proxyUrl = `ws://127.0.0.1:${fallbackProxyPort}`;
|
|
3263
3534
|
console.error(`[agentbridge] No daemon status found, using fallback proxy port: ${proxyUrl}`);
|
|
3264
3535
|
}
|
|
@@ -3378,7 +3649,7 @@ async function runCodex(args) {
|
|
|
3378
3649
|
return;
|
|
3379
3650
|
cleanedTuiPid = true;
|
|
3380
3651
|
try {
|
|
3381
|
-
|
|
3652
|
+
unlinkSync5(stateDir.tuiPidFile);
|
|
3382
3653
|
} catch {}
|
|
3383
3654
|
}
|
|
3384
3655
|
function requestChildTermination(reason) {
|
|
@@ -3452,7 +3723,7 @@ async function runCodex(args) {
|
|
|
3452
3723
|
classification = refineCleanExitClassification(readTurnInProgress(stateDir.statusFile));
|
|
3453
3724
|
}
|
|
3454
3725
|
const tuiLogTail = captureTuiLogTail({
|
|
3455
|
-
codexHome:
|
|
3726
|
+
codexHome: join12(homedir4(), ".codex"),
|
|
3456
3727
|
nativePid: nativeChildPid,
|
|
3457
3728
|
run: (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf-8", timeout: 2000 })
|
|
3458
3729
|
});
|
|
@@ -3508,12 +3779,12 @@ function guardNoLiveManagedTui(stateDir, proxyUrl) {
|
|
|
3508
3779
|
if (pid) {
|
|
3509
3780
|
if (!isProcessAlive(pid)) {
|
|
3510
3781
|
try {
|
|
3511
|
-
|
|
3782
|
+
unlinkSync5(stateDir.tuiPidFile);
|
|
3512
3783
|
} catch {}
|
|
3513
3784
|
} else if (!isManagedCodexTuiProcess(pid, proxyUrl)) {
|
|
3514
3785
|
appendWrapperLog(stateDir.codexWrapperLogFile, `stale tui pid file pointed at unmanaged live pid=${pid}; removing`);
|
|
3515
3786
|
try {
|
|
3516
|
-
|
|
3787
|
+
unlinkSync5(stateDir.tuiPidFile);
|
|
3517
3788
|
} catch {}
|
|
3518
3789
|
} else {
|
|
3519
3790
|
console.error(`[agentbridge] This pair already has a managed Codex TUI running (pid ${pid}).`);
|
|
@@ -3560,7 +3831,7 @@ async function waitForProxyReady(proxyUrl, maxRetries = 20, delayMs = 100) {
|
|
|
3560
3831
|
return;
|
|
3561
3832
|
}
|
|
3562
3833
|
} catch {}
|
|
3563
|
-
await new Promise((
|
|
3834
|
+
await new Promise((resolve5) => setTimeout(resolve5, delayMs));
|
|
3564
3835
|
}
|
|
3565
3836
|
throw new Error(`Timed out waiting for Codex proxy readiness on ${healthUrl}`);
|
|
3566
3837
|
}
|
|
@@ -3609,17 +3880,17 @@ var init_codex = __esm(() => {
|
|
|
3609
3880
|
});
|
|
3610
3881
|
|
|
3611
3882
|
// src/claude-session.ts
|
|
3612
|
-
import { readdirSync as
|
|
3883
|
+
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
3613
3884
|
import { homedir as homedir5 } from "os";
|
|
3614
|
-
import { join as
|
|
3885
|
+
import { join as join13 } from "path";
|
|
3615
3886
|
function encodeClaudeProjectDir(cwd) {
|
|
3616
3887
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
3617
3888
|
}
|
|
3618
|
-
function findLatestClaudeSession(cwd, claudeHome = process.env.CLAUDE_CONFIG_DIR ||
|
|
3619
|
-
const dir =
|
|
3889
|
+
function findLatestClaudeSession(cwd, claudeHome = process.env.CLAUDE_CONFIG_DIR || join13(homedir5(), ".claude")) {
|
|
3890
|
+
const dir = join13(claudeHome, "projects", encodeClaudeProjectDir(cwd));
|
|
3620
3891
|
let entries;
|
|
3621
3892
|
try {
|
|
3622
|
-
entries =
|
|
3893
|
+
entries = readdirSync5(dir);
|
|
3623
3894
|
} catch {
|
|
3624
3895
|
return null;
|
|
3625
3896
|
}
|
|
@@ -3630,10 +3901,10 @@ function findLatestClaudeSession(cwd, claudeHome = process.env.CLAUDE_CONFIG_DIR
|
|
|
3630
3901
|
const sessionId = name.slice(0, -".jsonl".length);
|
|
3631
3902
|
if (!SESSION_ID_PATTERN.test(sessionId))
|
|
3632
3903
|
continue;
|
|
3633
|
-
const file =
|
|
3904
|
+
const file = join13(dir, name);
|
|
3634
3905
|
let mtimeMs;
|
|
3635
3906
|
try {
|
|
3636
|
-
const st =
|
|
3907
|
+
const st = statSync6(file);
|
|
3637
3908
|
if (!st.isFile())
|
|
3638
3909
|
continue;
|
|
3639
3910
|
mtimeMs = st.mtimeMs;
|
|
@@ -3739,8 +4010,8 @@ __export(exports_kill, {
|
|
|
3739
4010
|
runKill: () => runKill,
|
|
3740
4011
|
formatKillReport: () => formatKillReport
|
|
3741
4012
|
});
|
|
3742
|
-
import { readFileSync as readFileSync10, unlinkSync as
|
|
3743
|
-
import { join as
|
|
4013
|
+
import { readFileSync as readFileSync10, unlinkSync as unlinkSync6 } from "fs";
|
|
4014
|
+
import { join as join14 } from "path";
|
|
3744
4015
|
async function runKill(args = []) {
|
|
3745
4016
|
const argError = validateKillArgs(args);
|
|
3746
4017
|
if (argError === "help") {
|
|
@@ -3792,7 +4063,7 @@ async function runKill(args = []) {
|
|
|
3792
4063
|
for (const dirName of listPairDirsSafe(base)) {
|
|
3793
4064
|
if (registeredIds.has(dirName))
|
|
3794
4065
|
continue;
|
|
3795
|
-
const stateDir = new StateDirResolver(
|
|
4066
|
+
const stateDir = new StateDirResolver(join14(base, "pairs", dirName));
|
|
3796
4067
|
results.push(await stopStateDir(`${dirName} (unregistered)`, stateDir, portsFromStateDir(stateDir)));
|
|
3797
4068
|
}
|
|
3798
4069
|
const legacy = detectLegacyRootDaemon(base);
|
|
@@ -3877,7 +4148,7 @@ No arguments stop this directory's registered pairs and any legacy-root daemon.
|
|
|
3877
4148
|
}
|
|
3878
4149
|
async function stopPairEntry(base, pair) {
|
|
3879
4150
|
const ports = portsForEntry(pair);
|
|
3880
|
-
const stateDir = new StateDirResolver(
|
|
4151
|
+
const stateDir = new StateDirResolver(join14(base, "pairs", pair.pairId));
|
|
3881
4152
|
return stopStateDir(pair.pairId, stateDir, ports);
|
|
3882
4153
|
}
|
|
3883
4154
|
function listPairDirsSafe(base) {
|
|
@@ -4046,7 +4317,7 @@ function readTuiPid2(stateDir) {
|
|
|
4046
4317
|
}
|
|
4047
4318
|
function removeTuiPidFile(stateDir) {
|
|
4048
4319
|
try {
|
|
4049
|
-
|
|
4320
|
+
unlinkSync6(stateDir.tuiPidFile);
|
|
4050
4321
|
} catch {}
|
|
4051
4322
|
}
|
|
4052
4323
|
function isManagedCodexTuiProcess2(pid, proxyUrl) {
|
|
@@ -4066,7 +4337,7 @@ var exports_pairs = {};
|
|
|
4066
4337
|
__export(exports_pairs, {
|
|
4067
4338
|
runPairs: () => runPairs
|
|
4068
4339
|
});
|
|
4069
|
-
import { join as
|
|
4340
|
+
import { join as join15 } from "path";
|
|
4070
4341
|
async function runPairs(args = []) {
|
|
4071
4342
|
const [command, ...rest] = args;
|
|
4072
4343
|
if (command === "rm") {
|
|
@@ -4232,7 +4503,7 @@ async function collectRows() {
|
|
|
4232
4503
|
}
|
|
4233
4504
|
async function rowForPair(base, pair) {
|
|
4234
4505
|
const ports = portsForEntry(pair);
|
|
4235
|
-
const stateDir = new StateDirResolver(
|
|
4506
|
+
const stateDir = new StateDirResolver(join15(base, "pairs", pair.pairId));
|
|
4236
4507
|
const lifecycle = new DaemonLifecycle({
|
|
4237
4508
|
stateDir,
|
|
4238
4509
|
controlPort: ports.controlPort,
|
|
@@ -4339,18 +4610,18 @@ var DAEMON_STATUS_FETCH_TIMEOUT_MS = 1000;
|
|
|
4339
4610
|
import { Database } from "bun:sqlite";
|
|
4340
4611
|
import {
|
|
4341
4612
|
copyFileSync,
|
|
4342
|
-
existsSync as
|
|
4613
|
+
existsSync as existsSync14,
|
|
4343
4614
|
mkdirSync as mkdirSync7,
|
|
4344
4615
|
readFileSync as readFileSync11
|
|
4345
4616
|
} from "fs";
|
|
4346
|
-
import { dirname as dirname5, join as
|
|
4617
|
+
import { dirname as dirname5, join as join16 } from "path";
|
|
4347
4618
|
function isKickoffText(text) {
|
|
4348
4619
|
if (!text)
|
|
4349
4620
|
return false;
|
|
4350
4621
|
return KICKOFF_FINGERPRINTS.some((fingerprint) => text.includes(fingerprint));
|
|
4351
4622
|
}
|
|
4352
4623
|
function extractFirstRealUserMessage(rolloutPath) {
|
|
4353
|
-
if (!
|
|
4624
|
+
if (!existsSync14(rolloutPath))
|
|
4354
4625
|
return null;
|
|
4355
4626
|
const raw = readFileSync11(rolloutPath, "utf-8");
|
|
4356
4627
|
for (const line of raw.split(`
|
|
@@ -4374,8 +4645,8 @@ function extractFirstRealUserMessage(rolloutPath) {
|
|
|
4374
4645
|
}
|
|
4375
4646
|
function scanResumePollution(options = {}) {
|
|
4376
4647
|
const codexHome2 = options.codexHome ?? codexHome();
|
|
4377
|
-
const dbPath = options.dbPath ??
|
|
4378
|
-
if (!
|
|
4648
|
+
const dbPath = options.dbPath ?? join16(codexHome2, "state_5.sqlite");
|
|
4649
|
+
if (!existsSync14(dbPath)) {
|
|
4379
4650
|
return { codexHome: codexHome2, dbPath, scanned: 0, candidates: [], applied: 0, renamed: 0, deleted: 0 };
|
|
4380
4651
|
}
|
|
4381
4652
|
const db = options.apply ? new Database(dbPath) : new Database(dbPath, { readonly: true });
|
|
@@ -4468,12 +4739,12 @@ function scanResumePollution(options = {}) {
|
|
|
4468
4739
|
}
|
|
4469
4740
|
function backupCodexStateFiles(dbPath, now = new Date().toISOString()) {
|
|
4470
4741
|
const safeStamp = now.replace(/[:.]/g, "-");
|
|
4471
|
-
const base =
|
|
4742
|
+
const base = join16(dirname5(dbPath), "agentbridge-backups", `resume-pollution-${safeStamp}`);
|
|
4472
4743
|
mkdirSync7(base, { recursive: true });
|
|
4473
4744
|
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
4474
|
-
if (!
|
|
4745
|
+
if (!existsSync14(path))
|
|
4475
4746
|
continue;
|
|
4476
|
-
const target =
|
|
4747
|
+
const target = join16(base, path.split("/").pop());
|
|
4477
4748
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
4478
4749
|
copyFileSync(path, target);
|
|
4479
4750
|
}
|
|
@@ -4521,9 +4792,8 @@ __export(exports_doctor, {
|
|
|
4521
4792
|
runDoctor: () => runDoctor,
|
|
4522
4793
|
formatDoctorReport: () => formatDoctorReport
|
|
4523
4794
|
});
|
|
4524
|
-
import { existsSync as
|
|
4525
|
-
import {
|
|
4526
|
-
import { join as join16 } from "path";
|
|
4795
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
|
|
4796
|
+
import { join as join17 } from "path";
|
|
4527
4797
|
async function runDoctor(args = []) {
|
|
4528
4798
|
if (args[0] === "resume-pollution") {
|
|
4529
4799
|
runResumePollution(args.slice(1));
|
|
@@ -4626,6 +4896,7 @@ async function buildDoctorReport(pair, registered) {
|
|
|
4626
4896
|
detail: env.ok ? "AgentBridge env matches cwd" : env.reasons.join("; "),
|
|
4627
4897
|
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 `agentbridge claude`\uFF0C\u4E0D\u8981\u590D\u7528\u5176\u4ED6\u76EE\u5F55\u7684\u4F1A\u8BDD\u73AF\u5883\u3002"
|
|
4628
4898
|
});
|
|
4899
|
+
checks.push(configParseabilityCheck(cwd));
|
|
4629
4900
|
checks.push({
|
|
4630
4901
|
name: "daemon health",
|
|
4631
4902
|
status: health ? "ok" : "warn",
|
|
@@ -4712,16 +4983,16 @@ function artifactAlignmentCheck() {
|
|
|
4712
4983
|
stamps.push({ label: "global-cli", commit });
|
|
4713
4984
|
} catch {}
|
|
4714
4985
|
}
|
|
4715
|
-
const cacheRoot =
|
|
4986
|
+
const cacheRoot = pluginCacheRoot();
|
|
4716
4987
|
try {
|
|
4717
|
-
for (const version of
|
|
4718
|
-
const commit = extractBundleCommit(
|
|
4988
|
+
for (const version of readdirSync6(cacheRoot)) {
|
|
4989
|
+
const commit = extractBundleCommit(join17(cacheRoot, version, "server", "daemon.js"));
|
|
4719
4990
|
if (commit)
|
|
4720
4991
|
stamps.push({ label: `plugin-cache@${version}`, commit });
|
|
4721
4992
|
}
|
|
4722
4993
|
} catch {}
|
|
4723
|
-
const repoBundle =
|
|
4724
|
-
if (
|
|
4994
|
+
const repoBundle = join17(process.cwd(), "plugins", "agentbridge", "server", "daemon.js");
|
|
4995
|
+
if (existsSync15(repoBundle)) {
|
|
4725
4996
|
const commit = extractBundleCommit(repoBundle);
|
|
4726
4997
|
if (commit)
|
|
4727
4998
|
stamps.push({ label: "repo-bundle", commit });
|
|
@@ -4753,8 +5024,31 @@ function extractBundleCommit(path) {
|
|
|
4753
5024
|
return null;
|
|
4754
5025
|
}
|
|
4755
5026
|
}
|
|
5027
|
+
function configParseabilityCheck(cwd) {
|
|
5028
|
+
const desc = new ConfigService(cwd).describeConfig();
|
|
5029
|
+
if (desc.state === "absent") {
|
|
5030
|
+
return {
|
|
5031
|
+
name: "config.json",
|
|
5032
|
+
status: "ok",
|
|
5033
|
+
detail: `no project config at ${desc.path} \u2014 built-in defaults in effect`
|
|
5034
|
+
};
|
|
5035
|
+
}
|
|
5036
|
+
if (desc.state === "corrupt") {
|
|
5037
|
+
return {
|
|
5038
|
+
name: "config.json",
|
|
5039
|
+
status: "warn",
|
|
5040
|
+
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" + "\u4FEE\u6B63\u8BE5\u6587\u4EF6\u7684 JSON \u8BED\u6CD5/\u5B57\u6BB5\u7C7B\u578B\u540E\u91CD\u542F `agentbridge claude` \u5373\u53EF\u91CD\u65B0\u751F\u6548\u3002"
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
return {
|
|
5045
|
+
name: "config.json",
|
|
5046
|
+
status: "ok",
|
|
5047
|
+
detail: desc.customValues ? `parsed at ${desc.path} \u2014 custom values in effect` : `parsed at ${desc.path} \u2014 all values match defaults`
|
|
5048
|
+
};
|
|
5049
|
+
}
|
|
4756
5050
|
function logCheck(name, path) {
|
|
4757
|
-
if (!
|
|
5051
|
+
if (!existsSync15(path)) {
|
|
4758
5052
|
return {
|
|
4759
5053
|
name,
|
|
4760
5054
|
status: "warn",
|
|
@@ -4762,7 +5056,7 @@ function logCheck(name, path) {
|
|
|
4762
5056
|
hint: "\u65E5\u5FD7\u4F1A\u5728\u76F8\u5E94\u8FDB\u7A0B\u9996\u6B21\u542F\u52A8\u65F6\u521B\u5EFA\uFF1B\u8FDB\u7A0B\u4ECE\u672A\u542F\u52A8\u8FC7\u65F6\u8FD9\u662F\u6B63\u5E38\u7684\u3002"
|
|
4763
5057
|
};
|
|
4764
5058
|
}
|
|
4765
|
-
const stat =
|
|
5059
|
+
const stat = statSync7(path);
|
|
4766
5060
|
if (stat.size > LARGE_LOG_WARN_BYTES) {
|
|
4767
5061
|
return {
|
|
4768
5062
|
name,
|
|
@@ -4804,7 +5098,9 @@ function printDoctorReport(report) {
|
|
|
4804
5098
|
}
|
|
4805
5099
|
var LARGE_LOG_WARN_BYTES;
|
|
4806
5100
|
var init_doctor = __esm(() => {
|
|
5101
|
+
init_plugin_cache();
|
|
4807
5102
|
init_build_info();
|
|
5103
|
+
init_config_service();
|
|
4808
5104
|
init_env_guard();
|
|
4809
5105
|
init_pair_resolver();
|
|
4810
5106
|
init_thread_state();
|
|
@@ -4836,6 +5132,9 @@ function formatAgent(name, usage, snapshotAt) {
|
|
|
4836
5132
|
if (usage.rateLimitedUntil > 0) {
|
|
4837
5133
|
parts.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
|
|
4838
5134
|
}
|
|
5135
|
+
if (usage.parsedVia === "positional") {
|
|
5136
|
+
parts.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
|
|
5137
|
+
}
|
|
4839
5138
|
const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
|
|
4840
5139
|
if (ageSec > 300) {
|
|
4841
5140
|
parts.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
|
|
@@ -4947,6 +5246,160 @@ var init_budget = __esm(() => {
|
|
|
4947
5246
|
init_render();
|
|
4948
5247
|
});
|
|
4949
5248
|
|
|
5249
|
+
// src/cli/logs.ts
|
|
5250
|
+
var exports_logs = {};
|
|
5251
|
+
__export(exports_logs, {
|
|
5252
|
+
tailLines: () => tailLines,
|
|
5253
|
+
runLogs: () => runLogs,
|
|
5254
|
+
parseLogsArgs: () => parseLogsArgs,
|
|
5255
|
+
followLog: () => followLog
|
|
5256
|
+
});
|
|
5257
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
5258
|
+
import { spawn as spawn4 } from "child_process";
|
|
5259
|
+
function parseLogsArgs(args) {
|
|
5260
|
+
let codex = false;
|
|
5261
|
+
let follow = false;
|
|
5262
|
+
let lines = DEFAULT_LINES;
|
|
5263
|
+
for (let i = 0;i < args.length; i++) {
|
|
5264
|
+
const a = args[i];
|
|
5265
|
+
if (a === "--codex") {
|
|
5266
|
+
codex = true;
|
|
5267
|
+
continue;
|
|
5268
|
+
}
|
|
5269
|
+
if (a === "-f" || a === "--follow") {
|
|
5270
|
+
follow = true;
|
|
5271
|
+
continue;
|
|
5272
|
+
}
|
|
5273
|
+
if (a === "-n" || a === "--lines") {
|
|
5274
|
+
const next = args[i + 1];
|
|
5275
|
+
if (next === undefined) {
|
|
5276
|
+
throw new Error(`${a} requires a positive integer (e.g. ${a} 200)`);
|
|
5277
|
+
}
|
|
5278
|
+
lines = parsePositiveInt(next, a);
|
|
5279
|
+
i++;
|
|
5280
|
+
continue;
|
|
5281
|
+
}
|
|
5282
|
+
if (a.startsWith("-n")) {
|
|
5283
|
+
lines = parsePositiveInt(a.slice(2), "-n");
|
|
5284
|
+
continue;
|
|
5285
|
+
}
|
|
5286
|
+
if (a.startsWith("--lines=")) {
|
|
5287
|
+
lines = parsePositiveInt(a.slice("--lines=".length), "--lines");
|
|
5288
|
+
continue;
|
|
5289
|
+
}
|
|
5290
|
+
throw new Error(`Unknown logs flag: ${a}`);
|
|
5291
|
+
}
|
|
5292
|
+
return { codex, follow, lines };
|
|
5293
|
+
}
|
|
5294
|
+
function parsePositiveInt(raw, flag) {
|
|
5295
|
+
if (!/^\d+$/.test(raw)) {
|
|
5296
|
+
throw new Error(`${flag} must be a positive integer, got "${raw}"`);
|
|
5297
|
+
}
|
|
5298
|
+
const n = Number.parseInt(raw, 10);
|
|
5299
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
5300
|
+
throw new Error(`${flag} must be a positive integer, got "${raw}"`);
|
|
5301
|
+
}
|
|
5302
|
+
return n;
|
|
5303
|
+
}
|
|
5304
|
+
function tailLines(text, count) {
|
|
5305
|
+
const body = text.endsWith(`
|
|
5306
|
+
`) ? text.slice(0, -1) : text;
|
|
5307
|
+
if (body.length === 0)
|
|
5308
|
+
return [];
|
|
5309
|
+
const all = body.split(`
|
|
5310
|
+
`);
|
|
5311
|
+
return all.length <= count ? all : all.slice(all.length - count);
|
|
5312
|
+
}
|
|
5313
|
+
async function runLogs(args) {
|
|
5314
|
+
const { pairFlag } = parsePairFlag(args);
|
|
5315
|
+
const rest = stripPairTokens(args);
|
|
5316
|
+
let options;
|
|
5317
|
+
try {
|
|
5318
|
+
options = parseLogsArgs(rest);
|
|
5319
|
+
} catch (err) {
|
|
5320
|
+
console.error(`[agentbridge] ${err instanceof Error ? err.message : String(err)}`);
|
|
5321
|
+
process.exit(1);
|
|
5322
|
+
return;
|
|
5323
|
+
}
|
|
5324
|
+
let resolution;
|
|
5325
|
+
try {
|
|
5326
|
+
resolution = resolvePairReadOnly(pairFlag);
|
|
5327
|
+
} catch (err) {
|
|
5328
|
+
console.error(`[agentbridge] ${err instanceof Error ? err.message : String(err)}`);
|
|
5329
|
+
process.exit(1);
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
const { pair } = resolution;
|
|
5333
|
+
const logPath = options.codex ? pair.stateDir.codexWrapperLogFile : pair.stateDir.logFile;
|
|
5334
|
+
const logLabel = options.codex ? "codex wrapper log" : "daemon log";
|
|
5335
|
+
if (!existsSync16(logPath)) {
|
|
5336
|
+
const which = options.codex ? "codex wrapper log" : "daemon log";
|
|
5337
|
+
console.error(`no ${which} for pair ${pair.name} yet \u2014 start it with \`abg claude\` (${logPath})`);
|
|
5338
|
+
process.exit(1);
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
if (options.follow) {
|
|
5342
|
+
await followLog(logPath, options.lines);
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5345
|
+
printTail(logPath, options.lines, logLabel, pair.name);
|
|
5346
|
+
}
|
|
5347
|
+
function printTail(logPath, count, label, pairName) {
|
|
5348
|
+
let text;
|
|
5349
|
+
try {
|
|
5350
|
+
text = readFileSync13(logPath, "utf8");
|
|
5351
|
+
} catch (err) {
|
|
5352
|
+
console.error(`[agentbridge] failed to read ${label} for pair ${pairName}: ` + `${err instanceof Error ? err.message : String(err)} (${logPath})`);
|
|
5353
|
+
process.exit(1);
|
|
5354
|
+
return;
|
|
5355
|
+
}
|
|
5356
|
+
const lines = tailLines(text, count);
|
|
5357
|
+
for (const line of lines)
|
|
5358
|
+
console.log(line);
|
|
5359
|
+
}
|
|
5360
|
+
function followLog(logPath, count) {
|
|
5361
|
+
return new Promise((resolvePromise) => {
|
|
5362
|
+
const child = spawn4("tail", ["-f", "-n", String(count), logPath], {
|
|
5363
|
+
stdio: "inherit"
|
|
5364
|
+
});
|
|
5365
|
+
child.on("error", (err) => {
|
|
5366
|
+
console.error(`[agentbridge] failed to follow log: ${err.message}`);
|
|
5367
|
+
process.exit(1);
|
|
5368
|
+
});
|
|
5369
|
+
child.on("exit", (code, signal) => {
|
|
5370
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
5371
|
+
resolvePromise();
|
|
5372
|
+
return;
|
|
5373
|
+
}
|
|
5374
|
+
if (code != null && code !== 0) {
|
|
5375
|
+
process.exit(code);
|
|
5376
|
+
return;
|
|
5377
|
+
}
|
|
5378
|
+
resolvePromise();
|
|
5379
|
+
});
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
function stripPairTokens(args) {
|
|
5383
|
+
const out = [];
|
|
5384
|
+
for (let i = 0;i < args.length; i++) {
|
|
5385
|
+
const a = args[i];
|
|
5386
|
+
if (a === "--pair") {
|
|
5387
|
+
const next = args[i + 1];
|
|
5388
|
+
if (next !== undefined && !next.startsWith("-"))
|
|
5389
|
+
i++;
|
|
5390
|
+
continue;
|
|
5391
|
+
}
|
|
5392
|
+
if (a.startsWith("--pair="))
|
|
5393
|
+
continue;
|
|
5394
|
+
out.push(a);
|
|
5395
|
+
}
|
|
5396
|
+
return out;
|
|
5397
|
+
}
|
|
5398
|
+
var DEFAULT_LINES = 100;
|
|
5399
|
+
var init_logs = __esm(() => {
|
|
5400
|
+
init_pair_resolver();
|
|
5401
|
+
});
|
|
5402
|
+
|
|
4950
5403
|
// src/cli.ts
|
|
4951
5404
|
function parseTopLevel(args) {
|
|
4952
5405
|
const pairTokens = [];
|
|
@@ -5019,6 +5472,10 @@ async function main(command, restArgs) {
|
|
|
5019
5472
|
const { runBudget: runBudget2 } = await Promise.resolve().then(() => (init_budget(), exports_budget));
|
|
5020
5473
|
await runBudget2(restArgs);
|
|
5021
5474
|
break;
|
|
5475
|
+
case "logs":
|
|
5476
|
+
const { runLogs: runLogs2 } = await Promise.resolve().then(() => (init_logs(), exports_logs));
|
|
5477
|
+
await runLogs2(restArgs);
|
|
5478
|
+
break;
|
|
5022
5479
|
case "--help":
|
|
5023
5480
|
case "-h":
|
|
5024
5481
|
case undefined:
|
|
@@ -5057,6 +5514,9 @@ Commands:
|
|
|
5057
5514
|
doctor [--json] Diagnose env, daemon, build drift, logs, and current thread
|
|
5058
5515
|
doctor resume-pollution [--apply] Find/fix old AgentBridge kickoff metadata
|
|
5059
5516
|
budget [--json] Show both agents' subscription quota snapshot (5h/weekly, drift, pause state)
|
|
5517
|
+
logs [--codex] [-f] [-n N]
|
|
5518
|
+
Tail this pair's daemon log (or the codex wrapper log with
|
|
5519
|
+
--codex). -n N: last N lines (default 100). -f: follow/stream.
|
|
5060
5520
|
kill [all | --all | --pair <name|id>]
|
|
5061
5521
|
Stop this directory's pairs (default), every pair (all/--all), or one (--pair)
|
|
5062
5522
|
|
|
@@ -5095,6 +5555,10 @@ Examples:
|
|
|
5095
5555
|
abg pairs # List all pairs and their ports/status
|
|
5096
5556
|
abg pairs --threads # Include current thread mapping
|
|
5097
5557
|
abg doctor --json # Emit a structured diagnostics report
|
|
5558
|
+
abg logs # Tail the last 100 lines of this pair's daemon log
|
|
5559
|
+
abg logs -f -n 200 # Follow the log, starting from the last 200 lines
|
|
5560
|
+
abg logs --codex # Tail the codex wrapper log instead
|
|
5561
|
+
abg --pair work logs # Tail the "work" pair's daemon log
|
|
5098
5562
|
abg pairs rm work # Stop this directory's "work" pair and free its slot
|
|
5099
5563
|
abg pairs rm work-1a2b3c4d # ...or by its full id (from that pair's directory)
|
|
5100
5564
|
abg pairs prune --dry-run # Preview orphan pair dirs (no registry entry, not live)
|
|
@@ -5116,7 +5580,7 @@ var MARKETPLACE_NAME = "agentbridge", PLUGIN_NAME = "agentbridge", REFRESH_COMMA
|
|
|
5116
5580
|
var init_cli = __esm(() => {
|
|
5117
5581
|
REFRESH_COMMANDS = new Set(["claude", "codex", "resume"]);
|
|
5118
5582
|
NOTIFY_COMMANDS = new Set(["claude", "codex", "init", "dev", "resume"]);
|
|
5119
|
-
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget", "resume"]);
|
|
5583
|
+
PAIR_AWARE_COMMANDS = new Set(["claude", "codex", "kill", "doctor", "budget", "resume", "logs"]);
|
|
5120
5584
|
if (import.meta.main) {
|
|
5121
5585
|
const { command, restArgs } = parseTopLevel(process.argv.slice(2));
|
|
5122
5586
|
main(command, restArgs).catch((err) => {
|
|
@@ -5129,6 +5593,9 @@ init_cli();
|
|
|
5129
5593
|
|
|
5130
5594
|
export {
|
|
5131
5595
|
parseTopLevel,
|
|
5596
|
+
REFRESH_COMMANDS,
|
|
5132
5597
|
PLUGIN_NAME,
|
|
5598
|
+
PAIR_AWARE_COMMANDS,
|
|
5599
|
+
NOTIFY_COMMANDS,
|
|
5133
5600
|
MARKETPLACE_NAME
|
|
5134
5601
|
};
|