@raysonmeng/agentbridge 0.1.17 → 0.1.19

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/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.17", "0.0.0-source"),
33
- commit: defineString("0d1e1bd", "source"),
32
+ version: defineString("0.1.19", "0.0.0-source"),
33
+ commit: defineString("dee06ce", "source"),
34
34
  bundle: defineBundle("dist"),
35
35
  contractVersion: defineNumber(1, CONTRACT_VERSION),
36
- codeHash: defineString("c22387f3269f", "source")
36
+ codeHash: defineString("9ddee00c61f9", "source")
37
37
  });
38
38
  function daemonStatusBuildInfo() {
39
39
  return { ...BUILD_INFO };
@@ -290,6 +290,9 @@ class StateDirResolver {
290
290
  get killedFile() {
291
291
  return join(this.stateDir, "killed");
292
292
  }
293
+ get admissionQuotaFile() {
294
+ return join(this.stateDir, "admission-quota.json");
295
+ }
293
296
  get updateCheckFile() {
294
297
  return join(this.stateDir, "update-check.json");
295
298
  }
@@ -1014,6 +1017,9 @@ class CodexAdapter extends EventEmitter {
1014
1017
  get activeThreadId() {
1015
1018
  return this.threadId;
1016
1019
  }
1020
+ canInject() {
1021
+ return !!this.threadId && this.appServerWs?.readyState === WebSocket.OPEN && !this.turnInProgress;
1022
+ }
1017
1023
  get capturedAppServerInfo() {
1018
1024
  return this.appServerInfo;
1019
1025
  }
@@ -3386,6 +3392,86 @@ async function fetchWithTimeout(url, timeoutMs = HEALTH_FETCH_TIMEOUT_MS) {
3386
3392
  }
3387
3393
  }
3388
3394
 
3395
+ // src/budget/admission-quota.ts
3396
+ function nodeFs() {
3397
+ return __require("fs");
3398
+ }
3399
+ function freshState(fiveHourResetEpoch) {
3400
+ return { version: 1, fiveHourResetEpoch, wrapUpUsed: 0, checkpointBatonUsed: false };
3401
+ }
3402
+ function parseAdmissionQuota(value) {
3403
+ if (typeof value !== "object" || value === null || Array.isArray(value))
3404
+ return null;
3405
+ const record = value;
3406
+ if (record.version !== 1)
3407
+ return null;
3408
+ const epoch = record.fiveHourResetEpoch;
3409
+ const used = record.wrapUpUsed;
3410
+ if (typeof epoch !== "number" || !Number.isFinite(epoch))
3411
+ return null;
3412
+ if (typeof used !== "number" || !Number.isFinite(used) || used < 0)
3413
+ return null;
3414
+ return {
3415
+ version: 1,
3416
+ fiveHourResetEpoch: epoch,
3417
+ wrapUpUsed: Math.floor(used),
3418
+ checkpointBatonUsed: record.checkpointBatonUsed === true
3419
+ };
3420
+ }
3421
+ function currentWindowState(path, fiveHourResetEpoch, log = () => {}) {
3422
+ let raw;
3423
+ try {
3424
+ raw = nodeFs().readFileSync(path, "utf-8");
3425
+ } catch {
3426
+ return freshState(fiveHourResetEpoch);
3427
+ }
3428
+ const text = String(raw).trim();
3429
+ if (text === "")
3430
+ return freshState(fiveHourResetEpoch);
3431
+ let parsed;
3432
+ try {
3433
+ parsed = JSON.parse(text);
3434
+ } catch {
3435
+ log(`admission-quota: skip malformed JSON ${path}`);
3436
+ return freshState(fiveHourResetEpoch);
3437
+ }
3438
+ const state = parseAdmissionQuota(parsed);
3439
+ if (!state || state.fiveHourResetEpoch !== fiveHourResetEpoch) {
3440
+ return freshState(fiveHourResetEpoch);
3441
+ }
3442
+ return state;
3443
+ }
3444
+ function persist(path, state, log) {
3445
+ try {
3446
+ atomicWriteJson(path, state);
3447
+ return true;
3448
+ } catch (error) {
3449
+ log(`admission-quota: write failed ${path}: ${error instanceof Error ? error.message : String(error)}`);
3450
+ return false;
3451
+ }
3452
+ }
3453
+ function consumeWrapUp(path, fiveHourResetEpoch, limit, log = () => {}) {
3454
+ if (!Number.isFinite(fiveHourResetEpoch))
3455
+ return { allowed: false, used: 0, remaining: 0 };
3456
+ const state = currentWindowState(path, fiveHourResetEpoch, log);
3457
+ if (state.wrapUpUsed >= limit) {
3458
+ return { allowed: false, used: state.wrapUpUsed, remaining: Math.max(0, limit - state.wrapUpUsed) };
3459
+ }
3460
+ const next = { ...state, wrapUpUsed: state.wrapUpUsed + 1 };
3461
+ if (!persist(path, next, log)) {
3462
+ return { allowed: false, used: state.wrapUpUsed, remaining: Math.max(0, limit - state.wrapUpUsed) };
3463
+ }
3464
+ return { allowed: true, used: next.wrapUpUsed, remaining: Math.max(0, limit - next.wrapUpUsed) };
3465
+ }
3466
+ function consumeCheckpointBaton(path, fiveHourResetEpoch, log = () => {}) {
3467
+ if (!Number.isFinite(fiveHourResetEpoch))
3468
+ return false;
3469
+ const state = currentWindowState(path, fiveHourResetEpoch, log);
3470
+ if (state.checkpointBatonUsed)
3471
+ return false;
3472
+ return persist(path, { ...state, checkpointBatonUsed: true }, log);
3473
+ }
3474
+
3389
3475
  // src/config-service.ts
3390
3476
  import { readFileSync as readFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4 } from "fs";
3391
3477
  import { join as join4 } from "path";
@@ -3410,7 +3496,9 @@ var DEFAULT_BUDGET_CONFIG = {
3410
3496
  reserveSlopePctPerHour: 0.4,
3411
3497
  reserveMaxPct: 7,
3412
3498
  finishingHorizonMinutes: 30,
3413
- resumeHysteresisPct: 5
3499
+ resumeHysteresisPct: 5,
3500
+ admissionAt: 85,
3501
+ wrapUpQuota: 2
3414
3502
  },
3415
3503
  allocation: {
3416
3504
  minRunwayRatio: 50,
@@ -3478,7 +3566,9 @@ function findShapeViolation(raw) {
3478
3566
  "reserveSlopePctPerHour",
3479
3567
  "reserveMaxPct",
3480
3568
  "finishingHorizonMinutes",
3481
- "resumeHysteresisPct"
3569
+ "resumeHysteresisPct",
3570
+ "admissionAt",
3571
+ "wrapUpQuota"
3482
3572
  ]) {
3483
3573
  if (key in maximize && !isCoercibleNumber(maximize[key])) {
3484
3574
  return `budget.maximize.${key} is present but not a number`;
@@ -3503,7 +3593,7 @@ function hasCustomDecisionValues(config) {
3503
3593
  const d = DEFAULT_CONFIG;
3504
3594
  const b = config.budget;
3505
3595
  const db = d.budget;
3506
- 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.allocation.minRunwayRatio !== db.allocation.minRunwayRatio || b.allocation.minRunwayGapHours !== db.allocation.minRunwayGapHours;
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;
3507
3597
  }
3508
3598
  function normalizeInteger(value, fallback) {
3509
3599
  if (typeof value === "number" && Number.isFinite(value))
@@ -3543,9 +3633,11 @@ function normalizeMaximizeConfig(raw, pauseAt, fallback = DEFAULT_BUDGET_CONFIG.
3543
3633
  reserveSlopePctPerHour: normalizeBoundedNumber(m.reserveSlopePctPerHour, fallback.reserveSlopePctPerHour, 0, 5),
3544
3634
  reserveMaxPct: normalizeBoundedInteger(m.reserveMaxPct, fallback.reserveMaxPct, 0, 30),
3545
3635
  finishingHorizonMinutes: normalizeBoundedInteger(m.finishingHorizonMinutes, fallback.finishingHorizonMinutes, 5, 180),
3546
- resumeHysteresisPct: normalizeBoundedInteger(m.resumeHysteresisPct, fallback.resumeHysteresisPct, 1, 30)
3636
+ resumeHysteresisPct: normalizeBoundedInteger(m.resumeHysteresisPct, fallback.resumeHysteresisPct, 1, 30),
3637
+ admissionAt: normalizeBoundedInteger(m.admissionAt, fallback.admissionAt, 50, 99),
3638
+ wrapUpQuota: Math.floor(normalizeBoundedInteger(m.wrapUpQuota, fallback.wrapUpQuota, 0, 10))
3547
3639
  };
3548
- if (normalized.targetUtil <= pauseAt) {
3640
+ if (normalized.targetUtil <= pauseAt || normalized.admissionAt >= normalized.targetUtil) {
3549
3641
  return { ...DEFAULT_BUDGET_CONFIG.maximize };
3550
3642
  }
3551
3643
  return normalized;
@@ -3628,7 +3720,9 @@ function applyBudgetEnvOverrides(budget, env = process.env) {
3628
3720
  reserveSlopePctPerHour: env.AGENTBRIDGE_BUDGET_RESERVE_SLOPE_PCT_PER_HOUR ?? budget.maximize.reserveSlopePctPerHour,
3629
3721
  reserveMaxPct: env.AGENTBRIDGE_BUDGET_RESERVE_MAX_PCT ?? budget.maximize.reserveMaxPct,
3630
3722
  finishingHorizonMinutes: env.AGENTBRIDGE_BUDGET_FINISHING_HORIZON_MINUTES ?? budget.maximize.finishingHorizonMinutes,
3631
- resumeHysteresisPct: env.AGENTBRIDGE_BUDGET_RESUME_HYSTERESIS_PCT ?? budget.maximize.resumeHysteresisPct
3723
+ resumeHysteresisPct: env.AGENTBRIDGE_BUDGET_RESUME_HYSTERESIS_PCT ?? budget.maximize.resumeHysteresisPct,
3724
+ admissionAt: env.AGENTBRIDGE_BUDGET_ADMISSION_AT ?? budget.maximize.admissionAt,
3725
+ wrapUpQuota: env.AGENTBRIDGE_BUDGET_WRAP_UP_QUOTA ?? budget.maximize.wrapUpQuota
3632
3726
  },
3633
3727
  allocation: {
3634
3728
  minRunwayRatio: env.AGENTBRIDGE_BUDGET_MIN_RUNWAY_RATIO ?? budget.allocation.minRunwayRatio,
@@ -3967,6 +4061,79 @@ function resumeBlockingEpochFor(usage, cfg, now) {
3967
4061
  return 0;
3968
4062
  return Math.min(...blockingResets);
3969
4063
  }
4064
+ var ADMISSION_WEEKLY_RUNWAY_ENTER_MULT = 2;
4065
+ var ADMISSION_WEEKLY_RUNWAY_EXIT_MULT = 3;
4066
+ function weeklyRunwayFloorSec(cfg, mult) {
4067
+ return cfg.maximize.finishingHorizonMinutes * 60 * mult;
4068
+ }
4069
+ function weeklyRunwayShort(usage, cfg, now, floorSec) {
4070
+ const weekly = usage.weekly;
4071
+ if (!weekly || weekly.resetEpoch <= now)
4072
+ return false;
4073
+ if (dynamicWindowVerdict(weekly, cfg, now).kind !== "will-fill")
4074
+ return false;
4075
+ const runway = weekly.runwaySeconds;
4076
+ if (typeof runway !== "number" || !Number.isFinite(runway))
4077
+ return false;
4078
+ return runway < floorSec;
4079
+ }
4080
+ function hardCapWindow(usage, cfg, now) {
4081
+ for (const { key, window } of freshWindows(usage, now)) {
4082
+ const rate = confidentRate(window);
4083
+ if (rate === null)
4084
+ continue;
4085
+ if (dynamicPauseAt(window, rate, cfg, now) === "admission-closed")
4086
+ return key;
4087
+ }
4088
+ return null;
4089
+ }
4090
+ var NO_ADMIT_CLOSE = { admitClose: false, window: null, reason: "" };
4091
+ function agentShouldAdmitClose(agent, usage, cfg, now) {
4092
+ if (!usage)
4093
+ return NO_ADMIT_CLOSE;
4094
+ if (!isDecisionGrade(usage, now))
4095
+ return NO_ADMIT_CLOSE;
4096
+ const fiveHour = usage.fiveHour;
4097
+ if (fiveHour && fiveHour.resetEpoch > now && fiveHour.util >= cfg.maximize.admissionAt) {
4098
+ return {
4099
+ admitClose: true,
4100
+ window: "fiveHour",
4101
+ reason: `${AGENT_LABEL[agent]} 5h\u7A97\u53E3 util ${pct(fiveHour.util)} \u2265 admissionAt ${pct(cfg.maximize.admissionAt)}\uFF08\u6536\u5C3E\u4FDD\u62A4\uFF1A\u62D2\u65B0\u4EFB\u52A1\u3001\u653E\u6536\u5C3E\uFF09`
4102
+ };
4103
+ }
4104
+ const hard = hardCapWindow(usage, cfg, now);
4105
+ if (hard !== null) {
4106
+ return {
4107
+ admitClose: true,
4108
+ window: hard,
4109
+ reason: `${AGENT_LABEL[agent]} ${WINDOW_LABEL[hard]}\u7A97\u53E3\u89E6\u53D1\u6536\u5C3E\u4FDD\u62A4\u786C\u7EBF\uFF08util \u5DF2\u8FBE targetUtil \u6216\u4E34\u8FD1\u91CD\u7F6E\u6536\u5C3E\u5E26\uFF09`
4110
+ };
4111
+ }
4112
+ if (weeklyRunwayShort(usage, cfg, now, weeklyRunwayFloorSec(cfg, ADMISSION_WEEKLY_RUNWAY_ENTER_MULT))) {
4113
+ return {
4114
+ admitClose: true,
4115
+ window: "weekly",
4116
+ reason: `${AGENT_LABEL[agent]} \u5468\u7A97\u53E3 runway \u4F4E\u4E8E ${ADMISSION_WEEKLY_RUNWAY_ENTER_MULT}\xD7\u6536\u5C3E\u89C6\u91CE\uFF08\u9632\u65B0\u957F\u4EFB\u52A1\u649E\u7A7F\u5468\u989D\u5EA6\uFF09`
4117
+ };
4118
+ }
4119
+ return NO_ADMIT_CLOSE;
4120
+ }
4121
+ function agentCanAdmitOpen(usage, cfg, now) {
4122
+ if (!isDecisionGrade(usage, now))
4123
+ return false;
4124
+ if (usage.rateLimitedUntil > now)
4125
+ return false;
4126
+ const fiveHour = usage.fiveHour;
4127
+ if (fiveHour && fiveHour.resetEpoch > now) {
4128
+ if (fiveHour.util >= cfg.maximize.admissionAt - cfg.maximize.resumeHysteresisPct)
4129
+ return false;
4130
+ }
4131
+ if (hardCapWindow(usage, cfg, now) !== null)
4132
+ return false;
4133
+ if (weeklyRunwayShort(usage, cfg, now, weeklyRunwayFloorSec(cfg, ADMISSION_WEEKLY_RUNWAY_EXIT_MULT)))
4134
+ return false;
4135
+ return true;
4136
+ }
3970
4137
 
3971
4138
  // src/budget/budget-state.ts
3972
4139
  var NO_RUNWAY = { claude: null, codex: null };
@@ -4112,6 +4279,18 @@ function renderBudgetInterventionDirective(claude, codex, side, reason, resumeEp
4112
4279
  ].join(`
4113
4280
  `);
4114
4281
  }
4282
+ function renderBudgetAdmissionDirective(claude, codex, side, reason, resetEpoch, cfg) {
4283
+ const resetText = `\u5BF9\u5E94\u7A97\u53E3\u7EA6 ${formatEpoch(resetEpoch)} \u5237\u65B0\uFF08\u4EE5\u5B9E\u6D4B\u4E3A\u51C6\uFF1B\u63D0\u524D\u5237\u65B0\u4F1A\u66F4\u65E9\u89E3\u9664\uFF09`;
4284
+ const head = side === "both" ? "\u3010\u9884\u7B97\u534F\u8C03 \xB7 \u8D26\u53F7\u7EA7\u3011\u53CC\u65B9\u8FDB\u5165\u6536\u5C3E\u4FDD\u62A4\uFF08admission-closed\uFF09\u3002" : "\u3010\u9884\u7B97\u534F\u8C03 \xB7 \u8D26\u53F7\u7EA7\u3011Codex \u4FA7\u8FDB\u5165\u6536\u5C3E\u4FDD\u62A4\uFF08admission-closed\uFF09\u3002";
4285
+ return [
4286
+ head,
4287
+ `\u89E6\u53D1\u539F\u56E0\uFF1A${reason}\u3002`,
4288
+ `${usageSummary("claude", claude)}\uFF1B${usageSummary("codex", codex)}\u3002`,
4289
+ `\u95F8\u95E8\u5DF2\u6536\u7D27\uFF1A\u65B0\u7684 Codex \u4EFB\u52A1\u4F1A\u88AB\u62D2\uFF08budget_admission\uFF09\uFF0C\u4F46\u4ECD\u53EF\u7528 reply \u5E26 wrap_up=true \u628A\u5F53\u524D\u534F\u4F5C\u6536\u5C3E\u5230 checkpoint` + `\uFF08\u6BCF\u7A97\u53E3\u81F3\u591A ${cfg.maximize.wrapUpQuota} \u4E2A\uFF09\uFF0Csteer \u4FEE\u6B63\u4E0D\u53D7\u9650\uFF1B${resetText}\u3002`,
4290
+ "\u5EFA\u8BAE\uFF1A\u4E0D\u8981\u518D\u5411 Codex \u6D3E\u65B0\u4EFB\u52A1\uFF1B\u628A\u5F53\u524D Codex \u534F\u4F5C\u6536\u5C3E\u3001\u5199 checkpoint\uFF0C\u53EF\u72EC\u7ACB\u63A8\u8FDB\u7684\u90E8\u5206 Claude \u53EF solo \u7EE7\u7EED\u3002"
4291
+ ].join(`
4292
+ `);
4293
+ }
4115
4294
  function balanceDirective(claude, codex, drift, basis, runway) {
4116
4295
  const heavier = drift.heavier ? AGENT_LABEL2[drift.heavier] : "\u672A\u77E5";
4117
4296
  const lighter = drift.lighter ? AGENT_LABEL2[drift.lighter] : "\u672A\u77E5";
@@ -4154,7 +4333,7 @@ function computeBudgetState(claude, codex, cfg, now, runway = NO_RUNWAY) {
4154
4333
  const paused = triggers.length > 0;
4155
4334
  const { drift, basis } = allocationDrift(claude, codex, runway, cfg);
4156
4335
  const parallel = { recommended: false, reason: null };
4157
- const adviceEligible = !paused && claude !== null && codex !== null && claude.rateLimitedUntil <= now && codex.rateLimitedUntil <= now && isDecisionGrade(claude, now) && isDecisionGrade(codex, now);
4336
+ const adviceEligible = !paused && claude !== null && codex !== null && claude.rateLimitedUntil <= now && codex.rateLimitedUntil <= now && isDecisionGrade(claude, now) && isDecisionGrade(codex, now) && !agentShouldAdmitClose("claude", claude, cfg, now).admitClose && !agentShouldAdmitClose("codex", codex, cfg, now).admitClose;
4158
4337
  const balanceActive = adviceEligible && drift.heavier !== null && drift.lighter !== null;
4159
4338
  const underutilization = adviceEligible && !balanceActive ? underutilizationState(claude, codex, cfg, now) : { recommended: false, reason: null };
4160
4339
  const resetEpochs = {
@@ -4445,6 +4624,58 @@ function computeResumeCandidate(sides, state, cfg, signals) {
4445
4624
  candidate.detail = detail;
4446
4625
  return candidate;
4447
4626
  }
4627
+ var INITIAL_ADMISSION_STATE = { side: null, fingerprint: null, reason: null };
4628
+ function nextAdmissionSide(prevSide, state, cfg) {
4629
+ const active = new Set(sideToAgents(prevSide));
4630
+ for (const agent of ["claude", "codex"]) {
4631
+ const usage = state.perAgent[agent];
4632
+ if (agentShouldAdmitClose(agent, usage, cfg, state.now).admitClose) {
4633
+ active.add(agent);
4634
+ } else if (active.has(agent) && agentCanAdmitOpen(usage, cfg, state.now)) {
4635
+ active.delete(agent);
4636
+ }
4637
+ }
4638
+ return agentsToSide(active);
4639
+ }
4640
+ function admissionReason(side, state, cfg) {
4641
+ return sideToAgents(side).map((agent) => {
4642
+ const usage = state.perAgent[agent];
4643
+ if (!usage)
4644
+ return `${AGENT_LABEL3[agent]} \u63A2\u6D4B\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u4FDD\u6301\u4E0A\u4E00\u8F6E\u6536\u5C3E\u4FDD\u62A4`;
4645
+ 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`;
4647
+ }
4648
+ const decision = agentShouldAdmitClose(agent, usage, cfg, state.now);
4649
+ if (decision.admitClose)
4650
+ return decision.reason;
4651
+ return `${AGENT_LABEL3[agent]} \u6536\u5C3E\u4FDD\u62A4\u51FA\u95F8\u6EDE\u56DE\u5E26\uFF0C\u5C1A\u672A\u6EE1\u8DB3\u5F00\u95F8\u6761\u4EF6`;
4652
+ }).join("\uFF1B");
4653
+ }
4654
+ function admissionFingerprint(state, side) {
4655
+ let reset = 0;
4656
+ for (const agent of sideToAgents(side)) {
4657
+ reset = Math.max(reset, state.pause.resetEpochs[agent] ?? 0);
4658
+ }
4659
+ return ["admission", side, Math.round(reset / RESET_FINGERPRINT_BUCKET_SEC)].join("|");
4660
+ }
4661
+ function classifyAdmission(prev, state, cfg) {
4662
+ const previousSide = prev.side;
4663
+ const currentSide = nextAdmissionSide(previousSide, state, cfg);
4664
+ if (currentSide) {
4665
+ const reason = admissionReason(currentSide, state, cfg);
4666
+ const uncertain = previousSide === currentSide && activeSideProbeUncertain(currentSide, state) && prev.fingerprint;
4667
+ const fingerprint = uncertain ? prev.fingerprint : admissionFingerprint(state, currentSide);
4668
+ const emit = !previousSide || previousSide !== currentSide || fingerprint !== prev.fingerprint;
4669
+ return {
4670
+ next: { side: currentSide, fingerprint, reason },
4671
+ effect: { kind: uncertain ? "hold-uncertain" : "enter", side: currentSide, reason, emit }
4672
+ };
4673
+ }
4674
+ if (previousSide) {
4675
+ return { next: INITIAL_ADMISSION_STATE, effect: { kind: "exit", previousSide } };
4676
+ }
4677
+ return { next: INITIAL_ADMISSION_STATE, effect: { kind: "none" } };
4678
+ }
4448
4679
 
4449
4680
  // src/budget/burn-view.ts
4450
4681
  function windowBurnRate(window) {
@@ -4586,9 +4817,13 @@ class BudgetCoordinator {
4586
4817
  onResume;
4587
4818
  resumeSignals;
4588
4819
  adviceCooldown;
4820
+ isCodexTurnActive;
4589
4821
  timer = null;
4590
4822
  running = false;
4591
4823
  fpState = INITIAL_FINGERPRINT_STATE;
4824
+ admissionState = INITIAL_ADMISSION_STATE;
4825
+ pendingAdmissionDirective = null;
4826
+ lastEmittedAdmissionFingerprint = null;
4592
4827
  resumeCandidate = {};
4593
4828
  latestSnapshot = null;
4594
4829
  pendingOverrideTier = null;
@@ -4612,6 +4847,7 @@ class BudgetCoordinator {
4612
4847
  cooldownSec: resolveAdviceCooldownSec(),
4613
4848
  log: this.log
4614
4849
  });
4850
+ this.isCodexTurnActive = options.isCodexTurnActive ?? (() => false);
4615
4851
  }
4616
4852
  async start() {
4617
4853
  if (this.running || !this.config.enabled)
@@ -4634,6 +4870,13 @@ class BudgetCoordinator {
4634
4870
  isGateClosed() {
4635
4871
  return this.fpState.side === "codex" || this.fpState.side === "both";
4636
4872
  }
4873
+ gateState() {
4874
+ if (this.fpState.side === "codex" || this.fpState.side === "both")
4875
+ return "closed";
4876
+ if (this.admissionState.side === "codex" || this.admissionState.side === "both")
4877
+ return "admission-closed";
4878
+ return "open";
4879
+ }
4637
4880
  getSnapshot() {
4638
4881
  return this.latestSnapshot;
4639
4882
  }
@@ -4722,8 +4965,14 @@ class BudgetCoordinator {
4722
4965
  applyState(state) {
4723
4966
  const { next, effect } = classifyPoll(this.fpState, state, this.config);
4724
4967
  this.fpState = next;
4968
+ this.admissionState = classifyAdmission(this.admissionState, state, this.config).next;
4969
+ this.applyAdmissionDirective(state);
4725
4970
  this.resumeCandidate = this.resumeSignals ? computeResumeCandidate(resumeCandidateSides(effect), state, this.config, this.resumeSignals()) : {};
4726
4971
  for (const side of effect.recoveredSides) {
4972
+ if (side === "codex" && (this.admissionState.side === "codex" || this.admissionState.side === "both")) {
4973
+ this.log(`Budget recovery for Codex held: pause cleared but still admission-closed`);
4974
+ continue;
4975
+ }
4727
4976
  const { id, directive } = this.emitRecovery(side, state);
4728
4977
  this.onResume(side, directive, id);
4729
4978
  }
@@ -4742,6 +4991,10 @@ class BudgetCoordinator {
4742
4991
  return;
4743
4992
  }
4744
4993
  case "advise": {
4994
+ if (this.gateState() !== "open") {
4995
+ this.fpState = { ...this.fpState, fingerprint: null };
4996
+ return;
4997
+ }
4745
4998
  if (effect.phase === "underutilized") {
4746
4999
  if (!this.adviceCooldown.tryAcquire("underutilization", state.now))
4747
5000
  return;
@@ -4755,6 +5008,51 @@ class BudgetCoordinator {
4755
5008
  return;
4756
5009
  }
4757
5010
  }
5011
+ applyAdmissionDirective(state) {
5012
+ const side = this.admissionState.side;
5013
+ if (side !== "codex" && side !== "both") {
5014
+ this.pendingAdmissionDirective = null;
5015
+ this.lastEmittedAdmissionFingerprint = null;
5016
+ return;
5017
+ }
5018
+ const fingerprint = this.admissionState.fingerprint;
5019
+ if (fingerprint === null || fingerprint === this.lastEmittedAdmissionFingerprint) {
5020
+ this.pendingAdmissionDirective = null;
5021
+ return;
5022
+ }
5023
+ if (this.isPaused()) {
5024
+ this.pendingAdmissionDirective = null;
5025
+ return;
5026
+ }
5027
+ const content = renderBudgetAdmissionDirective(state.perAgent.claude, state.perAgent.codex, side, this.admissionState.reason ?? "\u989D\u5EA6\u7A97\u53E3\u6536\u5C3E\u4FDD\u62A4", this.admissionResetEpoch(state), this.config);
5028
+ if (this.isCodexTurnActive()) {
5029
+ this.pendingAdmissionDirective = { content, fingerprint };
5030
+ return;
5031
+ }
5032
+ this.emitAdmission(content, fingerprint);
5033
+ }
5034
+ emitAdmission(content, fingerprint) {
5035
+ this.emitDirective("system_budget_admission", content);
5036
+ this.lastEmittedAdmissionFingerprint = fingerprint;
5037
+ this.pendingAdmissionDirective = null;
5038
+ }
5039
+ admissionResetEpoch(state) {
5040
+ const usage = state.perAgent.codex;
5041
+ const now = state.now;
5042
+ const fiveHour = usage?.fiveHour?.resetEpoch ?? 0;
5043
+ const weekly = usage?.weekly?.resetEpoch ?? 0;
5044
+ const fresh = fiveHour > now ? fiveHour : weekly > now ? weekly : 0;
5045
+ return fresh > 0 ? fresh : null;
5046
+ }
5047
+ onCodexTurnIdle() {
5048
+ const pending = this.pendingAdmissionDirective;
5049
+ if (!pending)
5050
+ return;
5051
+ this.pendingAdmissionDirective = null;
5052
+ if (this.isPaused() || this.gateState() !== "admission-closed")
5053
+ return;
5054
+ this.emitAdmission(pending.content, pending.fingerprint);
5055
+ }
4758
5056
  tierControlEnabled() {
4759
5057
  if (!this.config.codexTierControl)
4760
5058
  return false;
@@ -4836,6 +5134,7 @@ class BudgetCoordinator {
4836
5134
  driftPct: state.drift.pct,
4837
5135
  paused,
4838
5136
  gateClosed: this.isGateClosed(),
5137
+ gateState: this.gateState(),
4839
5138
  pauseSide: this.fpState.side,
4840
5139
  pauseReason: paused ? this.fpState.reason ?? state.pause.reason : null,
4841
5140
  resumeAfterEpoch: paused ? this.fpState.resumeEpoch ?? state.pause.resumeAfterEpoch : null,
@@ -5258,14 +5557,14 @@ function createQuotaSource(options) {
5258
5557
  // src/budget/pending-reader.ts
5259
5558
  import { createHash } from "crypto";
5260
5559
  import { join as join7 } from "path";
5261
- function nodeFs() {
5560
+ function nodeFs2() {
5262
5561
  return __require("fs");
5263
5562
  }
5264
5563
  function cwdMatches(entryCwd, optsCwd) {
5265
5564
  if (entryCwd === optsCwd)
5266
5565
  return true;
5267
5566
  try {
5268
- const fs2 = nodeFs();
5567
+ const fs2 = nodeFs2();
5269
5568
  return fs2.realpathSync(entryCwd) === fs2.realpathSync(optsCwd);
5270
5569
  } catch {
5271
5570
  return false;
@@ -5303,7 +5602,7 @@ function resolveStateDir2(homeDir) {
5303
5602
  function readPendingFile(path, log) {
5304
5603
  let raw;
5305
5604
  try {
5306
- raw = nodeFs().readFileSync(path, "utf-8");
5605
+ raw = nodeFs2().readFileSync(path, "utf-8");
5307
5606
  } catch (error) {
5308
5607
  log(`pending reader: skip unreadable ${path}: ${error instanceof Error ? error.message : String(error)}`);
5309
5608
  return null;
@@ -5327,7 +5626,7 @@ function listScopeFiles(stateDir, agent, log) {
5327
5626
  const pendingDir = join7(stateDir, "pending");
5328
5627
  let names;
5329
5628
  try {
5330
- names = nodeFs().readdirSync(pendingDir);
5629
+ names = nodeFs2().readdirSync(pendingDir);
5331
5630
  } catch {
5332
5631
  return [];
5333
5632
  }
@@ -6451,7 +6750,10 @@ function ensureBudgetCoordinatorStarted() {
6451
6750
  onPauseChange: (paused) => {
6452
6751
  log(`Budget intervention ${paused ? "ACTIVE" : "CLEARED"} ` + `(gate ${budgetCoordinator?.isGateClosed() ? "CLOSED" : "OPEN"})`);
6453
6752
  },
6454
- onSnapshot: () => broadcastStatus(),
6753
+ onSnapshot: () => {
6754
+ broadcastStatus();
6755
+ maybeFireCheckpointBaton("snapshot");
6756
+ },
6455
6757
  log,
6456
6758
  onResume: (side, _directive, resumeId) => {
6457
6759
  if (side === "claude") {
@@ -6462,7 +6764,8 @@ function ensureBudgetCoordinatorStarted() {
6462
6764
  enqueueCodex: enqueueCodexBudgetResume
6463
6765
  });
6464
6766
  },
6465
- resumeSignals: readResumeSignals
6767
+ resumeSignals: readResumeSignals,
6768
+ isCodexTurnActive: () => codex.turnInProgress
6466
6769
  });
6467
6770
  }
6468
6771
  budgetCoordinator.start();
@@ -6478,6 +6781,79 @@ function budgetPauseGateError() {
6478
6781
  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`;
6479
6782
  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`;
6480
6783
  }
6784
+ function budgetAdmissionGateError(windowResetEpoch, wrapUpLeft, quotaExhausted) {
6785
+ const resetAt = windowResetEpoch > 0 ? new Date(windowResetEpoch * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z") : "\u672A\u77E5";
6786
+ const quota = BUDGET_CONFIG.maximize.wrapUpQuota;
6787
+ if (quotaExhausted) {
6788
+ 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`;
6789
+ }
6790
+ return `\u989D\u5EA6\u7A97\u53E3\u6536\u5C3E\u4FDD\u62A4\u4E2D\uFF08admission-closed\uFF09\uFF1A\u4EC5\u63A5\u6536\u6536\u5C3E\u7C7B\u6CE8\u5165\uFF0C\u5DF2\u62D2\u7EDD\u8BE5\u65B0\u4EFB\u52A1\u3002` + `\u5982\u9700\u628A\u5F53\u524D\u534F\u4F5C\u6536\u5C3E\u5230 checkpoint\uFF0C\u53EF\u7528 reply \u5E26 wrap_up=true \u91CD\u53D1\uFF08\u672C\u7A97\u53E3\u8FD8\u5269 ${wrapUpLeft} \u4E2A\u6536\u5C3E\u914D\u989D\uFF09\uFF1Bsteer \u4FEE\u6B63\u4E0D\u53D7\u9650\u3002` + `\u65B0\u4EFB\u52A1\u8BF7\u7B49\u989D\u5EA6\u7A97\u53E3\u5237\u65B0\uFF08\u7EA6 ${resetAt}\uFF09\u540E\u518D\u6D3E\u3002`;
6791
+ }
6792
+ function evaluateInjectionBudgetGate(message, willInject, isSteer) {
6793
+ const gateState = budgetCoordinator?.gateState() ?? "open";
6794
+ if (gateState === "closed") {
6795
+ log(`Injection rejected by budget pause gate`);
6796
+ const resumeAfterEpoch3 = budgetCoordinator?.getSnapshot()?.resumeAfterEpoch ?? null;
6797
+ const retryAfterMs = retryAfterMsForResume(resumeAfterEpoch3, Date.now());
6798
+ return {
6799
+ allow: false,
6800
+ code: "budget_paused",
6801
+ error: budgetPauseGateError(),
6802
+ ...retryAfterMs !== undefined ? { retryAfterMs } : {}
6803
+ };
6804
+ }
6805
+ if (gateState === "admission-closed" && !isSteer) {
6806
+ const nowSec = Math.floor(Date.now() / 1000);
6807
+ const admSnap = budgetCoordinator?.getSnapshot()?.codex;
6808
+ const admFiveHour = admSnap?.fiveHour?.resetEpoch ?? 0;
6809
+ const admWeekly = admSnap?.weekly?.resetEpoch ?? 0;
6810
+ const admissionWindowReset = admFiveHour > nowSec ? admFiveHour : admWeekly > nowSec ? admWeekly : 0;
6811
+ if (admissionWindowReset <= 0) {
6812
+ log(`Injection rejected by admission gate: no fresh quota window (probe stale / snapshot lost)`);
6813
+ return { allow: false, code: "budget_admission", error: budgetAdmissionGateError(0, 0, true) };
6814
+ }
6815
+ if (message.wrapUp === true && willInject) {
6816
+ const peek = currentWindowState(stateDir.admissionQuotaFile, admissionWindowReset, log);
6817
+ if (peek.wrapUpUsed >= BUDGET_CONFIG.maximize.wrapUpQuota) {
6818
+ log(`Injection rejected by admission gate: wrap-up quota exhausted`);
6819
+ return { allow: false, code: "budget_admission", error: budgetAdmissionGateError(admissionWindowReset, 0, true) };
6820
+ }
6821
+ log(`Admission-closed: wrap-up permitted (${peek.wrapUpUsed}/${BUDGET_CONFIG.maximize.wrapUpQuota} used; slot committed on inject)`);
6822
+ return { allow: true, pendingWrapUpReset: admissionWindowReset };
6823
+ }
6824
+ if (message.wrapUp === true && !willInject) {
6825
+ return { allow: true, pendingWrapUpReset: null };
6826
+ }
6827
+ const left = Math.max(0, BUDGET_CONFIG.maximize.wrapUpQuota - currentWindowState(stateDir.admissionQuotaFile, admissionWindowReset, log).wrapUpUsed);
6828
+ log(`Injection rejected by admission gate: new task (set wrap_up to finish the current work)`);
6829
+ return { allow: false, code: "budget_admission", error: budgetAdmissionGateError(admissionWindowReset, left, false) };
6830
+ }
6831
+ return { allow: true, pendingWrapUpReset: null };
6832
+ }
6833
+ var CHECKPOINT_BATON_PROMPT = "\u3010\u9884\u7B97\u534F\u8C03 \xB7 \u7CFB\u7EDF\u53D1\u8D77\u3011\u8D26\u53F7\u7EA7\u989D\u5EA6\u5373\u5C06\u8017\u5C3D\uFF0C\u95F8\u95E8\u5DF2\u5173\u95ED\u3002\u8FD9\u662F\u672C\u989D\u5EA6\u7A97\u53E3\u552F\u4E00\u4E00\u6B21\u7CFB\u7EDF\u63D0\u9192\uFF1A" + "\u8BF7\u7ACB\u5373\u628A\u5F53\u524D\u8FDB\u5EA6\u5199\u5165 checkpoint\uFF08.agent/checkpoint.md\uFF1A\u4EFB\u52A1 / \u5DF2\u5B8C\u6210 / \u8FDB\u884C\u4E2D\u65AD\u70B9 / \u4E0B\u4E00\u6B65 / \u5173\u952E\u51B3\u7B56\u4E0E\u7EA6\u675F\uFF09\uFF0C" + "\u7136\u540E\u505C\u624B\u7B49\u5F85\u989D\u5EA6\u7A97\u53E3\u5237\u65B0\uFF1B\u5237\u65B0\u524D\u4E0D\u8981\u518D\u5F00\u65B0\u4EFB\u52A1\u3002\u6B64\u4E3A\u7CFB\u7EDF\u63D0\u9192\uFF0C\u65E0\u9700\u56DE\u590D Claude\u3002";
6834
+ function maybeFireCheckpointBaton(trigger) {
6835
+ if (!budgetCoordinator)
6836
+ return;
6837
+ if (budgetCoordinator.gateState() !== "closed")
6838
+ return;
6839
+ if (!codex.canInject())
6840
+ return;
6841
+ const nowSec = Math.floor(Date.now() / 1000);
6842
+ const snap = budgetCoordinator.getSnapshot()?.codex;
6843
+ const fiveHour = snap?.fiveHour?.resetEpoch ?? 0;
6844
+ const weekly = snap?.weekly?.resetEpoch ?? 0;
6845
+ const windowReset = fiveHour > nowSec ? fiveHour : weekly > nowSec ? weekly : 0;
6846
+ if (windowReset <= 0)
6847
+ return;
6848
+ if (!consumeCheckpointBaton(stateDir.admissionQuotaFile, windowReset, log))
6849
+ return;
6850
+ const injectionId = codex.injectMessage(CHECKPOINT_BATON_PROMPT);
6851
+ if (injectionId === null) {
6852
+ log(`Checkpoint baton (${trigger}): inject failed after consume \u2014 baton lost this window (reset ${windowReset})`);
6853
+ return;
6854
+ }
6855
+ log(`Checkpoint baton fired (${trigger}, window reset ${windowReset})`);
6856
+ }
6481
6857
  var tuiConnectionState = new TuiConnectionState({
6482
6858
  disconnectGraceMs: TUI_DISCONNECT_GRACE_MS,
6483
6859
  log,
@@ -6499,6 +6875,10 @@ function tryWriteStatusFile(reason) {
6499
6875
  codex.on("turnPhaseChanged", ({ phase, previous }) => {
6500
6876
  log(`Codex turn phase: ${previous} \u2192 ${phase}`);
6501
6877
  tryWriteStatusFile(`turnPhase:${phase}`);
6878
+ if (phase === "idle" || phase === "aborted") {
6879
+ budgetCoordinator?.onCodexTurnIdle();
6880
+ maybeFireCheckpointBaton("turnIdle");
6881
+ }
6502
6882
  broadcastStatus();
6503
6883
  });
6504
6884
  codex.on("steerFailed", ({ requestId, reason }) => {
@@ -6903,18 +7283,21 @@ async function handleClaudeToCodex(ws, message) {
6903
7283
  });
6904
7284
  return;
6905
7285
  }
6906
- if (budgetCoordinator?.isGateClosed()) {
6907
- const reason = budgetPauseGateError();
6908
- log(`Injection rejected by budget pause gate`);
6909
- const resumeAfterEpoch3 = budgetCoordinator?.getSnapshot()?.resumeAfterEpoch ?? null;
6910
- const retryAfterMs = retryAfterMsForResume(resumeAfterEpoch3, Date.now());
6911
- sendClaudeToCodexResult(ws, message.requestId, {
6912
- success: false,
6913
- code: "budget_paused",
6914
- error: reason,
6915
- ...retryAfterMs !== undefined ? { retryAfterMs } : {}
6916
- });
6917
- return;
7286
+ let pendingWrapUpReset = null;
7287
+ {
7288
+ const isSteer = codex.turnInProgress && message.onBusy === "steer";
7289
+ const willInject = !codex.turnInProgress || message.onBusy === "interrupt";
7290
+ const gate = evaluateInjectionBudgetGate(message, willInject, isSteer);
7291
+ if (!gate.allow) {
7292
+ sendClaudeToCodexResult(ws, message.requestId, {
7293
+ success: false,
7294
+ code: gate.code,
7295
+ error: gate.error,
7296
+ ...gate.retryAfterMs !== undefined ? { retryAfterMs: gate.retryAfterMs } : {}
7297
+ });
7298
+ return;
7299
+ }
7300
+ pendingWrapUpReset = gate.pendingWrapUpReset;
6918
7301
  }
6919
7302
  const requireReply = !!message.requireReply;
6920
7303
  let contentToSend = message.message.content;
@@ -7003,8 +7386,36 @@ async function handleClaudeToCodex(ws, message) {
7003
7386
  if (interruptThreadId && codex.activeThreadId !== interruptThreadId) {
7004
7387
  releaseInterruptKey();
7005
7388
  }
7389
+ {
7390
+ const gate = evaluateInjectionBudgetGate(message, true, false);
7391
+ if (!gate.allow) {
7392
+ releaseInterruptKey();
7393
+ log(`Interrupt-path injection rejected by budget gate after await (${gate.code})`);
7394
+ sendClaudeToCodexResult(ws, message.requestId, {
7395
+ success: false,
7396
+ code: gate.code,
7397
+ error: gate.error,
7398
+ ...gate.retryAfterMs !== undefined ? { retryAfterMs: gate.retryAfterMs } : {}
7399
+ });
7400
+ return;
7401
+ }
7402
+ pendingWrapUpReset = gate.pendingWrapUpReset;
7403
+ }
7006
7404
  }
7007
7405
  const injectThreadId = codex.activeThreadId;
7406
+ if (pendingWrapUpReset !== null) {
7407
+ const committed = consumeWrapUp(stateDir.admissionQuotaFile, pendingWrapUpReset, BUDGET_CONFIG.maximize.wrapUpQuota, log);
7408
+ if (!committed.allowed) {
7409
+ log(`Injection rejected by admission gate: wrap-up slot not durably recorded (write failure or raced to cap)`);
7410
+ sendClaudeToCodexResult(ws, message.requestId, {
7411
+ success: false,
7412
+ code: "budget_admission",
7413
+ error: budgetAdmissionGateError(pendingWrapUpReset, 0, true)
7414
+ });
7415
+ return;
7416
+ }
7417
+ log(`Admission wrap-up slot committed (${committed.used}/${BUDGET_CONFIG.maximize.wrapUpQuota})`);
7418
+ }
7008
7419
  const injectionId = codex.injectMessage(contentToSend, tierOverrides);
7009
7420
  if (injectionId === null) {
7010
7421
  if (idempotencyKey && injectThreadId) {