@raysonmeng/agentbridge 0.1.21 → 0.1.22
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 +33 -7
- package/dist/daemon.js +56 -6
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +108 -10
- package/plugins/agentbridge/server/daemon.js +56 -6
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"name": "agentbridge",
|
|
14
14
|
"description": "Bridge Claude Code and Codex through a shared daemon, push channel delivery, and reply/get_messages tools.",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.22",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "AgentBridge Contributors",
|
|
18
18
|
"email": "raysonmeng@qq.com"
|
package/dist/cli.js
CHANGED
|
@@ -179,7 +179,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
179
179
|
var require_package = __commonJS((exports, module) => {
|
|
180
180
|
module.exports = {
|
|
181
181
|
name: "@raysonmeng/agentbridge",
|
|
182
|
-
version: "0.1.
|
|
182
|
+
version: "0.1.22",
|
|
183
183
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
184
184
|
type: "module",
|
|
185
185
|
packageManager: "bun@1.3.11",
|
|
@@ -487,7 +487,7 @@ function findShapeViolation(raw) {
|
|
|
487
487
|
if (!isRecord(budget)) {
|
|
488
488
|
return "budget is present but not an object";
|
|
489
489
|
}
|
|
490
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
490
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
491
491
|
for (const key of numericKeys) {
|
|
492
492
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
493
493
|
return `budget.${key} is present but not a number`;
|
|
@@ -541,7 +541,7 @@ function hasCustomDecisionValues(config) {
|
|
|
541
541
|
const d = DEFAULT_CONFIG;
|
|
542
542
|
const b = config.budget;
|
|
543
543
|
const db = d.budget;
|
|
544
|
-
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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
544
|
+
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.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || 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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
545
545
|
}
|
|
546
546
|
function normalizeInteger(value, fallback) {
|
|
547
547
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -637,6 +637,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
637
637
|
return {
|
|
638
638
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
639
639
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
640
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
641
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
640
642
|
pauseAt,
|
|
641
643
|
resumeBelow,
|
|
642
644
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -654,6 +656,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
654
656
|
const overlay = {
|
|
655
657
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
656
658
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
659
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
660
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
657
661
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
658
662
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
659
663
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -793,6 +797,8 @@ var init_config_service = __esm(() => {
|
|
|
793
797
|
DEFAULT_BUDGET_CONFIG = {
|
|
794
798
|
enabled: true,
|
|
795
799
|
pollSeconds: 300,
|
|
800
|
+
budgetFreshTtlSec: 25,
|
|
801
|
+
idleAdviceActivityWindowSec: 600,
|
|
796
802
|
pauseAt: 90,
|
|
797
803
|
resumeBelow: 30,
|
|
798
804
|
syncDriftPct: 10,
|
|
@@ -1501,9 +1507,26 @@ var init_daemon_client = __esm(() => {
|
|
|
1501
1507
|
send: () => this.send({ type: "probe_incumbent" })
|
|
1502
1508
|
});
|
|
1503
1509
|
}
|
|
1510
|
+
async requestBudgetRefresh(timeoutMs = 3000) {
|
|
1511
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
const requestId = `budget_${Date.now()}_${this.nextRequestId++}`;
|
|
1515
|
+
return this.awaitTypedResponse({
|
|
1516
|
+
key: "budget_refresh",
|
|
1517
|
+
successEvent: "budgetRefresh",
|
|
1518
|
+
match: (payload) => payload.requestId === requestId,
|
|
1519
|
+
successValue: (payload) => payload.snapshot,
|
|
1520
|
+
failValue: null,
|
|
1521
|
+
timeoutMs,
|
|
1522
|
+
send: () => this.send({ type: "request_budget_refresh", requestId })
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1504
1525
|
awaitTypedResponse(opts) {
|
|
1505
|
-
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
1526
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send, match } = opts;
|
|
1506
1527
|
const onSuccess = (payload) => {
|
|
1528
|
+
if (match && !match(payload))
|
|
1529
|
+
return;
|
|
1507
1530
|
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
1508
1531
|
};
|
|
1509
1532
|
const onRejected = () => {
|
|
@@ -1604,6 +1627,9 @@ var init_daemon_client = __esm(() => {
|
|
|
1604
1627
|
case "incumbent_status":
|
|
1605
1628
|
this.emit("incumbentStatus", { connected: message.connected, alive: message.alive });
|
|
1606
1629
|
return;
|
|
1630
|
+
case "budget_refresh":
|
|
1631
|
+
this.emit("budgetRefresh", { requestId: message.requestId, snapshot: message.snapshot });
|
|
1632
|
+
return;
|
|
1607
1633
|
}
|
|
1608
1634
|
};
|
|
1609
1635
|
ws.onclose = (event) => {
|
|
@@ -1682,11 +1708,11 @@ function formatBuildInfo(build) {
|
|
|
1682
1708
|
var CODE_HASH_SENTINEL = "source", BUILD_INFO;
|
|
1683
1709
|
var init_build_info = __esm(() => {
|
|
1684
1710
|
BUILD_INFO = Object.freeze({
|
|
1685
|
-
version: defineString("0.1.
|
|
1686
|
-
commit: defineString("
|
|
1711
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
1712
|
+
commit: defineString("f5e401b", "source"),
|
|
1687
1713
|
bundle: defineBundle("dist"),
|
|
1688
1714
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
1689
|
-
codeHash: defineString("
|
|
1715
|
+
codeHash: defineString("9688463029af", "source")
|
|
1690
1716
|
});
|
|
1691
1717
|
});
|
|
1692
1718
|
|
package/dist/daemon.js
CHANGED
|
@@ -29,11 +29,11 @@ function defineNumber(value, fallback) {
|
|
|
29
29
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
30
30
|
}
|
|
31
31
|
var BUILD_INFO = Object.freeze({
|
|
32
|
-
version: defineString("0.1.
|
|
33
|
-
commit: defineString("
|
|
32
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
33
|
+
commit: defineString("f5e401b", "source"),
|
|
34
34
|
bundle: defineBundle("dist"),
|
|
35
35
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
36
|
-
codeHash: defineString("
|
|
36
|
+
codeHash: defineString("9688463029af", "source")
|
|
37
37
|
});
|
|
38
38
|
function daemonStatusBuildInfo() {
|
|
39
39
|
return { ...BUILD_INFO };
|
|
@@ -3478,6 +3478,8 @@ import { join as join4 } from "path";
|
|
|
3478
3478
|
var DEFAULT_BUDGET_CONFIG = {
|
|
3479
3479
|
enabled: true,
|
|
3480
3480
|
pollSeconds: 300,
|
|
3481
|
+
budgetFreshTtlSec: 25,
|
|
3482
|
+
idleAdviceActivityWindowSec: 600,
|
|
3481
3483
|
pauseAt: 90,
|
|
3482
3484
|
resumeBelow: 30,
|
|
3483
3485
|
syncDriftPct: 10,
|
|
@@ -3539,7 +3541,7 @@ function findShapeViolation(raw) {
|
|
|
3539
3541
|
if (!isRecord(budget)) {
|
|
3540
3542
|
return "budget is present but not an object";
|
|
3541
3543
|
}
|
|
3542
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
3544
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
3543
3545
|
for (const key of numericKeys) {
|
|
3544
3546
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
3545
3547
|
return `budget.${key} is present but not a number`;
|
|
@@ -3593,7 +3595,7 @@ function hasCustomDecisionValues(config) {
|
|
|
3593
3595
|
const d = DEFAULT_CONFIG;
|
|
3594
3596
|
const b = config.budget;
|
|
3595
3597
|
const db = d.budget;
|
|
3596
|
-
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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3598
|
+
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.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || 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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3597
3599
|
}
|
|
3598
3600
|
function normalizeInteger(value, fallback) {
|
|
3599
3601
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -3689,6 +3691,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3689
3691
|
return {
|
|
3690
3692
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
3691
3693
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
3694
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
3695
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
3692
3696
|
pauseAt,
|
|
3693
3697
|
resumeBelow,
|
|
3694
3698
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -3706,6 +3710,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3706
3710
|
const overlay = {
|
|
3707
3711
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
3708
3712
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
3713
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
3714
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
3709
3715
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
3710
3716
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
3711
3717
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -4846,6 +4852,7 @@ class BudgetCoordinator {
|
|
|
4846
4852
|
resumeSignals;
|
|
4847
4853
|
adviceCooldown;
|
|
4848
4854
|
isCodexTurnActive;
|
|
4855
|
+
hasRecentActivity;
|
|
4849
4856
|
timer = null;
|
|
4850
4857
|
running = false;
|
|
4851
4858
|
fpState = INITIAL_FINGERPRINT_STATE;
|
|
@@ -4876,6 +4883,7 @@ class BudgetCoordinator {
|
|
|
4876
4883
|
log: this.log
|
|
4877
4884
|
});
|
|
4878
4885
|
this.isCodexTurnActive = options.isCodexTurnActive ?? (() => false);
|
|
4886
|
+
this.hasRecentActivity = options.hasRecentActivity ?? (() => true);
|
|
4879
4887
|
}
|
|
4880
4888
|
async start() {
|
|
4881
4889
|
if (this.running || !this.config.enabled)
|
|
@@ -4908,6 +4916,24 @@ class BudgetCoordinator {
|
|
|
4908
4916
|
getSnapshot() {
|
|
4909
4917
|
return this.latestSnapshot;
|
|
4910
4918
|
}
|
|
4919
|
+
async refreshSnapshotReadonly() {
|
|
4920
|
+
let usage;
|
|
4921
|
+
try {
|
|
4922
|
+
usage = await this.source.fetchBoth();
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
this.log(`budget readonly refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4927
|
+
if (!usage)
|
|
4928
|
+
return null;
|
|
4929
|
+
const now = this.now();
|
|
4930
|
+
const runway = {
|
|
4931
|
+
claude: agentRunway(usage.claude, now),
|
|
4932
|
+
codex: agentRunway(usage.codex, now)
|
|
4933
|
+
};
|
|
4934
|
+
const state = computeBudgetState(usage.claude, usage.codex, this.config, now, runway);
|
|
4935
|
+
return this.toSnapshot(state, runway);
|
|
4936
|
+
}
|
|
4911
4937
|
getResumeCandidate() {
|
|
4912
4938
|
const { detail, ...rest } = this.resumeCandidate;
|
|
4913
4939
|
return detail ? {
|
|
@@ -5025,6 +5051,12 @@ class BudgetCoordinator {
|
|
|
5025
5051
|
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5026
5052
|
return;
|
|
5027
5053
|
}
|
|
5054
|
+
const activityWindowSec = this.config.idleAdviceActivityWindowSec;
|
|
5055
|
+
if (activityWindowSec > 0 && !this.hasRecentActivity(activityWindowSec)) {
|
|
5056
|
+
this.log(`budget advise suppressed: no agent activity in last ${activityWindowSec}s`);
|
|
5057
|
+
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5028
5060
|
if (effect.phase === "underutilized") {
|
|
5029
5061
|
if (!this.adviceCooldown.tryAcquire("underutilization", state.now))
|
|
5030
5062
|
return;
|
|
@@ -6795,7 +6827,8 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
6795
6827
|
});
|
|
6796
6828
|
},
|
|
6797
6829
|
resumeSignals: readResumeSignals,
|
|
6798
|
-
isCodexTurnActive: () => codex.turnInProgress
|
|
6830
|
+
isCodexTurnActive: () => codex.turnInProgress,
|
|
6831
|
+
hasRecentActivity: (windowSec) => codex.turnInProgress || Date.now() - lastActivityEpochMs <= windowSec * 1000
|
|
6799
6832
|
});
|
|
6800
6833
|
}
|
|
6801
6834
|
budgetCoordinator.start();
|
|
@@ -6924,6 +6957,7 @@ codex.on("steerFailed", ({ requestId, reason }) => {
|
|
|
6924
6957
|
});
|
|
6925
6958
|
codex.on("steerAccepted", ({ requestId }) => {
|
|
6926
6959
|
log("Steer accepted by app-server");
|
|
6960
|
+
recordAgentActivity();
|
|
6927
6961
|
const dispatch = pendingSteerDispatches.get(requestId);
|
|
6928
6962
|
pendingSteerDispatches.delete(requestId);
|
|
6929
6963
|
if (dispatch?.requireReply) {
|
|
@@ -6993,13 +7027,19 @@ codex.on("turnTrackingReset", (reason) => {
|
|
|
6993
7027
|
pendingSteerDispatches.clear();
|
|
6994
7028
|
resumeInjectionQueue.onTurnTrackingReset();
|
|
6995
7029
|
});
|
|
7030
|
+
var lastActivityEpochMs = 0;
|
|
7031
|
+
function recordAgentActivity() {
|
|
7032
|
+
lastActivityEpochMs = Date.now();
|
|
7033
|
+
}
|
|
6996
7034
|
codex.on("turnStarted", () => {
|
|
6997
7035
|
log("Codex turn started");
|
|
7036
|
+
recordAgentActivity();
|
|
6998
7037
|
emitToClaude(systemMessage("system_turn_started", "\u23F3 Codex is working on the current task. Wait for completion before sending a reply."));
|
|
6999
7038
|
});
|
|
7000
7039
|
codex.on("agentMessage", (msg) => {
|
|
7001
7040
|
if (msg.source !== "codex")
|
|
7002
7041
|
return;
|
|
7042
|
+
recordAgentActivity();
|
|
7003
7043
|
const route = routeCodexMessage(msg.content, {
|
|
7004
7044
|
mode: FILTER_MODE,
|
|
7005
7045
|
replyArmed: replyTracker.isArmed,
|
|
@@ -7217,6 +7257,11 @@ function handleControlMessage(ws, raw) {
|
|
|
7217
7257
|
log(`handleProbeIncumbent threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7218
7258
|
});
|
|
7219
7259
|
return;
|
|
7260
|
+
case "request_budget_refresh":
|
|
7261
|
+
handleRequestBudgetRefresh(ws, message.requestId).catch((err) => {
|
|
7262
|
+
log(`handleRequestBudgetRefresh threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7263
|
+
});
|
|
7264
|
+
return;
|
|
7220
7265
|
case "claude_to_codex": {
|
|
7221
7266
|
handleClaudeToCodex(ws, message).catch((err) => {
|
|
7222
7267
|
log(`handleClaudeToCodex threw for request ${message.requestId}: ${err?.message ?? err}`);
|
|
@@ -7575,6 +7620,11 @@ async function handleProbeIncumbent(ws) {
|
|
|
7575
7620
|
alive: stillConnected && alive
|
|
7576
7621
|
});
|
|
7577
7622
|
}
|
|
7623
|
+
async function handleRequestBudgetRefresh(ws, requestId) {
|
|
7624
|
+
const snapshot = budgetCoordinator ? await budgetCoordinator.refreshSnapshotReadonly() : null;
|
|
7625
|
+
log(`request_budget_refresh from #${ws.data.clientId}: ${snapshot ? "fresh" : "unavailable"}`);
|
|
7626
|
+
sendProtocolMessage(ws, { type: "budget_refresh", requestId, snapshot });
|
|
7627
|
+
}
|
|
7578
7628
|
async function probeLiveness2(ws, timeoutMs) {
|
|
7579
7629
|
return probeLiveness({
|
|
7580
7630
|
get readyState() {
|
package/package.json
CHANGED
|
@@ -14161,6 +14161,7 @@ var DEFAULT_MAX_BUFFERED_MESSAGES = 100;
|
|
|
14161
14161
|
var DEFAULT_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
14162
14162
|
var DEFAULT_DEDUPE_CAPACITY = 2048;
|
|
14163
14163
|
var DEFAULT_DEDUPE_TTL_MS = 20 * 60 * 1000;
|
|
14164
|
+
var DEFAULT_BUDGET_FRESH_TTL_MS = 25 * 1000;
|
|
14164
14165
|
var CLAUDE_INSTRUCTIONS = [
|
|
14165
14166
|
"Codex is an AI coding agent (OpenAI) running in a separate session on the same machine.",
|
|
14166
14167
|
"",
|
|
@@ -14224,6 +14225,10 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14224
14225
|
monotonicNow;
|
|
14225
14226
|
deliveredMessageIds = new Map;
|
|
14226
14227
|
budgetSnapshot = null;
|
|
14228
|
+
budgetFreshTtlMs;
|
|
14229
|
+
wallNow;
|
|
14230
|
+
requestFreshSnapshot = null;
|
|
14231
|
+
pendingBudgetRefresh = null;
|
|
14227
14232
|
constructor(logFile = new StateDirResolver().logFile, options = {}) {
|
|
14228
14233
|
super();
|
|
14229
14234
|
this.logFile = logFile;
|
|
@@ -14240,6 +14245,8 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14240
14245
|
this.dedupeCapacity = positiveIntegerOr(options.dedupeCapacity, DEFAULT_DEDUPE_CAPACITY);
|
|
14241
14246
|
this.dedupeTtlMs = positiveIntegerOr(options.dedupeTtlMs, DEFAULT_DEDUPE_TTL_MS);
|
|
14242
14247
|
this.monotonicNow = options.now ?? (() => performance.now());
|
|
14248
|
+
this.budgetFreshTtlMs = positiveIntegerOr(options.budgetFreshTtlMs, parsePositiveIntegerEnv("AGENTBRIDGE_BUDGET_FRESH_TTL_SEC", DEFAULT_BUDGET_FRESH_TTL_MS / 1000) * 1000);
|
|
14249
|
+
this.wallNow = options.wallNow ?? (() => Date.now());
|
|
14243
14250
|
this.server = new Server({ name: "agentbridge", version: "0.1.0" }, {
|
|
14244
14251
|
capabilities: {
|
|
14245
14252
|
experimental: { "claude/channel": {} },
|
|
@@ -14267,6 +14274,9 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14267
14274
|
setBudgetSnapshot(snapshot) {
|
|
14268
14275
|
this.budgetSnapshot = snapshot;
|
|
14269
14276
|
}
|
|
14277
|
+
setRequestFreshSnapshot(fetcher) {
|
|
14278
|
+
this.requestFreshSnapshot = fetcher;
|
|
14279
|
+
}
|
|
14270
14280
|
async pushNotification(message) {
|
|
14271
14281
|
this.log(`pushNotification (instance=${this.instanceId}, msgId=${message.id}, len=${message.content.length})`);
|
|
14272
14282
|
if (!this.rememberDelivery(message))
|
|
@@ -14543,13 +14553,42 @@ chat_id: ${this.sessionId}`);
|
|
|
14543
14553
|
content: [{ type: "text", text: `Resume acknowledged (resume_id=${resumeIdRaw}, status=${status}).` }]
|
|
14544
14554
|
};
|
|
14545
14555
|
}
|
|
14546
|
-
handleGetBudget() {
|
|
14547
|
-
|
|
14548
|
-
const
|
|
14556
|
+
async handleGetBudget() {
|
|
14557
|
+
let snapshot = this.budgetSnapshot;
|
|
14558
|
+
const fresh = snapshot !== null && this.isBudgetSnapshotFresh(snapshot);
|
|
14559
|
+
this.log(`get_budget called (instance=${this.instanceId}, hasSnapshot=${snapshot !== null}, fresh=${fresh})`);
|
|
14560
|
+
if (!fresh && this.requestFreshSnapshot) {
|
|
14561
|
+
const refreshed = await this.refreshBudgetSnapshot();
|
|
14562
|
+
snapshot = refreshed ?? this.budgetSnapshot;
|
|
14563
|
+
}
|
|
14564
|
+
const text = snapshot ? renderBudgetSnapshot(snapshot) : BUDGET_UNAVAILABLE_TEXT;
|
|
14549
14565
|
return {
|
|
14550
14566
|
content: [{ type: "text", text }]
|
|
14551
14567
|
};
|
|
14552
14568
|
}
|
|
14569
|
+
isBudgetSnapshotFresh(snapshot) {
|
|
14570
|
+
if (!snapshot.updatedAt || snapshot.updatedAt <= 0)
|
|
14571
|
+
return false;
|
|
14572
|
+
const ageMs = this.wallNow() - snapshot.updatedAt * 1000;
|
|
14573
|
+
return ageMs < this.budgetFreshTtlMs;
|
|
14574
|
+
}
|
|
14575
|
+
refreshBudgetSnapshot() {
|
|
14576
|
+
if (!this.requestFreshSnapshot)
|
|
14577
|
+
return Promise.resolve(null);
|
|
14578
|
+
if (!this.pendingBudgetRefresh) {
|
|
14579
|
+
this.pendingBudgetRefresh = this.requestFreshSnapshot().then((snapshot) => {
|
|
14580
|
+
if (snapshot)
|
|
14581
|
+
this.budgetSnapshot = snapshot;
|
|
14582
|
+
return snapshot;
|
|
14583
|
+
}).catch((error2) => {
|
|
14584
|
+
this.log(`get_budget refresh failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
14585
|
+
return null;
|
|
14586
|
+
}).finally(() => {
|
|
14587
|
+
this.pendingBudgetRefresh = null;
|
|
14588
|
+
});
|
|
14589
|
+
}
|
|
14590
|
+
return this.pendingBudgetRefresh;
|
|
14591
|
+
}
|
|
14553
14592
|
async handleReply(args) {
|
|
14554
14593
|
const text = args?.text;
|
|
14555
14594
|
if (!text) {
|
|
@@ -14667,11 +14706,11 @@ function defineNumber(value, fallback) {
|
|
|
14667
14706
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14668
14707
|
}
|
|
14669
14708
|
var BUILD_INFO = Object.freeze({
|
|
14670
|
-
version: defineString("0.1.
|
|
14671
|
-
commit: defineString("
|
|
14709
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
14710
|
+
commit: defineString("f5e401b", "source"),
|
|
14672
14711
|
bundle: defineBundle("plugin"),
|
|
14673
14712
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
14674
|
-
codeHash: defineString("
|
|
14713
|
+
codeHash: defineString("9688463029af", "source")
|
|
14675
14714
|
});
|
|
14676
14715
|
function sameRuntimeContract(a, b) {
|
|
14677
14716
|
if (!a || !b)
|
|
@@ -14875,9 +14914,26 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14875
14914
|
send: () => this.send({ type: "probe_incumbent" })
|
|
14876
14915
|
});
|
|
14877
14916
|
}
|
|
14917
|
+
async requestBudgetRefresh(timeoutMs = 3000) {
|
|
14918
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
14919
|
+
return null;
|
|
14920
|
+
}
|
|
14921
|
+
const requestId = `budget_${Date.now()}_${this.nextRequestId++}`;
|
|
14922
|
+
return this.awaitTypedResponse({
|
|
14923
|
+
key: "budget_refresh",
|
|
14924
|
+
successEvent: "budgetRefresh",
|
|
14925
|
+
match: (payload) => payload.requestId === requestId,
|
|
14926
|
+
successValue: (payload) => payload.snapshot,
|
|
14927
|
+
failValue: null,
|
|
14928
|
+
timeoutMs,
|
|
14929
|
+
send: () => this.send({ type: "request_budget_refresh", requestId })
|
|
14930
|
+
});
|
|
14931
|
+
}
|
|
14878
14932
|
awaitTypedResponse(opts) {
|
|
14879
|
-
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
14933
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send, match } = opts;
|
|
14880
14934
|
const onSuccess = (payload) => {
|
|
14935
|
+
if (match && !match(payload))
|
|
14936
|
+
return;
|
|
14881
14937
|
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
14882
14938
|
};
|
|
14883
14939
|
const onRejected = () => {
|
|
@@ -14978,6 +15034,9 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14978
15034
|
case "incumbent_status":
|
|
14979
15035
|
this.emit("incumbentStatus", { connected: message.connected, alive: message.alive });
|
|
14980
15036
|
return;
|
|
15037
|
+
case "budget_refresh":
|
|
15038
|
+
this.emit("budgetRefresh", { requestId: message.requestId, snapshot: message.snapshot });
|
|
15039
|
+
return;
|
|
14981
15040
|
}
|
|
14982
15041
|
};
|
|
14983
15042
|
ws.onclose = (event) => {
|
|
@@ -15694,6 +15753,8 @@ import { join as join2 } from "path";
|
|
|
15694
15753
|
var DEFAULT_BUDGET_CONFIG = {
|
|
15695
15754
|
enabled: true,
|
|
15696
15755
|
pollSeconds: 300,
|
|
15756
|
+
budgetFreshTtlSec: 25,
|
|
15757
|
+
idleAdviceActivityWindowSec: 600,
|
|
15697
15758
|
pauseAt: 90,
|
|
15698
15759
|
resumeBelow: 30,
|
|
15699
15760
|
syncDriftPct: 10,
|
|
@@ -15755,7 +15816,7 @@ function findShapeViolation(raw) {
|
|
|
15755
15816
|
if (!isRecord(budget)) {
|
|
15756
15817
|
return "budget is present but not an object";
|
|
15757
15818
|
}
|
|
15758
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
15819
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
15759
15820
|
for (const key of numericKeys) {
|
|
15760
15821
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
15761
15822
|
return `budget.${key} is present but not a number`;
|
|
@@ -15809,7 +15870,7 @@ function hasCustomDecisionValues(config2) {
|
|
|
15809
15870
|
const d = DEFAULT_CONFIG;
|
|
15810
15871
|
const b = config2.budget;
|
|
15811
15872
|
const db = d.budget;
|
|
15812
|
-
return config2.idleShutdownSeconds !== d.idleShutdownSeconds || config2.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config2.codex.appPort !== d.codex.appPort || config2.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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
15873
|
+
return config2.idleShutdownSeconds !== d.idleShutdownSeconds || config2.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config2.codex.appPort !== d.codex.appPort || config2.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || 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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
15813
15874
|
}
|
|
15814
15875
|
function normalizeInteger(value, fallback) {
|
|
15815
15876
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -15905,6 +15966,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15905
15966
|
return {
|
|
15906
15967
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
15907
15968
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
15969
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
15970
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
15908
15971
|
pauseAt,
|
|
15909
15972
|
resumeBelow,
|
|
15910
15973
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -15918,6 +15981,37 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15918
15981
|
allocation: normalizeAllocationConfig(budget.allocation, fallback.allocation)
|
|
15919
15982
|
};
|
|
15920
15983
|
}
|
|
15984
|
+
function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
15985
|
+
const overlay = {
|
|
15986
|
+
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
15987
|
+
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
15988
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
15989
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
15990
|
+
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
15991
|
+
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
15992
|
+
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
15993
|
+
parallel: {
|
|
15994
|
+
minRemainingPct: env.AGENTBRIDGE_BUDGET_PARALLEL_MIN_REMAINING_PCT ?? budget.parallel.minRemainingPct,
|
|
15995
|
+
timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
|
|
15996
|
+
},
|
|
15997
|
+
codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
|
|
15998
|
+
codexTiers: budget.codexTiers,
|
|
15999
|
+
maximize: {
|
|
16000
|
+
targetUtil: env.AGENTBRIDGE_BUDGET_TARGET_UTIL ?? budget.maximize.targetUtil,
|
|
16001
|
+
reserveSlopePctPerHour: env.AGENTBRIDGE_BUDGET_RESERVE_SLOPE_PCT_PER_HOUR ?? budget.maximize.reserveSlopePctPerHour,
|
|
16002
|
+
reserveMaxPct: env.AGENTBRIDGE_BUDGET_RESERVE_MAX_PCT ?? budget.maximize.reserveMaxPct,
|
|
16003
|
+
finishingHorizonMinutes: env.AGENTBRIDGE_BUDGET_FINISHING_HORIZON_MINUTES ?? budget.maximize.finishingHorizonMinutes,
|
|
16004
|
+
resumeHysteresisPct: env.AGENTBRIDGE_BUDGET_RESUME_HYSTERESIS_PCT ?? budget.maximize.resumeHysteresisPct,
|
|
16005
|
+
admissionAt: env.AGENTBRIDGE_BUDGET_ADMISSION_AT ?? budget.maximize.admissionAt,
|
|
16006
|
+
wrapUpQuota: env.AGENTBRIDGE_BUDGET_WRAP_UP_QUOTA ?? budget.maximize.wrapUpQuota
|
|
16007
|
+
},
|
|
16008
|
+
allocation: {
|
|
16009
|
+
minRunwayRatio: env.AGENTBRIDGE_BUDGET_MIN_RUNWAY_RATIO ?? budget.allocation.minRunwayRatio,
|
|
16010
|
+
minRunwayGapHours: env.AGENTBRIDGE_BUDGET_MIN_RUNWAY_GAP_HOURS ?? budget.allocation.minRunwayGapHours
|
|
16011
|
+
}
|
|
16012
|
+
};
|
|
16013
|
+
return normalizeBudgetConfig(overlay, budget);
|
|
16014
|
+
}
|
|
15921
16015
|
function normalizeConfig(raw) {
|
|
15922
16016
|
if (!isRecord(raw))
|
|
15923
16017
|
return null;
|
|
@@ -16397,8 +16491,12 @@ var config2 = configService.loadOrDefault(processLogger.log);
|
|
|
16397
16491
|
var CONTROL_PORT = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
|
|
16398
16492
|
var daemonLifecycle = new DaemonLifecycle({ stateDir, controlPort: CONTROL_PORT, log });
|
|
16399
16493
|
var CONTROL_WS_URL = daemonLifecycle.controlWsUrl;
|
|
16400
|
-
var
|
|
16494
|
+
var effectiveBudget = applyBudgetEnvOverrides(config2.budget);
|
|
16495
|
+
var claude = new ClaudeAdapter(stateDir.logFile, {
|
|
16496
|
+
budgetFreshTtlMs: effectiveBudget.budgetFreshTtlSec * 1000
|
|
16497
|
+
});
|
|
16401
16498
|
var daemonClient = new DaemonClient(CONTROL_WS_URL, { identity: currentClientIdentity });
|
|
16499
|
+
claude.setRequestFreshSnapshot(() => daemonClient.requestBudgetRefresh());
|
|
16402
16500
|
var shuttingDown = false;
|
|
16403
16501
|
var daemonDisabled = false;
|
|
16404
16502
|
var daemonDisabledReason = null;
|
|
@@ -29,11 +29,11 @@ function defineNumber(value, fallback) {
|
|
|
29
29
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
30
30
|
}
|
|
31
31
|
var BUILD_INFO = Object.freeze({
|
|
32
|
-
version: defineString("0.1.
|
|
33
|
-
commit: defineString("
|
|
32
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
33
|
+
commit: defineString("f5e401b", "source"),
|
|
34
34
|
bundle: defineBundle("plugin"),
|
|
35
35
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
36
|
-
codeHash: defineString("
|
|
36
|
+
codeHash: defineString("9688463029af", "source")
|
|
37
37
|
});
|
|
38
38
|
function daemonStatusBuildInfo() {
|
|
39
39
|
return { ...BUILD_INFO };
|
|
@@ -3478,6 +3478,8 @@ import { join as join4 } from "path";
|
|
|
3478
3478
|
var DEFAULT_BUDGET_CONFIG = {
|
|
3479
3479
|
enabled: true,
|
|
3480
3480
|
pollSeconds: 300,
|
|
3481
|
+
budgetFreshTtlSec: 25,
|
|
3482
|
+
idleAdviceActivityWindowSec: 600,
|
|
3481
3483
|
pauseAt: 90,
|
|
3482
3484
|
resumeBelow: 30,
|
|
3483
3485
|
syncDriftPct: 10,
|
|
@@ -3539,7 +3541,7 @@ function findShapeViolation(raw) {
|
|
|
3539
3541
|
if (!isRecord(budget)) {
|
|
3540
3542
|
return "budget is present but not an object";
|
|
3541
3543
|
}
|
|
3542
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
3544
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
3543
3545
|
for (const key of numericKeys) {
|
|
3544
3546
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
3545
3547
|
return `budget.${key} is present but not a number`;
|
|
@@ -3593,7 +3595,7 @@ function hasCustomDecisionValues(config) {
|
|
|
3593
3595
|
const d = DEFAULT_CONFIG;
|
|
3594
3596
|
const b = config.budget;
|
|
3595
3597
|
const db = d.budget;
|
|
3596
|
-
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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3598
|
+
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.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || 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 || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3597
3599
|
}
|
|
3598
3600
|
function normalizeInteger(value, fallback) {
|
|
3599
3601
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -3689,6 +3691,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3689
3691
|
return {
|
|
3690
3692
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
3691
3693
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
3694
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
3695
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
3692
3696
|
pauseAt,
|
|
3693
3697
|
resumeBelow,
|
|
3694
3698
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -3706,6 +3710,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3706
3710
|
const overlay = {
|
|
3707
3711
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
3708
3712
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
3713
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
3714
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
3709
3715
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
3710
3716
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
3711
3717
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -4846,6 +4852,7 @@ class BudgetCoordinator {
|
|
|
4846
4852
|
resumeSignals;
|
|
4847
4853
|
adviceCooldown;
|
|
4848
4854
|
isCodexTurnActive;
|
|
4855
|
+
hasRecentActivity;
|
|
4849
4856
|
timer = null;
|
|
4850
4857
|
running = false;
|
|
4851
4858
|
fpState = INITIAL_FINGERPRINT_STATE;
|
|
@@ -4876,6 +4883,7 @@ class BudgetCoordinator {
|
|
|
4876
4883
|
log: this.log
|
|
4877
4884
|
});
|
|
4878
4885
|
this.isCodexTurnActive = options.isCodexTurnActive ?? (() => false);
|
|
4886
|
+
this.hasRecentActivity = options.hasRecentActivity ?? (() => true);
|
|
4879
4887
|
}
|
|
4880
4888
|
async start() {
|
|
4881
4889
|
if (this.running || !this.config.enabled)
|
|
@@ -4908,6 +4916,24 @@ class BudgetCoordinator {
|
|
|
4908
4916
|
getSnapshot() {
|
|
4909
4917
|
return this.latestSnapshot;
|
|
4910
4918
|
}
|
|
4919
|
+
async refreshSnapshotReadonly() {
|
|
4920
|
+
let usage;
|
|
4921
|
+
try {
|
|
4922
|
+
usage = await this.source.fetchBoth();
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
this.log(`budget readonly refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4927
|
+
if (!usage)
|
|
4928
|
+
return null;
|
|
4929
|
+
const now = this.now();
|
|
4930
|
+
const runway = {
|
|
4931
|
+
claude: agentRunway(usage.claude, now),
|
|
4932
|
+
codex: agentRunway(usage.codex, now)
|
|
4933
|
+
};
|
|
4934
|
+
const state = computeBudgetState(usage.claude, usage.codex, this.config, now, runway);
|
|
4935
|
+
return this.toSnapshot(state, runway);
|
|
4936
|
+
}
|
|
4911
4937
|
getResumeCandidate() {
|
|
4912
4938
|
const { detail, ...rest } = this.resumeCandidate;
|
|
4913
4939
|
return detail ? {
|
|
@@ -5025,6 +5051,12 @@ class BudgetCoordinator {
|
|
|
5025
5051
|
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5026
5052
|
return;
|
|
5027
5053
|
}
|
|
5054
|
+
const activityWindowSec = this.config.idleAdviceActivityWindowSec;
|
|
5055
|
+
if (activityWindowSec > 0 && !this.hasRecentActivity(activityWindowSec)) {
|
|
5056
|
+
this.log(`budget advise suppressed: no agent activity in last ${activityWindowSec}s`);
|
|
5057
|
+
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5028
5060
|
if (effect.phase === "underutilized") {
|
|
5029
5061
|
if (!this.adviceCooldown.tryAcquire("underutilization", state.now))
|
|
5030
5062
|
return;
|
|
@@ -6795,7 +6827,8 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
6795
6827
|
});
|
|
6796
6828
|
},
|
|
6797
6829
|
resumeSignals: readResumeSignals,
|
|
6798
|
-
isCodexTurnActive: () => codex.turnInProgress
|
|
6830
|
+
isCodexTurnActive: () => codex.turnInProgress,
|
|
6831
|
+
hasRecentActivity: (windowSec) => codex.turnInProgress || Date.now() - lastActivityEpochMs <= windowSec * 1000
|
|
6799
6832
|
});
|
|
6800
6833
|
}
|
|
6801
6834
|
budgetCoordinator.start();
|
|
@@ -6924,6 +6957,7 @@ codex.on("steerFailed", ({ requestId, reason }) => {
|
|
|
6924
6957
|
});
|
|
6925
6958
|
codex.on("steerAccepted", ({ requestId }) => {
|
|
6926
6959
|
log("Steer accepted by app-server");
|
|
6960
|
+
recordAgentActivity();
|
|
6927
6961
|
const dispatch = pendingSteerDispatches.get(requestId);
|
|
6928
6962
|
pendingSteerDispatches.delete(requestId);
|
|
6929
6963
|
if (dispatch?.requireReply) {
|
|
@@ -6993,13 +7027,19 @@ codex.on("turnTrackingReset", (reason) => {
|
|
|
6993
7027
|
pendingSteerDispatches.clear();
|
|
6994
7028
|
resumeInjectionQueue.onTurnTrackingReset();
|
|
6995
7029
|
});
|
|
7030
|
+
var lastActivityEpochMs = 0;
|
|
7031
|
+
function recordAgentActivity() {
|
|
7032
|
+
lastActivityEpochMs = Date.now();
|
|
7033
|
+
}
|
|
6996
7034
|
codex.on("turnStarted", () => {
|
|
6997
7035
|
log("Codex turn started");
|
|
7036
|
+
recordAgentActivity();
|
|
6998
7037
|
emitToClaude(systemMessage("system_turn_started", "\u23F3 Codex is working on the current task. Wait for completion before sending a reply."));
|
|
6999
7038
|
});
|
|
7000
7039
|
codex.on("agentMessage", (msg) => {
|
|
7001
7040
|
if (msg.source !== "codex")
|
|
7002
7041
|
return;
|
|
7042
|
+
recordAgentActivity();
|
|
7003
7043
|
const route = routeCodexMessage(msg.content, {
|
|
7004
7044
|
mode: FILTER_MODE,
|
|
7005
7045
|
replyArmed: replyTracker.isArmed,
|
|
@@ -7217,6 +7257,11 @@ function handleControlMessage(ws, raw) {
|
|
|
7217
7257
|
log(`handleProbeIncumbent threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7218
7258
|
});
|
|
7219
7259
|
return;
|
|
7260
|
+
case "request_budget_refresh":
|
|
7261
|
+
handleRequestBudgetRefresh(ws, message.requestId).catch((err) => {
|
|
7262
|
+
log(`handleRequestBudgetRefresh threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7263
|
+
});
|
|
7264
|
+
return;
|
|
7220
7265
|
case "claude_to_codex": {
|
|
7221
7266
|
handleClaudeToCodex(ws, message).catch((err) => {
|
|
7222
7267
|
log(`handleClaudeToCodex threw for request ${message.requestId}: ${err?.message ?? err}`);
|
|
@@ -7575,6 +7620,11 @@ async function handleProbeIncumbent(ws) {
|
|
|
7575
7620
|
alive: stillConnected && alive
|
|
7576
7621
|
});
|
|
7577
7622
|
}
|
|
7623
|
+
async function handleRequestBudgetRefresh(ws, requestId) {
|
|
7624
|
+
const snapshot = budgetCoordinator ? await budgetCoordinator.refreshSnapshotReadonly() : null;
|
|
7625
|
+
log(`request_budget_refresh from #${ws.data.clientId}: ${snapshot ? "fresh" : "unavailable"}`);
|
|
7626
|
+
sendProtocolMessage(ws, { type: "budget_refresh", requestId, snapshot });
|
|
7627
|
+
}
|
|
7578
7628
|
async function probeLiveness2(ws, timeoutMs) {
|
|
7579
7629
|
return probeLiveness({
|
|
7580
7630
|
get readyState() {
|