@raysonmeng/agentbridge 0.1.13 → 0.1.15

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/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.15",
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",
@@ -255,9 +255,13 @@ __export(exports_update_notifier, {
255
255
  isUpdateCheckSuppressed: () => isUpdateCheckSuppressed,
256
256
  getCurrentVersion: () => getCurrentVersion,
257
257
  buildUpdateNotice: () => buildUpdateNotice,
258
- PACKAGE_NAME: () => PACKAGE_NAME
258
+ buildDismissedUpdateNotice: () => buildDismissedUpdateNotice,
259
+ PACKAGE_NAME: () => PACKAGE_NAME,
260
+ DEFAULT_UPDATE_PROMPT_TIMEOUT_MS: () => DEFAULT_UPDATE_PROMPT_TIMEOUT_MS
259
261
  });
260
262
  import { readFileSync } from "fs";
263
+ import { spawnSync } from "child_process";
264
+ import { createInterface } from "readline";
261
265
  function getCurrentVersion() {
262
266
  try {
263
267
  return require_package().version;
@@ -285,7 +289,8 @@ function readCache(stateDir) {
285
289
  return null;
286
290
  return {
287
291
  lastCheckMs: parsed.lastCheckMs,
288
- latest: typeof parsed.latest === "string" ? parsed.latest : null
292
+ latest: typeof parsed.latest === "string" ? parsed.latest : null,
293
+ dismissedVersion: typeof parsed.dismissedVersion === "string" ? parsed.dismissedVersion : undefined
289
294
  };
290
295
  } catch {
291
296
  return null;
@@ -327,7 +332,11 @@ async function refreshUpdateCache(deps = {}) {
327
332
  try {
328
333
  const latest = await fetchLatest(fetchImpl);
329
334
  const prev = readCache(stateDir);
330
- writeCache(stateDir, { lastCheckMs: now(), latest: latest ?? prev?.latest ?? null });
335
+ writeCache(stateDir, {
336
+ lastCheckMs: now(),
337
+ latest: latest ?? prev?.latest ?? null,
338
+ dismissedVersion: prev?.dismissedVersion
339
+ });
331
340
  } catch {}
332
341
  }
333
342
  function buildUpdateNotice(current, latest, isTTY) {
@@ -342,30 +351,109 @@ function buildUpdateNotice(current, latest, isTTY) {
342
351
  ].join(`
343
352
  `);
344
353
  }
354
+ function buildDismissedUpdateNotice(current, latest, isTTY) {
355
+ const yellow = isTTY ? "\x1B[33m" : "";
356
+ const bold = isTTY ? "\x1B[1m" : "";
357
+ const reset = isTTY ? "\x1B[0m" : "";
358
+ return `${yellow}\u26A0 AgentBridge update available: ${bold}${current}${reset}${yellow} \u2192 ${bold}${latest}${reset} (previously dismissed)`;
359
+ }
345
360
  function checkIntervalMs(env) {
346
361
  return parsePositiveIntEnv(CHECK_INTERVAL_ENV, DEFAULT_CHECK_INTERVAL_MS, undefined, env);
347
362
  }
348
- function maybeNotifyUpdate(deps = {}) {
363
+ function updatePromptDisabled(env) {
364
+ return env.AGENTBRIDGE_UPDATE_PROMPT === "0";
365
+ }
366
+ function defaultInstallUpdate(cmd, args) {
367
+ const res = spawnSync(cmd, args, { stdio: "inherit" });
368
+ if (res.error)
369
+ return { ok: false, status: res.status, error: res.error };
370
+ return { ok: res.status === 0, status: res.status };
371
+ }
372
+ function defaultPromptUpdate(opts) {
373
+ return new Promise((resolve) => {
374
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
375
+ let settled = false;
376
+ let timer;
377
+ const finish = (answer) => {
378
+ if (settled)
379
+ return;
380
+ settled = true;
381
+ clearTimeout(timer);
382
+ rl.close();
383
+ resolve(answer);
384
+ };
385
+ timer = setTimeout(() => {
386
+ process.stderr.write(`
387
+ `);
388
+ finish(false);
389
+ }, opts.timeoutMs);
390
+ timer.unref?.();
391
+ rl.question(`Update AgentBridge now? [y/N] `, (answer) => {
392
+ finish(/^y(?:es)?$/i.test(answer.trim()));
393
+ });
394
+ });
395
+ }
396
+ function recordDismissal(stateDir, cache, latest) {
397
+ writeCache(stateDir, {
398
+ ...cache,
399
+ latest,
400
+ dismissedVersion: latest
401
+ });
402
+ }
403
+ async function maybeNotifyUpdate(deps = {}) {
349
404
  try {
350
405
  const env = deps.env ?? process.env;
351
406
  const isTTY = deps.isTTY ?? Boolean(process.stderr.isTTY);
352
407
  if (isUpdateCheckSuppressed(env, isTTY))
353
- return;
408
+ return "continue";
354
409
  const current = deps.current ?? getCurrentVersion();
355
410
  const stateDir = deps.stateDir ?? new StateDirResolver;
356
411
  const now = deps.now ?? Date.now;
357
412
  const print = deps.print ?? ((m) => process.stderr.write(m + `
358
413
  `));
414
+ const inputIsTTY = deps.inputIsTTY ?? Boolean(process.stdin.isTTY);
415
+ const promptTimeoutMs = deps.promptTimeoutMs ?? DEFAULT_UPDATE_PROMPT_TIMEOUT_MS;
359
416
  const cache = readCache(stateDir);
360
417
  if (cache?.latest && isStableUpgrade(current, cache.latest)) {
361
- print(buildUpdateNotice(current, cache.latest, isTTY));
418
+ if (cache.dismissedVersion === cache.latest) {
419
+ print(buildDismissedUpdateNotice(current, cache.latest, isTTY));
420
+ } else {
421
+ print(buildUpdateNotice(current, cache.latest, isTTY));
422
+ if (!updatePromptDisabled(env) && inputIsTTY) {
423
+ const promptUpdate = deps.promptUpdate ?? defaultPromptUpdate;
424
+ let accepted = false;
425
+ try {
426
+ accepted = await promptUpdate({ current, latest: cache.latest, timeoutMs: promptTimeoutMs });
427
+ } catch {
428
+ accepted = false;
429
+ }
430
+ if (accepted) {
431
+ const installUpdate = deps.installUpdate ?? defaultInstallUpdate;
432
+ const cmd = "npm";
433
+ const args = ["install", "-g", `${PACKAGE_NAME}@latest`];
434
+ const result = installUpdate(cmd, args);
435
+ if (result.ok) {
436
+ print("AgentBridge CLI updated. \u8BF7\u91CD\u65B0\u8FD0\u884C\u547D\u4EE4\u3002");
437
+ print("Plugin: in Claude, run `/plugin marketplace update agentbridge` and then `/reload-plugins`.");
438
+ return "updated";
439
+ }
440
+ const detail = result.error?.message ? ` (${result.error.message})` : typeof result.status === "number" ? ` (exit ${result.status})` : "";
441
+ print(`\u26A0 AgentBridge update failed${detail}; continuing with the current command.`);
442
+ } else {
443
+ recordDismissal(stateDir, cache, cache.latest);
444
+ }
445
+ }
446
+ }
362
447
  }
363
448
  if (deps.refresh && (!cache || now() - cache.lastCheckMs >= checkIntervalMs(env))) {
364
449
  refreshUpdateCache({ stateDir, now, fetchImpl: deps.fetchImpl }).catch(() => {});
365
450
  }
366
- } catch {}
451
+ return "continue";
452
+ } catch {
453
+ return "continue";
454
+ }
367
455
  }
368
- var PACKAGE_NAME = "@raysonmeng/agentbridge", REGISTRY_URL, ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json", DEFAULT_CHECK_INTERVAL_MS, FETCH_TIMEOUT_MS = 2500, CHECK_INTERVAL_ENV = "AGENTBRIDGE_UPDATE_CHECK_INTERVAL_MS";
456
+ var PACKAGE_NAME = "@raysonmeng/agentbridge", REGISTRY_URL, ABBREVIATED_ACCEPT = "application/vnd.npm.install-v1+json", DEFAULT_CHECK_INTERVAL_MS, FETCH_TIMEOUT_MS = 2500, DEFAULT_UPDATE_PROMPT_TIMEOUT_MS = 15000, CHECK_INTERVAL_ENV = "AGENTBRIDGE_UPDATE_CHECK_INTERVAL_MS";
369
457
  var init_update_notifier = __esm(() => {
370
458
  init_atomic_json();
371
459
  init_state_dir();
@@ -438,6 +526,9 @@ function normalizeBoundedInteger(value, fallback, min, max) {
438
526
  return fallback;
439
527
  return parsed;
440
528
  }
529
+ function normalizeStrategy(value, fallback) {
530
+ return value === "conserve" || value === "maximize" ? value : fallback;
531
+ }
441
532
  function normalizeBoolean(value, fallback) {
442
533
  if (typeof value === "boolean")
443
534
  return value;
@@ -486,9 +577,27 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
486
577
  timeWindowSec: normalizeBoundedInteger(parallel.timeWindowSec, fallback.parallel.timeWindowSec, 60, 604800)
487
578
  },
488
579
  codexTierControl: normalizeBoolean(budget.codexTierControl, fallback.codexTierControl) && codexTiers.full !== null,
489
- codexTiers
580
+ codexTiers,
581
+ strategy: normalizeStrategy(budget.strategy, fallback.strategy)
490
582
  };
491
583
  }
584
+ function applyBudgetEnvOverrides(budget, env = process.env) {
585
+ const overlay = {
586
+ enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
587
+ pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
588
+ pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
589
+ resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
590
+ syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
591
+ parallel: {
592
+ minRemainingPct: env.AGENTBRIDGE_BUDGET_PARALLEL_MIN_REMAINING_PCT ?? budget.parallel.minRemainingPct,
593
+ timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
594
+ },
595
+ codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
596
+ codexTiers: budget.codexTiers,
597
+ strategy: env.AGENTBRIDGE_BUDGET_STRATEGY ?? budget.strategy
598
+ };
599
+ return normalizeBudgetConfig(overlay, budget);
600
+ }
492
601
  function normalizeConfig(raw) {
493
602
  if (!isRecord(raw))
494
603
  return null;
@@ -615,7 +724,8 @@ var init_config_service = __esm(() => {
615
724
  full: null,
616
725
  balanced: { effort: "medium" },
617
726
  eco: { effort: "low" }
618
- }
727
+ },
728
+ strategy: "conserve"
619
729
  };
620
730
  DEFAULT_CONFIG = {
621
731
  version: "1.0",
@@ -1015,7 +1125,7 @@ var exports_dev = {};
1015
1125
  __export(exports_dev, {
1016
1126
  runDev: () => runDev
1017
1127
  });
1018
- import { execFileSync as execFileSync3, spawnSync } from "child_process";
1128
+ import { execFileSync as execFileSync3, spawnSync as spawnSync2 } from "child_process";
1019
1129
  import { resolve as resolve2 } from "path";
1020
1130
  import { existsSync as existsSync5, cpSync, rmSync } from "fs";
1021
1131
  async function runDev(args = []) {
@@ -1041,7 +1151,7 @@ async function runDev(args = []) {
1041
1151
  `);
1042
1152
  } else {
1043
1153
  console.log("Building CLI from source...");
1044
- const cliBuild = spawnSync("bun", ["run", "build:cli"], {
1154
+ const cliBuild = spawnSync2("bun", ["run", "build:cli"], {
1045
1155
  cwd: projectRoot,
1046
1156
  stdio: "inherit"
1047
1157
  });
@@ -1052,7 +1162,7 @@ async function runDev(args = []) {
1052
1162
  console.log(` \u2713 CLI built successfully
1053
1163
  `);
1054
1164
  console.log("Building plugin from source...");
1055
- const buildResult = spawnSync("bun", ["run", "build:plugin"], {
1165
+ const buildResult = spawnSync2("bun", ["run", "build:plugin"], {
1056
1166
  cwd: projectRoot,
1057
1167
  stdio: "inherit"
1058
1168
  });
@@ -1475,11 +1585,11 @@ function formatBuildInfo(build) {
1475
1585
  var CODE_HASH_SENTINEL = "source", BUILD_INFO;
1476
1586
  var init_build_info = __esm(() => {
1477
1587
  BUILD_INFO = Object.freeze({
1478
- version: defineString("0.1.13", "0.0.0-source"),
1479
- commit: defineString("7a71869", "source"),
1588
+ version: defineString("0.1.15", "0.0.0-source"),
1589
+ commit: defineString("89d4c9a", "source"),
1480
1590
  bundle: defineBundle("dist"),
1481
1591
  contractVersion: defineNumber(1, CONTRACT_VERSION),
1482
- codeHash: defineString("e1fd67d07c62", "source")
1592
+ codeHash: defineString("71ce81854ec9", "source")
1483
1593
  });
1484
1594
  });
1485
1595
 
@@ -3660,12 +3770,26 @@ function readUsableCurrentThread(identity, env = process.env) {
3660
3770
  const state = readRawCurrentThread(identity.stateDir);
3661
3771
  if (!state)
3662
3772
  return null;
3663
- if (state.status !== "current")
3664
- return null;
3665
3773
  if (state.pairId !== identity.pairId)
3666
3774
  return null;
3667
3775
  if (state.cwd !== identity.cwd)
3668
3776
  return null;
3777
+ if (state.status === "pending") {
3778
+ const rolloutPath2 = findCodexRolloutFile(state.threadId, env);
3779
+ if (!rolloutPath2)
3780
+ return null;
3781
+ const promoted = {
3782
+ ...state,
3783
+ status: "current",
3784
+ rolloutPath: rolloutPath2,
3785
+ rolloutVerifiedAt: nowIso(),
3786
+ updatedAt: nowIso()
3787
+ };
3788
+ try {
3789
+ atomicWriteJson(identity.stateDir.currentThreadFile, promoted);
3790
+ } catch {}
3791
+ return promoted;
3792
+ }
3669
3793
  if (state.rolloutPath && existsSync12(state.rolloutPath))
3670
3794
  return state;
3671
3795
  const rolloutPath = findCodexRolloutFile(state.threadId, env);
@@ -3759,10 +3883,12 @@ function resolveCodexResumeArgs(parsed, pair, env = process.env) {
3759
3883
  const current = readUsableCurrentThread(identity, env);
3760
3884
  if (parsed.resumeCurrent) {
3761
3885
  if (!current) {
3886
+ const raw = readRawCurrentThread(identity.stateDir);
3887
+ const pending = raw && raw.status === "pending" && raw.pairId === identity.pairId && raw.cwd === identity.cwd ? raw : null;
3762
3888
  return {
3763
3889
  rest: parsed.rest,
3764
3890
  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>`."
3891
+ 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
3892
  };
3767
3893
  }
3768
3894
  return {
@@ -5069,6 +5195,215 @@ var init_pairs = __esm(() => {
5069
5195
  init_thread_state();
5070
5196
  init_kill();
5071
5197
  });
5198
+ // src/budget/types.ts
5199
+ var STALE_MAX_AGE_SEC = 600;
5200
+
5201
+ // src/budget/budget-state.ts
5202
+ function isDecisionGrade(usage, now) {
5203
+ if (!usage)
5204
+ return false;
5205
+ const freshWindow = usage.fiveHour !== null && usage.fiveHour.resetEpoch > now || usage.weekly !== null && usage.weekly.resetEpoch > now;
5206
+ if (!freshWindow)
5207
+ return false;
5208
+ if (usage.fetchedAt > 0 && now - usage.fetchedAt > STALE_MAX_AGE_SEC)
5209
+ return false;
5210
+ return true;
5211
+ }
5212
+ var init_budget_state = () => {};
5213
+
5214
+ // src/budget/burn-view.ts
5215
+ function agentWeeklyFiveHourWindowsLeft(usage, now) {
5216
+ if (!usage || usage.stale || !usage.ok)
5217
+ return null;
5218
+ if (!isDecisionGrade(usage, now))
5219
+ return null;
5220
+ const weekly = usage.weekly;
5221
+ if (!weekly || weekly.resetEpoch <= now)
5222
+ return null;
5223
+ if (weekly.burnConfident !== true)
5224
+ return null;
5225
+ if (weekly.runwaySeconds === undefined)
5226
+ return null;
5227
+ return weekly.fiveHourWindowsLeft ?? null;
5228
+ }
5229
+ var init_burn_view = __esm(() => {
5230
+ init_budget_state();
5231
+ });
5232
+
5233
+ // src/budget/render.ts
5234
+ function resolveGuardHardHint(env = process.env) {
5235
+ const raw = env.AGENTBRIDGE_GUARD_HARD_HINT;
5236
+ if (raw === undefined || raw.trim() === "")
5237
+ return DEFAULT_GUARD_HARD_PCT;
5238
+ const parsed = Number(raw);
5239
+ if (!Number.isFinite(parsed) || parsed < 1 || parsed > 100)
5240
+ return DEFAULT_GUARD_HARD_PCT;
5241
+ return parsed;
5242
+ }
5243
+ function formatEpoch(epochSeconds) {
5244
+ if (!epochSeconds || epochSeconds <= 0)
5245
+ return "\u672A\u77E5";
5246
+ return new Date(epochSeconds * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
5247
+ }
5248
+ function formatWindow(window, label) {
5249
+ if (!window)
5250
+ return `${label} \u672A\u77E5`;
5251
+ return `${label} ${window.util}%\uFF08\u91CD\u7F6E ${formatEpoch(window.resetEpoch)}\uFF09`;
5252
+ }
5253
+ function formatAgent(name, usage, snapshotAt) {
5254
+ if (!usage)
5255
+ return `${name}\uFF1A\u672A\u77E5\uFF08\u63A2\u6D4B\u4E0D\u53EF\u7528\uFF09`;
5256
+ const parts = [
5257
+ formatWindow(usage.fiveHour, "5h"),
5258
+ formatWindow(usage.weekly, "\u5468"),
5259
+ `\u95E8\u63A7 ${usage.gateUtil}%`,
5260
+ `\u9884\u8B66 ${usage.warnUtil}%`
5261
+ ];
5262
+ if (usage.rateLimitedUntil > 0) {
5263
+ parts.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
5264
+ }
5265
+ if (usage.parsedVia === "positional") {
5266
+ parts.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
5267
+ }
5268
+ const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
5269
+ if (ageSec > 300) {
5270
+ parts.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
5271
+ } else if (usage.stale) {
5272
+ parts.push("\uFF08\u7F13\u5B58\u6570\u636E\uFF09");
5273
+ }
5274
+ return `${name}\uFF1A${parts.join(" \xB7 ")}`;
5275
+ }
5276
+ function formatDuration(seconds) {
5277
+ const totalMinutes = Math.max(0, Math.round(seconds / 60));
5278
+ const hours = Math.floor(totalMinutes / 60);
5279
+ const minutes = totalMinutes % 60;
5280
+ if (hours === 0)
5281
+ return `${minutes}\u5206\u949F`;
5282
+ return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
5283
+ }
5284
+ function formatClockTime(epochSeconds) {
5285
+ const date = new Date(epochSeconds * 1000);
5286
+ const hh = String(date.getHours()).padStart(2, "0");
5287
+ const mm = String(date.getMinutes()).padStart(2, "0");
5288
+ return `${hh}:${mm}`;
5289
+ }
5290
+ function formatWindowRate(label, rate) {
5291
+ if (!rate)
5292
+ return null;
5293
+ if (!rate.confident)
5294
+ return `${label} \u91C7\u6837\u4E2D`;
5295
+ return `${label} \u2248${rate.pctPerHour.toFixed(2)}%/h`;
5296
+ }
5297
+ function formatRunwaySegment(runway, basisWindow, snapshotAt) {
5298
+ const truncatedByReset = basisWindow !== null && basisWindow.resetEpoch > 0 && snapshotAt + runway.seconds >= basisWindow.resetEpoch - RESET_TRUNCATION_EPSILON_SEC;
5299
+ const clock = runway.depletedAtEpoch ? formatClockTime(runway.depletedAtEpoch) : null;
5300
+ let clockNote;
5301
+ if (clock) {
5302
+ clockNote = truncatedByReset ? `\u81F3 ${clock} \u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C` : `\u81F3 ${clock}\uFF0C`;
5303
+ } else {
5304
+ clockNote = truncatedByReset ? "\u7A97\u53E3\u5237\u65B0\u5373\u622A\u65AD\uFF0C" : "";
5305
+ }
5306
+ return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
5307
+ }
5308
+ function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
5309
+ const parts = [
5310
+ formatWindowRate("5h", rates.fiveHour),
5311
+ formatWindowRate("\u5468", rates.weekly)
5312
+ ].filter((part) => part !== null);
5313
+ if (parts.length === 0 && !runway)
5314
+ return null;
5315
+ if (runway) {
5316
+ const basisWindow = usage ? usage[runway.basis] : null;
5317
+ parts.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
5318
+ }
5319
+ if (guardHardPct !== null) {
5320
+ 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`);
5321
+ }
5322
+ return `${name} \u71C3\u5C3D\u7387\uFF1A${parts.join(" \xB7 ")}`;
5323
+ }
5324
+ function formatFiveHourWindowsLeftLine(snapshot) {
5325
+ const values = [];
5326
+ const claude = agentWeeklyFiveHourWindowsLeft(snapshot.claude, snapshot.updatedAt);
5327
+ const codex = agentWeeklyFiveHourWindowsLeft(snapshot.codex, snapshot.updatedAt);
5328
+ if (claude !== null)
5329
+ values.push(["Claude", claude]);
5330
+ if (codex !== null)
5331
+ values.push(["Codex", codex]);
5332
+ if (values.length === 0)
5333
+ return null;
5334
+ const unique = [...new Set(values.map(([, value]) => value.toFixed(1)))];
5335
+ if (unique.length === 1)
5336
+ return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ~${unique[0]} \u4E2A 5h \u7A97\u53E3`;
5337
+ const byAgent = values.map(([name, value]) => `${name} ~${value.toFixed(1)}`).join(" / ");
5338
+ return `\u6309\u5F53\u524D\u8282\u594F\uFF0C\u5468\u989D\u5EA6\u8FD8\u591F ${byAgent} \u4E2A 5h \u7A97\u53E3`;
5339
+ }
5340
+ function renderBudgetSnapshot(snapshot, options = {}) {
5341
+ const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
5342
+ const lines = [];
5343
+ 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)}`);
5344
+ lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
5345
+ lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
5346
+ if (snapshot.burnRate) {
5347
+ const claudeLine = formatBurnRateLine("Claude", snapshot.claude, snapshot.burnRate.claude, snapshot.runway?.claude ?? null, snapshot.updatedAt, guardHardPct);
5348
+ if (claudeLine)
5349
+ lines.push(claudeLine);
5350
+ const codexLine = formatBurnRateLine("Codex", snapshot.codex, snapshot.burnRate.codex, snapshot.runway?.codex ?? null, snapshot.updatedAt, null);
5351
+ if (codexLine)
5352
+ lines.push(codexLine);
5353
+ }
5354
+ const fiveHourWindowsLeftLine = formatFiveHourWindowsLeftLine(snapshot);
5355
+ if (fiveHourWindowsLeftLine)
5356
+ lines.push(fiveHourWindowsLeftLine);
5357
+ if (snapshot.claude && snapshot.codex) {
5358
+ const abs = Math.abs(snapshot.driftPct);
5359
+ if (abs > 0) {
5360
+ const heavier = snapshot.driftPct > 0 ? "Claude" : "Codex";
5361
+ const lighter = snapshot.driftPct > 0 ? "Codex" : "Claude";
5362
+ lines.push(`\u6F02\u79FB\uFF1A${heavier} \u6BD4 ${lighter} \u9AD8 ${abs} \u4E2A\u767E\u5206\u70B9`);
5363
+ } else {
5364
+ lines.push("\u6F02\u79FB\uFF1A\u53CC\u65B9\u6301\u5E73");
5365
+ }
5366
+ }
5367
+ if (snapshot.paused) {
5368
+ 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` : "";
5369
+ const reason = snapshot.pauseReason ?? "\u989D\u5EA6\u63A5\u8FD1\u8017\u5C3D";
5370
+ if (snapshot.pauseSide === "claude" && !snapshot.gateClosed) {
5371
+ 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}`);
5372
+ } else if (snapshot.pauseSide === "codex") {
5373
+ 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}`);
5374
+ } else {
5375
+ lines.push(`\u6682\u505C\uFF1A\u53CC\u4FA7\u8054\u5408\u6682\u505C\uFF08\u95F8\u95E8\u5173\u95ED\uFF09 \u2014 ${reason}${resume}`);
5376
+ }
5377
+ } else {
5378
+ lines.push("\u6682\u505C\uFF1A\u5426");
5379
+ }
5380
+ if (snapshot.parallelRecommended) {
5381
+ 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");
5382
+ }
5383
+ if (snapshot.codexTier !== "full") {
5384
+ lines.push(`Codex \u6863\u4F4D\uFF1A${snapshot.codexTier}`);
5385
+ }
5386
+ if (snapshot.claudeAdvice) {
5387
+ lines.push(`Claude \u5EFA\u8BAE\uFF1A${snapshot.claudeAdvice}`);
5388
+ }
5389
+ 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");
5390
+ return lines.join(`
5391
+ `);
5392
+ }
5393
+ 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";
5394
+ var init_render = __esm(() => {
5395
+ init_burn_view();
5396
+ WINDOW_LABELS = {
5397
+ fiveHour: "5h \u7A97\u53E3",
5398
+ weekly: "\u5468\u7A97\u53E3"
5399
+ };
5400
+ PHASE_LABELS = {
5401
+ normal: "normal\uFF08\u6B63\u5E38\uFF09",
5402
+ balance: "balance\uFF08\u9700\u5747\u8861\uFF09",
5403
+ parallel: "parallel\uFF08\u5EFA\u8BAE\u5E76\u884C\u63D0\u901F\uFF09",
5404
+ paused: "paused\uFF08\u9884\u7B97\u5E72\u9884\u4E2D\uFF09"
5405
+ };
5406
+ });
5072
5407
 
5073
5408
  // src/daemon-status.ts
5074
5409
  async function fetchDaemonStatus(port, path = "/healthz", timeoutMs = DAEMON_STATUS_FETCH_TIMEOUT_MS) {
@@ -5272,8 +5607,10 @@ var exports_doctor = {};
5272
5607
  __export(exports_doctor, {
5273
5608
  runDoctor: () => runDoctor,
5274
5609
  formatDoctorReport: () => formatDoctorReport,
5610
+ evaluateBudgetStrategyGuard: () => evaluateBudgetStrategyGuard,
5275
5611
  evaluateArtifactAlignment: () => evaluateArtifactAlignment,
5276
- describeBuildDrift: () => describeBuildDrift
5612
+ describeBuildDrift: () => describeBuildDrift,
5613
+ V3_DEFAULT_TARGET_UTIL: () => V3_DEFAULT_TARGET_UTIL
5277
5614
  });
5278
5615
  import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync7 } from "fs";
5279
5616
  import { join as join17 } from "path";
@@ -5381,6 +5718,7 @@ async function buildDoctorReport(pair, registered) {
5381
5718
  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
5719
  });
