codex-multi-auth 2.7.0 → 2.8.0

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.
Files changed (56) hide show
  1. package/.codex-plugin/plugin.json +9 -9
  2. package/README.md +435 -433
  3. package/dist/lib/cli.d.ts +2 -0
  4. package/dist/lib/cli.d.ts.map +1 -1
  5. package/dist/lib/codex-cli/writer.d.ts +51 -0
  6. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  7. package/dist/lib/codex-cli/writer.js +89 -8
  8. package/dist/lib/codex-manager/login-menu-actions.d.ts.map +1 -1
  9. package/dist/lib/codex-manager/login-menu-actions.js +12 -1
  10. package/dist/lib/codex-manager/login-menu-data.d.ts.map +1 -1
  11. package/dist/lib/codex-manager/login-menu-data.js +19 -8
  12. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
  13. package/dist/lib/codex-manager/repair-commands.js +57 -8
  14. package/dist/lib/forecast.d.ts.map +1 -1
  15. package/dist/lib/forecast.js +30 -4
  16. package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
  17. package/dist/lib/policy/runtime-policy.js +12 -3
  18. package/dist/lib/quota-readiness.d.ts +1 -0
  19. package/dist/lib/quota-readiness.d.ts.map +1 -1
  20. package/dist/lib/quota-readiness.js +12 -2
  21. package/dist/lib/refresh-lease.d.ts +12 -0
  22. package/dist/lib/refresh-lease.d.ts.map +1 -1
  23. package/dist/lib/refresh-lease.js +24 -2
  24. package/dist/lib/refresh-queue.d.ts.map +1 -1
  25. package/dist/lib/refresh-queue.js +29 -3
  26. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  27. package/dist/lib/request/request-transformer.js +13 -2
  28. package/dist/lib/request/response-handler.d.ts.map +1 -1
  29. package/dist/lib/request/response-handler.js +10 -1
  30. package/dist/lib/rotation.d.ts.map +1 -1
  31. package/dist/lib/rotation.js +8 -1
  32. package/dist/lib/runtime/account-check.d.ts +1 -1
  33. package/dist/lib/runtime/account-check.d.ts.map +1 -1
  34. package/dist/lib/runtime/account-check.js +8 -0
  35. package/dist/lib/runtime/first-run.d.ts +12 -6
  36. package/dist/lib/runtime/first-run.d.ts.map +1 -1
  37. package/dist/lib/runtime/first-run.js +140 -9
  38. package/dist/lib/runtime/rotation-storage-meta.d.ts.map +1 -1
  39. package/dist/lib/runtime/rotation-storage-meta.js +8 -2
  40. package/dist/lib/storage/account-port.d.ts +2 -0
  41. package/dist/lib/storage/account-port.d.ts.map +1 -1
  42. package/dist/lib/storage/account-port.js +1 -0
  43. package/dist/lib/storage/import-export.d.ts +1 -0
  44. package/dist/lib/storage/import-export.d.ts.map +1 -1
  45. package/dist/lib/storage/import-export.js +25 -0
  46. package/dist/lib/storage/project-migration.d.ts +1 -1
  47. package/dist/lib/storage/project-migration.d.ts.map +1 -1
  48. package/dist/lib/storage/project-migration.js +28 -2
  49. package/dist/lib/storage.d.ts +10 -0
  50. package/dist/lib/storage.d.ts.map +1 -1
  51. package/dist/lib/storage.js +16 -1
  52. package/dist/lib/ui/auth-menu-builder.d.ts +10 -0
  53. package/dist/lib/ui/auth-menu-builder.d.ts.map +1 -1
  54. package/dist/lib/ui/auth-menu-builder.js +29 -4
  55. package/package.json +1 -1
  56. package/scripts/codex.js +126 -13
