@raysonmeng/agentbridge 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/cli.js +92 -32
- package/dist/daemon.js +93 -15
- package/package.json +1 -1
- package/plugins/agentbridge/.claude-plugin/plugin.json +1 -1
- package/plugins/agentbridge/server/bridge-server.js +170 -38
- package/plugins/agentbridge/server/daemon.js +93 -15
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"name": "agentbridge",
|
|
14
14
|
"description": "Bridge Claude Code and Codex through a shared daemon, push channel delivery, and reply/get_messages tools.",
|
|
15
|
-
"version": "0.1.
|
|
15
|
+
"version": "0.1.22",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "AgentBridge Contributors",
|
|
18
18
|
"email": "raysonmeng@qq.com"
|
package/dist/cli.js
CHANGED
|
@@ -179,7 +179,7 @@ function parsePositiveIntEnv(name, fallback, log = () => {}, env = process.env)
|
|
|
179
179
|
var require_package = __commonJS((exports, module) => {
|
|
180
180
|
module.exports = {
|
|
181
181
|
name: "@raysonmeng/agentbridge",
|
|
182
|
-
version: "0.1.
|
|
182
|
+
version: "0.1.22",
|
|
183
183
|
description: "Bridge between Claude Code and Codex \u2014 bidirectional agent communication via MCP Channel + JSON-RPC",
|
|
184
184
|
type: "module",
|
|
185
185
|
packageManager: "bun@1.3.11",
|
|
@@ -487,7 +487,7 @@ function findShapeViolation(raw) {
|
|
|
487
487
|
if (!isRecord(budget)) {
|
|
488
488
|
return "budget is present but not an object";
|
|
489
489
|
}
|
|
490
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
490
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
491
491
|
for (const key of numericKeys) {
|
|
492
492
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
493
493
|
return `budget.${key} is present but not a number`;
|
|
@@ -541,7 +541,7 @@ function hasCustomDecisionValues(config) {
|
|
|
541
541
|
const d = DEFAULT_CONFIG;
|
|
542
542
|
const b = config.budget;
|
|
543
543
|
const db = d.budget;
|
|
544
|
-
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
544
|
+
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
545
545
|
}
|
|
546
546
|
function normalizeInteger(value, fallback) {
|
|
547
547
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -637,6 +637,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
637
637
|
return {
|
|
638
638
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
639
639
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
640
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
641
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
640
642
|
pauseAt,
|
|
641
643
|
resumeBelow,
|
|
642
644
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -654,6 +656,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
654
656
|
const overlay = {
|
|
655
657
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
656
658
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
659
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
660
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
657
661
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
658
662
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
659
663
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -793,6 +797,8 @@ var init_config_service = __esm(() => {
|
|
|
793
797
|
DEFAULT_BUDGET_CONFIG = {
|
|
794
798
|
enabled: true,
|
|
795
799
|
pollSeconds: 300,
|
|
800
|
+
budgetFreshTtlSec: 25,
|
|
801
|
+
idleAdviceActivityWindowSec: 600,
|
|
796
802
|
pauseAt: 90,
|
|
797
803
|
resumeBelow: 30,
|
|
798
804
|
syncDriftPct: 10,
|
|
@@ -1501,9 +1507,26 @@ var init_daemon_client = __esm(() => {
|
|
|
1501
1507
|
send: () => this.send({ type: "probe_incumbent" })
|
|
1502
1508
|
});
|
|
1503
1509
|
}
|
|
1510
|
+
async requestBudgetRefresh(timeoutMs = 3000) {
|
|
1511
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
const requestId = `budget_${Date.now()}_${this.nextRequestId++}`;
|
|
1515
|
+
return this.awaitTypedResponse({
|
|
1516
|
+
key: "budget_refresh",
|
|
1517
|
+
successEvent: "budgetRefresh",
|
|
1518
|
+
match: (payload) => payload.requestId === requestId,
|
|
1519
|
+
successValue: (payload) => payload.snapshot,
|
|
1520
|
+
failValue: null,
|
|
1521
|
+
timeoutMs,
|
|
1522
|
+
send: () => this.send({ type: "request_budget_refresh", requestId })
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1504
1525
|
awaitTypedResponse(opts) {
|
|
1505
|
-
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
1526
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send, match } = opts;
|
|
1506
1527
|
const onSuccess = (payload) => {
|
|
1528
|
+
if (match && !match(payload))
|
|
1529
|
+
return;
|
|
1507
1530
|
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
1508
1531
|
};
|
|
1509
1532
|
const onRejected = () => {
|
|
@@ -1604,6 +1627,9 @@ var init_daemon_client = __esm(() => {
|
|
|
1604
1627
|
case "incumbent_status":
|
|
1605
1628
|
this.emit("incumbentStatus", { connected: message.connected, alive: message.alive });
|
|
1606
1629
|
return;
|
|
1630
|
+
case "budget_refresh":
|
|
1631
|
+
this.emit("budgetRefresh", { requestId: message.requestId, snapshot: message.snapshot });
|
|
1632
|
+
return;
|
|
1607
1633
|
}
|
|
1608
1634
|
};
|
|
1609
1635
|
ws.onclose = (event) => {
|
|
@@ -1682,11 +1708,11 @@ function formatBuildInfo(build) {
|
|
|
1682
1708
|
var CODE_HASH_SENTINEL = "source", BUILD_INFO;
|
|
1683
1709
|
var init_build_info = __esm(() => {
|
|
1684
1710
|
BUILD_INFO = Object.freeze({
|
|
1685
|
-
version: defineString("0.1.
|
|
1686
|
-
commit: defineString("
|
|
1711
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
1712
|
+
commit: defineString("f5e401b", "source"),
|
|
1687
1713
|
bundle: defineBundle("dist"),
|
|
1688
1714
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
1689
|
-
codeHash: defineString("
|
|
1715
|
+
codeHash: defineString("9688463029af", "source")
|
|
1690
1716
|
});
|
|
1691
1717
|
});
|
|
1692
1718
|
|
|
@@ -5294,6 +5320,45 @@ var init_pairs = __esm(() => {
|
|
|
5294
5320
|
init_thread_state();
|
|
5295
5321
|
init_kill();
|
|
5296
5322
|
});
|
|
5323
|
+
// src/budget/format-time.ts
|
|
5324
|
+
function parts(epochSeconds, options) {
|
|
5325
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
5326
|
+
timeZone: BEIJING_TZ,
|
|
5327
|
+
hour12: false,
|
|
5328
|
+
...options
|
|
5329
|
+
});
|
|
5330
|
+
const out = {};
|
|
5331
|
+
for (const part of fmt.formatToParts(new Date(epochSeconds * 1000))) {
|
|
5332
|
+
out[part.type] = part.value;
|
|
5333
|
+
}
|
|
5334
|
+
return out;
|
|
5335
|
+
}
|
|
5336
|
+
function formatBeijing(epochSeconds) {
|
|
5337
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
5338
|
+
return "\u672A\u77E5";
|
|
5339
|
+
const d = new Date(epochSeconds * 1000);
|
|
5340
|
+
if (Number.isNaN(d.getTime()))
|
|
5341
|
+
return "\u672A\u77E5";
|
|
5342
|
+
const p = parts(epochSeconds, {
|
|
5343
|
+
year: "numeric",
|
|
5344
|
+
month: "2-digit",
|
|
5345
|
+
day: "2-digit",
|
|
5346
|
+
hour: "2-digit",
|
|
5347
|
+
minute: "2-digit"
|
|
5348
|
+
});
|
|
5349
|
+
return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}`;
|
|
5350
|
+
}
|
|
5351
|
+
function formatBeijingClock(epochSeconds) {
|
|
5352
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
5353
|
+
return "\u672A\u77E5";
|
|
5354
|
+
const d = new Date(epochSeconds * 1000);
|
|
5355
|
+
if (Number.isNaN(d.getTime()))
|
|
5356
|
+
return "\u672A\u77E5";
|
|
5357
|
+
const p = parts(epochSeconds, { hour: "2-digit", minute: "2-digit" });
|
|
5358
|
+
return `${p.hour}:${p.minute}`;
|
|
5359
|
+
}
|
|
5360
|
+
var BEIJING_TZ = "Asia/Shanghai";
|
|
5361
|
+
|
|
5297
5362
|
// src/budget/types.ts
|
|
5298
5363
|
var STALE_MAX_AGE_SEC = 600;
|
|
5299
5364
|
|
|
@@ -5349,9 +5414,7 @@ function resolveGuardHardHint(env = process.env) {
|
|
|
5349
5414
|
return parsed;
|
|
5350
5415
|
}
|
|
5351
5416
|
function formatEpoch(epochSeconds) {
|
|
5352
|
-
|
|
5353
|
-
return "\u672A\u77E5";
|
|
5354
|
-
return new Date(epochSeconds * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
5417
|
+
return formatBeijing(epochSeconds);
|
|
5355
5418
|
}
|
|
5356
5419
|
function formatWindow(window, label) {
|
|
5357
5420
|
if (!window)
|
|
@@ -5361,25 +5424,25 @@ function formatWindow(window, label) {
|
|
|
5361
5424
|
function formatAgent(name, usage, snapshotAt) {
|
|
5362
5425
|
if (!usage)
|
|
5363
5426
|
return `${name}\uFF1A\u672A\u77E5\uFF08\u63A2\u6D4B\u4E0D\u53EF\u7528\uFF09`;
|
|
5364
|
-
const
|
|
5427
|
+
const parts2 = [
|
|
5365
5428
|
formatWindow(usage.fiveHour, "5h"),
|
|
5366
5429
|
formatWindow(usage.weekly, "\u5468"),
|
|
5367
5430
|
`\u95E8\u63A7 ${usage.gateUtil}%`,
|
|
5368
5431
|
`\u9884\u8B66 ${usage.warnUtil}%`
|
|
5369
5432
|
];
|
|
5370
5433
|
if (usage.rateLimitedUntil > 0) {
|
|
5371
|
-
|
|
5434
|
+
parts2.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
|
|
5372
5435
|
}
|
|
5373
5436
|
if (usage.parsedVia === "positional") {
|
|
5374
|
-
|
|
5437
|
+
parts2.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
|
|
5375
5438
|
}
|
|
5376
5439
|
const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
|
|
5377
5440
|
if (ageSec > 300) {
|
|
5378
|
-
|
|
5441
|
+
parts2.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
|
|
5379
5442
|
} else if (usage.stale) {
|
|
5380
|
-
|
|
5443
|
+
parts2.push("\uFF08\u7F13\u5B58\u6570\u636E\uFF09");
|
|
5381
5444
|
}
|
|
5382
|
-
return `${name}\uFF1A${
|
|
5445
|
+
return `${name}\uFF1A${parts2.join(" \xB7 ")}`;
|
|
5383
5446
|
}
|
|
5384
5447
|
function formatDuration(seconds) {
|
|
5385
5448
|
const totalMinutes = Math.max(0, Math.round(seconds / 60));
|
|
@@ -5390,10 +5453,7 @@ function formatDuration(seconds) {
|
|
|
5390
5453
|
return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
|
|
5391
5454
|
}
|
|
5392
5455
|
function formatClockTime(epochSeconds) {
|
|
5393
|
-
|
|
5394
|
-
const hh = String(date.getHours()).padStart(2, "0");
|
|
5395
|
-
const mm = String(date.getMinutes()).padStart(2, "0");
|
|
5396
|
-
return `${hh}:${mm}`;
|
|
5456
|
+
return formatBeijingClock(epochSeconds);
|
|
5397
5457
|
}
|
|
5398
5458
|
function formatWindowRate(label, rate) {
|
|
5399
5459
|
if (!rate)
|
|
@@ -5414,20 +5474,20 @@ function formatRunwaySegment(runway, basisWindow, snapshotAt) {
|
|
|
5414
5474
|
return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
|
|
5415
5475
|
}
|
|
5416
5476
|
function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
|
|
5417
|
-
const
|
|
5477
|
+
const parts2 = [
|
|
5418
5478
|
formatWindowRate("5h", rates.fiveHour),
|
|
5419
5479
|
formatWindowRate("\u5468", rates.weekly)
|
|
5420
5480
|
].filter((part) => part !== null);
|
|
5421
|
-
if (
|
|
5481
|
+
if (parts2.length === 0 && !runway)
|
|
5422
5482
|
return null;
|
|
5423
5483
|
if (runway) {
|
|
5424
5484
|
const basisWindow = usage ? usage[runway.basis] : null;
|
|
5425
|
-
|
|
5485
|
+
parts2.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
|
|
5426
5486
|
}
|
|
5427
5487
|
if (guardHardPct !== null) {
|
|
5428
|
-
|
|
5488
|
+
parts2.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`);
|
|
5429
5489
|
}
|
|
5430
|
-
return `${name} \u71C3\u5C3D\u7387\uFF1A${
|
|
5490
|
+
return `${name} \u71C3\u5C3D\u7387\uFF1A${parts2.join(" \xB7 ")}`;
|
|
5431
5491
|
}
|
|
5432
5492
|
function formatFiveHourWindowsLeftLine(snapshot) {
|
|
5433
5493
|
const values = [];
|
|
@@ -5471,7 +5531,7 @@ function formatDynamicLineLine(snapshot) {
|
|
|
5471
5531
|
const lines = snapshot.dynamicPauseLine;
|
|
5472
5532
|
if (!lines)
|
|
5473
5533
|
return null;
|
|
5474
|
-
const
|
|
5534
|
+
const parts2 = [];
|
|
5475
5535
|
const entries = [
|
|
5476
5536
|
["Claude", lines.claude, snapshot.claude],
|
|
5477
5537
|
["Codex", lines.codex, snapshot.codex]
|
|
@@ -5480,16 +5540,16 @@ function formatDynamicLineLine(snapshot) {
|
|
|
5480
5540
|
if (line === null)
|
|
5481
5541
|
continue;
|
|
5482
5542
|
const headroom = usage ? `\uFF08util ${usage.gateUtil}%\uFF0C\u4F59\u91CF ${(line - usage.gateUtil).toFixed(1)}\uFF09` : "";
|
|
5483
|
-
|
|
5543
|
+
parts2.push(`${name} ${line.toFixed(1)}%${headroom}`);
|
|
5484
5544
|
}
|
|
5485
|
-
if (
|
|
5545
|
+
if (parts2.length === 0)
|
|
5486
5546
|
return null;
|
|
5487
|
-
return `\u52A8\u6001\u6682\u505C\u7EBF\uFF1A${
|
|
5547
|
+
return `\u52A8\u6001\u6682\u505C\u7EBF\uFF1A${parts2.join(" \xB7 ")}`;
|
|
5488
5548
|
}
|
|
5489
5549
|
function renderBudgetSnapshot(snapshot, options = {}) {
|
|
5490
5550
|
const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
|
|
5491
5551
|
const lines = [];
|
|
5492
|
-
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase] ?? snapshot.phase} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
5552
|
+
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase] ?? snapshot.phase} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}\uFF08\u65F6\u95F4\u5747\u4E3A\u5317\u4EAC\u65F6\u95F4 UTC+8\uFF09`);
|
|
5493
5553
|
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
5494
5554
|
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
5495
5555
|
if (snapshot.burnRate) {
|
|
@@ -5733,8 +5793,8 @@ function extractUserText(entry) {
|
|
|
5733
5793
|
const content = entry.payload.content;
|
|
5734
5794
|
if (!Array.isArray(content))
|
|
5735
5795
|
return null;
|
|
5736
|
-
const
|
|
5737
|
-
return
|
|
5796
|
+
const parts2 = content.map((item) => typeof item?.text === "string" ? item.text : typeof item?.input_text?.text === "string" ? item.input_text.text : null).filter((part) => !!part);
|
|
5797
|
+
return parts2.length > 0 ? parts2.join(`
|
|
5738
5798
|
`) : null;
|
|
5739
5799
|
}
|
|
5740
5800
|
return null;
|
package/dist/daemon.js
CHANGED
|
@@ -29,11 +29,11 @@ function defineNumber(value, fallback) {
|
|
|
29
29
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
30
30
|
}
|
|
31
31
|
var BUILD_INFO = Object.freeze({
|
|
32
|
-
version: defineString("0.1.
|
|
33
|
-
commit: defineString("
|
|
32
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
33
|
+
commit: defineString("f5e401b", "source"),
|
|
34
34
|
bundle: defineBundle("dist"),
|
|
35
35
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
36
|
-
codeHash: defineString("
|
|
36
|
+
codeHash: defineString("9688463029af", "source")
|
|
37
37
|
});
|
|
38
38
|
function daemonStatusBuildInfo() {
|
|
39
39
|
return { ...BUILD_INFO };
|
|
@@ -3478,6 +3478,8 @@ import { join as join4 } from "path";
|
|
|
3478
3478
|
var DEFAULT_BUDGET_CONFIG = {
|
|
3479
3479
|
enabled: true,
|
|
3480
3480
|
pollSeconds: 300,
|
|
3481
|
+
budgetFreshTtlSec: 25,
|
|
3482
|
+
idleAdviceActivityWindowSec: 600,
|
|
3481
3483
|
pauseAt: 90,
|
|
3482
3484
|
resumeBelow: 30,
|
|
3483
3485
|
syncDriftPct: 10,
|
|
@@ -3539,7 +3541,7 @@ function findShapeViolation(raw) {
|
|
|
3539
3541
|
if (!isRecord(budget)) {
|
|
3540
3542
|
return "budget is present but not an object";
|
|
3541
3543
|
}
|
|
3542
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
3544
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
3543
3545
|
for (const key of numericKeys) {
|
|
3544
3546
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
3545
3547
|
return `budget.${key} is present but not a number`;
|
|
@@ -3593,7 +3595,7 @@ function hasCustomDecisionValues(config) {
|
|
|
3593
3595
|
const d = DEFAULT_CONFIG;
|
|
3594
3596
|
const b = config.budget;
|
|
3595
3597
|
const db = d.budget;
|
|
3596
|
-
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3598
|
+
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3597
3599
|
}
|
|
3598
3600
|
function normalizeInteger(value, fallback) {
|
|
3599
3601
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -3689,6 +3691,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3689
3691
|
return {
|
|
3690
3692
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
3691
3693
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
3694
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
3695
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
3692
3696
|
pauseAt,
|
|
3693
3697
|
resumeBelow,
|
|
3694
3698
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -3706,6 +3710,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3706
3710
|
const overlay = {
|
|
3707
3711
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
3708
3712
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
3713
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
3714
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
3709
3715
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
3710
3716
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
3711
3717
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -3861,6 +3867,36 @@ function retryAfterMsForResume(resumeAfterEpoch, nowMs) {
|
|
|
3861
3867
|
return remainingMs > 0 ? remainingMs : undefined;
|
|
3862
3868
|
}
|
|
3863
3869
|
|
|
3870
|
+
// src/budget/format-time.ts
|
|
3871
|
+
var BEIJING_TZ = "Asia/Shanghai";
|
|
3872
|
+
function parts(epochSeconds, options) {
|
|
3873
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
3874
|
+
timeZone: BEIJING_TZ,
|
|
3875
|
+
hour12: false,
|
|
3876
|
+
...options
|
|
3877
|
+
});
|
|
3878
|
+
const out = {};
|
|
3879
|
+
for (const part of fmt.formatToParts(new Date(epochSeconds * 1000))) {
|
|
3880
|
+
out[part.type] = part.value;
|
|
3881
|
+
}
|
|
3882
|
+
return out;
|
|
3883
|
+
}
|
|
3884
|
+
function formatBeijing(epochSeconds) {
|
|
3885
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
3886
|
+
return "\u672A\u77E5";
|
|
3887
|
+
const d = new Date(epochSeconds * 1000);
|
|
3888
|
+
if (Number.isNaN(d.getTime()))
|
|
3889
|
+
return "\u672A\u77E5";
|
|
3890
|
+
const p = parts(epochSeconds, {
|
|
3891
|
+
year: "numeric",
|
|
3892
|
+
month: "2-digit",
|
|
3893
|
+
day: "2-digit",
|
|
3894
|
+
hour: "2-digit",
|
|
3895
|
+
minute: "2-digit"
|
|
3896
|
+
});
|
|
3897
|
+
return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}`;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3864
3900
|
// src/budget/types.ts
|
|
3865
3901
|
var STALE_MAX_AGE_SEC = 600;
|
|
3866
3902
|
|
|
@@ -4149,14 +4185,12 @@ function pct2(value) {
|
|
|
4149
4185
|
return `${Math.round(value * 10) / 10}%`;
|
|
4150
4186
|
}
|
|
4151
4187
|
function formatEpoch(epoch) {
|
|
4152
|
-
|
|
4153
|
-
return "\u672A\u77E5";
|
|
4154
|
-
return new Date(epoch * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
4188
|
+
return formatBeijing(epoch);
|
|
4155
4189
|
}
|
|
4156
4190
|
function usageSummary(name, usage) {
|
|
4157
4191
|
if (!usage)
|
|
4158
4192
|
return `${AGENT_LABEL2[name]} \u672A\u77E5`;
|
|
4159
|
-
return `${AGENT_LABEL2[name]} gate=${pct2(usage.gateUtil)} warn=${pct2(usage.warnUtil)} 5h\u91CD\u7F6E=${formatEpoch(usage.fiveHour?.resetEpoch ?? 0)}`;
|
|
4193
|
+
return `${AGENT_LABEL2[name]} gate=${pct2(usage.gateUtil)} warn=${pct2(usage.warnUtil)} 5h\u91CD\u7F6E=${formatEpoch(usage.fiveHour?.resetEpoch ?? 0)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`;
|
|
4160
4194
|
}
|
|
4161
4195
|
function resumeAfterEpoch(claude, codex, cfg, now) {
|
|
4162
4196
|
const epochs = [
|
|
@@ -4452,7 +4486,7 @@ function pct3(value) {
|
|
|
4452
4486
|
return `${Math.round(value * 10) / 10}%`;
|
|
4453
4487
|
}
|
|
4454
4488
|
function formatEpoch2(epoch) {
|
|
4455
|
-
return
|
|
4489
|
+
return formatBeijing(epoch);
|
|
4456
4490
|
}
|
|
4457
4491
|
var INITIAL_FINGERPRINT_STATE = {
|
|
4458
4492
|
side: null,
|
|
@@ -4500,7 +4534,7 @@ function activeSideReason(agent, usage, cfg, now) {
|
|
|
4500
4534
|
if (!usage)
|
|
4501
4535
|
return `${AGENT_LABEL3[agent]} \u63A2\u6D4B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u4FDD\u6301\u4E0A\u4E00\u8F6E\u9884\u7B97\u5E72\u9884`;
|
|
4502
4536
|
if (usage.rateLimitedUntil > now) {
|
|
4503
|
-
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}`;
|
|
4537
|
+
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`;
|
|
4504
4538
|
}
|
|
4505
4539
|
const decision = agentShouldPause(agent, usage, cfg, now);
|
|
4506
4540
|
if (decision.pause)
|
|
@@ -4643,7 +4677,7 @@ function admissionReason(side, state, cfg) {
|
|
|
4643
4677
|
if (!usage)
|
|
4644
4678
|
return `${AGENT_LABEL3[agent]} \u63A2\u6D4B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u4FDD\u6301\u4E0A\u4E00\u8F6E\u6536\u5C3E\u4FDD\u62A4`;
|
|
4645
4679
|
if (usage.rateLimitedUntil > state.now) {
|
|
4646
|
-
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF0C\u4FDD\u6301\u6536\u5C3E\u4FDD\u62A4`;
|
|
4680
|
+
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09\uFF0C\u4FDD\u6301\u6536\u5C3E\u4FDD\u62A4`;
|
|
4647
4681
|
}
|
|
4648
4682
|
const decision = agentShouldAdmitClose(agent, usage, cfg, state.now);
|
|
4649
4683
|
if (decision.admitClose)
|
|
@@ -4818,6 +4852,7 @@ class BudgetCoordinator {
|
|
|
4818
4852
|
resumeSignals;
|
|
4819
4853
|
adviceCooldown;
|
|
4820
4854
|
isCodexTurnActive;
|
|
4855
|
+
hasRecentActivity;
|
|
4821
4856
|
timer = null;
|
|
4822
4857
|
running = false;
|
|
4823
4858
|
fpState = INITIAL_FINGERPRINT_STATE;
|
|
@@ -4848,6 +4883,7 @@ class BudgetCoordinator {
|
|
|
4848
4883
|
log: this.log
|
|
4849
4884
|
});
|
|
4850
4885
|
this.isCodexTurnActive = options.isCodexTurnActive ?? (() => false);
|
|
4886
|
+
this.hasRecentActivity = options.hasRecentActivity ?? (() => true);
|
|
4851
4887
|
}
|
|
4852
4888
|
async start() {
|
|
4853
4889
|
if (this.running || !this.config.enabled)
|
|
@@ -4880,6 +4916,24 @@ class BudgetCoordinator {
|
|
|
4880
4916
|
getSnapshot() {
|
|
4881
4917
|
return this.latestSnapshot;
|
|
4882
4918
|
}
|
|
4919
|
+
async refreshSnapshotReadonly() {
|
|
4920
|
+
let usage;
|
|
4921
|
+
try {
|
|
4922
|
+
usage = await this.source.fetchBoth();
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
this.log(`budget readonly refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4927
|
+
if (!usage)
|
|
4928
|
+
return null;
|
|
4929
|
+
const now = this.now();
|
|
4930
|
+
const runway = {
|
|
4931
|
+
claude: agentRunway(usage.claude, now),
|
|
4932
|
+
codex: agentRunway(usage.codex, now)
|
|
4933
|
+
};
|
|
4934
|
+
const state = computeBudgetState(usage.claude, usage.codex, this.config, now, runway);
|
|
4935
|
+
return this.toSnapshot(state, runway);
|
|
4936
|
+
}
|
|
4883
4937
|
getResumeCandidate() {
|
|
4884
4938
|
const { detail, ...rest } = this.resumeCandidate;
|
|
4885
4939
|
return detail ? {
|
|
@@ -4997,6 +5051,12 @@ class BudgetCoordinator {
|
|
|
4997
5051
|
this.fpState = { ...this.fpState, fingerprint: null };
|
|
4998
5052
|
return;
|
|
4999
5053
|
}
|
|
5054
|
+
const activityWindowSec = this.config.idleAdviceActivityWindowSec;
|
|
5055
|
+
if (activityWindowSec > 0 && !this.hasRecentActivity(activityWindowSec)) {
|
|
5056
|
+
this.log(`budget advise suppressed: no agent activity in last ${activityWindowSec}s`);
|
|
5057
|
+
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5000
5060
|
if (effect.phase === "underutilized") {
|
|
5001
5061
|
if (!this.adviceCooldown.tryAcquire("underutilization", state.now))
|
|
5002
5062
|
return;
|
|
@@ -6767,7 +6827,8 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
6767
6827
|
});
|
|
6768
6828
|
},
|
|
6769
6829
|
resumeSignals: readResumeSignals,
|
|
6770
|
-
isCodexTurnActive: () => codex.turnInProgress
|
|
6830
|
+
isCodexTurnActive: () => codex.turnInProgress,
|
|
6831
|
+
hasRecentActivity: (windowSec) => codex.turnInProgress || Date.now() - lastActivityEpochMs <= windowSec * 1000
|
|
6771
6832
|
});
|
|
6772
6833
|
}
|
|
6773
6834
|
budgetCoordinator.start();
|
|
@@ -6778,13 +6839,13 @@ function stopBudgetCoordinator() {
|
|
|
6778
6839
|
function budgetPauseGateError() {
|
|
6779
6840
|
const snapshot = budgetCoordinator?.getSnapshot() ?? null;
|
|
6780
6841
|
const reason = snapshot?.pauseReason ?? "Codex \u4FA7\u989D\u5EA6\u63A5\u8FD1\u8017\u5C3D";
|
|
6781
|
-
const resumeAt = snapshot?.resumeAfterEpoch ?
|
|
6842
|
+
const resumeAt = snapshot?.resumeAfterEpoch ? `${formatBeijing(snapshot.resumeAfterEpoch)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09` : null;
|
|
6782
6843
|
const sideHint = snapshot?.pauseSide === "both" ? "\u53CC\u4FA7\u989D\u5EA6\u5747\u5DF2\u8017\u5C3D\uFF0C\u8BF7\u5199 checkpoint \u7B49\u5F85\u5237\u65B0" : "\u4F60\u53EF\u7EE7\u7EED solo \u63A8\u8FDB\u53EF\u72EC\u7ACB\u90E8\u5206\uFF0C\u5E76\u5199 checkpoint \u6807\u6CE8\u5206\u5DE5\u65AD\u70B9";
|
|
6783
6844
|
const reopenText = `Codex \u4FA7\u5404\u7A97\u53E3 util \u56DE\u843D\u81F3\u52A8\u6001\u6682\u505C\u7EBF \u2212 ${BUDGET_CONFIG.maximize.resumeHysteresisPct}% \u4EE5\u4E0B\u6216\u5BF9\u5E94\u7A97\u53E3\u5237\u65B0\u540E\u95F8\u95E8\u81EA\u52A8\u653E\u5F00`;
|
|
6784
6845
|
return `\u9884\u7B97\u6682\u505C\uFF08\u95F8\u95E8\u5173\u95ED\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u8F6C\u53D1\uFF1A${reason}\u3002` + reopenText + (resumeAt ? `\uFF08\u9884\u8BA1\u6062\u590D ${resumeAt}\uFF0C\u4EE5\u5B9E\u6D4B\u4E3A\u51C6\uFF1B\u63D0\u524D\u5237\u65B0\u4F1A\u66F4\u65E9\u89E3\u9664\uFF09` : "") + `\u3002\u6536\u5230 RESUME \u901A\u77E5\u524D\u8BF7\u52FF\u91CD\u8BD5\u5411 Codex \u53D1\u9001 reply\uFF1B${sideHint}\u3002`;
|
|
6785
6846
|
}
|
|
6786
6847
|
function budgetAdmissionGateError(windowResetEpoch, wrapUpLeft, quotaExhausted) {
|
|
6787
|
-
const resetAt = windowResetEpoch > 0 ?
|
|
6848
|
+
const resetAt = windowResetEpoch > 0 ? `${formatBeijing(windowResetEpoch)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09` : "\u672A\u77E5";
|
|
6788
6849
|
const quota = BUDGET_CONFIG.maximize.wrapUpQuota;
|
|
6789
6850
|
if (quotaExhausted) {
|
|
6790
6851
|
return `\u989D\u5EA6\u7A97\u53E3\u6536\u5C3E\u4FDD\u62A4\u4E2D\uFF08admission-closed\uFF09\uFF1A\u672C\u7A97\u53E3 wrap-up \u914D\u989D\uFF08\u6BCF\u7A97\u53E3 ${quota} \u4E2A\uFF09\u5DF2\u7528\u5C3D\uFF0C\u5DF2\u62D2\u7EDD\u8F6C\u53D1\u3002` + `\u8BF7\u52FF\u518D\u6D3E\u65B0\u4EFB\u52A1\uFF1B\u5199 checkpoint\uFF0C\u7B49\u989D\u5EA6\u7A97\u53E3\u5237\u65B0\uFF08\u7EA6 ${resetAt}\uFF09\u540E\u518D\u7EE7\u7EED\u3002`;
|
|
@@ -6896,6 +6957,7 @@ codex.on("steerFailed", ({ requestId, reason }) => {
|
|
|
6896
6957
|
});
|
|
6897
6958
|
codex.on("steerAccepted", ({ requestId }) => {
|
|
6898
6959
|
log("Steer accepted by app-server");
|
|
6960
|
+
recordAgentActivity();
|
|
6899
6961
|
const dispatch = pendingSteerDispatches.get(requestId);
|
|
6900
6962
|
pendingSteerDispatches.delete(requestId);
|
|
6901
6963
|
if (dispatch?.requireReply) {
|
|
@@ -6965,13 +7027,19 @@ codex.on("turnTrackingReset", (reason) => {
|
|
|
6965
7027
|
pendingSteerDispatches.clear();
|
|
6966
7028
|
resumeInjectionQueue.onTurnTrackingReset();
|
|
6967
7029
|
});
|
|
7030
|
+
var lastActivityEpochMs = 0;
|
|
7031
|
+
function recordAgentActivity() {
|
|
7032
|
+
lastActivityEpochMs = Date.now();
|
|
7033
|
+
}
|
|
6968
7034
|
codex.on("turnStarted", () => {
|
|
6969
7035
|
log("Codex turn started");
|
|
7036
|
+
recordAgentActivity();
|
|
6970
7037
|
emitToClaude(systemMessage("system_turn_started", "\u23F3 Codex is working on the current task. Wait for completion before sending a reply."));
|
|
6971
7038
|
});
|
|
6972
7039
|
codex.on("agentMessage", (msg) => {
|
|
6973
7040
|
if (msg.source !== "codex")
|
|
6974
7041
|
return;
|
|
7042
|
+
recordAgentActivity();
|
|
6975
7043
|
const route = routeCodexMessage(msg.content, {
|
|
6976
7044
|
mode: FILTER_MODE,
|
|
6977
7045
|
replyArmed: replyTracker.isArmed,
|
|
@@ -7189,6 +7257,11 @@ function handleControlMessage(ws, raw) {
|
|
|
7189
7257
|
log(`handleProbeIncumbent threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7190
7258
|
});
|
|
7191
7259
|
return;
|
|
7260
|
+
case "request_budget_refresh":
|
|
7261
|
+
handleRequestBudgetRefresh(ws, message.requestId).catch((err) => {
|
|
7262
|
+
log(`handleRequestBudgetRefresh threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7263
|
+
});
|
|
7264
|
+
return;
|
|
7192
7265
|
case "claude_to_codex": {
|
|
7193
7266
|
handleClaudeToCodex(ws, message).catch((err) => {
|
|
7194
7267
|
log(`handleClaudeToCodex threw for request ${message.requestId}: ${err?.message ?? err}`);
|
|
@@ -7547,6 +7620,11 @@ async function handleProbeIncumbent(ws) {
|
|
|
7547
7620
|
alive: stillConnected && alive
|
|
7548
7621
|
});
|
|
7549
7622
|
}
|
|
7623
|
+
async function handleRequestBudgetRefresh(ws, requestId) {
|
|
7624
|
+
const snapshot = budgetCoordinator ? await budgetCoordinator.refreshSnapshotReadonly() : null;
|
|
7625
|
+
log(`request_budget_refresh from #${ws.data.clientId}: ${snapshot ? "fresh" : "unavailable"}`);
|
|
7626
|
+
sendProtocolMessage(ws, { type: "budget_refresh", requestId, snapshot });
|
|
7627
|
+
}
|
|
7550
7628
|
async function probeLiveness2(ws, timeoutMs) {
|
|
7551
7629
|
return probeLiveness({
|
|
7552
7630
|
get readyState() {
|
package/package.json
CHANGED
|
@@ -13866,6 +13866,45 @@ class StateDirResolver {
|
|
|
13866
13866
|
}
|
|
13867
13867
|
}
|
|
13868
13868
|
|
|
13869
|
+
// src/budget/format-time.ts
|
|
13870
|
+
var BEIJING_TZ = "Asia/Shanghai";
|
|
13871
|
+
function parts(epochSeconds, options) {
|
|
13872
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
13873
|
+
timeZone: BEIJING_TZ,
|
|
13874
|
+
hour12: false,
|
|
13875
|
+
...options
|
|
13876
|
+
});
|
|
13877
|
+
const out = {};
|
|
13878
|
+
for (const part of fmt.formatToParts(new Date(epochSeconds * 1000))) {
|
|
13879
|
+
out[part.type] = part.value;
|
|
13880
|
+
}
|
|
13881
|
+
return out;
|
|
13882
|
+
}
|
|
13883
|
+
function formatBeijing(epochSeconds) {
|
|
13884
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
13885
|
+
return "\u672A\u77E5";
|
|
13886
|
+
const d = new Date(epochSeconds * 1000);
|
|
13887
|
+
if (Number.isNaN(d.getTime()))
|
|
13888
|
+
return "\u672A\u77E5";
|
|
13889
|
+
const p = parts(epochSeconds, {
|
|
13890
|
+
year: "numeric",
|
|
13891
|
+
month: "2-digit",
|
|
13892
|
+
day: "2-digit",
|
|
13893
|
+
hour: "2-digit",
|
|
13894
|
+
minute: "2-digit"
|
|
13895
|
+
});
|
|
13896
|
+
return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}`;
|
|
13897
|
+
}
|
|
13898
|
+
function formatBeijingClock(epochSeconds) {
|
|
13899
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
13900
|
+
return "\u672A\u77E5";
|
|
13901
|
+
const d = new Date(epochSeconds * 1000);
|
|
13902
|
+
if (Number.isNaN(d.getTime()))
|
|
13903
|
+
return "\u672A\u77E5";
|
|
13904
|
+
const p = parts(epochSeconds, { hour: "2-digit", minute: "2-digit" });
|
|
13905
|
+
return `${p.hour}:${p.minute}`;
|
|
13906
|
+
}
|
|
13907
|
+
|
|
13869
13908
|
// src/budget/types.ts
|
|
13870
13909
|
var STALE_MAX_AGE_SEC = 600;
|
|
13871
13910
|
|
|
@@ -13909,9 +13948,7 @@ function resolveGuardHardHint(env = process.env) {
|
|
|
13909
13948
|
return parsed;
|
|
13910
13949
|
}
|
|
13911
13950
|
function formatEpoch(epochSeconds) {
|
|
13912
|
-
|
|
13913
|
-
return "\u672A\u77E5";
|
|
13914
|
-
return new Date(epochSeconds * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
13951
|
+
return formatBeijing(epochSeconds);
|
|
13915
13952
|
}
|
|
13916
13953
|
function formatWindow(window, label) {
|
|
13917
13954
|
if (!window)
|
|
@@ -13921,25 +13958,25 @@ function formatWindow(window, label) {
|
|
|
13921
13958
|
function formatAgent(name, usage, snapshotAt) {
|
|
13922
13959
|
if (!usage)
|
|
13923
13960
|
return `${name}\uFF1A\u672A\u77E5\uFF08\u63A2\u6D4B\u4E0D\u53EF\u7528\uFF09`;
|
|
13924
|
-
const
|
|
13961
|
+
const parts2 = [
|
|
13925
13962
|
formatWindow(usage.fiveHour, "5h"),
|
|
13926
13963
|
formatWindow(usage.weekly, "\u5468"),
|
|
13927
13964
|
`\u95E8\u63A7 ${usage.gateUtil}%`,
|
|
13928
13965
|
`\u9884\u8B66 ${usage.warnUtil}%`
|
|
13929
13966
|
];
|
|
13930
13967
|
if (usage.rateLimitedUntil > 0) {
|
|
13931
|
-
|
|
13968
|
+
parts2.push(`\u9650\u6D41\u81F3 ${formatEpoch(usage.rateLimitedUntil)}`);
|
|
13932
13969
|
}
|
|
13933
13970
|
if (usage.parsedVia === "positional") {
|
|
13934
|
-
|
|
13971
|
+
parts2.push("\u26A0\uFE0F \u7A97\u53E3\u8BC6\u522B\u4F7F\u7528\u4F4D\u7F6E\u515C\u5E95");
|
|
13935
13972
|
}
|
|
13936
13973
|
const ageSec = usage.fetchedAt > 0 ? snapshotAt - usage.fetchedAt : 0;
|
|
13937
13974
|
if (ageSec > 300) {
|
|
13938
|
-
|
|
13975
|
+
parts2.push(`\u26A0\uFE0F \u6570\u636E\u91C7\u96C6\u4E8E ${Math.round(ageSec / 60)} \u5206\u949F\u524D`);
|
|
13939
13976
|
} else if (usage.stale) {
|
|
13940
|
-
|
|
13977
|
+
parts2.push("\uFF08\u7F13\u5B58\u6570\u636E\uFF09");
|
|
13941
13978
|
}
|
|
13942
|
-
return `${name}\uFF1A${
|
|
13979
|
+
return `${name}\uFF1A${parts2.join(" \xB7 ")}`;
|
|
13943
13980
|
}
|
|
13944
13981
|
var WINDOW_LABELS = {
|
|
13945
13982
|
fiveHour: "5h \u7A97\u53E3",
|
|
@@ -13955,10 +13992,7 @@ function formatDuration(seconds) {
|
|
|
13955
13992
|
return `${hours}\u5C0F\u65F6${minutes}\u5206\u949F`;
|
|
13956
13993
|
}
|
|
13957
13994
|
function formatClockTime(epochSeconds) {
|
|
13958
|
-
|
|
13959
|
-
const hh = String(date4.getHours()).padStart(2, "0");
|
|
13960
|
-
const mm = String(date4.getMinutes()).padStart(2, "0");
|
|
13961
|
-
return `${hh}:${mm}`;
|
|
13995
|
+
return formatBeijingClock(epochSeconds);
|
|
13962
13996
|
}
|
|
13963
13997
|
function formatWindowRate(label, rate) {
|
|
13964
13998
|
if (!rate)
|
|
@@ -13979,20 +14013,20 @@ function formatRunwaySegment(runway, basisWindow, snapshotAt) {
|
|
|
13979
14013
|
return `\u7EA6\u53EF\u518D\u5DE5\u4F5C ${formatDuration(runway.seconds)}\uFF08${clockNote}${WINDOW_LABELS[runway.basis]}\u4E3A\u7EA6\u675F\uFF09`;
|
|
13980
14014
|
}
|
|
13981
14015
|
function formatBurnRateLine(name, usage, rates, runway, snapshotAt, guardHardPct) {
|
|
13982
|
-
const
|
|
14016
|
+
const parts2 = [
|
|
13983
14017
|
formatWindowRate("5h", rates.fiveHour),
|
|
13984
14018
|
formatWindowRate("\u5468", rates.weekly)
|
|
13985
14019
|
].filter((part) => part !== null);
|
|
13986
|
-
if (
|
|
14020
|
+
if (parts2.length === 0 && !runway)
|
|
13987
14021
|
return null;
|
|
13988
14022
|
if (runway) {
|
|
13989
14023
|
const basisWindow = usage ? usage[runway.basis] : null;
|
|
13990
|
-
|
|
14024
|
+
parts2.push(formatRunwaySegment(runway, basisWindow, snapshotAt));
|
|
13991
14025
|
}
|
|
13992
14026
|
if (guardHardPct !== null) {
|
|
13993
|
-
|
|
14027
|
+
parts2.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`);
|
|
13994
14028
|
}
|
|
13995
|
-
return `${name} \u71C3\u5C3D\u7387\uFF1A${
|
|
14029
|
+
return `${name} \u71C3\u5C3D\u7387\uFF1A${parts2.join(" \xB7 ")}`;
|
|
13996
14030
|
}
|
|
13997
14031
|
function formatFiveHourWindowsLeftLine(snapshot) {
|
|
13998
14032
|
const values = [];
|
|
@@ -14037,7 +14071,7 @@ function formatDynamicLineLine(snapshot) {
|
|
|
14037
14071
|
const lines = snapshot.dynamicPauseLine;
|
|
14038
14072
|
if (!lines)
|
|
14039
14073
|
return null;
|
|
14040
|
-
const
|
|
14074
|
+
const parts2 = [];
|
|
14041
14075
|
const entries = [
|
|
14042
14076
|
["Claude", lines.claude, snapshot.claude],
|
|
14043
14077
|
["Codex", lines.codex, snapshot.codex]
|
|
@@ -14046,11 +14080,11 @@ function formatDynamicLineLine(snapshot) {
|
|
|
14046
14080
|
if (line === null)
|
|
14047
14081
|
continue;
|
|
14048
14082
|
const headroom = usage ? `\uFF08util ${usage.gateUtil}%\uFF0C\u4F59\u91CF ${(line - usage.gateUtil).toFixed(1)}\uFF09` : "";
|
|
14049
|
-
|
|
14083
|
+
parts2.push(`${name} ${line.toFixed(1)}%${headroom}`);
|
|
14050
14084
|
}
|
|
14051
|
-
if (
|
|
14085
|
+
if (parts2.length === 0)
|
|
14052
14086
|
return null;
|
|
14053
|
-
return `\u52A8\u6001\u6682\u505C\u7EBF\uFF1A${
|
|
14087
|
+
return `\u52A8\u6001\u6682\u505C\u7EBF\uFF1A${parts2.join(" \xB7 ")}`;
|
|
14054
14088
|
}
|
|
14055
14089
|
var PHASE_LABELS = {
|
|
14056
14090
|
normal: "normal\uFF08\u6B63\u5E38\uFF09",
|
|
@@ -14062,7 +14096,7 @@ var PHASE_LABELS = {
|
|
|
14062
14096
|
function renderBudgetSnapshot(snapshot, options = {}) {
|
|
14063
14097
|
const guardHardPct = options.guardHardPct ?? resolveGuardHardHint();
|
|
14064
14098
|
const lines = [];
|
|
14065
|
-
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase] ?? snapshot.phase} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}`);
|
|
14099
|
+
lines.push(`\u3010\u9884\u7B97\u5FEB\u7167 \xB7 \u8D26\u53F7\u7EA7\u3011\u9636\u6BB5\uFF1A${PHASE_LABELS[snapshot.phase] ?? snapshot.phase} \xB7 \u66F4\u65B0\u4E8E ${formatEpoch(snapshot.updatedAt)}\uFF08\u65F6\u95F4\u5747\u4E3A\u5317\u4EAC\u65F6\u95F4 UTC+8\uFF09`);
|
|
14066
14100
|
lines.push(formatAgent("Claude", snapshot.claude, snapshot.updatedAt));
|
|
14067
14101
|
lines.push(formatAgent("Codex", snapshot.codex, snapshot.updatedAt));
|
|
14068
14102
|
if (snapshot.burnRate) {
|
|
@@ -14127,6 +14161,7 @@ var DEFAULT_MAX_BUFFERED_MESSAGES = 100;
|
|
|
14127
14161
|
var DEFAULT_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
14128
14162
|
var DEFAULT_DEDUPE_CAPACITY = 2048;
|
|
14129
14163
|
var DEFAULT_DEDUPE_TTL_MS = 20 * 60 * 1000;
|
|
14164
|
+
var DEFAULT_BUDGET_FRESH_TTL_MS = 25 * 1000;
|
|
14130
14165
|
var CLAUDE_INSTRUCTIONS = [
|
|
14131
14166
|
"Codex is an AI coding agent (OpenAI) running in a separate session on the same machine.",
|
|
14132
14167
|
"",
|
|
@@ -14190,6 +14225,10 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14190
14225
|
monotonicNow;
|
|
14191
14226
|
deliveredMessageIds = new Map;
|
|
14192
14227
|
budgetSnapshot = null;
|
|
14228
|
+
budgetFreshTtlMs;
|
|
14229
|
+
wallNow;
|
|
14230
|
+
requestFreshSnapshot = null;
|
|
14231
|
+
pendingBudgetRefresh = null;
|
|
14193
14232
|
constructor(logFile = new StateDirResolver().logFile, options = {}) {
|
|
14194
14233
|
super();
|
|
14195
14234
|
this.logFile = logFile;
|
|
@@ -14206,6 +14245,8 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14206
14245
|
this.dedupeCapacity = positiveIntegerOr(options.dedupeCapacity, DEFAULT_DEDUPE_CAPACITY);
|
|
14207
14246
|
this.dedupeTtlMs = positiveIntegerOr(options.dedupeTtlMs, DEFAULT_DEDUPE_TTL_MS);
|
|
14208
14247
|
this.monotonicNow = options.now ?? (() => performance.now());
|
|
14248
|
+
this.budgetFreshTtlMs = positiveIntegerOr(options.budgetFreshTtlMs, parsePositiveIntegerEnv("AGENTBRIDGE_BUDGET_FRESH_TTL_SEC", DEFAULT_BUDGET_FRESH_TTL_MS / 1000) * 1000);
|
|
14249
|
+
this.wallNow = options.wallNow ?? (() => Date.now());
|
|
14209
14250
|
this.server = new Server({ name: "agentbridge", version: "0.1.0" }, {
|
|
14210
14251
|
capabilities: {
|
|
14211
14252
|
experimental: { "claude/channel": {} },
|
|
@@ -14233,6 +14274,9 @@ class ClaudeAdapter extends EventEmitter {
|
|
|
14233
14274
|
setBudgetSnapshot(snapshot) {
|
|
14234
14275
|
this.budgetSnapshot = snapshot;
|
|
14235
14276
|
}
|
|
14277
|
+
setRequestFreshSnapshot(fetcher) {
|
|
14278
|
+
this.requestFreshSnapshot = fetcher;
|
|
14279
|
+
}
|
|
14236
14280
|
async pushNotification(message) {
|
|
14237
14281
|
this.log(`pushNotification (instance=${this.instanceId}, msgId=${message.id}, len=${message.content.length})`);
|
|
14238
14282
|
if (!this.rememberDelivery(message))
|
|
@@ -14356,21 +14400,21 @@ Codex: ${msg.content}`;
|
|
|
14356
14400
|
`);
|
|
14357
14401
|
const noticeText = notices.map((notice) => `WARNING: ${notice}`).join(`
|
|
14358
14402
|
`);
|
|
14359
|
-
const
|
|
14403
|
+
const parts2 = [];
|
|
14360
14404
|
if (count > 0) {
|
|
14361
|
-
|
|
14405
|
+
parts2.push(`[${count} new message${count > 1 ? "s" : ""} from Codex]
|
|
14362
14406
|
chat_id: ${this.sessionId}`);
|
|
14363
14407
|
}
|
|
14364
14408
|
if (noticeText)
|
|
14365
|
-
|
|
14409
|
+
parts2.push(noticeText);
|
|
14366
14410
|
if (formatted)
|
|
14367
|
-
|
|
14411
|
+
parts2.push(formatted);
|
|
14368
14412
|
this.log(`get_messages returning ${count} message(s) ` + `(instance=${this.instanceId}, dropped=${dropped}, oversized=${oversized}, oversizedBytes=${oversizedBytes})`);
|
|
14369
14413
|
return {
|
|
14370
14414
|
content: [
|
|
14371
14415
|
{
|
|
14372
14416
|
type: "text",
|
|
14373
|
-
text:
|
|
14417
|
+
text: parts2.join(`
|
|
14374
14418
|
|
|
14375
14419
|
`)
|
|
14376
14420
|
}
|
|
@@ -14509,13 +14553,42 @@ chat_id: ${this.sessionId}`);
|
|
|
14509
14553
|
content: [{ type: "text", text: `Resume acknowledged (resume_id=${resumeIdRaw}, status=${status}).` }]
|
|
14510
14554
|
};
|
|
14511
14555
|
}
|
|
14512
|
-
handleGetBudget() {
|
|
14513
|
-
|
|
14514
|
-
const
|
|
14556
|
+
async handleGetBudget() {
|
|
14557
|
+
let snapshot = this.budgetSnapshot;
|
|
14558
|
+
const fresh = snapshot !== null && this.isBudgetSnapshotFresh(snapshot);
|
|
14559
|
+
this.log(`get_budget called (instance=${this.instanceId}, hasSnapshot=${snapshot !== null}, fresh=${fresh})`);
|
|
14560
|
+
if (!fresh && this.requestFreshSnapshot) {
|
|
14561
|
+
const refreshed = await this.refreshBudgetSnapshot();
|
|
14562
|
+
snapshot = refreshed ?? this.budgetSnapshot;
|
|
14563
|
+
}
|
|
14564
|
+
const text = snapshot ? renderBudgetSnapshot(snapshot) : BUDGET_UNAVAILABLE_TEXT;
|
|
14515
14565
|
return {
|
|
14516
14566
|
content: [{ type: "text", text }]
|
|
14517
14567
|
};
|
|
14518
14568
|
}
|
|
14569
|
+
isBudgetSnapshotFresh(snapshot) {
|
|
14570
|
+
if (!snapshot.updatedAt || snapshot.updatedAt <= 0)
|
|
14571
|
+
return false;
|
|
14572
|
+
const ageMs = this.wallNow() - snapshot.updatedAt * 1000;
|
|
14573
|
+
return ageMs < this.budgetFreshTtlMs;
|
|
14574
|
+
}
|
|
14575
|
+
refreshBudgetSnapshot() {
|
|
14576
|
+
if (!this.requestFreshSnapshot)
|
|
14577
|
+
return Promise.resolve(null);
|
|
14578
|
+
if (!this.pendingBudgetRefresh) {
|
|
14579
|
+
this.pendingBudgetRefresh = this.requestFreshSnapshot().then((snapshot) => {
|
|
14580
|
+
if (snapshot)
|
|
14581
|
+
this.budgetSnapshot = snapshot;
|
|
14582
|
+
return snapshot;
|
|
14583
|
+
}).catch((error2) => {
|
|
14584
|
+
this.log(`get_budget refresh failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
14585
|
+
return null;
|
|
14586
|
+
}).finally(() => {
|
|
14587
|
+
this.pendingBudgetRefresh = null;
|
|
14588
|
+
});
|
|
14589
|
+
}
|
|
14590
|
+
return this.pendingBudgetRefresh;
|
|
14591
|
+
}
|
|
14519
14592
|
async handleReply(args) {
|
|
14520
14593
|
const text = args?.text;
|
|
14521
14594
|
if (!text) {
|
|
@@ -14633,11 +14706,11 @@ function defineNumber(value, fallback) {
|
|
|
14633
14706
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
14634
14707
|
}
|
|
14635
14708
|
var BUILD_INFO = Object.freeze({
|
|
14636
|
-
version: defineString("0.1.
|
|
14637
|
-
commit: defineString("
|
|
14709
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
14710
|
+
commit: defineString("f5e401b", "source"),
|
|
14638
14711
|
bundle: defineBundle("plugin"),
|
|
14639
14712
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
14640
|
-
codeHash: defineString("
|
|
14713
|
+
codeHash: defineString("9688463029af", "source")
|
|
14641
14714
|
});
|
|
14642
14715
|
function sameRuntimeContract(a, b) {
|
|
14643
14716
|
if (!a || !b)
|
|
@@ -14841,9 +14914,26 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14841
14914
|
send: () => this.send({ type: "probe_incumbent" })
|
|
14842
14915
|
});
|
|
14843
14916
|
}
|
|
14917
|
+
async requestBudgetRefresh(timeoutMs = 3000) {
|
|
14918
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
14919
|
+
return null;
|
|
14920
|
+
}
|
|
14921
|
+
const requestId = `budget_${Date.now()}_${this.nextRequestId++}`;
|
|
14922
|
+
return this.awaitTypedResponse({
|
|
14923
|
+
key: "budget_refresh",
|
|
14924
|
+
successEvent: "budgetRefresh",
|
|
14925
|
+
match: (payload) => payload.requestId === requestId,
|
|
14926
|
+
successValue: (payload) => payload.snapshot,
|
|
14927
|
+
failValue: null,
|
|
14928
|
+
timeoutMs,
|
|
14929
|
+
send: () => this.send({ type: "request_budget_refresh", requestId })
|
|
14930
|
+
});
|
|
14931
|
+
}
|
|
14844
14932
|
awaitTypedResponse(opts) {
|
|
14845
|
-
const { key, successEvent, successValue, failValue, timeoutMs, send } = opts;
|
|
14933
|
+
const { key, successEvent, successValue, failValue, timeoutMs, send, match } = opts;
|
|
14846
14934
|
const onSuccess = (payload) => {
|
|
14935
|
+
if (match && !match(payload))
|
|
14936
|
+
return;
|
|
14847
14937
|
this.pendingEventWaiters.settle(key, successValue(payload));
|
|
14848
14938
|
};
|
|
14849
14939
|
const onRejected = () => {
|
|
@@ -14944,6 +15034,9 @@ class DaemonClient extends EventEmitter2 {
|
|
|
14944
15034
|
case "incumbent_status":
|
|
14945
15035
|
this.emit("incumbentStatus", { connected: message.connected, alive: message.alive });
|
|
14946
15036
|
return;
|
|
15037
|
+
case "budget_refresh":
|
|
15038
|
+
this.emit("budgetRefresh", { requestId: message.requestId, snapshot: message.snapshot });
|
|
15039
|
+
return;
|
|
14947
15040
|
}
|
|
14948
15041
|
};
|
|
14949
15042
|
ws.onclose = (event) => {
|
|
@@ -15660,6 +15753,8 @@ import { join as join2 } from "path";
|
|
|
15660
15753
|
var DEFAULT_BUDGET_CONFIG = {
|
|
15661
15754
|
enabled: true,
|
|
15662
15755
|
pollSeconds: 300,
|
|
15756
|
+
budgetFreshTtlSec: 25,
|
|
15757
|
+
idleAdviceActivityWindowSec: 600,
|
|
15663
15758
|
pauseAt: 90,
|
|
15664
15759
|
resumeBelow: 30,
|
|
15665
15760
|
syncDriftPct: 10,
|
|
@@ -15721,7 +15816,7 @@ function findShapeViolation(raw) {
|
|
|
15721
15816
|
if (!isRecord(budget)) {
|
|
15722
15817
|
return "budget is present but not an object";
|
|
15723
15818
|
}
|
|
15724
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
15819
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
15725
15820
|
for (const key of numericKeys) {
|
|
15726
15821
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
15727
15822
|
return `budget.${key} is present but not a number`;
|
|
@@ -15775,7 +15870,7 @@ function hasCustomDecisionValues(config2) {
|
|
|
15775
15870
|
const d = DEFAULT_CONFIG;
|
|
15776
15871
|
const b = config2.budget;
|
|
15777
15872
|
const db = d.budget;
|
|
15778
|
-
return config2.idleShutdownSeconds !== d.idleShutdownSeconds || config2.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config2.codex.appPort !== d.codex.appPort || config2.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
15873
|
+
return config2.idleShutdownSeconds !== d.idleShutdownSeconds || config2.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config2.codex.appPort !== d.codex.appPort || config2.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
15779
15874
|
}
|
|
15780
15875
|
function normalizeInteger(value, fallback) {
|
|
15781
15876
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -15871,6 +15966,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15871
15966
|
return {
|
|
15872
15967
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
15873
15968
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
15969
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
15970
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
15874
15971
|
pauseAt,
|
|
15875
15972
|
resumeBelow,
|
|
15876
15973
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -15884,6 +15981,37 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
15884
15981
|
allocation: normalizeAllocationConfig(budget.allocation, fallback.allocation)
|
|
15885
15982
|
};
|
|
15886
15983
|
}
|
|
15984
|
+
function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
15985
|
+
const overlay = {
|
|
15986
|
+
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
15987
|
+
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
15988
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
15989
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
15990
|
+
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
15991
|
+
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
15992
|
+
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
15993
|
+
parallel: {
|
|
15994
|
+
minRemainingPct: env.AGENTBRIDGE_BUDGET_PARALLEL_MIN_REMAINING_PCT ?? budget.parallel.minRemainingPct,
|
|
15995
|
+
timeWindowSec: env.AGENTBRIDGE_BUDGET_PARALLEL_TIME_WINDOW_SEC ?? budget.parallel.timeWindowSec
|
|
15996
|
+
},
|
|
15997
|
+
codexTierControl: env.AGENTBRIDGE_BUDGET_CODEX_TIER_CONTROL ?? budget.codexTierControl,
|
|
15998
|
+
codexTiers: budget.codexTiers,
|
|
15999
|
+
maximize: {
|
|
16000
|
+
targetUtil: env.AGENTBRIDGE_BUDGET_TARGET_UTIL ?? budget.maximize.targetUtil,
|
|
16001
|
+
reserveSlopePctPerHour: env.AGENTBRIDGE_BUDGET_RESERVE_SLOPE_PCT_PER_HOUR ?? budget.maximize.reserveSlopePctPerHour,
|
|
16002
|
+
reserveMaxPct: env.AGENTBRIDGE_BUDGET_RESERVE_MAX_PCT ?? budget.maximize.reserveMaxPct,
|
|
16003
|
+
finishingHorizonMinutes: env.AGENTBRIDGE_BUDGET_FINISHING_HORIZON_MINUTES ?? budget.maximize.finishingHorizonMinutes,
|
|
16004
|
+
resumeHysteresisPct: env.AGENTBRIDGE_BUDGET_RESUME_HYSTERESIS_PCT ?? budget.maximize.resumeHysteresisPct,
|
|
16005
|
+
admissionAt: env.AGENTBRIDGE_BUDGET_ADMISSION_AT ?? budget.maximize.admissionAt,
|
|
16006
|
+
wrapUpQuota: env.AGENTBRIDGE_BUDGET_WRAP_UP_QUOTA ?? budget.maximize.wrapUpQuota
|
|
16007
|
+
},
|
|
16008
|
+
allocation: {
|
|
16009
|
+
minRunwayRatio: env.AGENTBRIDGE_BUDGET_MIN_RUNWAY_RATIO ?? budget.allocation.minRunwayRatio,
|
|
16010
|
+
minRunwayGapHours: env.AGENTBRIDGE_BUDGET_MIN_RUNWAY_GAP_HOURS ?? budget.allocation.minRunwayGapHours
|
|
16011
|
+
}
|
|
16012
|
+
};
|
|
16013
|
+
return normalizeBudgetConfig(overlay, budget);
|
|
16014
|
+
}
|
|
15887
16015
|
function normalizeConfig(raw) {
|
|
15888
16016
|
if (!isRecord(raw))
|
|
15889
16017
|
return null;
|
|
@@ -16363,8 +16491,12 @@ var config2 = configService.loadOrDefault(processLogger.log);
|
|
|
16363
16491
|
var CONTROL_PORT = parseInt(process.env.AGENTBRIDGE_CONTROL_PORT ?? "4502", 10);
|
|
16364
16492
|
var daemonLifecycle = new DaemonLifecycle({ stateDir, controlPort: CONTROL_PORT, log });
|
|
16365
16493
|
var CONTROL_WS_URL = daemonLifecycle.controlWsUrl;
|
|
16366
|
-
var
|
|
16494
|
+
var effectiveBudget = applyBudgetEnvOverrides(config2.budget);
|
|
16495
|
+
var claude = new ClaudeAdapter(stateDir.logFile, {
|
|
16496
|
+
budgetFreshTtlMs: effectiveBudget.budgetFreshTtlSec * 1000
|
|
16497
|
+
});
|
|
16367
16498
|
var daemonClient = new DaemonClient(CONTROL_WS_URL, { identity: currentClientIdentity });
|
|
16499
|
+
claude.setRequestFreshSnapshot(() => daemonClient.requestBudgetRefresh());
|
|
16368
16500
|
var shuttingDown = false;
|
|
16369
16501
|
var daemonDisabled = false;
|
|
16370
16502
|
var daemonDisabledReason = null;
|
|
@@ -29,11 +29,11 @@ function defineNumber(value, fallback) {
|
|
|
29
29
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
30
30
|
}
|
|
31
31
|
var BUILD_INFO = Object.freeze({
|
|
32
|
-
version: defineString("0.1.
|
|
33
|
-
commit: defineString("
|
|
32
|
+
version: defineString("0.1.22", "0.0.0-source"),
|
|
33
|
+
commit: defineString("f5e401b", "source"),
|
|
34
34
|
bundle: defineBundle("plugin"),
|
|
35
35
|
contractVersion: defineNumber(1, CONTRACT_VERSION),
|
|
36
|
-
codeHash: defineString("
|
|
36
|
+
codeHash: defineString("9688463029af", "source")
|
|
37
37
|
});
|
|
38
38
|
function daemonStatusBuildInfo() {
|
|
39
39
|
return { ...BUILD_INFO };
|
|
@@ -3478,6 +3478,8 @@ import { join as join4 } from "path";
|
|
|
3478
3478
|
var DEFAULT_BUDGET_CONFIG = {
|
|
3479
3479
|
enabled: true,
|
|
3480
3480
|
pollSeconds: 300,
|
|
3481
|
+
budgetFreshTtlSec: 25,
|
|
3482
|
+
idleAdviceActivityWindowSec: 600,
|
|
3481
3483
|
pauseAt: 90,
|
|
3482
3484
|
resumeBelow: 30,
|
|
3483
3485
|
syncDriftPct: 10,
|
|
@@ -3539,7 +3541,7 @@ function findShapeViolation(raw) {
|
|
|
3539
3541
|
if (!isRecord(budget)) {
|
|
3540
3542
|
return "budget is present but not an object";
|
|
3541
3543
|
}
|
|
3542
|
-
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct"];
|
|
3544
|
+
const numericKeys = ["pauseAt", "resumeBelow", "pollSeconds", "syncDriftPct", "budgetFreshTtlSec", "idleAdviceActivityWindowSec"];
|
|
3543
3545
|
for (const key of numericKeys) {
|
|
3544
3546
|
if (key in budget && !isCoercibleNumber(budget[key])) {
|
|
3545
3547
|
return `budget.${key} is present but not a number`;
|
|
@@ -3593,7 +3595,7 @@ function hasCustomDecisionValues(config) {
|
|
|
3593
3595
|
const d = DEFAULT_CONFIG;
|
|
3594
3596
|
const b = config.budget;
|
|
3595
3597
|
const db = d.budget;
|
|
3596
|
-
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3598
|
+
return config.idleShutdownSeconds !== d.idleShutdownSeconds || config.turnCoordination.attentionWindowSeconds !== d.turnCoordination.attentionWindowSeconds || config.codex.appPort !== d.codex.appPort || config.codex.proxyPort !== d.codex.proxyPort || b.enabled !== db.enabled || b.pollSeconds !== db.pollSeconds || b.budgetFreshTtlSec !== db.budgetFreshTtlSec || b.idleAdviceActivityWindowSec !== db.idleAdviceActivityWindowSec || b.pauseAt !== db.pauseAt || b.resumeBelow !== db.resumeBelow || b.syncDriftPct !== db.syncDriftPct || b.parallel.minRemainingPct !== db.parallel.minRemainingPct || b.parallel.timeWindowSec !== db.parallel.timeWindowSec || b.codexTierControl !== db.codexTierControl || b.maximize.targetUtil !== db.maximize.targetUtil || b.maximize.reserveSlopePctPerHour !== db.maximize.reserveSlopePctPerHour || b.maximize.reserveMaxPct !== db.maximize.reserveMaxPct || b.maximize.finishingHorizonMinutes !== db.maximize.finishingHorizonMinutes || b.maximize.resumeHysteresisPct !== db.maximize.resumeHysteresisPct || b.maximize.admissionAt !== db.maximize.admissionAt || b.maximize.wrapUpQuota !== db.maximize.wrapUpQuota || b.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
|
|
3597
3599
|
}
|
|
3598
3600
|
function normalizeInteger(value, fallback) {
|
|
3599
3601
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -3689,6 +3691,8 @@ function normalizeBudgetConfig(raw, fallback = DEFAULT_BUDGET_CONFIG) {
|
|
|
3689
3691
|
return {
|
|
3690
3692
|
enabled: normalizeBoolean(budget.enabled, fallback.enabled),
|
|
3691
3693
|
pollSeconds: normalizeBoundedInteger(budget.pollSeconds, fallback.pollSeconds, 5, 3600),
|
|
3694
|
+
budgetFreshTtlSec: normalizeBoundedInteger(budget.budgetFreshTtlSec, fallback.budgetFreshTtlSec, 1, 300),
|
|
3695
|
+
idleAdviceActivityWindowSec: normalizeBoundedInteger(budget.idleAdviceActivityWindowSec, fallback.idleAdviceActivityWindowSec, 0, 86400),
|
|
3692
3696
|
pauseAt,
|
|
3693
3697
|
resumeBelow,
|
|
3694
3698
|
syncDriftPct: normalizeBoundedInteger(budget.syncDriftPct, fallback.syncDriftPct, 1, 100),
|
|
@@ -3706,6 +3710,8 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
|
|
|
3706
3710
|
const overlay = {
|
|
3707
3711
|
enabled: env.AGENTBRIDGE_BUDGET_ENABLED ?? budget.enabled,
|
|
3708
3712
|
pollSeconds: env.AGENTBRIDGE_BUDGET_POLL_SECONDS ?? budget.pollSeconds,
|
|
3713
|
+
budgetFreshTtlSec: env.AGENTBRIDGE_BUDGET_FRESH_TTL_SEC ?? budget.budgetFreshTtlSec,
|
|
3714
|
+
idleAdviceActivityWindowSec: env.AGENTBRIDGE_BUDGET_IDLE_ADVICE_ACTIVITY_WINDOW_SEC ?? budget.idleAdviceActivityWindowSec,
|
|
3709
3715
|
pauseAt: env.AGENTBRIDGE_BUDGET_PAUSE_AT ?? budget.pauseAt,
|
|
3710
3716
|
resumeBelow: env.AGENTBRIDGE_BUDGET_RESUME_BELOW ?? budget.resumeBelow,
|
|
3711
3717
|
syncDriftPct: env.AGENTBRIDGE_BUDGET_SYNC_DRIFT_PCT ?? budget.syncDriftPct,
|
|
@@ -3861,6 +3867,36 @@ function retryAfterMsForResume(resumeAfterEpoch, nowMs) {
|
|
|
3861
3867
|
return remainingMs > 0 ? remainingMs : undefined;
|
|
3862
3868
|
}
|
|
3863
3869
|
|
|
3870
|
+
// src/budget/format-time.ts
|
|
3871
|
+
var BEIJING_TZ = "Asia/Shanghai";
|
|
3872
|
+
function parts(epochSeconds, options) {
|
|
3873
|
+
const fmt = new Intl.DateTimeFormat("en-CA", {
|
|
3874
|
+
timeZone: BEIJING_TZ,
|
|
3875
|
+
hour12: false,
|
|
3876
|
+
...options
|
|
3877
|
+
});
|
|
3878
|
+
const out = {};
|
|
3879
|
+
for (const part of fmt.formatToParts(new Date(epochSeconds * 1000))) {
|
|
3880
|
+
out[part.type] = part.value;
|
|
3881
|
+
}
|
|
3882
|
+
return out;
|
|
3883
|
+
}
|
|
3884
|
+
function formatBeijing(epochSeconds) {
|
|
3885
|
+
if (!epochSeconds || epochSeconds <= 0)
|
|
3886
|
+
return "\u672A\u77E5";
|
|
3887
|
+
const d = new Date(epochSeconds * 1000);
|
|
3888
|
+
if (Number.isNaN(d.getTime()))
|
|
3889
|
+
return "\u672A\u77E5";
|
|
3890
|
+
const p = parts(epochSeconds, {
|
|
3891
|
+
year: "numeric",
|
|
3892
|
+
month: "2-digit",
|
|
3893
|
+
day: "2-digit",
|
|
3894
|
+
hour: "2-digit",
|
|
3895
|
+
minute: "2-digit"
|
|
3896
|
+
});
|
|
3897
|
+
return `${p.year}-${p.month}-${p.day} ${p.hour}:${p.minute}`;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3864
3900
|
// src/budget/types.ts
|
|
3865
3901
|
var STALE_MAX_AGE_SEC = 600;
|
|
3866
3902
|
|
|
@@ -4149,14 +4185,12 @@ function pct2(value) {
|
|
|
4149
4185
|
return `${Math.round(value * 10) / 10}%`;
|
|
4150
4186
|
}
|
|
4151
4187
|
function formatEpoch(epoch) {
|
|
4152
|
-
|
|
4153
|
-
return "\u672A\u77E5";
|
|
4154
|
-
return new Date(epoch * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
4188
|
+
return formatBeijing(epoch);
|
|
4155
4189
|
}
|
|
4156
4190
|
function usageSummary(name, usage) {
|
|
4157
4191
|
if (!usage)
|
|
4158
4192
|
return `${AGENT_LABEL2[name]} \u672A\u77E5`;
|
|
4159
|
-
return `${AGENT_LABEL2[name]} gate=${pct2(usage.gateUtil)} warn=${pct2(usage.warnUtil)} 5h\u91CD\u7F6E=${formatEpoch(usage.fiveHour?.resetEpoch ?? 0)}`;
|
|
4193
|
+
return `${AGENT_LABEL2[name]} gate=${pct2(usage.gateUtil)} warn=${pct2(usage.warnUtil)} 5h\u91CD\u7F6E=${formatEpoch(usage.fiveHour?.resetEpoch ?? 0)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`;
|
|
4160
4194
|
}
|
|
4161
4195
|
function resumeAfterEpoch(claude, codex, cfg, now) {
|
|
4162
4196
|
const epochs = [
|
|
@@ -4452,7 +4486,7 @@ function pct3(value) {
|
|
|
4452
4486
|
return `${Math.round(value * 10) / 10}%`;
|
|
4453
4487
|
}
|
|
4454
4488
|
function formatEpoch2(epoch) {
|
|
4455
|
-
return
|
|
4489
|
+
return formatBeijing(epoch);
|
|
4456
4490
|
}
|
|
4457
4491
|
var INITIAL_FINGERPRINT_STATE = {
|
|
4458
4492
|
side: null,
|
|
@@ -4500,7 +4534,7 @@ function activeSideReason(agent, usage, cfg, now) {
|
|
|
4500
4534
|
if (!usage)
|
|
4501
4535
|
return `${AGENT_LABEL3[agent]} \u63A2\u6D4B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u4FDD\u6301\u4E0A\u4E00\u8F6E\u9884\u7B97\u5E72\u9884`;
|
|
4502
4536
|
if (usage.rateLimitedUntil > now) {
|
|
4503
|
-
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}`;
|
|
4537
|
+
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09`;
|
|
4504
4538
|
}
|
|
4505
4539
|
const decision = agentShouldPause(agent, usage, cfg, now);
|
|
4506
4540
|
if (decision.pause)
|
|
@@ -4643,7 +4677,7 @@ function admissionReason(side, state, cfg) {
|
|
|
4643
4677
|
if (!usage)
|
|
4644
4678
|
return `${AGENT_LABEL3[agent]} \u63A2\u6D4B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u4FDD\u6301\u4E0A\u4E00\u8F6E\u6536\u5C3E\u4FDD\u62A4`;
|
|
4645
4679
|
if (usage.rateLimitedUntil > state.now) {
|
|
4646
|
-
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF0C\u4FDD\u6301\u6536\u5C3E\u4FDD\u62A4`;
|
|
4680
|
+
return `${AGENT_LABEL3[agent]} \u63A2\u9488\u88AB\u9650\u6D41\u81F3 ${formatEpoch2(usage.rateLimitedUntil)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09\uFF0C\u4FDD\u6301\u6536\u5C3E\u4FDD\u62A4`;
|
|
4647
4681
|
}
|
|
4648
4682
|
const decision = agentShouldAdmitClose(agent, usage, cfg, state.now);
|
|
4649
4683
|
if (decision.admitClose)
|
|
@@ -4818,6 +4852,7 @@ class BudgetCoordinator {
|
|
|
4818
4852
|
resumeSignals;
|
|
4819
4853
|
adviceCooldown;
|
|
4820
4854
|
isCodexTurnActive;
|
|
4855
|
+
hasRecentActivity;
|
|
4821
4856
|
timer = null;
|
|
4822
4857
|
running = false;
|
|
4823
4858
|
fpState = INITIAL_FINGERPRINT_STATE;
|
|
@@ -4848,6 +4883,7 @@ class BudgetCoordinator {
|
|
|
4848
4883
|
log: this.log
|
|
4849
4884
|
});
|
|
4850
4885
|
this.isCodexTurnActive = options.isCodexTurnActive ?? (() => false);
|
|
4886
|
+
this.hasRecentActivity = options.hasRecentActivity ?? (() => true);
|
|
4851
4887
|
}
|
|
4852
4888
|
async start() {
|
|
4853
4889
|
if (this.running || !this.config.enabled)
|
|
@@ -4880,6 +4916,24 @@ class BudgetCoordinator {
|
|
|
4880
4916
|
getSnapshot() {
|
|
4881
4917
|
return this.latestSnapshot;
|
|
4882
4918
|
}
|
|
4919
|
+
async refreshSnapshotReadonly() {
|
|
4920
|
+
let usage;
|
|
4921
|
+
try {
|
|
4922
|
+
usage = await this.source.fetchBoth();
|
|
4923
|
+
} catch (error) {
|
|
4924
|
+
this.log(`budget readonly refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4927
|
+
if (!usage)
|
|
4928
|
+
return null;
|
|
4929
|
+
const now = this.now();
|
|
4930
|
+
const runway = {
|
|
4931
|
+
claude: agentRunway(usage.claude, now),
|
|
4932
|
+
codex: agentRunway(usage.codex, now)
|
|
4933
|
+
};
|
|
4934
|
+
const state = computeBudgetState(usage.claude, usage.codex, this.config, now, runway);
|
|
4935
|
+
return this.toSnapshot(state, runway);
|
|
4936
|
+
}
|
|
4883
4937
|
getResumeCandidate() {
|
|
4884
4938
|
const { detail, ...rest } = this.resumeCandidate;
|
|
4885
4939
|
return detail ? {
|
|
@@ -4997,6 +5051,12 @@ class BudgetCoordinator {
|
|
|
4997
5051
|
this.fpState = { ...this.fpState, fingerprint: null };
|
|
4998
5052
|
return;
|
|
4999
5053
|
}
|
|
5054
|
+
const activityWindowSec = this.config.idleAdviceActivityWindowSec;
|
|
5055
|
+
if (activityWindowSec > 0 && !this.hasRecentActivity(activityWindowSec)) {
|
|
5056
|
+
this.log(`budget advise suppressed: no agent activity in last ${activityWindowSec}s`);
|
|
5057
|
+
this.fpState = { ...this.fpState, fingerprint: null };
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5000
5060
|
if (effect.phase === "underutilized") {
|
|
5001
5061
|
if (!this.adviceCooldown.tryAcquire("underutilization", state.now))
|
|
5002
5062
|
return;
|
|
@@ -6767,7 +6827,8 @@ function ensureBudgetCoordinatorStarted() {
|
|
|
6767
6827
|
});
|
|
6768
6828
|
},
|
|
6769
6829
|
resumeSignals: readResumeSignals,
|
|
6770
|
-
isCodexTurnActive: () => codex.turnInProgress
|
|
6830
|
+
isCodexTurnActive: () => codex.turnInProgress,
|
|
6831
|
+
hasRecentActivity: (windowSec) => codex.turnInProgress || Date.now() - lastActivityEpochMs <= windowSec * 1000
|
|
6771
6832
|
});
|
|
6772
6833
|
}
|
|
6773
6834
|
budgetCoordinator.start();
|
|
@@ -6778,13 +6839,13 @@ function stopBudgetCoordinator() {
|
|
|
6778
6839
|
function budgetPauseGateError() {
|
|
6779
6840
|
const snapshot = budgetCoordinator?.getSnapshot() ?? null;
|
|
6780
6841
|
const reason = snapshot?.pauseReason ?? "Codex \u4FA7\u989D\u5EA6\u63A5\u8FD1\u8017\u5C3D";
|
|
6781
|
-
const resumeAt = snapshot?.resumeAfterEpoch ?
|
|
6842
|
+
const resumeAt = snapshot?.resumeAfterEpoch ? `${formatBeijing(snapshot.resumeAfterEpoch)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09` : null;
|
|
6782
6843
|
const sideHint = snapshot?.pauseSide === "both" ? "\u53CC\u4FA7\u989D\u5EA6\u5747\u5DF2\u8017\u5C3D\uFF0C\u8BF7\u5199 checkpoint \u7B49\u5F85\u5237\u65B0" : "\u4F60\u53EF\u7EE7\u7EED solo \u63A8\u8FDB\u53EF\u72EC\u7ACB\u90E8\u5206\uFF0C\u5E76\u5199 checkpoint \u6807\u6CE8\u5206\u5DE5\u65AD\u70B9";
|
|
6783
6844
|
const reopenText = `Codex \u4FA7\u5404\u7A97\u53E3 util \u56DE\u843D\u81F3\u52A8\u6001\u6682\u505C\u7EBF \u2212 ${BUDGET_CONFIG.maximize.resumeHysteresisPct}% \u4EE5\u4E0B\u6216\u5BF9\u5E94\u7A97\u53E3\u5237\u65B0\u540E\u95F8\u95E8\u81EA\u52A8\u653E\u5F00`;
|
|
6784
6845
|
return `\u9884\u7B97\u6682\u505C\uFF08\u95F8\u95E8\u5173\u95ED\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u8F6C\u53D1\uFF1A${reason}\u3002` + reopenText + (resumeAt ? `\uFF08\u9884\u8BA1\u6062\u590D ${resumeAt}\uFF0C\u4EE5\u5B9E\u6D4B\u4E3A\u51C6\uFF1B\u63D0\u524D\u5237\u65B0\u4F1A\u66F4\u65E9\u89E3\u9664\uFF09` : "") + `\u3002\u6536\u5230 RESUME \u901A\u77E5\u524D\u8BF7\u52FF\u91CD\u8BD5\u5411 Codex \u53D1\u9001 reply\uFF1B${sideHint}\u3002`;
|
|
6785
6846
|
}
|
|
6786
6847
|
function budgetAdmissionGateError(windowResetEpoch, wrapUpLeft, quotaExhausted) {
|
|
6787
|
-
const resetAt = windowResetEpoch > 0 ?
|
|
6848
|
+
const resetAt = windowResetEpoch > 0 ? `${formatBeijing(windowResetEpoch)}\uFF08\u5317\u4EAC\u65F6\u95F4\uFF09` : "\u672A\u77E5";
|
|
6788
6849
|
const quota = BUDGET_CONFIG.maximize.wrapUpQuota;
|
|
6789
6850
|
if (quotaExhausted) {
|
|
6790
6851
|
return `\u989D\u5EA6\u7A97\u53E3\u6536\u5C3E\u4FDD\u62A4\u4E2D\uFF08admission-closed\uFF09\uFF1A\u672C\u7A97\u53E3 wrap-up \u914D\u989D\uFF08\u6BCF\u7A97\u53E3 ${quota} \u4E2A\uFF09\u5DF2\u7528\u5C3D\uFF0C\u5DF2\u62D2\u7EDD\u8F6C\u53D1\u3002` + `\u8BF7\u52FF\u518D\u6D3E\u65B0\u4EFB\u52A1\uFF1B\u5199 checkpoint\uFF0C\u7B49\u989D\u5EA6\u7A97\u53E3\u5237\u65B0\uFF08\u7EA6 ${resetAt}\uFF09\u540E\u518D\u7EE7\u7EED\u3002`;
|
|
@@ -6896,6 +6957,7 @@ codex.on("steerFailed", ({ requestId, reason }) => {
|
|
|
6896
6957
|
});
|
|
6897
6958
|
codex.on("steerAccepted", ({ requestId }) => {
|
|
6898
6959
|
log("Steer accepted by app-server");
|
|
6960
|
+
recordAgentActivity();
|
|
6899
6961
|
const dispatch = pendingSteerDispatches.get(requestId);
|
|
6900
6962
|
pendingSteerDispatches.delete(requestId);
|
|
6901
6963
|
if (dispatch?.requireReply) {
|
|
@@ -6965,13 +7027,19 @@ codex.on("turnTrackingReset", (reason) => {
|
|
|
6965
7027
|
pendingSteerDispatches.clear();
|
|
6966
7028
|
resumeInjectionQueue.onTurnTrackingReset();
|
|
6967
7029
|
});
|
|
7030
|
+
var lastActivityEpochMs = 0;
|
|
7031
|
+
function recordAgentActivity() {
|
|
7032
|
+
lastActivityEpochMs = Date.now();
|
|
7033
|
+
}
|
|
6968
7034
|
codex.on("turnStarted", () => {
|
|
6969
7035
|
log("Codex turn started");
|
|
7036
|
+
recordAgentActivity();
|
|
6970
7037
|
emitToClaude(systemMessage("system_turn_started", "\u23F3 Codex is working on the current task. Wait for completion before sending a reply."));
|
|
6971
7038
|
});
|
|
6972
7039
|
codex.on("agentMessage", (msg) => {
|
|
6973
7040
|
if (msg.source !== "codex")
|
|
6974
7041
|
return;
|
|
7042
|
+
recordAgentActivity();
|
|
6975
7043
|
const route = routeCodexMessage(msg.content, {
|
|
6976
7044
|
mode: FILTER_MODE,
|
|
6977
7045
|
replyArmed: replyTracker.isArmed,
|
|
@@ -7189,6 +7257,11 @@ function handleControlMessage(ws, raw) {
|
|
|
7189
7257
|
log(`handleProbeIncumbent threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7190
7258
|
});
|
|
7191
7259
|
return;
|
|
7260
|
+
case "request_budget_refresh":
|
|
7261
|
+
handleRequestBudgetRefresh(ws, message.requestId).catch((err) => {
|
|
7262
|
+
log(`handleRequestBudgetRefresh threw for #${ws.data.clientId}: ${err?.message ?? err}`);
|
|
7263
|
+
});
|
|
7264
|
+
return;
|
|
7192
7265
|
case "claude_to_codex": {
|
|
7193
7266
|
handleClaudeToCodex(ws, message).catch((err) => {
|
|
7194
7267
|
log(`handleClaudeToCodex threw for request ${message.requestId}: ${err?.message ?? err}`);
|
|
@@ -7547,6 +7620,11 @@ async function handleProbeIncumbent(ws) {
|
|
|
7547
7620
|
alive: stillConnected && alive
|
|
7548
7621
|
});
|
|
7549
7622
|
}
|
|
7623
|
+
async function handleRequestBudgetRefresh(ws, requestId) {
|
|
7624
|
+
const snapshot = budgetCoordinator ? await budgetCoordinator.refreshSnapshotReadonly() : null;
|
|
7625
|
+
log(`request_budget_refresh from #${ws.data.clientId}: ${snapshot ? "fresh" : "unavailable"}`);
|
|
7626
|
+
sendProtocolMessage(ws, { type: "budget_refresh", requestId, snapshot });
|
|
7627
|
+
}
|
|
7550
7628
|
async function probeLiveness2(ws, timeoutMs) {
|
|
7551
7629
|
return probeLiveness({
|
|
7552
7630
|
get readyState() {
|