5383
5720
  checks.push(configParseabilityCheck(cwd, cli));
5721
+ checks.push(budgetStrategyGuardCheck(cwd));
5384
5722
  checks.push({
5385
5723
  name: "daemon health",
5386
5724
  status: health ? "ok" : "warn",
@@ -5580,6 +5918,33 @@ function configParseabilityCheck(cwd, cli) {
5580
5918
  detail: desc.customValues ? `parsed at ${desc.path} \u2014 custom values in effect` : `parsed at ${desc.path} \u2014 all values match defaults`
5581
5919
  };
5582
5920
  }
5921
+ function evaluateBudgetStrategyGuard(strategy, guardHardPct, targetUtilPct = V3_DEFAULT_TARGET_UTIL) {
5922
+ if (strategy !== "maximize") {
5923
+ return {
5924
+ name: "budget strategy",
5925
+ status: "ok",
5926
+ detail: "strategy=conserve \u2014 v2-equivalent budget behavior (v3 maximize is opt-in)"
5927
+ };
5928
+ }
5929
+ if (guardHardPct >= targetUtilPct) {
5930
+ return {
5931
+ name: "budget strategy",
5932
+ status: "ok",
5933
+ detail: `strategy=maximize \u2014 outer guard hard line ${guardHardPct}% covers targetUtil ${targetUtilPct}%`
5934
+ };
5935
+ }
5936
+ return {
5937
+ name: "budget strategy",
5938
+ status: "warn",
5939
+ 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`,
5940
+ 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"
5941
+ };
5942
+ }
5943
+ function budgetStrategyGuardCheck(cwd) {
5944
+ const config = new ConfigService(cwd).loadOrDefault();
5945
+ const budget = applyBudgetEnvOverrides(config.budget);
5946
+ return evaluateBudgetStrategyGuard(budget.strategy, resolveGuardHardHint());
5947
+ }
5583
5948
  function logCheck(name, path, cli) {
5584
5949
  if (!existsSync15(path)) {
5585
5950
  return {
@@ -5629,12 +5994,13 @@ function printDoctorReport(report) {
5629
5994
  console.log(line);
5630
5995
  }
5631
5996
  }
5632
- var LARGE_LOG_WARN_BYTES;
5997
+ var LARGE_LOG_WARN_BYTES, V3_DEFAULT_TARGET_UTIL = 97;
5633
5998
  var init_doctor = __esm(() => {
5634
5999
  init_plugin_cache();
5635
6000
  init_build_info();
5636
6001
  init_cli_invocation();
5637
6002
  init_config_service();
6003
+ init_render();
5638
6004
  init_env_guard();
5639
6005
  init_pair_resolver();
5640
6006
  init_thread_state();
@@ -5643,91 +6009,6 @@ var init_doctor = __esm(() => {
5643
6009
  LARGE_LOG_WARN_BYTES = 100 * 1024 * 1024;
5644
6010
  });
5645
6011
 
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
6012
  // src/cli/budget.ts
5732
6013
  var exports_budget = {};
5733
6014
  __export(exports_budget, {
@@ -5968,7 +6249,9 @@ async function main(command, restArgs) {
5968
6249
  if (command && NOTIFY_COMMANDS.has(command)) {
5969
6250
  try {
5970
6251
  const { maybeNotifyUpdate: maybeNotifyUpdate2 } = await Promise.resolve().then(() => (init_update_notifier(), exports_update_notifier));
5971
- maybeNotifyUpdate2({ refresh: REFRESH_COMMANDS.has(command) });
6252
+ const decision = await maybeNotifyUpdate2({ refresh: REFRESH_COMMANDS.has(command) });
6253
+ if (decision === "updated")
6254
+ process.exit(0);
5972
6255
  } catch {}
5973
6256
  }
5974
6257
  switch (command) {