@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.
@@ -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.13",
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.13",
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.13", "0.0.0-source"),
1479
- commit: defineString("7a71869", "source"),
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("e1fd67d07c62", "source")
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.13", "0.0.0-source"),
30
- commit: defineString("7a71869", "source"),
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("e1fd67d07c62", "source")
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
- return { util: bucket.util, resetEpoch: bucket.resetEpoch };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raysonmeng/agentbridge",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Bridge between Claude Code and Codex — bidirectional agent communication via MCP Channel + JSON-RPC",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.11",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbridge",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Bridge Claude Code and Codex with a shared daemon, push channel delivery, and bidirectional reply tooling.",
5
5
  "author": {
6
6
  "name": "AgentBridge Contributors",
@@ -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.13", "0.0.0-source"),
14393
- commit: defineString("7a71869", "source"),
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("e1fd67d07c62", "source")
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.13", "0.0.0-source"),
30
- commit: defineString("7a71869", "source"),
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("e1fd67d07c62", "source")
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
- return { util: bucket.util, resetEpoch: bucket.resetEpoch };
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,