package/dist/lib/cli.d.ts CHANGED
@@ -24,6 +24,8 @@ export interface ExistingAccountInfo {
24
24
  quota5hResetAtMs?: number;
25
25
  quota7dLeftPercent?: number;
26
26
  quota7dResetAtMs?: number;
27
+ quotaPrimaryWindowMinutes?: number;
28
+ quotaSecondaryWindowMinutes?: number;
27
29
  quotaRateLimited?: boolean;
28
30
  quotaExhausted?: boolean;
29
31
  isCurrentAccount?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../lib/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../lib/cli.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,57 @@ interface ActiveSelection {
6
6
  expiresAt?: number;
7
7
  idToken?: string;
8
8
  }
9
+ /**
10
+ * Whether `config.toml` may be rewritten to pin the credential store.
11
+ *
12
+ * Exported so callers that only *plan* a rewrite — `doctor --fix --dry-run` —
13
+ * can report the same outcome the real run would produce, instead of promising
14
+ * a change the opt-out would suppress.
15
+ */
16
+ export declare function shouldEnforceCodexCliFileAuthStore(): boolean;
17
+ /**
18
+ * Reads the top-level `cli_auth_credentials_store` value out of a raw
19
+ * `config.toml`.
20
+ *
21
+ * Mirrors the scoping rules of {@link ensureCodexCliFileAuthStore}: assignments
22
+ * inside a `[table]` belong to that table, so only lines before the first table
23
+ * header describe the official CLI's default credential store. Reporting a
24
+ * profile-scoped value as the effective one would let `doctor` call a config
25
+ * healthy while the CLI still reaches for the keychain.
26
+ *
27
+ * Both TOML string forms count. A literal string (`'file'`) is as valid as a
28
+ * basic string (`"file"`), so treating only the latter as real would make
29
+ * `doctor` warn about a perfectly healthy config. Literal strings have no
30
+ * escape sequences, and the values in play here are bare identifiers, so a
31
+ * non-greedy character class is an exact match for both forms.
32
+ *
33
+ * @param rawConfig - Full text of `config.toml`.
34
+ * @returns The declared mode, or `null` when no top-level assignment exists.
35
+ */
36
+ export declare function readTopLevelCodexCliAuthStoreMode(rawConfig: string): string | null;
37
+ /**
38
+ * Persists `cli_auth_credentials_store = "file"` as a top-level key in the
39
+ * official Codex CLI `config.toml`.
40
+ *
41
+ * The wrapper already appends `-c cli_auth_credentials_store="file"` to every
42
+ * command it forwards, but that only covers processes it launches itself.
43
+ * Third-party front-ends (menu-bar apps such as CodexBar, editor extensions)
44
+ * exec the official binary directly, so the *persisted* value is what decides
45
+ * whether the official CLI reaches into the macOS login keychain. Leaving it on
46
+ * `"keychain"` is what produces the repeated "Always Allow" prompts reported in
47
+ * issue #641.
48
+ *
49
+ * Only top-level assignments are rewritten. A `cli_auth_credentials_store`
50
+ * inside a `[profiles.*]` table must be left alone: rewriting it in place would
51
+ * mangle a scoped setting while still leaving the top-level default pointed at
52
+ * the keychain, so the prompts would continue.
53
+ *
54
+ * @param configPath - Config file to reconcile; defaults to the resolved Codex
55
+ * CLI `config.toml`.
56
+ * @returns `true` when the file was rewritten, `false` when it was already
57
+ * correct or enforcement is disabled.
58
+ */
59
+ export declare function ensureCodexCliFileAuthStore(configPath?: string): Promise<boolean>;
9
60
  export declare function setCodexCliActiveSelection(selection: ActiveSelection): Promise<boolean>;
10
61
  export declare function getLastCodexCliSelectionWriteTimestamp(): number;
11
62
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../lib/codex-cli/writer.ts"],"names":[],"mappings":"AAoBA,UAAU,eAAe;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AA2XD,wBAAsB,0BAA0B,CAC/C,SAAS,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,CAAC,CAqMlB;AAED,wBAAgB,sCAAsC,IAAI,MAAM,CAE/D"}
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../lib/codex-cli/writer.ts"],"names":[],"mappings":"AAoBA,UAAU,eAAe;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AA8ND;;;;;;GAMG;AACH,wBAAgB,kCAAkC,IAAI,OAAO,CAG5D;AAiBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASlF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,2BAA2B,CAChD,UAAU,GAAE,MAAgC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAgDlB;AAmHD,wBAAsB,0BAA0B,CAC/C,SAAS,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,CAAC,CAqMlB;AAED,wBAAgB,sCAAsC,IAAI,MAAM,CAE/D"}
@@ -199,30 +199,111 @@ async function atomicWriteText(path, content) {
199
199
  async function atomicWriteJson(path, payload) {
200
200
  return atomicWriteText(path, JSON.stringify(payload, null, 2));
201
201
  }
202
- function shouldEnforceCodexCliFileAuthStore() {
202
+ /**
203
+ * Whether `config.toml` may be rewritten to pin the credential store.
204
+ *
205
+ * Exported so callers that only *plan* a rewrite — `doctor --fix --dry-run` —
206
+ * can report the same outcome the real run would produce, instead of promising
207
+ * a change the opt-out would suppress.
208
+ */
209
+ export function shouldEnforceCodexCliFileAuthStore() {
203
210
  const override = (process.env.CODEX_MULTI_AUTH_ENFORCE_CLI_FILE_AUTH_STORE ?? "1").trim();
204
211
  return override !== "0";
205
212
  }
206
- function ensureTrailingNewline(value) {
207
- return value.endsWith("\n") ? value : `${value}\n`;
213
+ /**
214
+ * Matches a complete TOML table header, mirroring `readTomlTableName` in
215
+ * `lib/runtime/config-toml.ts` (plus an optional trailing comment).
216
+ *
217
+ * A loose `/^\s*\[/` would also match a continuation line of a multi-line array
218
+ * such as `[1, 2],`, cutting the top-level scan short. The writer would then
219
+ * miss a real top-level assignment below it and insert a second one — a
220
+ * duplicate key, which is invalid TOML and stops the official CLI from starting.
221
+ */
222
+ const TOML_TABLE_HEADER_PATTERN = /^\s*\[{1,2}\s*[^\]]+?\s*\]{1,2}\s*(?:#.*)?$/;
223
+ function ensureTrailingNewline(value, lineEnding = "\n") {
224
+ return value.endsWith("\n") ? value : `${value}${lineEnding}`;
208
225
  }
209
- async function ensureCodexCliFileAuthStore(configPath) {
226
+ /**
227
+ * Reads the top-level `cli_auth_credentials_store` value out of a raw
228
+ * `config.toml`.
229
+ *
230
+ * Mirrors the scoping rules of {@link ensureCodexCliFileAuthStore}: assignments
231
+ * inside a `[table]` belong to that table, so only lines before the first table
232
+ * header describe the official CLI's default credential store. Reporting a
233
+ * profile-scoped value as the effective one would let `doctor` call a config
234
+ * healthy while the CLI still reaches for the keychain.
235
+ *
236
+ * Both TOML string forms count. A literal string (`'file'`) is as valid as a
237
+ * basic string (`"file"`), so treating only the latter as real would make
238
+ * `doctor` warn about a perfectly healthy config. Literal strings have no
239
+ * escape sequences, and the values in play here are bare identifiers, so a
240
+ * non-greedy character class is an exact match for both forms.
241
+ *
242
+ * @param rawConfig - Full text of `config.toml`.
243
+ * @returns The declared mode, or `null` when no top-level assignment exists.
244
+ */
245
+ export function readTopLevelCodexCliAuthStoreMode(rawConfig) {
246
+ for (const line of rawConfig.split(/\r?\n/)) {
247
+ if (TOML_TABLE_HEADER_PATTERN.test(line))
248
+ break;
249
+ const match = line.match(/^\s*cli_auth_credentials_store\s*=\s*(?:"([^"]*)"|'([^']*)')\s*(?:#.*)?$/);
250
+ if (match)
251
+ return (match[1] ?? match[2] ?? "").trim() || null;
252
+ }
253
+ return null;
254
+ }
255
+ /**
256
+ * Persists `cli_auth_credentials_store = "file"` as a top-level key in the
257
+ * official Codex CLI `config.toml`.
258
+ *
259
+ * The wrapper already appends `-c cli_auth_credentials_store="file"` to every
260
+ * command it forwards, but that only covers processes it launches itself.
261
+ * Third-party front-ends (menu-bar apps such as CodexBar, editor extensions)
262
+ * exec the official binary directly, so the *persisted* value is what decides
263
+ * whether the official CLI reaches into the macOS login keychain. Leaving it on
264
+ * `"keychain"` is what produces the repeated "Always Allow" prompts reported in
265
+ * issue #641.
266
+ *
267
+ * Only top-level assignments are rewritten. A `cli_auth_credentials_store`
268
+ * inside a `[profiles.*]` table must be left alone: rewriting it in place would
269
+ * mangle a scoped setting while still leaving the top-level default pointed at
270
+ * the keychain, so the prompts would continue.
271
+ *
272
+ * @param configPath - Config file to reconcile; defaults to the resolved Codex
273
+ * CLI `config.toml`.
274
+ * @returns `true` when the file was rewritten, `false` when it was already
275
+ * correct or enforcement is disabled.
276
+ */
277
+ export async function ensureCodexCliFileAuthStore(configPath = getCodexCliConfigPath()) {
210
278
  if (!shouldEnforceCodexCliFileAuthStore())
211
279
  return false;
212
280
  const desired = 'cli_auth_credentials_store = "file"';
213
281
  const raw = existsSync(configPath) ? await fs.readFile(configPath, "utf-8") : "";
282
+ // Quote style is not ours to normalize: a literal-string `'file'` already
283
+ // keeps the CLI off the keychain, so rewriting it to `"file"` would churn a
284
+ // healthy config on every wrapper startup for no behavioral gain.
285
+ if (readTopLevelCodexCliAuthStoreMode(raw) === "file")
286
+ return false;
287
+ // Preserve the file's existing line endings, as the runtime config rewriter
288
+ // does. Splitting on /\r?\n/ and rejoining with "\n" would silently convert a
289
+ // CRLF config.toml to LF -- an edit the user never asked for, and one this
290
+ // code now risks on every wrapper startup rather than only on account switch.
291
+ const lineEnding = raw.includes("\r\n") ? "\r\n" : "\n";
214
292
  const lines = raw.length > 0 ? raw.split(/\r?\n/) : [];
215
293
  const assignmentRegex = /^\s*cli_auth_credentials_store\s*=/;
216
294
  let replaced = false;
295
+ let inTopLevelTable = true;
217
296
  const nextLines = lines.map((line) => {
218
- if (!replaced && assignmentRegex.test(line)) {
297
+ if (TOML_TABLE_HEADER_PATTERN.test(line))
298
+ inTopLevelTable = false;
299
+ if (inTopLevelTable && !replaced && assignmentRegex.test(line)) {
219
300
  replaced = true;
220
301
  return desired;
221
302
  }
222
303
  return line;
223
304
  });
224
305
  if (!replaced) {
225
- let insertAt = nextLines.findIndex((line) => /^\s*\[/.test(line));
306
+ let insertAt = nextLines.findIndex((line) => TOML_TABLE_HEADER_PATTERN.test(line));
226
307
  if (insertAt < 0)
227
308
  insertAt = nextLines.length;
228
309
  nextLines.splice(insertAt, 0, desired);
@@ -230,8 +311,8 @@ async function ensureCodexCliFileAuthStore(configPath) {
230
311
  nextLines.splice(insertAt + 1, 0, "");
231
312
  }
232
313
  }
233
- const nextRaw = ensureTrailingNewline(nextLines.join("\n"));
234
- if (nextRaw === ensureTrailingNewline(raw)) {
314
+ const nextRaw = ensureTrailingNewline(nextLines.join(lineEnding), lineEnding);
315
+ if (nextRaw === ensureTrailingNewline(raw, lineEnding)) {
235
316
  return false;
236
317
  }
237
318
  await atomicWriteText(configPath, nextRaw);
@@ -1 +1 @@
1
- {"version":3,"file":"login-menu-actions.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAEN,KAAK,gBAAgB,EAGrB,KAAK,kBAAkB,EAGvB,MAAM,eAAe,CAAC;AASvB,OAAO,EACN,KAAK,eAAe,EAKpB,MAAM,kBAAkB,CAAC;AAG1B;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7D,gBAAgB;AAChB,wBAAsB,qBAAqB,CAC1C,YAAY,EAAE,kBAAkB,GAAG,IAAI,EACvC,sBAAsB,GAAE,MAAM,GAAG,IAAW,GAC1C,OAAO,CAAC,eAAe,CAAC,CA6D1B;AAED,gBAAgB;AAChB,wBAAsB,uBAAuB,CAC5C,YAAY,EAAE,kBAAkB,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CA0C5B;AAED,gBAAgB;AAChB,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,kBAAkB,EAAE,GAC3B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkCpC;AA2GD,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CA8Gf"}
1
+ {"version":3,"file":"login-menu-actions.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAEN,KAAK,gBAAgB,EAIrB,KAAK,kBAAkB,EAGvB,MAAM,eAAe,CAAC;AASvB,OAAO,EACN,KAAK,eAAe,EAKpB,MAAM,kBAAkB,CAAC;AAG1B;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7D,gBAAgB;AAChB,wBAAsB,qBAAqB,CAC1C,YAAY,EAAE,kBAAkB,GAAG,IAAI,EACvC,sBAAsB,GAAE,MAAM,GAAG,IAAW,GAC1C,OAAO,CAAC,eAAe,CAAC,CA6D1B;AAED,gBAAgB;AAChB,wBAAsB,uBAAuB,CAC5C,YAAY,EAAE,kBAAkB,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CA0C5B;AAED,gBAAgB;AAChB,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,kBAAkB,EAAE,GAC3B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkCpC;AA8GD,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CA0Hf"}
@@ -1,7 +1,7 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { sanitizeEmail } from "../accounts.js";
3
3
  import { MODEL_FAMILIES } from "../prompts/codex.js";
4
- import { findMatchingAccountIndex, loadAccounts, setStoragePath, withAccountStorageTransaction, } from "../storage.js";
4
+ import { findMatchingAccountIndex, loadAccounts, reconcilePinnedAccountIndex, setStoragePath, withAccountStorageTransaction, } from "../storage.js";
5
5
  import { UI_COPY } from "../ui/ui-copy.js";
6
6
  import { getUiRuntimeOptions } from "../ui/runtime.js";
7
7
  import { select } from "../ui/select.js";
@@ -179,6 +179,9 @@ function replaceManageActionStorage(target, source) {
179
179
  target.activeIndexByFamily = {
180
180
  ...source.activeIndexByFamily,
181
181
  };
182
+ // Keep the in-memory view's manual pin in sync with what was just persisted;
183
+ // the pin was re-resolved (or cleared) against the post-deletion account list.
184
+ target.pinnedAccountIndex = source.pinnedAccountIndex;
182
185
  }
183
186
  function resolveManageActionAccountIndex(storage, fallbackIndex, account) {
184
187
  if (account) {
@@ -236,8 +239,16 @@ export async function handleManageAction(storage, menuResult) {
236
239
  if (!matchesManageActionAccount(selectedAccount, nextAccount)) {
237
240
  return;
238
241
  }
242
+ // Capture the pinned account BEFORE the splice so the manual pin can be
243
+ // followed by identity afterwards. resetManageActionSelection only remaps
244
+ // activeIndex; without this the pin keeps its raw slot and silently points
245
+ // at a different account (or wedges the pool). See #474.
246
+ const pinnedAccount = typeof nextStorage.pinnedAccountIndex === "number"
247
+ ? nextStorage.accounts[nextStorage.pinnedAccountIndex]
248
+ : undefined;
239
249
  nextStorage.accounts.splice(nextIndex, 1);
240
250
  resetManageActionSelection(nextStorage, nextIndex);
251
+ nextStorage.pinnedAccountIndex = reconcilePinnedAccountIndex(pinnedAccount, nextStorage.accounts);
241
252
  await persist(nextStorage);
242
253
  replaceManageActionStorage(storage, nextStorage);
243
254
  deleted = true;
@@ -1 +1 @@
1
- {"version":3,"file":"login-menu-data.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-data.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EAEN,KAAK,wBAAwB,EAE7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEN,KAAK,cAAc,EAGnB,MAAM,mBAAmB,CAAC;AAe3B,OAAO,EAKN,KAAK,8BAA8B,EACnC,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAczE;;;;;;GAMG;AAEH,eAAO,MAAM,iCAAiC,QAAa,CAAC;AA+E5D,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAChB,GAAG,SAAa,GACd,MAAM,CAkBR;AAED,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACnD,OAAO,CAAC,cAAc,CAAC,CAiGzB;AA0JD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,cAAc,GAAG,IAAI,EACjC,eAAe,EAAE,wBAAwB,EACzC,cAAc,GAAE,8BAA8B,GAAG,IAAW,GAC1D,mBAAmB,EAAE,CA6EvB;AAED,wBAAsB,qCAAqC,CAC1D,OAAO,EAAE,gBAAgB,EACzB,GAAG,SAAa,GACd,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAgBhD;AAsBD,wBAAsB,oCAAoC,CACzD,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CAyBlB"}
1
+ {"version":3,"file":"login-menu-data.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-data.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EAEN,KAAK,wBAAwB,EAE7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEN,KAAK,cAAc,EAGnB,MAAM,mBAAmB,CAAC;AAe3B,OAAO,EAKN,KAAK,8BAA8B,EACnC,MAAM,uCAAuC,CAAC;AAE/C,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAezE;;;;;;GAMG;AAEH,eAAO,MAAM,iCAAiC,QAAa,CAAC;AA+E5D,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAChB,GAAG,SAAa,GACd,MAAM,CAkBR;AAED,wBAAsB,wBAAwB,CAC7C,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACnD,OAAO,CAAC,cAAc,CAAC,CAiGzB;AAoKD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,cAAc,GAAG,IAAI,EACjC,eAAe,EAAE,wBAAwB,EACzC,cAAc,GAAE,8BAA8B,GAAG,IAAW,GAC1D,mBAAmB,EAAE,CA+EvB;AAED,wBAAsB,qCAAqC,CAC1D,OAAO,EAAE,gBAAgB,EACzB,GAAG,SAAa,GACd,OAAO,CAAC,8BAA8B,GAAG,IAAI,CAAC,CAgBhD;AAsBD,wBAAsB,oCAAoC,CACzD,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CAyBlB"}
@@ -9,6 +9,7 @@ import { resolveActiveIndex } from "../runtime/account-status.js";
9
9
  import { getAppBindStatus } from "../runtime/app-bind.js";
10
10
  import { isDisplayCurrentAccount, readAppRuntimeHelperAccountSignal, resolveAccountCurrentMarkers, resolveRuntimeCurrentAccount, } from "../runtime/runtime-current-account.js";
11
11
  import { loadPersistedRuntimeObservabilitySnapshot } from "../runtime/runtime-observability.js";
12
+ import { resolveQuotaWindowLabel } from "../ui/auth-menu-builder.js";
12
13
  import { hasUsableAccessToken } from "./account-credentials.js";
13
14
  import { formatAccountQuotaSummary, formatRateLimitEntry, } from "./formatters/index.js";
14
15
  import { cloneQuotaCacheData, DEFAULT_LIVE_PROBE_MODEL, getPersistedQuotaViewForAccount, updateQuotaCacheForAccount, } from "./quota-cache-helpers.js";
@@ -175,17 +176,25 @@ function parseLeftPercentFromQuotaSummary(summary, windowLabel) {
175
176
  return -1;
176
177
  return Math.max(0, Math.min(100, value));
177
178
  }
178
- function readQuotaLeftPercent(account, windowLabel) {
179
- const direct = windowLabel === "5h"
179
+ function readQuotaLeftPercent(account, window) {
180
+ const isPrimary = window === "primary";
181
+ const direct = isPrimary
180
182
  ? account.quota5hLeftPercent
181
183
  : account.quota7dLeftPercent;
182
184
  if (typeof direct === "number" && Number.isFinite(direct)) {
183
185
  return Math.max(0, Math.min(100, Math.round(direct)));
184
186
  }
185
- return parseLeftPercentFromQuotaSummary(account.quotaSummary, windowLabel);
187
+ // The summary string is labelled by DURATION (formatAccountQuotaSummary), so a
188
+ // Codex Business row reads "30d 18%, ...". Searching it for the positional
189
+ // "5h"/"7d" finds nothing, scores the account -1, and sinks it to the bottom of
190
+ // a ready-first sort. Resolve the label from the window's real duration.
191
+ const label = resolveQuotaWindowLabel(isPrimary
192
+ ? account.quotaPrimaryWindowMinutes
193
+ : account.quotaSecondaryWindowMinutes, isPrimary ? "5h" : "7d");
194
+ return parseLeftPercentFromQuotaSummary(account.quotaSummary, label);
186
195
  }
187
196
  function readQuotaFloorPercent(account) {
188
- return Math.min(readQuotaLeftPercent(account, "5h"), readQuotaLeftPercent(account, "7d"));
197
+ return Math.min(readQuotaLeftPercent(account, "primary"), readQuotaLeftPercent(account, "secondary"));
189
198
  }
190
199
  function accountStatusSortBucket(status) {
191
200
  switch (status) {
@@ -220,12 +229,12 @@ function compareReadyFirstAccounts(left, right) {
220
229
  const rightFloor = readQuotaFloorPercent(right);
221
230
  if (leftFloor !== rightFloor)
222
231
  return rightFloor - leftFloor;
223
- const left5h = readQuotaLeftPercent(left, "5h");
224
- const right5h = readQuotaLeftPercent(right, "5h");
232
+ const left5h = readQuotaLeftPercent(left, "primary");
233
+ const right5h = readQuotaLeftPercent(right, "primary");
225
234
  if (left5h !== right5h)
226
235
  return right5h - left5h;
227
- const left7d = readQuotaLeftPercent(left, "7d");
228
- const right7d = readQuotaLeftPercent(right, "7d");
236
+ const left7d = readQuotaLeftPercent(left, "secondary");
237
+ const right7d = readQuotaLeftPercent(right, "secondary");
229
238
  if (left7d !== right7d)
230
239
  return right7d - left7d;
231
240
  const leftLastUsed = left.lastUsed ?? 0;
@@ -291,6 +300,8 @@ export function toExistingAccountInfo(storage, quotaCache, displaySettings, runt
291
300
  quota5hResetAtMs: entry?.primary.resetAtMs,
292
301
  quota7dLeftPercent: quotaLeftPercentFromUsed(entry?.secondary.usedPercent),
293
302
  quota7dResetAtMs: entry?.secondary.resetAtMs,
303
+ quotaPrimaryWindowMinutes: entry?.primary.windowMinutes,
304
+ quotaSecondaryWindowMinutes: entry?.secondary.windowMinutes,
294
305
  quotaRateLimited: entry?.status === 429,
295
306
  quotaExhausted,
296
307
  isCurrentAccount,
@@ -1 +1 @@
1
- {"version":3,"file":"repair-commands.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/repair-commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repair-commands.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/repair-commands.ts"],"names":[],"mappings":"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"}
@@ -9,7 +9,7 @@ import { isCodexUnavailableError } from "../errors.js";
9
9
  import { queuedRefresh } from "../refresh-queue.js";
10
10
  import { findMatchingAccountIndex, getStoragePath, loadAccounts, loadFlaggedAccounts, setStoragePath, withAccountStorageTransaction, withAccountAndFlaggedStorageTransaction, withFlaggedStorageTransaction, } from "../storage.js";
11
11
  import { getCodexCliAuthPath, getCodexCliConfigPath, loadCodexCliState, } from "../codex-cli/state.js";
12
- import { setCodexCliActiveSelection } from "../codex-cli/writer.js";
12
+ import { ensureCodexCliFileAuthStore, readTopLevelCodexCliAuthStoreMode, setCodexCliActiveSelection, shouldEnforceCodexCliFileAuthStore, } from "../codex-cli/writer.js";
13
13
  import { MODEL_FAMILIES } from "../prompts/codex.js";
14
14
  import { DEFAULT_PROBE_MODEL, resolveNormalizedModel } from "../request/helpers/model-map.js";
15
15
  import { loadPersistedRuntimeObservabilitySnapshot } from "../runtime/runtime-observability.js";
@@ -1300,14 +1300,16 @@ export async function runDoctor(args, deps) {
1300
1300
  : "Codex config file does not exist",
1301
1301
  details: codexConfigPath,
1302
1302
  });
1303
+ const supplementalFixActions = [];
1304
+ // Tracked separately from account storage: pinning the auth store is a real
1305
+ // remediation even on a machine with no accounts yet, which is exactly the
1306
+ // state a user hitting repeated keychain prompts tends to be in.
1307
+ let authStoreFixChanged = false;
1303
1308
  let codexAuthStoreMode;
1304
1309
  if (codexConfigFileExists) {
1305
1310
  try {
1306
1311
  const configRaw = await fs.readFile(codexConfigPath, "utf-8");
1307
- const match = configRaw.match(/^\s*cli_auth_credentials_store\s*=\s*"([^"]+)"\s*$/m);
1308
- if (match?.[1]) {
1309
- codexAuthStoreMode = match[1].trim();
1310
- }
1312
+ codexAuthStoreMode = readTopLevelCodexCliAuthStoreMode(configRaw) ?? undefined;
1311
1313
  }
1312
1314
  catch (error) {
1313
1315
  addCheck({
@@ -1319,13 +1321,59 @@ export async function runDoctor(args, deps) {
1319
1321
  }
1320
1322
  }
1321
1323
  if (!checks.some((check) => check.key === "codex-auth-store")) {
1324
+ // A config that is not pinned to "file" is what makes the official CLI
1325
+ // reach into the macOS login keychain, so `--fix` repairs it directly
1326
+ // instead of waiting for the incidental `pendingCodexActiveSync` path
1327
+ // below, which only fires when there is an active account to mirror.
1328
+ if (options.fix &&
1329
+ codexAuthStoreMode !== "file" &&
1330
+ shouldEnforceCodexCliFileAuthStore()) {
1331
+ if (options.dryRun) {
1332
+ authStoreFixChanged = true;
1333
+ supplementalFixActions.push({
1334
+ key: "codex-auth-store",
1335
+ message: "Prepared Codex CLI credential store pin to file in config.toml (dry-run)",
1336
+ });
1337
+ }
1338
+ else {
1339
+ try {
1340
+ if (await ensureCodexCliFileAuthStore(codexConfigPath)) {
1341
+ codexAuthStoreMode = "file";
1342
+ authStoreFixChanged = true;
1343
+ supplementalFixActions.push({
1344
+ key: "codex-auth-store",
1345
+ message: "Pinned Codex CLI credential store to file in config.toml",
1346
+ });
1347
+ }
1348
+ }
1349
+ catch (error) {
1350
+ addCheck({
1351
+ key: "codex-auth-store-fix",
1352
+ severity: "warn",
1353
+ message: "Failed to pin Codex CLI credential store to file",
1354
+ details: error instanceof Error ? error.message : String(error),
1355
+ });
1356
+ }
1357
+ }
1358
+ }
1359
+ const authStoreDetails = [
1360
+ codexAuthStoreMode ? `mode=${codexAuthStoreMode}` : "mode=unset",
1361
+ ];
1362
+ if (codexAuthStoreMode !== "file" && !options.fix) {
1363
+ authStoreDetails.push("run `codex-multi-auth doctor --fix` to pin it to file");
1364
+ }
1365
+ if (process.platform === "darwin" && codexAuthStoreMode === "file") {
1366
+ // Purely informational: reading or deleting keychain items would
1367
+ // itself raise the prompt this check exists to eliminate.
1368
+ authStoreDetails.push("credentials saved by an earlier official `codex login` may still sit in the login keychain; they are no longer read and can be removed manually via Keychain Access");
1369
+ }
1322
1370
  addCheck({
1323
1371
  key: "codex-auth-store",
1324
1372
  severity: codexAuthStoreMode === "file" ? "ok" : "warn",
1325
1373
  message: codexAuthStoreMode === "file"
1326
1374
  ? "Codex auth storage is set to file"
1327
1375
  : "Codex auth storage is not explicitly set to file",
1328
- details: codexAuthStoreMode ? `mode=${codexAuthStoreMode}` : "mode=unset",
1376
+ details: authStoreDetails.join("; "),
1329
1377
  });
1330
1378
  }
1331
1379
  const codexCliState = await loadCodexCliState({ forceRefresh: true });
@@ -1343,7 +1391,6 @@ export async function runDoctor(args, deps) {
1343
1391
  let fixChanged = false;
1344
1392
  let storageFixChanged = false;
1345
1393
  let structuralFixActions = [];
1346
- const supplementalFixActions = [];
1347
1394
  let doctorRefreshMutation = null;
1348
1395
  let pendingCodexActiveSync = null;
1349
1396
  if (options.fix && storageForChecks && storageForChecks.accounts.length > 0) {
@@ -1677,7 +1724,9 @@ export async function runDoctor(args, deps) {
1677
1724
  }
1678
1725
  }
1679
1726
  const fixActions = [...structuralFixActions, ...supplementalFixActions];
1680
- if (options.fix && storageForChecks && storageForChecks.accounts.length > 0) {
1727
+ if (options.fix &&
1728
+ ((storageForChecks && storageForChecks.accounts.length > 0) ||
1729
+ authStoreFixChanged)) {
1681
1730
  fixChanged = storageFixChanged || fixActions.length > 0;
1682
1731
  addCheck({
1683
1732
  key: "auto-fix",
@@ -1 +1 @@
1
- {"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../lib/forecast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"forecast.d.ts","sourceRoot":"","sources":["../../lib/forecast.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { formatAccountLabel, formatWaitTime } from "./accounts.js";
2
- import { findQuotaCacheEntryForAccount, isQuotaCacheEntryExhausted, quotaLeftPercentFromUsed, } from "./quota-readiness.js";
2
+ import { findQuotaCacheEntryForAccount, isQuotaCacheEntryExhausted, quotaUsedPercentIsExhausted, } from "./quota-readiness.js";
3
3
  import { getRateLimitResetTimeForFamily } from "./runtime/account-status.js";
4
4
  function clampRisk(score) {
5
5
  if (!Number.isFinite(score))
@@ -49,8 +49,10 @@ function getLiveQuotaWaitMs(snapshot, now, onlyExhausted) {
49
49
  // resets ~7d out; folding that in would overstate the wait by orders of
50
50
  // magnitude and invert the account recommendation (stress audit H5). On a
51
51
  // 429 we honor every future window, since the upstream said "slow down now"
52
- // regardless of the usage gauge.
53
- if (onlyExhausted && quotaLeftPercentFromUsed(window?.usedPercent) !== 0) {
52
+ // regardless of the usage gauge. The test is the RAW usedPercent, never the
53
+ // rounded left-percent: 100 - 99.6 rounds to 0 left, which would bench a
54
+ // window that still has quota and falsely mark the account "delayed".
55
+ if (onlyExhausted && !quotaUsedPercentIsExhausted(window?.usedPercent)) {
54
56
  continue;
55
57
  }
56
58
  const resetAt = window?.resetAtMs;
@@ -135,8 +137,13 @@ export function evaluateForecastAccount(input) {
135
137
  }
136
138
  const quotaEntry = findQuotaCacheEntryForAccount(input.quotaCache, account, input.allAccounts ?? [account]);
137
139
  if (isQuotaCacheEntryExhausted(quotaEntry, now)) {
140
+ // Only a window that is genuinely at/over 100% used should contribute its
141
+ // reset time. Testing the ROUNDED left-percent instead would treat a
142
+ // 99.6%-used sibling as at-limit and fold its (possibly far-future) reset
143
+ // into the Math.max below, overstating the wait for an account whose
144
+ // actually-exhausted window recovers much sooner.
138
145
  const resetAts = [quotaEntry?.primary, quotaEntry?.secondary]
139
- .filter((window) => quotaLeftPercentFromUsed(window?.usedPercent) === 0 &&
146
+ .filter((window) => quotaUsedPercentIsExhausted(window?.usedPercent) &&
140
147
  typeof window?.resetAtMs === "number" &&
141
148
  Number.isFinite(window.resetAtMs) &&
142
149
  window.resetAtMs > now)
@@ -207,6 +214,25 @@ export function evaluateForecastAccount(input) {
207
214
  if (liveWait > 0 && availability === "ready") {
208
215
  availability = "delayed";
209
216
  }
217
+ // A live window at 100% used with NO resetAtMs has no known recovery time —
218
+ // the probe reports it fully consumed and cannot say when it refills. On a
219
+ // 200 probe getLiveQuotaWaitMs then yields 0 (no reset to wait on) and the
220
+ // 429 branch never fires, so without this the account would stay "ready" and
221
+ // be recommended as a healthy pick. Treat that case as exhausted and downgrade
222
+ // a "ready" account to "delayed". A window WITH a resetAtMs is only *delayed*,
223
+ // not exhausted: getLiveQuotaWaitMs already contributes its wait above, so a
224
+ // 429 with known reset times stays a recoverable "delayed" account rather than
225
+ // a blocked one. The raw-usedPercent test (not rounded left-percent) keeps
226
+ // 99.6% from being falsely benched.
227
+ const liveExhausted = (quotaUsedPercentIsExhausted(quota.primary.usedPercent) &&
228
+ typeof quota.primary.resetAtMs !== "number") ||
229
+ (quotaUsedPercentIsExhausted(quota.secondary.usedPercent) &&
230
+ typeof quota.secondary.resetAtMs !== "number");
231
+ if (liveExhausted) {
232
+ exhausted = true;
233
+ if (availability === "ready")
234
+ availability = "delayed";
235
+ }
210
236
  const primaryUsage = describeQuotaUsage("primary", quota.primary.usedPercent);
211
237
  if (primaryUsage)
212
238
  reasons.push(primaryUsage);
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-policy.d.ts","sourceRoot":"","sources":["../../../lib/policy/runtime-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIN,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEN,KAAK,4BAA4B,EACjC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,oBAAoB,EAGpB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,qBAAqB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IAClC,eAAe,EAAE,kBAAkB,CAAC;IACpC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,EAAE,4BAA4B,CAAC;CACtC;AAED,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,WAAW,EAAE,MAAM,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,sBAAsB,CAC3C,QAAQ,SAAgB,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAO7B;AAsDD,wBAAsB,qBAAqB,CAAC,KAAK,EAAE;IAClD,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqGjC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACrC,GAAG,oBAAoB,CAgCvB"}
1
+ {"version":3,"file":"runtime-policy.d.ts","sourceRoot":"","sources":["../../../lib/policy/runtime-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAGN,KAAK,kBAAkB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAKN,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEN,KAAK,4BAA4B,EACjC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,oBAAoB,EAGpB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,qBAAqB,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IAClC,eAAe,EAAE,kBAAkB,CAAC;IACpC,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,EAAE,4BAA4B,CAAC;CACtC;AAED,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,WAAW,EAAE,MAAM,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAsB,sBAAsB,CAC3C,QAAQ,SAAgB,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAO7B;AA6DD,wBAAsB,qBAAqB,CAAC,KAAK,EAAE;IAClD,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqGjC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IACjD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACrC,GAAG,oBAAoB,CAgCvB"}
@@ -1,6 +1,6 @@
1
1
  import { resolveEntitlementAccountKey } from "../entitlement-cache.js";
2
2
  import { getAccountPolicyKey, loadAccountPolicyStore, } from "../account-policy.js";
3
- import { evaluateBudgetGuard, getBudgetWindowStart, loadBudgetGuardStore, } from "../budget-guard.js";
3
+ import { evaluateBudgetGuard, getBudgetWindowStart, loadBudgetGuardStore, normalizeBudgetKey, } from "../budget-guard.js";
4
4
  import { resolveProjectRoutingProfile, } from "../routing-profiles.js";
5
5
  import { appendUsageLedgerRow, summarizeUsageLedger, } from "../usage/index.js";
6
6
  export async function loadRuntimePolicyState(startDir = process.cwd()) {
@@ -33,12 +33,21 @@ function intersects(left, right) {
33
33
  }
34
34
  async function evaluateBudgets(input) {
35
35
  const keys = new Set();
36
+ // `global` is already normalize-clean. Project/profile keys can carry uppercase
37
+ // or spaces, but budget-guard STORES limits only under normalizeBudgetKey (see
38
+ // upsertBudgetLimit and load-time normalizeStore). Look them up the same way, or
39
+ // a key like `project:MyApp` never matches its stored `project:myapp` and the
40
+ // budget is silently unenforced.
36
41
  keys.add("global");
37
42
  if (input.state.project.projectKey) {
38
- keys.add(`project:${input.state.project.projectKey}`);
43
+ const projectKey = normalizeBudgetKey(`project:${input.state.project.projectKey}`);
44
+ if (projectKey)
45
+ keys.add(projectKey);
39
46
  }
40
47
  if (input.state.project.profile?.budgetKey) {
41
- keys.add(input.state.project.profile.budgetKey);
48
+ const budgetKey = normalizeBudgetKey(input.state.project.profile.budgetKey);
49
+ if (budgetKey)
50
+ keys.add(budgetKey);
42
51
  }
43
52
  const evaluations = [];
44
53
  for (const key of keys) {
@@ -11,6 +11,7 @@ export type QuotaEmailFallbackState = {
11
11
  export declare function buildQuotaEmailFallbackState(accounts: readonly QuotaCacheAccountRef[]): ReadonlyMap<string, QuotaEmailFallbackState>;
12
12
  export declare function hasSafeQuotaEmailFallback(emailFallbackState: ReadonlyMap<string, QuotaEmailFallbackState>, account: QuotaCacheAccountRef): boolean;
13
13
  export declare function quotaLeftPercentFromUsed(usedPercent: number | undefined): number | undefined;
14
+ export declare function quotaUsedPercentIsExhausted(usedPercent: number | undefined): boolean;
14
15
  export declare function isQuotaCacheEntryExhausted(entry: Pick<QuotaCacheEntry, "primary" | "secondary"> & {
15
16
  updatedAt?: number;
16
17
  } | null | undefined, now?: number): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"quota-readiness.d.ts","sourceRoot":"","sources":["../../lib/quota-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAoB,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC;AAIlF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGhF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAG5E;AAED,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAQT;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,SAAS,oBAAoB,EAAE,GACvC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAiB9C;AAED,wBAAgB,yBAAyB,CACxC,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAChE,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAMT;AAED,wBAAgB,wBAAwB,CACvC,WAAW,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,GAAG,SAAS,CAKpB;AA4BD,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,WAAW,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACjG,GAAG,SAAa,GACd,OAAO,CAQT;AAED,wBAAgB,6BAA6B,CAC5C,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EACxC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,kBAAkB,+CAAyC,GACzD,eAAe,GAAG,IAAI,CAWxB"}
1
+ {"version":3,"file":"quota-readiness.d.ts","sourceRoot":"","sources":["../../lib/quota-readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAoB,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC;AAIlF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGhF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAG5E;AAED,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAQT;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,SAAS,oBAAoB,EAAE,GACvC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAiB9C;AAED,wBAAgB,yBAAyB,CACxC,kBAAkB,EAAE,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAChE,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAMT;AAED,wBAAgB,wBAAwB,CACvC,WAAW,EAAE,MAAM,GAAG,SAAS,GAC7B,MAAM,GAAG,SAAS,CAKpB;AAQD,wBAAgB,2BAA2B,CAC1C,WAAW,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAMT;AA2BD,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,WAAW,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EACjG,GAAG,SAAa,GACd,OAAO,CAQT;AAED,wBAAgB,6BAA6B,CAC5C,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EACxC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,kBAAkB,+CAAyC,GACzD,eAAe,GAAG,IAAI,CAWxB"}
@@ -53,6 +53,17 @@ export function quotaLeftPercentFromUsed(usedPercent) {
53
53
  }
54
54
  return Math.max(0, Math.min(100, Math.round(100 - usedPercent)));
55
55
  }
56
+ // A window is exhausted only when it is genuinely at/over 100% used. Base this on
57
+ // the RAW usedPercent, never the rounded quotaLeftPercentFromUsed: rounding
58
+ // 100 - 99.6 = 0.4 down to 0 left-percent would falsely bench a window that still
59
+ // has ~0.4% quota (any usedPercent in (99.5, 100) rounds to 0 left). The header
60
+ // parser preserves fractional used-percent, so this input is expected;
61
+ // quotaLeftPercentFromUsed stays for DISPLAY only.
62
+ export function quotaUsedPercentIsExhausted(usedPercent) {
63
+ return (typeof usedPercent === "number" &&
64
+ Number.isFinite(usedPercent) &&
65
+ usedPercent >= 100);
66
+ }
56
67
  function quotaWindowIsExhausted(window, now = Date.now(), updatedAt) {
57
68
  if (typeof window?.resetAtMs === "number" && now >= window.resetAtMs) {
58
69
  return false;
@@ -69,8 +80,7 @@ function quotaWindowIsExhausted(window, now = Date.now(), updatedAt) {
69
80
  now >= updatedAt + window.windowMinutes * 60_000) {
70
81
  return false;
71
82
  }
72
- const leftPercent = quotaLeftPercentFromUsed(window?.usedPercent);
73
- return typeof leftPercent === "number" && leftPercent <= 0;
83
+ return quotaUsedPercentIsExhausted(window?.usedPercent);
74
84
  }
75
85
  export function isQuotaCacheEntryExhausted(entry, now = Date.now()) {
76
86
  // Codex quota windows are cumulative gates: a 0% remaining active window blocks use