@raysonmeng/agentbridge 0.1.13 → 0.1.14
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 +289 -96
- package/dist/daemon.js +148 -13
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +133 -6
- package/plugins/agentbridge/server/daemon.js +148 -13
|
@@ -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.14",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "AgentBridge Contributors",
|
|
18
18
|
"email": "raysonmeng@qq.com"
|
package/dist/cli.js
CHANGED
|
@@ -176,7 +176,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
176
176
|
var require_package = __commonJS((exports, module) => {
|
|
177
177
|
module.exports = {
|
|
178
178
|
name: "@raysonmeng/agentbridge",
|
|
179
|
-
version: "0.1.
|
|
179
|
+
version: "0.1.14",
|
|
180
180
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
181
181
|
type: "module",
|
|
182
182
|
packageManager: "bun@1.3.11",
|
|
@@ -438,6 +438,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
|
|
|
438
438
|
return fallback;
|
|
439
439
|
return parsed;
|
|
440
440
|
}
|
|
441
|
+
function normalizeStrategy(value, fallback) {
|
|
442
|
+
return value === "conserve" || value === "maximize" ? value : fallback;
|
|
443
|
+
}
|
|
441
444
|
function normalizeBoolean(value, fallback) {
|
|
442
445
|
if (typeof value === "boolean")
|
|
443
446
|
return value;
|
|
@@ -486,9 +489,27 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
486
489
|
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
487
490
|
},
|
|
488
491
|
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
489
|
-
codexTiers
|
|
492
|
+
codexTiers,
|
|
493
|
+
strategy: normalizeStrategy(budget.strategy, fallback.strategy)
|
|
490
494
|
};
|
|
491
495
|
}
|
|
496
|
+
function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
497
|
+
const overlay = {
|
|
498
|
+
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
499
|
+
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
500
|
+
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
501
|
+
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
502
|
+
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
503
|
+
parallel: {
|
|
504
|
+
minRemainingPct: env.AGENTBRIDGE_BUDGET_PARALLEL_MIN_REMAINING_PCT ?? budget.parallel.minRemainingPct,
|
|
505
|
+
timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
|
|
506
|
+
},
|
|
507
|
+
codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
|
|
508
|
+
codexTiers: budget.codexTiers,
|
|
509
|
+
strategy: env.AGENTBRIDGE_BUDGET_STRATEGY ?? budget.strategy
|
|
510
|
+
};
|
|
511
|
+
return normalizeBudgetConfig(overlay, budget);
|
|
512
|
+
}
|
|
492
513
|
function normalizeConfig(raw) {
|
|
493
514
|
if (!isRecord(raw))
|
|
494
515
|
return null;
|
|
@@ -615,7 +636,8 @@ var init_config_service = __esm(() => {
|
|
|
615
636
|
full: null,
|
|
616
637
|
balanced: { effort: "medium" },
|
|
617
638
|
eco: { effort: "low" }
|
|
618
|
-
}
|
|
639
|
+
},
|
|
640
|
+
strategy: "conserve"
|
|
619
641
|
};
|
|
620
642
|
DEFAULT_CONFIG = {
|
|
621
643
|
version: "1.0",
|
|
@@ -1475,11 +1497,11 @@ function formatBuildInfo(build) {
|
|
|
1475
1497
|
var CODE_HASH_SENTINEL = "source", BUILD_INFO;
|
|
1476
1498
|
var init_build_info = __esm(() => {
|
|
1477
1499
|
BUILD_INFO = Object.freeze({
|
|
1478
|
-
version: defineString("0.1.
|
|
1479
|
-
commit: defineString("
|
|
1500
|
+
version: defineString("0.1.14", "0.0.0-source"),
|
|
1501
|
+
commit: defineString("f5a9562", "source"),
|
|
1480
1502
|
bundle: defineBundle("dist"),
|
|
1481
1503
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
1482
|
-
codeHash: defineString("
|
|
1504
|
+
codeHash: defineString("e05d18c3cc72", "source")
|
|
1483
1505
|
});
|
|
1484
1506
|
});
|
|
1485
1507
|
|
|
@@ -3660,12 +3682,26 @@ function readUsableCurrentThread(identity, env = process.env) {
|
|
|
3660
3682
|
const state = readRawCurrentThread(identity.stateDir);
|
|
3661
3683
|
if (!state)
|
|
3662
3684
|
return null;
|
|
3663
|
-
if (state.status !== "current")
|
|
3664
|
-
return null;
|
|
3665
3685
|
if (state.pairId !== identity.pairId)
|
|
3666
3686
|
return null;
|
|
3667
3687
|
if (state.cwd !== identity.cwd)
|
|
3668
3688
|
return null;
|
|
3689
|
+
if (state.status === "pending") {
|
|
3690
|
+
const rolloutPath2 = findCodexRolloutFile(state.threadId, env);
|
|
3691
|
+
if (!rolloutPath2)
|
|
3692
|
+
return null;
|
|
3693
|
+
const promoted = {
|
|
3694
|
+
...state,
|
|
3695
|
+
status: "current",
|
|
3696
|
+
rolloutPath: rolloutPath2,
|
|
3697
|
+
rolloutVerifiedAt: nowIso(),
|
|
3698
|
+
updatedAt: nowIso()
|
|
3699
|
+
};
|
|
3700
|
+
try {
|
|
3701
|
+
atomicWriteJson(identity.stateDir.currentThreadFile, promoted);
|
|
3702
|
+
} catch {}
|
|
3703
|
+
return promoted;
|
|
3704
|
+
}
|
|
3669
3705
|
if (state.rolloutPath && existsSync12(state.rolloutPath))
|
|
3670
3706
|
return state;
|
|
3671
3707
|
const rolloutPath = findCodexRolloutFile(state.threadId, env);
|
|
@@ -3759,10 +3795,12 @@ function resolveCodexResumeArgs(parsed, pair, env = process.env) {
|
|
|
3759
3795
|
const current = readUsableCurrentThread(identity, env);
|
|
3760
3796
|
if (parsed.resumeCurrent) {
|
|
3761
3797
|
if (!current) {
|
|
3798
|
+
const raw = readRawCurrentThread(identity.stateDir);
|
|
3799
|
+
const pending = raw && raw.status === "pending" && raw.pairId === identity.pairId && raw.cwd === identity.cwd ? raw : null;
|
|
3762
3800
|
return {
|
|
3763
3801
|
rest: parsed.rest,
|
|
3764
3802
|
mode: "resume-current",
|
|
3765
|
-
error: "No verified current Codex thread for this pair. Start a new one with `abg codex --new`, or resume a specific thread with `abg codex resume <threadId>`."
|
|
3803
|
+
error: pending ? `No verified current Codex thread for this pair. Found a pending (unverified) thread ${pending.threadId} \u2014 its Codex rollout file was not found. Try \`abg codex resume ${pending.threadId}\`, or start fresh with \`abg codex --new\`.` : "No verified current Codex thread for this pair. Start a new one with `abg codex --new`, or resume a specific thread with `abg codex resume <threadId>`."
|
|
3766
3804
|
};
|
|
3767
3805
|
}
|
|
3768
3806
|
return {
|
|
@@ -5069,6 +5107,215 @@ var init_pairs = __esm(() => {
|
|
|
5069
5107
|
init_thread_state();
|
|
5070
5108
|
init_kill();
|
|
5071
5109
|
});
|
|
5110
|
+
// src/budget/types.ts
|
|
5111
|
+
var STALE_MAX_AGE_SEC = 600;
|
|
5112
|
+
|
|
5113
|
+
// src/budget/budget-state.ts
|
|
5114
|
+
function isDecisionGrade(usage, now) {
|
|
5115
|
+
if (!usage)
|
|
5116
|
+
return false;
|
|
5117
|
+
const freshWindow = usage.fiveHour !== null && usage.fiveHour.resetEpoch > now || usage.weekly !== null && usage.weekly.resetEpoch > now;
|
|
5118
|
+
if (!freshWindow)
|
|
5119
|
+
return false;
|
|
5120
|
+
if (usage.fetchedAt > 0 && now - usage.fetchedAt > STALE_MAX_AGE_SEC)
|
|
5121
|
+
return false;
|
|
5122
|
+
return true;
|
|
5123
|
+
}
|
|
5124
|
+
var init_budget_state = () => {};
|
|
5125
|
+
|
|
5126
|
+
// src/budget/burn-view.ts
|
|
5127
|
+
function agentWeeklyFiveHourWindowsLeft(usage, now) {
|
|
5128
|
+
if (!usage || usage.stale || !usage.ok)
|
|
5129
|
+
return null;
|
|
5130
|
+
if (!isDecisionGrade(usage, now))
|
|
5131
|
+
return null;
|
|
5132
|
+
const weekly = usage.weekly;
|
|
5133
|
+
if (!weekly || weekly.resetEpoch <= now)
|
|
5134
|
+
return null;
|
|
5135
|
+
if (weekly.burnConfident !== true)
|
|
5136
|
+
return null;
|
|
5137
|
+
if (weekly.runwaySeconds === undefined)
|
|
5138
|
+
return null;
|
|
5139
|
+
return weekly.fiveHourWindowsLeft ?? null;
|
|
5140
|
+
}
|
|
5141
|
+
var init_burn_view = __esm(() => {
|
|
5142
|
+
init_budget_state();
|
|
5143
|
+
});
|
|
5144
|
+
|
|
5145
|
+
// src/budget/render.ts
|
|
5146
|
+
function resolveGuardHardHint(env = process.env) {
|
|
5147
|
+
const raw = env.AGENTBRIDGE_GUARD_HARD_HINT;
|
|
5148
|
+
if (raw === undefined || raw.trim() === "")
|
|
5149
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
5150
|
+
const parsed = Number(raw);
|
|
5151
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 100)
|
|
5152
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
5153
|
+
return parsed;
|
|
5154
|
+
}
|
|
5155
|
+
function formatEpoch(epochSeconds) {
|
|
5156
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
5157
|
+
return "\u672A\u77E5";
|
|
5158
|
+
return new Date(epochSeconds * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
5159
|
+
}
|
|
5160
|
+
function formatWindow(window, label) {
|
|
5161
|
+
if (!window)
|
|
5162
|
+
return `${label} \u672A\u77E5`;
|
|
5163
|
+
return `${label} ${window.util}%\uFF08\u91CD\u7F6E ${formatEpoch(window.resetEpoch)}\uFF09`;
|
|
5164
|
+
}
|
|
5165
|
+
function formatAgent(name, usage, snapshotAt) {
|
|
5166
|
+
if (!usage)
|
|
5167
|
+
return `${name}\uFF1A\u672A\u77E5\uFF08\u63A2\u6D4B\u4E0D\u53EF\u7528\uFF09`;
|
|
5168
|
+
const parts = [
|
|
5169
|
+
formatWindow(usage.fiveHour, "5h"),
|
|
5170
|
+
formatWindow(usage.weekly, "\u5468"),
|
|
5171
|
+
`\u95E8\u63A7 ${usage.gateUtil}%`,
|
|
5172
|
+
`\u9884\u8B66 ${usage.warnUtil}%`
|
|
5173
|
+
];
|
|
5174
|
+
if (usage.rateLimitedUntil > 0) {
|
|
5175
|
+
parts.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
|
|
5176
|
+
}
|
|
5177
|
+
if (usage.parsedVia === "positional") {
|
|
5178
|
+
parts.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
|
|
5179
|
+
}
|
|
5180
|
+
const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
|
|
5181
|
+
if (ageSec > 300) {
|
|
5182
|
+
parts.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
|
|
5183
|
+
} else if (usage.stale) {
|
|
5184
|
+
parts.push("\uFF08\u7F13\u5B58\u6570\u636E\uFF09");
|
|
5185
|
+
}
|
|
5186
|
+
return `${name}\uFF1A${parts.join(" \xB7 ")}`;
|
|
5187
|
+
}
|
|
5188
|
+
function formatDuration(seconds) {
|
|
5189
|
+
const totalMinutes = Math.max(0, Math.round(seconds / 60));
|
|
5190
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
5191
|
+
const minutes = totalMinutes % 60;
|
|
5192
|
+
if (hours === 0)
|
|
5193
|
+
return `${minutes}\u5206\u949F`;
|
|
5194
|
+
return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
|
|
5195
|
+
}
|
|
5196
|
+
function formatClockTime(epochSeconds) {
|
|
5197
|
+
const date = new Date(epochSeconds * 1000);
|
|
5198
|
+
const hh = String(date.getHours()).padStart(2, "0");
|
|
5199
|
+
const mm = String(date.getMinutes()).padStart(2, "0");
|
|
5200
|
+
return `${hh}:${mm}`;
|
|
5201
|
+
}
|
|
5202
|
+
function formatWindowRate(label, rate) {
|
|
5203
|
+
if (!rate)
|
|
5204
|
+
return null;
|
|
5205
|
+
if (!rate.confident)
|
|
5206
|
+
return `${label} \u91C7\u6837\u4E2D`;
|
|
5207
|
+
return `${label} \u2248${rate.pctPerHour.toFixed(2)}%/h`;
|
|
5208
|
+
}
|
|
5209
|
+
function formatRunwaySegment(runway, basisWindow, snapshotAt) {
|
|
5210
|
+
const truncatedByReset = basisWindow !== null && basisWindow.resetEpoch > 0 && snapshotAt + runway.seconds >= basisWindow.resetEpoch - RESET_TRUNCATION_EPSILON_SEC;
|
|
5211
|
+
const clock = runway.depletedAtEpoch ? formatClockTime(runway.depletedAtEpoch) : null;
|
|
5212
|
+
let clockNote;
|
|
5213
|
+
if (clock) {
|
|
5214
|
+
clockNote = truncatedByReset ? `\u81F3 ${clock} \u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C` : `\u81F3 ${clock}\uFF0C`;
|
|
5215
|
+
} else {
|
|
5216
|
+
clockNote = truncatedByReset ? "\u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C" : "";
|
|
5217
|
+
}
|
|
5218
|
+
return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
|
|
5219
|
+
}
|
|
5220
|
+
function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
|
|
5221
|
+
const parts = [
|
|
5222
|
+
formatWindowRate("5h", rates.fiveHour),
|
|
5223
|
+
formatWindowRate("\u5468", rates.weekly)
|
|
5224
|
+
].filter((part) => part !== null);
|
|
5225
|
+
if (parts.length === 0 && !runway)
|
|
5226
|
+
return null;
|
|
5227
|
+
if (runway) {
|
|
5228
|
+
const basisWindow = usage ? usage[runway.basis] : null;
|
|
5229
|
+
parts.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
|
|
5230
|
+
}
|
|
5231
|
+
if (guardHardPct !== null) {
|
|
5232
|
+
parts.push(`\u5916\u5C42 guard \u786C\u7EBF ${guardHardPct}%\uFF08v3 \u4E0D\u53EF\u8D8A\u8FC7\uFF1Brunway \u4E3A\u4E2D\u6027\u53E3\u5F84\uFF0CClaude \u4F1A\u5148\u5728\u786C\u7EBF\u88AB\u5916\u5C42\u505C\u4F4F\uFF09`);
|
|
5233
|
+
}
|
|
5234
|
+
return `${name} \u71C3\u5C3D\u7387\uFF1A${parts.join(" \xB7 ")}`;
|
|
5235
|
+
}
|
|
5236
|
+
function formatFiveHourWindowsLeftLine(snapshot) {
|
|
5237
|
+
const values = [];
|
|
5238
|
+
const claude = agentWeeklyFiveHourWindowsLeft(snapshot.claude, snapshot.updatedAt);
|
|
5239
|
+
const codex = agentWeeklyFiveHourWindowsLeft(snapshot.codex, snapshot.updatedAt);
|
|
5240
|
+
if (claude !== null)
|
|
5241
|
+
values.push(["Claude", claude]);
|
|
5242
|
+
if (codex !== null)
|
|
5243
|
+
values.push(["Codex", codex]);
|
|
5244
|
+
if (values.length === 0)
|
|
5245
|
+
return null;
|
|
5246
|
+
const unique = [...new Set(values.map(([, value]) => value.toFixed(1)))];
|
|
5247
|
+
if (unique.length === 1)
|
|
5248
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ~${unique[0]} \u4E2A 5h \u7A97\u53E3`;
|
|
5249
|
+
const byAgent = values.map(([name, value]) => `${name} ~${value.toFixed(1)}`).join(" / ");
|
|
5250
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ${byAgent} \u4E2A 5h \u7A97\u53E3`;
|
|
5251
|
+
}
|
|
5252
|
+
function renderBudgetSnapshot(snapshot, options = {}) {
|
|
5253
|
+
const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
|
|
5254
|
+
const lines = [];
|
|
5255
|
+
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase]} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
5256
|
+
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
5257
|
+
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
5258
|
+
if (snapshot.burnRate) {
|
|
5259
|
+
const claudeLine = formatBurnRateLine("Claude", snapshot.claude, snapshot.burnRate.claude, snapshot.runway?.claude ?? null, snapshot.updatedAt, guardHardPct);
|
|
5260
|
+
if (claudeLine)
|
|
5261
|
+
lines.push(claudeLine);
|
|
5262
|
+
const codexLine = formatBurnRateLine("Codex", snapshot.codex, snapshot.burnRate.codex, snapshot.runway?.codex ?? null, snapshot.updatedAt, null);
|
|
5263
|
+
if (codexLine)
|
|
5264
|
+
lines.push(codexLine);
|
|
5265
|
+
}
|
|
5266
|
+
const fiveHourWindowsLeftLine = formatFiveHourWindowsLeftLine(snapshot);
|
|
5267
|
+
if (fiveHourWindowsLeftLine)
|
|
5268
|
+
lines.push(fiveHourWindowsLeftLine);
|
|
5269
|
+
if (snapshot.claude && snapshot.codex) {
|
|
5270
|
+
const abs = Math.abs(snapshot.driftPct);
|
|
5271
|
+
if (abs > 0) {
|
|
5272
|
+
const heavier = snapshot.driftPct > 0 ? "Claude" : "Codex";
|
|
5273
|
+
const lighter = snapshot.driftPct > 0 ? "Codex" : "Claude";
|
|
5274
|
+
lines.push(`\u6F02\u79FB\uFF1A${heavier} \u6BD4 ${lighter} \u9AD8 ${abs} \u4E2A\u767E\u5206\u70B9`);
|
|
5275
|
+
} else {
|
|
5276
|
+
lines.push("\u6F02\u79FB\uFF1A\u53CC\u65B9\u6301\u5E73");
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
if (snapshot.paused) {
|
|
5280
|
+
const resume = snapshot.resumeAfterEpoch ? `\uFF1B\u9884\u8BA1\u6062\u590D ${formatEpoch(snapshot.resumeAfterEpoch)}\uFF08\u4EE5\u5B9E\u6D4B\u4E3A\u51C6\uFF1B\u63D0\u524D\u5237\u65B0\u4F1A\u66F4\u65E9\u89E3\u9664\uFF09` : "";
|
|
5281
|
+
const reason = snapshot.pauseReason ?? "\u989D\u5EA6\u63A5\u8FD1\u8017\u5C3D";
|
|
5282
|
+
if (snapshot.pauseSide === "claude" && !snapshot.gateClosed) {
|
|
5283
|
+
lines.push(`\u63A5\u529B\u4E2D\uFF1AClaude \u4FA7\u989D\u5EA6\u8017\u5C3D\uFF0C\u5DF2\u4EA4\u63A5 Codex \u7EE7\u7EED\u63A8\u8FDB\uFF08\u95F8\u95E8\u5F00\u653E\uFF09 \u2014 ${reason}${resume}`);
|
|
5284
|
+
} else if (snapshot.pauseSide === "codex") {
|
|
5285
|
+
lines.push(`\u6682\u505C\uFF1ACodex \u4FA7\u989D\u5EA6\u8017\u5C3D\uFF08\u95F8\u95E8\u5173\u95ED\uFF0CClaude \u53EF solo \u63A8\u8FDB\u72EC\u7ACB\u90E8\u5206\uFF09 \u2014 ${reason}${resume}`);
|
|
5286
|
+
} else {
|
|
5287
|
+
lines.push(`\u6682\u505C\uFF1A\u53CC\u4FA7\u8054\u5408\u6682\u505C\uFF08\u95F8\u95E8\u5173\u95ED\uFF09 \u2014 ${reason}${resume}`);
|
|
5288
|
+
}
|
|
5289
|
+
} else {
|
|
5290
|
+
lines.push("\u6682\u505C\uFF1A\u5426");
|
|
5291
|
+
}
|
|
5292
|
+
if (snapshot.parallelRecommended) {
|
|
5293
|
+
lines.push("\u5E76\u884C\u5EFA\u8BAE\uFF1A\u989D\u5EA6\u5BCC\u4F59\u4E14\u4E34\u8FD1\u7ED3\u7B97\uFF0C\u5EFA\u8BAE\u62C6\u5206\u66F4\u591A\u5E76\u884C\u5B50\u4EFB\u52A1");
|
|
5294
|
+
}
|
|
5295
|
+
if (snapshot.codexTier !== "full") {
|
|
5296
|
+
lines.push(`Codex \u6863\u4F4D\uFF1A${snapshot.codexTier}`);
|
|
5297
|
+
}
|
|
5298
|
+
if (snapshot.claudeAdvice) {
|
|
5299
|
+
lines.push(`Claude \u5EFA\u8BAE\uFF1A${snapshot.claudeAdvice}`);
|
|
5300
|
+
}
|
|
5301
|
+
lines.push("\u6CE8\uFF1A\u767E\u5206\u6BD4\u4E3A\u8BA2\u9605\u8D26\u53F7\u7EA7\u7528\u91CF\uFF08\u540C\u673A\u5176\u4ED6\u4F1A\u8BDD\u5171\u4EAB\u540C\u4E00\u989D\u5EA6\u6C60\uFF09\u3002");
|
|
5302
|
+
return lines.join(`
|
|
5303
|
+
`);
|
|
5304
|
+
}
|
|
5305
|
+
var DEFAULT_GUARD_HARD_PCT = 92, WINDOW_LABELS, RESET_TRUNCATION_EPSILON_SEC = 60, PHASE_LABELS, BUDGET_UNAVAILABLE_TEXT = "\u9884\u7B97\u611F\u77E5\u4E0D\u53EF\u7528\uFF1A\u672A\u68C0\u6D4B\u5230 agent-quota-guard \u63A2\u9488\uFF08~/.budget-guard/bin/budget-probe\uFF09\u6216 budget \u529F\u80FD\u5DF2\u7981\u7528\u3002\u534F\u4F5C\u4E0D\u53D7\u5F71\u54CD\u3002";
|
|
5306
|
+
var init_render = __esm(() => {
|
|
5307
|
+
init_burn_view();
|
|
5308
|
+
WINDOW_LABELS = {
|
|
5309
|
+
fiveHour: "5h \u7A97\u53E3",
|
|
5310
|
+
weekly: "\u5468\u7A97\u53E3"
|
|
5311
|
+
};
|
|
5312
|
+
PHASE_LABELS = {
|
|
5313
|
+
normal: "normal\uFF08\u6B63\u5E38\uFF09",
|
|
5314
|
+
balance: "balance\uFF08\u9700\u5747\u8861\uFF09",
|
|
5315
|
+
parallel: "parallel\uFF08\u5EFA\u8BAE\u5E76\u884C\u63D0\u901F\uFF09",
|
|
5316
|
+
paused: "paused\uFF08\u9884\u7B97\u5E72\u9884\u4E2D\uFF09"
|
|
5317
|
+
};
|
|
5318
|
+
});
|
|
5072
5319
|
|
|
5073
5320
|
// src/daemon-status.ts
|
|
5074
5321
|
async function fetchDaemonStatus(port, path = "/healthz", timeoutMs = DAEMON_STATUS_FETCH_TIMEOUT_MS) {
|
|
@@ -5272,8 +5519,10 @@ var exports_doctor = {};
|
|
|
5272
5519
|
__export(exports_doctor, {
|
|
5273
5520
|
runDoctor: () => runDoctor,
|
|
5274
5521
|
formatDoctorReport: () => formatDoctorReport,
|
|
5522
|
+
evaluateBudgetStrategyGuard: () => evaluateBudgetStrategyGuard,
|
|
5275
5523
|
evaluateArtifactAlignment: () => evaluateArtifactAlignment,
|
|
5276
|
-
describeBuildDrift: () => describeBuildDrift
|
|
5524
|
+
describeBuildDrift: () => describeBuildDrift,
|
|
5525
|
+
V3_DEFAULT_TARGET_UTIL: () => V3_DEFAULT_TARGET_UTIL
|
|
5277
5526
|
});
|
|
5278
5527
|
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
|
|
5279
5528
|
import { join as join17 } from "path";
|
|
@@ -5381,6 +5630,7 @@ async function buildDoctorReport(pair, registered) {
|
|
|
5381
5630
|
hint: env.ok ? undefined : `\u73AF\u5883\u53D8\u91CF\u4E0E\u5F53\u524D\u76EE\u5F55\u4E0D\u5339\u914D\uFF1A\u8BF7\u5728\u6B63\u786E\u7684\u9879\u76EE\u76EE\u5F55\u91CC\u91CD\u65B0\u8FD0\u884C \`${cli} claude\`\uFF0C\u4E0D\u8981\u590D\u7528\u5176\u4ED6\u76EE\u5F55\u7684\u4F1A\u8BDD\u73AF\u5883\u3002`
|
|
5382
5631
|
});
|
|
5383
5632
|
checks.push(configParseabilityCheck(cwd, cli));
|
|
5633
|
+
checks.push(budgetStrategyGuardCheck(cwd));
|
|
5384
5634
|
checks.push({
|
|
5385
5635
|
name: "daemon health",
|
|
5386
5636
|
status: health ? "ok" : "warn",
|
|
@@ -5580,6 +5830,33 @@ function configParseabilityCheck(cwd, cli) {
|
|
|
5580
5830
|
detail: desc.customValues ? `parsed at ${desc.path} \u2014 custom values in effect` : `parsed at ${desc.path} \u2014 all values match defaults`
|
|
5581
5831
|
};
|
|
5582
5832
|
}
|
|
5833
|
+
function evaluateBudgetStrategyGuard(strategy, guardHardPct, targetUtilPct = V3_DEFAULT_TARGET_UTIL) {
|
|
5834
|
+
if (strategy !== "maximize") {
|
|
5835
|
+
return {
|
|
5836
|
+
name: "budget strategy",
|
|
5837
|
+
status: "ok",
|
|
5838
|
+
detail: "strategy=conserve \u2014 v2-equivalent budget behavior (v3 maximize is opt-in)"
|
|
5839
|
+
};
|
|
5840
|
+
}
|
|
5841
|
+
if (guardHardPct >= targetUtilPct) {
|
|
5842
|
+
return {
|
|
5843
|
+
name: "budget strategy",
|
|
5844
|
+
status: "ok",
|
|
5845
|
+
detail: `strategy=maximize \u2014 outer guard hard line ${guardHardPct}% covers targetUtil ${targetUtilPct}%`
|
|
5846
|
+
};
|
|
5847
|
+
}
|
|
5848
|
+
return {
|
|
5849
|
+
name: "budget strategy",
|
|
5850
|
+
status: "warn",
|
|
5851
|
+
detail: `strategy=maximize but the outer quota-guard hard line (${guardHardPct}%) is below ` + `targetUtil (${targetUtilPct}%) \u2014 the ${guardHardPct}\u2192${targetUtilPct} band is unreachable for Claude`,
|
|
5852
|
+
hint: "v3 \u4E0D\u53EF\u8D8A\u8FC7\u5916\u5C42 quota-guard \u786C\u7EBF\uFF1AClaude \u4FA7\u8FBE\u5230 guard \u786C\u7EBF\u65F6\u8FDB\u7A0B\u4F1A\u88AB\u5916\u5C42\u5F3A\u505C\uFF0C" + `maximize \u7684 ${guardHardPct}%\u2192${targetUtilPct}% \u533A\u95F4\u5B9E\u9645\u70E7\u4E0D\u5230\u3002\u60F3\u771F\u6B63\u7528\u5230 targetUtil\uFF0C` + "\u9700\u81EA\u884C\u8C03\u9AD8 quota-guard \u7684 BUDGET_HARD\uFF08\u672C\u4ED3\u5E93\u4E0D\u4EE3\u6539\u5916\u5C42\u914D\u7F6E\uFF09\uFF1B\u5C55\u793A\u4FA7\u5DF2\u6309 guard \u7EBF\u6536\u53E3\u3002"
|
|
5853
|
+
};
|
|
5854
|
+
}
|
|
5855
|
+
function budgetStrategyGuardCheck(cwd) {
|
|
5856
|
+
const config = new ConfigService(cwd).loadOrDefault();
|
|
5857
|
+
const budget = applyBudgetEnvOverrides(config.budget);
|
|
5858
|
+
return evaluateBudgetStrategyGuard(budget.strategy, resolveGuardHardHint());
|
|
5859
|
+
}
|
|
5583
5860
|
function logCheck(name, path, cli) {
|
|
5584
5861
|
if (!existsSync15(path)) {
|
|
5585
5862
|
return {
|
|
@@ -5629,12 +5906,13 @@ function printDoctorReport(report) {
|
|
|
5629
5906
|
console.log(line);
|
|
5630
5907
|
}
|
|
5631
5908
|
}
|
|
5632
|
-
var LARGE_LOG_WARN_BYTES;
|
|
5909
|
+
var LARGE_LOG_WARN_BYTES, V3_DEFAULT_TARGET_UTIL = 97;
|
|
5633
5910
|
var init_doctor = __esm(() => {
|
|
5634
5911
|
init_plugin_cache();
|
|
5635
5912
|
init_build_info();
|
|
5636
5913
|
init_cli_invocation();
|
|
5637
5914
|
init_config_service();
|
|
5915
|
+
init_render();
|
|
5638
5916
|
init_env_guard();
|
|
5639
5917
|
init_pair_resolver();
|
|
5640
5918
|
init_thread_state();
|
|
@@ -5643,91 +5921,6 @@ var init_doctor = __esm(() => {
|
|
|
5643
5921
|
LARGE_LOG_WARN_BYTES = 100 * 1024 * 1024;
|
|
5644
5922
|
});
|
|
5645
5923
|
|
|
5646
|
-
// src/budget/render.ts
|
|
5647
|
-
function formatEpoch(epochSeconds) {
|
|
5648
|
-
if (!epochSeconds || epochSeconds <= 0)
|
|
5649
|
-
return "\u672A\u77E5";
|
|
5650
|
-
return new Date(epochSeconds * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
5651
|
-
}
|
|
5652
|
-
function formatWindow(window, label) {
|
|
5653
|
-
if (!window)
|
|
5654
|
-
return `${label} \u672A\u77E5`;
|
|
5655
|
-
return `${label} ${window.util}%\uFF08\u91CD\u7F6E ${formatEpoch(window.resetEpoch)}\uFF09`;
|
|
5656
|
-
}
|
|
5657
|
-
function formatAgent(name, usage, snapshotAt) {
|
|
5658
|
-
if (!usage)
|
|
5659
|
-
return `${name}\uFF1A\u672A\u77E5\uFF08\u63A2\u6D4B\u4E0D\u53EF\u7528\uFF09`;
|
|
5660
|
-
const parts = [
|
|
5661
|
-
formatWindow(usage.fiveHour, "5h"),
|
|
5662
|
-
formatWindow(usage.weekly, "\u5468"),
|
|
5663
|
-
`\u95E8\u63A7 ${usage.gateUtil}%`,
|
|
5664
|
-
`\u9884\u8B66 ${usage.warnUtil}%`
|
|
5665
|
-
];
|
|
5666
|
-
if (usage.rateLimitedUntil > 0) {
|
|
5667
|
-
parts.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
|
|
5668
|
-
}
|
|
5669
|
-
if (usage.parsedVia === "positional") {
|
|
5670
|
-
parts.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
|
|
5671
|
-
}
|
|
5672
|
-
const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
|
|
5673
|
-
if (ageSec > 300) {
|
|
5674
|
-
parts.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
|
|
5675
|
-
} else if (usage.stale) {
|
|
5676
|
-
parts.push("\uFF08\u7F13\u5B58\u6570\u636E\uFF09");
|
|
5677
|
-
}
|
|
5678
|
-
return `${name}\uFF1A${parts.join(" \xB7 ")}`;
|
|
5679
|
-
}
|
|
5680
|
-
function renderBudgetSnapshot(snapshot) {
|
|
5681
|
-
const lines = [];
|
|
5682
|
-
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase]} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
5683
|
-
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
5684
|
-
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
5685
|
-
if (snapshot.claude && snapshot.codex) {
|
|
5686
|
-
const abs = Math.abs(snapshot.driftPct);
|
|
5687
|
-
if (abs > 0) {
|
|
5688
|
-
const heavier = snapshot.driftPct > 0 ? "Claude" : "Codex";
|
|
5689
|
-
const lighter = snapshot.driftPct > 0 ? "Codex" : "Claude";
|
|
5690
|
-
lines.push(`\u6F02\u79FB\uFF1A${heavier} \u6BD4 ${lighter} \u9AD8 ${abs} \u4E2A\u767E\u5206\u70B9`);
|
|
5691
|
-
} else {
|
|
5692
|
-
lines.push("\u6F02\u79FB\uFF1A\u53CC\u65B9\u6301\u5E73");
|
|
5693
|
-
}
|
|
5694
|
-
}
|
|
5695
|
-
if (snapshot.paused) {
|
|
5696
|
-
const resume = snapshot.resumeAfterEpoch ? `\uFF1B\u9884\u8BA1\u6062\u590D ${formatEpoch(snapshot.resumeAfterEpoch)}\uFF08\u4EE5\u5B9E\u6D4B\u4E3A\u51C6\uFF1B\u63D0\u524D\u5237\u65B0\u4F1A\u66F4\u65E9\u89E3\u9664\uFF09` : "";
|
|
5697
|
-
const reason = snapshot.pauseReason ?? "\u989D\u5EA6\u63A5\u8FD1\u8017\u5C3D";
|
|
5698
|
-
if (snapshot.pauseSide === "claude" && !snapshot.gateClosed) {
|
|
5699
|
-
lines.push(`\u63A5\u529B\u4E2D\uFF1AClaude \u4FA7\u989D\u5EA6\u8017\u5C3D\uFF0C\u5DF2\u4EA4\u63A5 Codex \u7EE7\u7EED\u63A8\u8FDB\uFF08\u95F8\u95E8\u5F00\u653E\uFF09 \u2014 ${reason}${resume}`);
|
|
5700
|
-
} else if (snapshot.pauseSide === "codex") {
|
|
5701
|
-
lines.push(`\u6682\u505C\uFF1ACodex \u4FA7\u989D\u5EA6\u8017\u5C3D\uFF08\u95F8\u95E8\u5173\u95ED\uFF0CClaude \u53EF solo \u63A8\u8FDB\u72EC\u7ACB\u90E8\u5206\uFF09 \u2014 ${reason}${resume}`);
|
|
5702
|
-
} else {
|
|
5703
|
-
lines.push(`\u6682\u505C\uFF1A\u53CC\u4FA7\u8054\u5408\u6682\u505C\uFF08\u95F8\u95E8\u5173\u95ED\uFF09 \u2014 ${reason}${resume}`);
|
|
5704
|
-
}
|
|
5705
|
-
} else {
|
|
5706
|
-
lines.push("\u6682\u505C\uFF1A\u5426");
|
|
5707
|
-
}
|
|
5708
|
-
if (snapshot.parallelRecommended) {
|
|
5709
|
-
lines.push("\u5E76\u884C\u5EFA\u8BAE\uFF1A\u989D\u5EA6\u5BCC\u4F59\u4E14\u4E34\u8FD1\u7ED3\u7B97\uFF0C\u5EFA\u8BAE\u62C6\u5206\u66F4\u591A\u5E76\u884C\u5B50\u4EFB\u52A1");
|
|
5710
|
-
}
|
|
5711
|
-
if (snapshot.codexTier !== "full") {
|
|
5712
|
-
lines.push(`Codex \u6863\u4F4D\uFF1A${snapshot.codexTier}`);
|
|
5713
|
-
}
|
|
5714
|
-
if (snapshot.claudeAdvice) {
|
|
5715
|
-
lines.push(`Claude \u5EFA\u8BAE\uFF1A${snapshot.claudeAdvice}`);
|
|
5716
|
-
}
|
|
5717
|
-
lines.push("\u6CE8\uFF1A\u767E\u5206\u6BD4\u4E3A\u8BA2\u9605\u8D26\u53F7\u7EA7\u7528\u91CF\uFF08\u540C\u673A\u5176\u4ED6\u4F1A\u8BDD\u5171\u4EAB\u540C\u4E00\u989D\u5EA6\u6C60\uFF09\u3002");
|
|
5718
|
-
return lines.join(`
|
|
5719
|
-
`);
|
|
5720
|
-
}
|
|
5721
|
-
var PHASE_LABELS, BUDGET_UNAVAILABLE_TEXT = "\u9884\u7B97\u611F\u77E5\u4E0D\u53EF\u7528\uFF1A\u672A\u68C0\u6D4B\u5230 agent-quota-guard \u63A2\u9488\uFF08~/.budget-guard/bin/budget-probe\uFF09\u6216 budget \u529F\u80FD\u5DF2\u7981\u7528\u3002\u534F\u4F5C\u4E0D\u53D7\u5F71\u54CD\u3002";
|
|
5722
|
-
var init_render = __esm(() => {
|
|
5723
|
-
PHASE_LABELS = {
|
|
5724
|
-
normal: "normal\uFF08\u6B63\u5E38\uFF09",
|
|
5725
|
-
balance: "balance\uFF08\u9700\u5747\u8861\uFF09",
|
|
5726
|
-
parallel: "parallel\uFF08\u5EFA\u8BAE\u5E76\u884C\u63D0\u901F\uFF09",
|
|
5727
|
-
paused: "paused\uFF08\u9884\u7B97\u5E72\u9884\u4E2D\uFF09"
|
|
5728
|
-
};
|
|
5729
|
-
});
|
|
5730
|
-
|
|
5731
5924
|
// src/cli/budget.ts
|
|
5732
5925
|
var exports_budget = {};
|
|
5733
5926
|
__export(exports_budget, {
|
package/dist/daemon.js
CHANGED
|
@@ -26,11 +26,11 @@ function defineNumber(value, fallback) {
|
|
|
26
26
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
27
27
|
}
|
|
28
28
|
var BUILD_INFO = Object.freeze({
|
|
29
|
-
version: defineString("0.1.
|
|
30
|
-
commit: defineString("
|
|
29
|
+
version: defineString("0.1.14", "0.0.0-source"),
|
|
30
|
+
commit: defineString("f5a9562", "source"),
|
|
31
31
|
bundle: defineBundle("dist"),
|
|
32
32
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
33
|
-
codeHash: defineString("
|
|
33
|
+
codeHash: defineString("e05d18c3cc72", "source")
|
|
34
34
|
});
|
|
35
35
|
function daemonStatusBuildInfo() {
|
|
36
36
|
return { ...BUILD_INFO };
|
|
@@ -3401,7 +3401,8 @@ var DEFAULT_BUDGET_CONFIG = {
|
|
|
3401
3401
|
full: null,
|
|
3402
3402
|
balanced: { effort: "medium" },
|
|
3403
3403
|
eco: { effort: "low" }
|
|
3404
|
-
}
|
|
3404
|
+
},
|
|
3405
|
+
strategy: "conserve"
|
|
3405
3406
|
};
|
|
3406
3407
|
var DEFAULT_CONFIG = {
|
|
3407
3408
|
version: "1.0",
|
|
@@ -3479,6 +3480,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
|
|
|
3479
3480
|
return fallback;
|
|
3480
3481
|
return parsed;
|
|
3481
3482
|
}
|
|
3483
|
+
function normalizeStrategy(value, fallback) {
|
|
3484
|
+
return value === "conserve" || value === "maximize" ? value : fallback;
|
|
3485
|
+
}
|
|
3482
3486
|
function normalizeBoolean(value, fallback) {
|
|
3483
3487
|
if (typeof value === "boolean")
|
|
3484
3488
|
return value;
|
|
@@ -3527,7 +3531,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3527
3531
|
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
3528
3532
|
},
|
|
3529
3533
|
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
3530
|
-
codexTiers
|
|
3534
|
+
codexTiers,
|
|
3535
|
+
strategy: normalizeStrategy(budget.strategy, fallback.strategy)
|
|
3531
3536
|
};
|
|
3532
3537
|
}
|
|
3533
3538
|
function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
@@ -3542,7 +3547,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3542
3547
|
timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
|
|
3543
3548
|
},
|
|
3544
3549
|
codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
|
|
3545
|
-
codexTiers: budget.codexTiers
|
|
3550
|
+
codexTiers: budget.codexTiers,
|
|
3551
|
+
strategy: env.AGENTBRIDGE_BUDGET_STRATEGY ?? budget.strategy
|
|
3546
3552
|
};
|
|
3547
3553
|
return normalizeBudgetConfig(overlay, budget);
|
|
3548
3554
|
}
|
|
@@ -4038,6 +4044,54 @@ function classifyPoll(prev, state, cfg) {
|
|
|
4038
4044
|
return { next: prev, effect: { kind: "none" } };
|
|
4039
4045
|
}
|
|
4040
4046
|
|
|
4047
|
+
// src/budget/burn-view.ts
|
|
4048
|
+
function windowBurnRate(window) {
|
|
4049
|
+
if (!window || window.burnRate === undefined)
|
|
4050
|
+
return null;
|
|
4051
|
+
return {
|
|
4052
|
+
pctPerHour: window.burnRate,
|
|
4053
|
+
confident: window.burnConfident === true
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
function agentBurnRates(usage) {
|
|
4057
|
+
if (!usage)
|
|
4058
|
+
return { fiveHour: null, weekly: null };
|
|
4059
|
+
return {
|
|
4060
|
+
fiveHour: windowBurnRate(usage.fiveHour),
|
|
4061
|
+
weekly: windowBurnRate(usage.weekly)
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
function agentRunway(usage, now) {
|
|
4065
|
+
if (!usage || usage.stale || !usage.ok)
|
|
4066
|
+
return null;
|
|
4067
|
+
if (!isDecisionGrade(usage, now))
|
|
4068
|
+
return null;
|
|
4069
|
+
let best = null;
|
|
4070
|
+
const candidates = [
|
|
4071
|
+
["fiveHour", usage.fiveHour],
|
|
4072
|
+
["weekly", usage.weekly]
|
|
4073
|
+
];
|
|
4074
|
+
for (const [basis, window] of candidates) {
|
|
4075
|
+
if (!window || window.resetEpoch <= now)
|
|
4076
|
+
continue;
|
|
4077
|
+
if (window.burnConfident !== true)
|
|
4078
|
+
continue;
|
|
4079
|
+
if (window.runwaySeconds === undefined)
|
|
4080
|
+
continue;
|
|
4081
|
+
if (best === null || window.runwaySeconds < best.seconds) {
|
|
4082
|
+
best = {
|
|
4083
|
+
seconds: window.runwaySeconds,
|
|
4084
|
+
basis,
|
|
4085
|
+
depletedAtEpoch: window.depletedAtEpoch ?? null
|
|
4086
|
+
};
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
return best;
|
|
4090
|
+
}
|
|
4091
|
+
function hasAnyBurnSignal(rates, runway) {
|
|
4092
|
+
return rates.claude.fiveHour !== null || rates.claude.weekly !== null || rates.codex.fiveHour !== null || rates.codex.weekly !== null || runway.claude !== null || runway.codex !== null;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4041
4095
|
// src/budget/budget-coordinator.ts
|
|
4042
4096
|
var LOW_UTIL_PCT = 50;
|
|
4043
4097
|
var NEAR_PAUSE_MARGIN_PCT = 10;
|
|
@@ -4348,8 +4402,22 @@ class BudgetCoordinator {
|
|
|
4348
4402
|
resumeAfterEpoch: paused ? this.fpState.resumeEpoch ?? state.pause.resumeAfterEpoch : null,
|
|
4349
4403
|
parallelRecommended: paused ? false : state.parallel.recommended,
|
|
4350
4404
|
codexTier: state.effort.codexTier,
|
|
4351
|
-
claudeAdvice: state.effort.claudeAdvice
|
|
4405
|
+
claudeAdvice: state.effort.claudeAdvice,
|
|
4406
|
+
...this.burnRateSnapshotFields(state)
|
|
4407
|
+
};
|
|
4408
|
+
}
|
|
4409
|
+
burnRateSnapshotFields(state) {
|
|
4410
|
+
const rates = {
|
|
4411
|
+
claude: agentBurnRates(state.perAgent.claude),
|
|
4412
|
+
codex: agentBurnRates(state.perAgent.codex)
|
|
4413
|
+
};
|
|
4414
|
+
const runway = {
|
|
4415
|
+
claude: agentRunway(state.perAgent.claude, state.now),
|
|
4416
|
+
codex: agentRunway(state.perAgent.codex, state.now)
|
|
4352
4417
|
};
|
|
4418
|
+
if (!hasAnyBurnSignal(rates, runway))
|
|
4419
|
+
return {};
|
|
4420
|
+
return { burnRate: rates, runway };
|
|
4353
4421
|
}
|
|
4354
4422
|
}
|
|
4355
4423
|
|
|
@@ -4358,6 +4426,57 @@ import { execFile } from "child_process";
|
|
|
4358
4426
|
import { existsSync as existsSync5 } from "fs";
|
|
4359
4427
|
import { homedir as homedir2 } from "os";
|
|
4360
4428
|
import { basename, join as join5 } from "path";
|
|
4429
|
+
function parseBurnFields(record) {
|
|
4430
|
+
const group = {};
|
|
4431
|
+
let any = false;
|
|
4432
|
+
const takeNumber = (value, min) => {
|
|
4433
|
+
if (value === undefined)
|
|
4434
|
+
return "absent";
|
|
4435
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
4436
|
+
return "invalid";
|
|
4437
|
+
if (min === "zero" && value < 0)
|
|
4438
|
+
return "invalid";
|
|
4439
|
+
if (min === "positive" && value <= 0)
|
|
4440
|
+
return "invalid";
|
|
4441
|
+
return value;
|
|
4442
|
+
};
|
|
4443
|
+
const burnRate = takeNumber(record.burn_rate_pct_per_hour ?? record.burnRatePctPerHour, "zero");
|
|
4444
|
+
if (burnRate === "invalid")
|
|
4445
|
+
return null;
|
|
4446
|
+
if (burnRate !== "absent") {
|
|
4447
|
+
group.burnRate = burnRate;
|
|
4448
|
+
any = true;
|
|
4449
|
+
}
|
|
4450
|
+
const confidentRaw = record.burn_confident ?? record.burnConfident;
|
|
4451
|
+
if (confidentRaw !== undefined) {
|
|
4452
|
+
if (typeof confidentRaw !== "boolean")
|
|
4453
|
+
return null;
|
|
4454
|
+
group.burnConfident = confidentRaw;
|
|
4455
|
+
any = true;
|
|
4456
|
+
}
|
|
4457
|
+
const runwaySeconds = takeNumber(record.runway_seconds ?? record.runwaySeconds, "zero");
|
|
4458
|
+
if (runwaySeconds === "invalid")
|
|
4459
|
+
return null;
|
|
4460
|
+
if (runwaySeconds !== "absent") {
|
|
4461
|
+
group.runwaySeconds = runwaySeconds;
|
|
4462
|
+
any = true;
|
|
4463
|
+
}
|
|
4464
|
+
const depletedAtEpoch = takeNumber(record.depleted_at_epoch ?? record.depletedAtEpoch, "positive");
|
|
4465
|
+
if (depletedAtEpoch === "invalid")
|
|
4466
|
+
return null;
|
|
4467
|
+
if (depletedAtEpoch !== "absent") {
|
|
4468
|
+
group.depletedAtEpoch = depletedAtEpoch;
|
|
4469
|
+
any = true;
|
|
4470
|
+
}
|
|
4471
|
+
const fiveHourWindowsLeft = takeNumber(record.five_hour_windows_left ?? record.fiveHourWindowsLeft, "zero");
|
|
4472
|
+
if (fiveHourWindowsLeft === "invalid")
|
|
4473
|
+
return null;
|
|
4474
|
+
if (fiveHourWindowsLeft !== "absent") {
|
|
4475
|
+
group.fiveHourWindowsLeft = fiveHourWindowsLeft;
|
|
4476
|
+
any = true;
|
|
4477
|
+
}
|
|
4478
|
+
return any ? group : null;
|
|
4479
|
+
}
|
|
4361
4480
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
4362
4481
|
var MAX_BUFFER = 1024 * 1024;
|
|
4363
4482
|
function defaultRunner(command, args, options) {
|
|
@@ -4419,7 +4538,8 @@ function normalizeBucket(value, fetchedAt) {
|
|
|
4419
4538
|
id,
|
|
4420
4539
|
util: clamp(util, 0, 100),
|
|
4421
4540
|
resetEpoch: Math.max(0, resetEpoch),
|
|
4422
|
-
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter)
|
|
4541
|
+
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter),
|
|
4542
|
+
burn: parseBurnFields(bucket)
|
|
4423
4543
|
};
|
|
4424
4544
|
}
|
|
4425
4545
|
function normalizeTopLevelBucket(record, util, fetchedAt) {
|
|
@@ -4432,13 +4552,27 @@ function normalizeTopLevelBucket(record, util, fetchedAt) {
|
|
|
4432
4552
|
id: "top_level",
|
|
4433
4553
|
util: clamp(util, 0, 100),
|
|
4434
4554
|
resetEpoch: Math.max(0, resetEpoch),
|
|
4435
|
-
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter)
|
|
4555
|
+
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter),
|
|
4556
|
+
burn: parseBurnFields(record)
|
|
4436
4557
|
};
|
|
4437
4558
|
}
|
|
4438
4559
|
function toWindow(bucket) {
|
|
4439
4560
|
if (!bucket)
|
|
4440
4561
|
return null;
|
|
4441
|
-
|
|
4562
|
+
const window = { util: bucket.util, resetEpoch: bucket.resetEpoch };
|
|
4563
|
+
if (bucket.burn) {
|
|
4564
|
+
if (bucket.burn.burnRate !== undefined)
|
|
4565
|
+
window.burnRate = bucket.burn.burnRate;
|
|
4566
|
+
if (bucket.burn.burnConfident !== undefined)
|
|
4567
|
+
window.burnConfident = bucket.burn.burnConfident;
|
|
4568
|
+
if (bucket.burn.runwaySeconds !== undefined)
|
|
4569
|
+
window.runwaySeconds = bucket.burn.runwaySeconds;
|
|
4570
|
+
if (bucket.burn.depletedAtEpoch !== undefined)
|
|
4571
|
+
window.depletedAtEpoch = bucket.burn.depletedAtEpoch;
|
|
4572
|
+
if (bucket.burn.fiveHourWindowsLeft !== undefined)
|
|
4573
|
+
window.fiveHourWindowsLeft = bucket.burn.fiveHourWindowsLeft;
|
|
4574
|
+
}
|
|
4575
|
+
return window;
|
|
4442
4576
|
}
|
|
4443
4577
|
function bucketSortKey(bucket) {
|
|
4444
4578
|
if (bucket.resetAfterSeconds !== null)
|
|
@@ -4518,10 +4652,11 @@ function normalizeTolerantProbeRecord(record) {
|
|
|
4518
4652
|
};
|
|
4519
4653
|
}
|
|
4520
4654
|
var PROBE_SCHEMA_PARSERS = {
|
|
4521
|
-
"1": normalizeTolerantProbeRecord
|
|
4655
|
+
"1": normalizeTolerantProbeRecord,
|
|
4656
|
+
"2": normalizeTolerantProbeRecord
|
|
4522
4657
|
};
|
|
4523
4658
|
function schemaVersionKey(record) {
|
|
4524
|
-
const value = record.schema_version ?? record.schemaVersion;
|
|
4659
|
+
const value = record.schema_version ?? record.schemaVersion ?? record.probe_schema ?? record.probeSchema;
|
|
4525
4660
|
if (typeof value === "number" && Number.isFinite(value))
|
|
4526
4661
|
return String(value);
|
|
4527
4662
|
if (typeof value === "string" && value.trim() !== "")
|
|
@@ -5119,7 +5254,7 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
5119
5254
|
if (!BUDGET_CONFIG.enabled)
|
|
5120
5255
|
return;
|
|
5121
5256
|
if (!budgetCoordinator) {
|
|
5122
|
-
log(`Budget coordinator config: pollSeconds=${BUDGET_CONFIG.pollSeconds} pauseAt=${BUDGET_CONFIG.pauseAt} ` + `resumeBelow=${BUDGET_CONFIG.resumeBelow} syncDriftPct=${BUDGET_CONFIG.syncDriftPct} ` + `parallel=${BUDGET_CONFIG.parallel.minRemainingPct}%/${BUDGET_CONFIG.parallel.timeWindowSec}s ` + `codexTierControl=${BUDGET_CONFIG.codexTierControl} ` + `codexTiersFull=${BUDGET_CONFIG.codexTiers.full ? "configured" : "missing"}`);
|
|
5257
|
+
log(`Budget coordinator config: pollSeconds=${BUDGET_CONFIG.pollSeconds} pauseAt=${BUDGET_CONFIG.pauseAt} ` + `resumeBelow=${BUDGET_CONFIG.resumeBelow} syncDriftPct=${BUDGET_CONFIG.syncDriftPct} ` + `parallel=${BUDGET_CONFIG.parallel.minRemainingPct}%/${BUDGET_CONFIG.parallel.timeWindowSec}s ` + `codexTierControl=${BUDGET_CONFIG.codexTierControl} ` + `codexTiersFull=${BUDGET_CONFIG.codexTiers.full ? "configured" : "missing"} ` + `strategy=${BUDGET_CONFIG.strategy}`);
|
|
5123
5258
|
budgetCoordinator = new BudgetCoordinator({
|
|
5124
5259
|
source: createQuotaSource({ log }),
|
|
5125
5260
|
config: BUDGET_CONFIG,
|
package/package.json
CHANGED
|
@@ -13863,7 +13863,48 @@ class StateDirResolver {
|
|
|
13863
13863
|
}
|
|
13864
13864
|
}
|
|
13865
13865
|
|
|
13866
|
+
// src/budget/types.ts
|
|
13867
|
+
var STALE_MAX_AGE_SEC = 600;
|
|
13868
|
+
|
|
13869
|
+
// src/budget/budget-state.ts
|
|
13870
|
+
function isDecisionGrade(usage, now) {
|
|
13871
|
+
if (!usage)
|
|
13872
|
+
return false;
|
|
13873
|
+
const freshWindow = usage.fiveHour !== null && usage.fiveHour.resetEpoch > now || usage.weekly !== null && usage.weekly.resetEpoch > now;
|
|
13874
|
+
if (!freshWindow)
|
|
13875
|
+
return false;
|
|
13876
|
+
if (usage.fetchedAt > 0 && now - usage.fetchedAt > STALE_MAX_AGE_SEC)
|
|
13877
|
+
return false;
|
|
13878
|
+
return true;
|
|
13879
|
+
}
|
|
13880
|
+
|
|
13881
|
+
// src/budget/burn-view.ts
|
|
13882
|
+
function agentWeeklyFiveHourWindowsLeft(usage, now) {
|
|
13883
|
+
if (!usage || usage.stale || !usage.ok)
|
|
13884
|
+
return null;
|
|
13885
|
+
if (!isDecisionGrade(usage, now))
|
|
13886
|
+
return null;
|
|
13887
|
+
const weekly = usage.weekly;
|
|
13888
|
+
if (!weekly || weekly.resetEpoch <= now)
|
|
13889
|
+
return null;
|
|
13890
|
+
if (weekly.burnConfident !== true)
|
|
13891
|
+
return null;
|
|
13892
|
+
if (weekly.runwaySeconds === undefined)
|
|
13893
|
+
return null;
|
|
13894
|
+
return weekly.fiveHourWindowsLeft ?? null;
|
|
13895
|
+
}
|
|
13896
|
+
|
|
13866
13897
|
// src/budget/render.ts
|
|
13898
|
+
var DEFAULT_GUARD_HARD_PCT = 92;
|
|
13899
|
+
function resolveGuardHardHint(env = process.env) {
|
|
13900
|
+
const raw = env.AGENTBRIDGE_GUARD_HARD_HINT;
|
|
13901
|
+
if (raw === undefined || raw.trim() === "")
|
|
13902
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
13903
|
+
const parsed = Number(raw);
|
|
13904
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 100)
|
|
13905
|
+
return DEFAULT_GUARD_HARD_PCT;
|
|
13906
|
+
return parsed;
|
|
13907
|
+
}
|
|
13867
13908
|
function formatEpoch(epochSeconds) {
|
|
13868
13909
|
if (!epochSeconds || epochSeconds <= 0)
|
|
13869
13910
|
return "\u672A\u77E5";
|
|
@@ -13897,17 +13938,98 @@ function formatAgent(name, usage, snapshotAt) {
|
|
|
13897
13938
|
}
|
|
13898
13939
|
return `${name}\uFF1A${parts.join(" \xB7 ")}`;
|
|
13899
13940
|
}
|
|
13941
|
+
var WINDOW_LABELS = {
|
|
13942
|
+
fiveHour: "5h \u7A97\u53E3",
|
|
13943
|
+
weekly: "\u5468\u7A97\u53E3"
|
|
13944
|
+
};
|
|
13945
|
+
var RESET_TRUNCATION_EPSILON_SEC = 60;
|
|
13946
|
+
function formatDuration(seconds) {
|
|
13947
|
+
const totalMinutes = Math.max(0, Math.round(seconds / 60));
|
|
13948
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
13949
|
+
const minutes = totalMinutes % 60;
|
|
13950
|
+
if (hours === 0)
|
|
13951
|
+
return `${minutes}\u5206\u949F`;
|
|
13952
|
+
return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
|
|
13953
|
+
}
|
|
13954
|
+
function formatClockTime(epochSeconds) {
|
|
13955
|
+
const date4 = new Date(epochSeconds * 1000);
|
|
13956
|
+
const hh = String(date4.getHours()).padStart(2, "0");
|
|
13957
|
+
const mm = String(date4.getMinutes()).padStart(2, "0");
|
|
13958
|
+
return `${hh}:${mm}`;
|
|
13959
|
+
}
|
|
13960
|
+
function formatWindowRate(label, rate) {
|
|
13961
|
+
if (!rate)
|
|
13962
|
+
return null;
|
|
13963
|
+
if (!rate.confident)
|
|
13964
|
+
return `${label} \u91C7\u6837\u4E2D`;
|
|
13965
|
+
return `${label} \u2248${rate.pctPerHour.toFixed(2)}%/h`;
|
|
13966
|
+
}
|
|
13967
|
+
function formatRunwaySegment(runway, basisWindow, snapshotAt) {
|
|
13968
|
+
const truncatedByReset = basisWindow !== null && basisWindow.resetEpoch > 0 && snapshotAt + runway.seconds >= basisWindow.resetEpoch - RESET_TRUNCATION_EPSILON_SEC;
|
|
13969
|
+
const clock = runway.depletedAtEpoch ? formatClockTime(runway.depletedAtEpoch) : null;
|
|
13970
|
+
let clockNote;
|
|
13971
|
+
if (clock) {
|
|
13972
|
+
clockNote = truncatedByReset ? `\u81F3 ${clock} \u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C` : `\u81F3 ${clock}\uFF0C`;
|
|
13973
|
+
} else {
|
|
13974
|
+
clockNote = truncatedByReset ? "\u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C" : "";
|
|
13975
|
+
}
|
|
13976
|
+
return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
|
|
13977
|
+
}
|
|
13978
|
+
function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
|
|
13979
|
+
const parts = [
|
|
13980
|
+
formatWindowRate("5h", rates.fiveHour),
|
|
13981
|
+
formatWindowRate("\u5468", rates.weekly)
|
|
13982
|
+
].filter((part) => part !== null);
|
|
13983
|
+
if (parts.length === 0 && !runway)
|
|
13984
|
+
return null;
|
|
13985
|
+
if (runway) {
|
|
13986
|
+
const basisWindow = usage ? usage[runway.basis] : null;
|
|
13987
|
+
parts.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
|
|
13988
|
+
}
|
|
13989
|
+
if (guardHardPct !== null) {
|
|
13990
|
+
parts.push(`\u5916\u5C42 guard \u786C\u7EBF ${guardHardPct}%\uFF08v3 \u4E0D\u53EF\u8D8A\u8FC7\uFF1Brunway \u4E3A\u4E2D\u6027\u53E3\u5F84\uFF0CClaude \u4F1A\u5148\u5728\u786C\u7EBF\u88AB\u5916\u5C42\u505C\u4F4F\uFF09`);
|
|
13991
|
+
}
|
|
13992
|
+
return `${name} \u71C3\u5C3D\u7387\uFF1A${parts.join(" \xB7 ")}`;
|
|
13993
|
+
}
|
|
13994
|
+
function formatFiveHourWindowsLeftLine(snapshot) {
|
|
13995
|
+
const values = [];
|
|
13996
|
+
const claude = agentWeeklyFiveHourWindowsLeft(snapshot.claude, snapshot.updatedAt);
|
|
13997
|
+
const codex = agentWeeklyFiveHourWindowsLeft(snapshot.codex, snapshot.updatedAt);
|
|
13998
|
+
if (claude !== null)
|
|
13999
|
+
values.push(["Claude", claude]);
|
|
14000
|
+
if (codex !== null)
|
|
14001
|
+
values.push(["Codex", codex]);
|
|
14002
|
+
if (values.length === 0)
|
|
14003
|
+
return null;
|
|
14004
|
+
const unique = [...new Set(values.map(([, value]) => value.toFixed(1)))];
|
|
14005
|
+
if (unique.length === 1)
|
|
14006
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ~${unique[0]} \u4E2A 5h \u7A97\u53E3`;
|
|
14007
|
+
const byAgent = values.map(([name, value]) => `${name} ~${value.toFixed(1)}`).join(" / ");
|
|
14008
|
+
return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ${byAgent} \u4E2A 5h \u7A97\u53E3`;
|
|
14009
|
+
}
|
|
13900
14010
|
var PHASE_LABELS = {
|
|
13901
14011
|
normal: "normal\uFF08\u6B63\u5E38\uFF09",
|
|
13902
14012
|
balance: "balance\uFF08\u9700\u5747\u8861\uFF09",
|
|
13903
14013
|
parallel: "parallel\uFF08\u5EFA\u8BAE\u5E76\u884C\u63D0\u901F\uFF09",
|
|
13904
14014
|
paused: "paused\uFF08\u9884\u7B97\u5E72\u9884\u4E2D\uFF09"
|
|
13905
14015
|
};
|
|
13906
|
-
function renderBudgetSnapshot(snapshot) {
|
|
14016
|
+
function renderBudgetSnapshot(snapshot, options = {}) {
|
|
14017
|
+
const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
|
|
13907
14018
|
const lines = [];
|
|
13908
14019
|
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase]} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
13909
14020
|
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
13910
14021
|
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
14022
|
+
if (snapshot.burnRate) {
|
|
14023
|
+
const claudeLine = formatBurnRateLine("Claude", snapshot.claude, snapshot.burnRate.claude, snapshot.runway?.claude ?? null, snapshot.updatedAt, guardHardPct);
|
|
14024
|
+
if (claudeLine)
|
|
14025
|
+
lines.push(claudeLine);
|
|
14026
|
+
const codexLine = formatBurnRateLine("Codex", snapshot.codex, snapshot.burnRate.codex, snapshot.runway?.codex ?? null, snapshot.updatedAt, null);
|
|
14027
|
+
if (codexLine)
|
|
14028
|
+
lines.push(codexLine);
|
|
14029
|
+
}
|
|
14030
|
+
const fiveHourWindowsLeftLine = formatFiveHourWindowsLeftLine(snapshot);
|
|
14031
|
+
if (fiveHourWindowsLeftLine)
|
|
14032
|
+
lines.push(fiveHourWindowsLeftLine);
|
|
13911
14033
|
if (snapshot.claude && snapshot.codex) {
|
|
13912
14034
|
const abs = Math.abs(snapshot.driftPct);
|
|
13913
14035
|
if (abs > 0) {
|
|
@@ -14389,11 +14511,11 @@ function defineNumber(value, fallback) {
|
|
|
14389
14511
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14390
14512
|
}
|
|
14391
14513
|
var BUILD_INFO = Object.freeze({
|
|
14392
|
-
version: defineString("0.1.
|
|
14393
|
-
commit: defineString("
|
|
14514
|
+
version: defineString("0.1.14", "0.0.0-source"),
|
|
14515
|
+
commit: defineString("f5a9562", "source"),
|
|
14394
14516
|
bundle: defineBundle("plugin"),
|
|
14395
14517
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
14396
|
-
codeHash: defineString("
|
|
14518
|
+
codeHash: defineString("e05d18c3cc72", "source")
|
|
14397
14519
|
});
|
|
14398
14520
|
function sameRuntimeContract(a, b) {
|
|
14399
14521
|
if (!a || !b)
|
|
@@ -15424,7 +15546,8 @@ var DEFAULT_BUDGET_CONFIG = {
|
|
|
15424
15546
|
full: null,
|
|
15425
15547
|
balanced: { effort: "medium" },
|
|
15426
15548
|
eco: { effort: "low" }
|
|
15427
|
-
}
|
|
15549
|
+
},
|
|
15550
|
+
strategy: "conserve"
|
|
15428
15551
|
};
|
|
15429
15552
|
var DEFAULT_CONFIG = {
|
|
15430
15553
|
version: "1.0",
|
|
@@ -15502,6 +15625,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
|
|
|
15502
15625
|
return fallback;
|
|
15503
15626
|
return parsed;
|
|
15504
15627
|
}
|
|
15628
|
+
function normalizeStrategy(value, fallback) {
|
|
15629
|
+
return value === "conserve" || value === "maximize" ? value : fallback;
|
|
15630
|
+
}
|
|
15505
15631
|
function normalizeBoolean(value, fallback) {
|
|
15506
15632
|
if (typeof value === "boolean")
|
|
15507
15633
|
return value;
|
|
@@ -15550,7 +15676,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15550
15676
|
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
15551
15677
|
},
|
|
15552
15678
|
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
15553
|
-
codexTiers
|
|
15679
|
+
codexTiers,
|
|
15680
|
+
strategy: normalizeStrategy(budget.strategy, fallback.strategy)
|
|
15554
15681
|
};
|
|
15555
15682
|
}
|
|
15556
15683
|
function normalizeConfig(raw) {
|
|
@@ -26,11 +26,11 @@ function defineNumber(value, fallback) {
|
|
|
26
26
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
27
27
|
}
|
|
28
28
|
var BUILD_INFO = Object.freeze({
|
|
29
|
-
version: defineString("0.1.
|
|
30
|
-
commit: defineString("
|
|
29
|
+
version: defineString("0.1.14", "0.0.0-source"),
|
|
30
|
+
commit: defineString("f5a9562", "source"),
|
|
31
31
|
bundle: defineBundle("plugin"),
|
|
32
32
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
33
|
-
codeHash: defineString("
|
|
33
|
+
codeHash: defineString("e05d18c3cc72", "source")
|
|
34
34
|
});
|
|
35
35
|
function daemonStatusBuildInfo() {
|
|
36
36
|
return { ...BUILD_INFO };
|
|
@@ -3401,7 +3401,8 @@ var DEFAULT_BUDGET_CONFIG = {
|
|
|
3401
3401
|
full: null,
|
|
3402
3402
|
balanced: { effort: "medium" },
|
|
3403
3403
|
eco: { effort: "low" }
|
|
3404
|
-
}
|
|
3404
|
+
},
|
|
3405
|
+
strategy: "conserve"
|
|
3405
3406
|
};
|
|
3406
3407
|
var DEFAULT_CONFIG = {
|
|
3407
3408
|
version: "1.0",
|
|
@@ -3479,6 +3480,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
|
|
|
3479
3480
|
return fallback;
|
|
3480
3481
|
return parsed;
|
|
3481
3482
|
}
|
|
3483
|
+
function normalizeStrategy(value, fallback) {
|
|
3484
|
+
return value === "conserve" || value === "maximize" ? value : fallback;
|
|
3485
|
+
}
|
|
3482
3486
|
function normalizeBoolean(value, fallback) {
|
|
3483
3487
|
if (typeof value === "boolean")
|
|
3484
3488
|
return value;
|
|
@@ -3527,7 +3531,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3527
3531
|
timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
|
|
3528
3532
|
},
|
|
3529
3533
|
codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
|
|
3530
|
-
codexTiers
|
|
3534
|
+
codexTiers,
|
|
3535
|
+
strategy: normalizeStrategy(budget.strategy, fallback.strategy)
|
|
3531
3536
|
};
|
|
3532
3537
|
}
|
|
3533
3538
|
function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
@@ -3542,7 +3547,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3542
3547
|
timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
|
|
3543
3548
|
},
|
|
3544
3549
|
codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
|
|
3545
|
-
codexTiers: budget.codexTiers
|
|
3550
|
+
codexTiers: budget.codexTiers,
|
|
3551
|
+
strategy: env.AGENTBRIDGE_BUDGET_STRATEGY ?? budget.strategy
|
|
3546
3552
|
};
|
|
3547
3553
|
return normalizeBudgetConfig(overlay, budget);
|
|
3548
3554
|
}
|
|
@@ -4038,6 +4044,54 @@ function classifyPoll(prev, state, cfg) {
|
|
|
4038
4044
|
return { next: prev, effect: { kind: "none" } };
|
|
4039
4045
|
}
|
|
4040
4046
|
|
|
4047
|
+
// src/budget/burn-view.ts
|
|
4048
|
+
function windowBurnRate(window) {
|
|
4049
|
+
if (!window || window.burnRate === undefined)
|
|
4050
|
+
return null;
|
|
4051
|
+
return {
|
|
4052
|
+
pctPerHour: window.burnRate,
|
|
4053
|
+
confident: window.burnConfident === true
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
function agentBurnRates(usage) {
|
|
4057
|
+
if (!usage)
|
|
4058
|
+
return { fiveHour: null, weekly: null };
|
|
4059
|
+
return {
|
|
4060
|
+
fiveHour: windowBurnRate(usage.fiveHour),
|
|
4061
|
+
weekly: windowBurnRate(usage.weekly)
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
4064
|
+
function agentRunway(usage, now) {
|
|
4065
|
+
if (!usage || usage.stale || !usage.ok)
|
|
4066
|
+
return null;
|
|
4067
|
+
if (!isDecisionGrade(usage, now))
|
|
4068
|
+
return null;
|
|
4069
|
+
let best = null;
|
|
4070
|
+
const candidates = [
|
|
4071
|
+
["fiveHour", usage.fiveHour],
|
|
4072
|
+
["weekly", usage.weekly]
|
|
4073
|
+
];
|
|
4074
|
+
for (const [basis, window] of candidates) {
|
|
4075
|
+
if (!window || window.resetEpoch <= now)
|
|
4076
|
+
continue;
|
|
4077
|
+
if (window.burnConfident !== true)
|
|
4078
|
+
continue;
|
|
4079
|
+
if (window.runwaySeconds === undefined)
|
|
4080
|
+
continue;
|
|
4081
|
+
if (best === null || window.runwaySeconds < best.seconds) {
|
|
4082
|
+
best = {
|
|
4083
|
+
seconds: window.runwaySeconds,
|
|
4084
|
+
basis,
|
|
4085
|
+
depletedAtEpoch: window.depletedAtEpoch ?? null
|
|
4086
|
+
};
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
return best;
|
|
4090
|
+
}
|
|
4091
|
+
function hasAnyBurnSignal(rates, runway) {
|
|
4092
|
+
return rates.claude.fiveHour !== null || rates.claude.weekly !== null || rates.codex.fiveHour !== null || rates.codex.weekly !== null || runway.claude !== null || runway.codex !== null;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4041
4095
|
// src/budget/budget-coordinator.ts
|
|
4042
4096
|
var LOW_UTIL_PCT = 50;
|
|
4043
4097
|
var NEAR_PAUSE_MARGIN_PCT = 10;
|
|
@@ -4348,8 +4402,22 @@ class BudgetCoordinator {
|
|
|
4348
4402
|
resumeAfterEpoch: paused ? this.fpState.resumeEpoch ?? state.pause.resumeAfterEpoch : null,
|
|
4349
4403
|
parallelRecommended: paused ? false : state.parallel.recommended,
|
|
4350
4404
|
codexTier: state.effort.codexTier,
|
|
4351
|
-
claudeAdvice: state.effort.claudeAdvice
|
|
4405
|
+
claudeAdvice: state.effort.claudeAdvice,
|
|
4406
|
+
...this.burnRateSnapshotFields(state)
|
|
4407
|
+
};
|
|
4408
|
+
}
|
|
4409
|
+
burnRateSnapshotFields(state) {
|
|
4410
|
+
const rates = {
|
|
4411
|
+
claude: agentBurnRates(state.perAgent.claude),
|
|
4412
|
+
codex: agentBurnRates(state.perAgent.codex)
|
|
4413
|
+
};
|
|
4414
|
+
const runway = {
|
|
4415
|
+
claude: agentRunway(state.perAgent.claude, state.now),
|
|
4416
|
+
codex: agentRunway(state.perAgent.codex, state.now)
|
|
4352
4417
|
};
|
|
4418
|
+
if (!hasAnyBurnSignal(rates, runway))
|
|
4419
|
+
return {};
|
|
4420
|
+
return { burnRate: rates, runway };
|
|
4353
4421
|
}
|
|
4354
4422
|
}
|
|
4355
4423
|
|
|
@@ -4358,6 +4426,57 @@ import { execFile } from "child_process";
|
|
|
4358
4426
|
import { existsSync as existsSync5 } from "fs";
|
|
4359
4427
|
import { homedir as homedir2 } from "os";
|
|
4360
4428
|
import { basename, join as join5 } from "path";
|
|
4429
|
+
function parseBurnFields(record) {
|
|
4430
|
+
const group = {};
|
|
4431
|
+
let any = false;
|
|
4432
|
+
const takeNumber = (value, min) => {
|
|
4433
|
+
if (value === undefined)
|
|
4434
|
+
return "absent";
|
|
4435
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
4436
|
+
return "invalid";
|
|
4437
|
+
if (min === "zero" && value < 0)
|
|
4438
|
+
return "invalid";
|
|
4439
|
+
if (min === "positive" && value <= 0)
|
|
4440
|
+
return "invalid";
|
|
4441
|
+
return value;
|
|
4442
|
+
};
|
|
4443
|
+
const burnRate = takeNumber(record.burn_rate_pct_per_hour ?? record.burnRatePctPerHour, "zero");
|
|
4444
|
+
if (burnRate === "invalid")
|
|
4445
|
+
return null;
|
|
4446
|
+
if (burnRate !== "absent") {
|
|
4447
|
+
group.burnRate = burnRate;
|
|
4448
|
+
any = true;
|
|
4449
|
+
}
|
|
4450
|
+
const confidentRaw = record.burn_confident ?? record.burnConfident;
|
|
4451
|
+
if (confidentRaw !== undefined) {
|
|
4452
|
+
if (typeof confidentRaw !== "boolean")
|
|
4453
|
+
return null;
|
|
4454
|
+
group.burnConfident = confidentRaw;
|
|
4455
|
+
any = true;
|
|
4456
|
+
}
|
|
4457
|
+
const runwaySeconds = takeNumber(record.runway_seconds ?? record.runwaySeconds, "zero");
|
|
4458
|
+
if (runwaySeconds === "invalid")
|
|
4459
|
+
return null;
|
|
4460
|
+
if (runwaySeconds !== "absent") {
|
|
4461
|
+
group.runwaySeconds = runwaySeconds;
|
|
4462
|
+
any = true;
|
|
4463
|
+
}
|
|
4464
|
+
const depletedAtEpoch = takeNumber(record.depleted_at_epoch ?? record.depletedAtEpoch, "positive");
|
|
4465
|
+
if (depletedAtEpoch === "invalid")
|
|
4466
|
+
return null;
|
|
4467
|
+
if (depletedAtEpoch !== "absent") {
|
|
4468
|
+
group.depletedAtEpoch = depletedAtEpoch;
|
|
4469
|
+
any = true;
|
|
4470
|
+
}
|
|
4471
|
+
const fiveHourWindowsLeft = takeNumber(record.five_hour_windows_left ?? record.fiveHourWindowsLeft, "zero");
|
|
4472
|
+
if (fiveHourWindowsLeft === "invalid")
|
|
4473
|
+
return null;
|
|
4474
|
+
if (fiveHourWindowsLeft !== "absent") {
|
|
4475
|
+
group.fiveHourWindowsLeft = fiveHourWindowsLeft;
|
|
4476
|
+
any = true;
|
|
4477
|
+
}
|
|
4478
|
+
return any ? group : null;
|
|
4479
|
+
}
|
|
4361
4480
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
4362
4481
|
var MAX_BUFFER = 1024 * 1024;
|
|
4363
4482
|
function defaultRunner(command, args, options) {
|
|
@@ -4419,7 +4538,8 @@ function normalizeBucket(value, fetchedAt) {
|
|
|
4419
4538
|
id,
|
|
4420
4539
|
util: clamp(util, 0, 100),
|
|
4421
4540
|
resetEpoch: Math.max(0, resetEpoch),
|
|
4422
|
-
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter)
|
|
4541
|
+
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter),
|
|
4542
|
+
burn: parseBurnFields(bucket)
|
|
4423
4543
|
};
|
|
4424
4544
|
}
|
|
4425
4545
|
function normalizeTopLevelBucket(record, util, fetchedAt) {
|
|
@@ -4432,13 +4552,27 @@ function normalizeTopLevelBucket(record, util, fetchedAt) {
|
|
|
4432
4552
|
id: "top_level",
|
|
4433
4553
|
util: clamp(util, 0, 100),
|
|
4434
4554
|
resetEpoch: Math.max(0, resetEpoch),
|
|
4435
|
-
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter)
|
|
4555
|
+
resetAfterSeconds: resetAfter === null ? null : Math.max(0, resetAfter),
|
|
4556
|
+
burn: parseBurnFields(record)
|
|
4436
4557
|
};
|
|
4437
4558
|
}
|
|
4438
4559
|
function toWindow(bucket) {
|
|
4439
4560
|
if (!bucket)
|
|
4440
4561
|
return null;
|
|
4441
|
-
|
|
4562
|
+
const window = { util: bucket.util, resetEpoch: bucket.resetEpoch };
|
|
4563
|
+
if (bucket.burn) {
|
|
4564
|
+
if (bucket.burn.burnRate !== undefined)
|
|
4565
|
+
window.burnRate = bucket.burn.burnRate;
|
|
4566
|
+
if (bucket.burn.burnConfident !== undefined)
|
|
4567
|
+
window.burnConfident = bucket.burn.burnConfident;
|
|
4568
|
+
if (bucket.burn.runwaySeconds !== undefined)
|
|
4569
|
+
window.runwaySeconds = bucket.burn.runwaySeconds;
|
|
4570
|
+
if (bucket.burn.depletedAtEpoch !== undefined)
|
|
4571
|
+
window.depletedAtEpoch = bucket.burn.depletedAtEpoch;
|
|
4572
|
+
if (bucket.burn.fiveHourWindowsLeft !== undefined)
|
|
4573
|
+
window.fiveHourWindowsLeft = bucket.burn.fiveHourWindowsLeft;
|
|
4574
|
+
}
|
|
4575
|
+
return window;
|
|
4442
4576
|
}
|
|
4443
4577
|
function bucketSortKey(bucket) {
|
|
4444
4578
|
if (bucket.resetAfterSeconds !== null)
|
|
@@ -4518,10 +4652,11 @@ function normalizeTolerantProbeRecord(record) {
|
|
|
4518
4652
|
};
|
|
4519
4653
|
}
|
|
4520
4654
|
var PROBE_SCHEMA_PARSERS = {
|
|
4521
|
-
"1": normalizeTolerantProbeRecord
|
|
4655
|
+
"1": normalizeTolerantProbeRecord,
|
|
4656
|
+
"2": normalizeTolerantProbeRecord
|
|
4522
4657
|
};
|
|
4523
4658
|
function schemaVersionKey(record) {
|
|
4524
|
-
const value = record.schema_version ?? record.schemaVersion;
|
|
4659
|
+
const value = record.schema_version ?? record.schemaVersion ?? record.probe_schema ?? record.probeSchema;
|
|
4525
4660
|
if (typeof value === "number" && Number.isFinite(value))
|
|
4526
4661
|
return String(value);
|
|
4527
4662
|
if (typeof value === "string" && value.trim() !== "")
|
|
@@ -5119,7 +5254,7 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
5119
5254
|
if (!BUDGET_CONFIG.enabled)
|
|
5120
5255
|
return;
|
|
5121
5256
|
if (!budgetCoordinator) {
|
|
5122
|
-
log(`Budget coordinator config: pollSeconds=${BUDGET_CONFIG.pollSeconds} pauseAt=${BUDGET_CONFIG.pauseAt} ` + `resumeBelow=${BUDGET_CONFIG.resumeBelow} syncDriftPct=${BUDGET_CONFIG.syncDriftPct} ` + `parallel=${BUDGET_CONFIG.parallel.minRemainingPct}%/${BUDGET_CONFIG.parallel.timeWindowSec}s ` + `codexTierControl=${BUDGET_CONFIG.codexTierControl} ` + `codexTiersFull=${BUDGET_CONFIG.codexTiers.full ? "configured" : "missing"}`);
|
|
5257
|
+
log(`Budget coordinator config: pollSeconds=${BUDGET_CONFIG.pollSeconds} pauseAt=${BUDGET_CONFIG.pauseAt} ` + `resumeBelow=${BUDGET_CONFIG.resumeBelow} syncDriftPct=${BUDGET_CONFIG.syncDriftPct} ` + `parallel=${BUDGET_CONFIG.parallel.minRemainingPct}%/${BUDGET_CONFIG.parallel.timeWindowSec}s ` + `codexTierControl=${BUDGET_CONFIG.codexTierControl} ` + `codexTiersFull=${BUDGET_CONFIG.codexTiers.full ? "configured" : "missing"} ` + `strategy=${BUDGET_CONFIG.strategy}`);
|
|
5123
5258
|
budgetCoordinator = new BudgetCoordinator({
|
|
5124
5259
|
source: createQuotaSource({ log }),
|
|
5125
5260
|
config: BUDGET_CONFIG,
|