@bitkyc08/opencodex 2.44.0 → 2.45.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 (38) hide show
  1. package/gui/dist/assets/index-CCfD72yq.js +115 -0
  2. package/gui/dist/assets/index-J96sug5C.css +1 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +1 -1
  5. package/src/adapters/openai-responses.ts +14 -0
  6. package/src/chat/outbound.ts +286 -35
  7. package/src/claude/compatibility.ts +192 -0
  8. package/src/claude/model-info.ts +14 -2
  9. package/src/cli/account-extended.ts +24 -1
  10. package/src/cli/init.ts +70 -13
  11. package/src/codex/catalog/provider-fetch.ts +38 -9
  12. package/src/codex/catalog/sync.ts +34 -2
  13. package/src/codex/catalog.ts +1 -1
  14. package/src/config/initialize.ts +132 -0
  15. package/src/config/rebase-provenance.ts +26 -0
  16. package/src/config.ts +50 -1
  17. package/src/generated/compatibility-version.json +41 -29
  18. package/src/lib/windows-secret-acl.ts +8 -4
  19. package/src/providers/quota.ts +26 -15
  20. package/src/responses/state.ts +48 -3
  21. package/src/server/chat-completions.ts +32 -36
  22. package/src/server/chat-native-sse.ts +23 -3
  23. package/src/server/chat-native.ts +15 -8
  24. package/src/server/claude-messages.ts +27 -0
  25. package/src/server/index.ts +5 -1
  26. package/src/server/management/agent-settings-routes.ts +101 -14
  27. package/src/server/management/logs-usage-routes.ts +9 -2
  28. package/src/server/request-log-cursor.ts +84 -0
  29. package/src/server/request-log.ts +46 -3
  30. package/src/server/responses/agent-task-recovery.ts +50 -14
  31. package/src/server/responses/codex-ws-exchange.ts +79 -0
  32. package/src/server/responses/compact.ts +39 -33
  33. package/src/server/responses/core.ts +43 -27
  34. package/src/storage/cleanup.ts +49 -35
  35. package/src/types/config.ts +4 -0
  36. package/src/usage/log.ts +22 -0
  37. package/gui/dist/assets/index-B7_K1Hsj.js +0 -115
  38. package/gui/dist/assets/index-ltx3L-WS.css +0 -1
@@ -0,0 +1,132 @@
1
+ import {
2
+ closeSync, constants, fchmodSync, fstatSync, linkSync, lstatSync,
3
+ openSync, unlinkSync, writeFileSync,
4
+ } from "node:fs";
5
+ import { dirname } from "node:path";
6
+ import { assertNotRealHomeUnderTest } from "../lib/test-home-guard";
7
+ import { forgetEphemeralSecretPath, hardenSecretPath } from "../lib/windows-secret-acl";
8
+ import { isMissingPathError, nextAtomicTempSequence } from "./atomic-write";
9
+
10
+ type PublicationState = "not-published" | "published" | "uncertain";
11
+
12
+ /** Messages contain no candidate bytes or raw filesystem error text. */
13
+ export class InitialConfigPublicationError extends Error {
14
+ constructor(
15
+ readonly publication: PublicationState,
16
+ readonly residualTemp: boolean,
17
+ readonly hardLinkUnavailable: boolean,
18
+ options?: ErrorOptions,
19
+ ) {
20
+ super(hardLinkUnavailable
21
+ ? "Initial config requires hard-link publication; the filesystem or its permissions denied it."
22
+ : "Initial config publication did not finish.", options);
23
+ this.name = "InitialConfigPublicationError";
24
+ }
25
+ }
26
+
27
+ /** Narrow fault boundary; publication must be a single link operation. */
28
+ export interface InitialConfigPublicationIO {
29
+ harden(fd: number, temp: string, target: string): void;
30
+ write(fd: number, bytes: string): void;
31
+ link(temp: string, target: string): void;
32
+ unlink(temp: string): void;
33
+ close(fd: number): void;
34
+ }
35
+
36
+ function hardenInitialConfig(fd: number, temp: string, target: string): void {
37
+ if (process.platform === "win32") {
38
+ hardenSecretPath(temp, { required: true, timeoutMemoKey: target });
39
+ } else {
40
+ fchmodSync(fd, 0o600);
41
+ }
42
+ }
43
+
44
+ function identifiesDescriptor(fd: number, path: string): boolean {
45
+ const opened = fstatSync(fd);
46
+ const entry = lstatSync(path);
47
+ return opened.isFile() && entry.isFile()
48
+ && opened.dev === entry.dev && opened.ino === entry.ino;
49
+ }
50
+
51
+ function verifyPrivateTemp(fd: number, temp: string): void {
52
+ if (!identifiesDescriptor(fd, temp)
53
+ || (process.platform !== "win32" && (fstatSync(fd).mode & 0o777) !== 0o600)) {
54
+ throw new Error("Initial config temporary file identity or permissions changed.");
55
+ }
56
+ }
57
+
58
+ function removeOwnedTemp(fd: number, temp: string, unlink: (path: string) => void): boolean {
59
+ for (let attempt = 0; attempt < 2; attempt++) {
60
+ try {
61
+ if (!identifiesDescriptor(fd, temp)) return false;
62
+ unlink(temp);
63
+ forgetEphemeralSecretPath(temp);
64
+ return true;
65
+ } catch (error) {
66
+ if (isMissingPathError(error)) {
67
+ forgetEphemeralSecretPath(temp);
68
+ return true;
69
+ }
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * Publish complete bytes without replacing any entry at target. Never truncate:
77
+ * even an error from link can mean a remote filesystem already published the inode.
78
+ * Cleanup removes only our temporary name, never the target or another inode.
79
+ */
80
+ export function publishInitialConfigNoReplace(
81
+ target: string,
82
+ bytes: string,
83
+ io: Partial<InitialConfigPublicationIO> = {},
84
+ ): boolean {
85
+ assertNotRealHomeUnderTest(dirname(target));
86
+ const temp = `${target}.ocx.${process.pid}.${nextAtomicTempSequence()}.tmp`;
87
+ let fd: number | undefined;
88
+ let publication: PublicationState = "not-published";
89
+ let collided = false;
90
+ let failure: unknown;
91
+ let failed = false;
92
+ let hardLinkUnavailable = false;
93
+ let residualTemp = false;
94
+ try {
95
+ fd = openSync(temp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
96
+ (io.harden ?? hardenInitialConfig)(fd, temp, target);
97
+ verifyPrivateTemp(fd, temp);
98
+ (io.write ?? ((descriptor: number, value: string) => writeFileSync(descriptor, value, { encoding: "utf8" })))(fd, bytes);
99
+ verifyPrivateTemp(fd, temp);
100
+ try {
101
+ publication = "uncertain";
102
+ (io.link ?? linkSync)(temp, target);
103
+ publication = "published";
104
+ } catch (error) {
105
+ const code = (error as NodeJS.ErrnoException | undefined)?.code;
106
+ // EEXIST normally means a competitor won. A shared target means our
107
+ // publication may nevertheless have happened (e.g. a remote FS retry).
108
+ if (code === "EEXIST" && !identifiesDescriptor(fd, target)) collided = true;
109
+ else {
110
+ hardLinkUnavailable = ["EOPNOTSUPP", "ENOTSUP", "ENOSYS", "EXDEV", "EPERM"].includes(code ?? "");
111
+ throw error;
112
+ }
113
+ }
114
+ if (!collided && !identifiesDescriptor(fd, target)) {
115
+ throw new Error("Initial config published target identity changed.");
116
+ }
117
+ } catch (error) {
118
+ failed = true;
119
+ failure = error;
120
+ } finally {
121
+ if (fd !== undefined) {
122
+ // Unlink-only cleanup preserves all bytes if another name shares this inode.
123
+ residualTemp = !removeOwnedTemp(fd, temp, io.unlink ?? unlinkSync);
124
+ try { (io.close ?? closeSync)(fd); }
125
+ catch (error) { if (!failed) failure = error; failed = true; }
126
+ }
127
+ }
128
+ if (failed || residualTemp) {
129
+ throw new InitialConfigPublicationError(publication, residualTemp, hardLinkUnavailable, { cause: failure });
130
+ }
131
+ return !collided;
132
+ }
@@ -66,3 +66,29 @@ export function deleteConfigTopLevelKey<K extends keyof OcxConfig>(config: OcxCo
66
66
  export function clearPendingConfigTopLevelDeletions(config: OcxConfig): void {
67
67
  pendingTopLevelDeletions.delete(config);
68
68
  }
69
+
70
+ /**
71
+ * Capture field replacements and deletion intent for a synchronous live-config save.
72
+ * Restore before yielding on failure: an asynchronous rollback could overwrite a newer
73
+ * mutation. Descriptors preserve absent versus explicitly undefined properties; the
74
+ * private pending set must also retain its original presence, even when it was empty.
75
+ * Unrelated fields and the live object's identity/baselines are left in place.
76
+ */
77
+ export function captureConfigTopLevelRollback(
78
+ config: OcxConfig,
79
+ keys: readonly (keyof OcxConfig)[],
80
+ ): () => void {
81
+ const descriptors = new Map([...new Set<keyof OcxConfig>([...keys, CONFIG_REBASE_PROVENANCE_KEY])]
82
+ .map(key => [key, Object.getOwnPropertyDescriptor(config, key)] as const));
83
+ const pending = pendingTopLevelDeletions.get(config);
84
+ const pendingBefore = pending === undefined ? undefined : new Set(pending);
85
+ return () => {
86
+ for (const [key, descriptor] of descriptors) {
87
+ if (descriptor) Object.defineProperty(config, key, descriptor);
88
+ else deleteConfigTopLevelKey(config, key);
89
+ }
90
+ // The absent fields above are restoration, not new user deletion commands.
91
+ if (pendingBefore === undefined) pendingTopLevelDeletions.delete(config);
92
+ else pendingTopLevelDeletions.set(config, new Set(pendingBefore));
93
+ };
94
+ }
package/src/config.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { chmodSync, constants as fsConstants, copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, truncateSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { chmodSync, constants as fsConstants, copyFileSync, existsSync, linkSync, lstatSync, mkdirSync, readFileSync, truncateSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { Database } from "bun:sqlite";
5
5
  import * as z from "zod/v4";
@@ -123,6 +123,7 @@ export {
123
123
  type AtomicWriteIO,
124
124
  } from "./config/atomic-write";
125
125
  import { getConfigDir, getConfigPath, hardenConfigDir } from "./config/paths";
126
+ import { InitialConfigPublicationError, publishInitialConfigNoReplace, type InitialConfigPublicationIO } from "./config/initialize";
126
127
  import {
127
128
  describeProxyForLog,
128
129
  readWindowsSystemProxy,
@@ -2811,6 +2812,16 @@ export function readConfigDiagnostics(): ConfigDiagnostics {
2811
2812
  return readConfigFileSnapshot().diagnostics;
2812
2813
  }
2813
2814
 
2815
+ /** Read-only init preflight. Occupied unsafe entries are never treated as absence. */
2816
+ export function observeInitialConfigState(): "missing" | "exists" | "invalid" {
2817
+ try {
2818
+ if (!lstatSync(getConfigPath()).isFile()) return "invalid";
2819
+ } catch (error) {
2820
+ return isMissingPathError(error) ? "missing" : "invalid";
2821
+ }
2822
+ return readConfigFileSnapshot().diagnostics.source === "file" ? "exists" : "invalid";
2823
+ }
2824
+
2814
2825
  /**
2815
2826
  * The persisted config, plus a digest of the EXACT bytes it was parsed from.
2816
2827
  *
@@ -3123,6 +3134,44 @@ function persistConfigUnlocked(config: OcxConfig): boolean {
3123
3134
  return true;
3124
3135
  }
3125
3136
 
3137
+ export type PersistedConfigInitializationOutcome = "created" | "exists" | "invalid";
3138
+
3139
+ /** Initialize only a missing config; ordinary explicit updates still use saveConfig. */
3140
+ export function initializePersistedConfigIfMissing(
3141
+ config: OcxConfig,
3142
+ io?: Partial<InitialConfigPublicationIO>,
3143
+ ): PersistedConfigInitializationOutcome {
3144
+ assertNotRealHomeUnderTest(getConfigDir());
3145
+ const before = observeInitialConfigState();
3146
+ if (before !== "missing") return before;
3147
+ let published = false;
3148
+ try {
3149
+ const persisted = withConfigMutationLockSync((): OcxConfig | "exists" | "invalid" => {
3150
+ const current = observeInitialConfigState();
3151
+ if (current !== "missing") return current;
3152
+ const projected = projectCustomModelCatalogMigration(undefined, projectConfigRebaseProvenance(config));
3153
+ if (!validateConfigCandidate(projected).ok) throw new Error("Initial configuration is invalid.");
3154
+ if (!publishInitialConfigNoReplace(getConfigPath(), JSON.stringify(projected, null, 2) + "\n", io)) {
3155
+ return observeInitialConfigState() === "exists" ? "exists" : "invalid";
3156
+ }
3157
+ published = true;
3158
+ recordOwnedConfigPath(getConfigDir(), getConfigPath());
3159
+ bumpGenerationForCooperatingConfigWrite();
3160
+ return projected;
3161
+ });
3162
+ if (typeof persisted === "string") return persisted;
3163
+ adoptCustomModelCatalogMigration(config, persisted);
3164
+ if (persisted.configRebaseProvenance === undefined) delete config.configRebaseProvenance;
3165
+ else config.configRebaseProvenance = structuredClone(persisted.configRebaseProvenance);
3166
+ clearPendingConfigTopLevelDeletions(config);
3167
+ refreshUserCostOverlays(persisted);
3168
+ return "created";
3169
+ } catch (cause) {
3170
+ if (published) throw new InitialConfigPublicationError("published", false, false, { cause });
3171
+ throw cause;
3172
+ }
3173
+ }
3174
+
3126
3175
  /** Persist `config` to config.json under the config-mutation lock. */
3127
3176
  export function saveConfig(config: OcxConfig): void {
3128
3177
  // Keep the real-home assertion ahead of even lock-directory preparation.
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "path": "package.json",
13
- "sha256": "fdd2966ed9753b148b517949cf28dda1833e4fd1de39910b9454e488d3fac898"
13
+ "sha256": "3bf62e3b02229bf4f2c8652b3d780693c0c9c8498d7ff72180cce2cf795b186e"
14
14
  },
15
15
  {
16
16
  "path": "scripts/model-metadata.source.json",
@@ -366,7 +366,7 @@
366
366
  },
367
367
  {
368
368
  "path": "src/adapters/openai-responses.ts",
369
- "sha256": "4dba6844ff372f20c377bf55745d15d2e536228a150d2736341ae9a60b80d3b5"
369
+ "sha256": "1324c424d410df423092f5c5dfebced671ca7a3d65d85ad35d08bcb8b2e2cd24"
370
370
  },
371
371
  {
372
372
  "path": "src/adapters/opencode-go.ts",
@@ -422,7 +422,7 @@
422
422
  },
423
423
  {
424
424
  "path": "src/chat/outbound.ts",
425
- "sha256": "59ba1a09aca598b43812abbe740ebd3cdac95f8055e44c0eb6301c241767913a"
425
+ "sha256": "a7912ce4fde3601115b1bb3b92470a365136ffb5b8f346cc871cef72bcdcfe97"
426
426
  },
427
427
  {
428
428
  "path": "src/claude/agents-inject.ts",
@@ -444,6 +444,10 @@
444
444
  "path": "src/claude/auth-mode.ts",
445
445
  "sha256": "c2850d987754a58d258c9199901ec71634e3388498b229c09fa622cee65f81ac"
446
446
  },
447
+ {
448
+ "path": "src/claude/compatibility.ts",
449
+ "sha256": "cc28522a80a180a922dc63ada519847687d69578af28f0d4431ceb0983b937e3"
450
+ },
447
451
  {
448
452
  "path": "src/claude/context-windows.ts",
449
453
  "sha256": "c400f4b76319ec14855128af40fe52b60d31d217b89e291641522a51d2583d97"
@@ -522,7 +526,7 @@
522
526
  },
523
527
  {
524
528
  "path": "src/claude/model-info.ts",
525
- "sha256": "0e1d4698e300e25bdedffa31fb413f4a6151e59bdd29179981543864ba74f33b"
529
+ "sha256": "ac4dd859de82c6f6033c2754faea80a15bf538f284e19be673098f388a90aec7"
526
530
  },
527
531
  {
528
532
  "path": "src/claude/outbound.ts",
@@ -550,7 +554,7 @@
550
554
  },
551
555
  {
552
556
  "path": "src/cli/account-extended.ts",
553
- "sha256": "9308c5fc1ee6ea481749cc353288be347d78d2cf35b85839749b94813115020d"
557
+ "sha256": "238f1047c46cbce11c5dfe96440a4b4dba30e6b9936e59c6bebb5aef5dc95a4f"
554
558
  },
555
559
  {
556
560
  "path": "src/cli/account-main.ts",
@@ -670,7 +674,7 @@
670
674
  },
671
675
  {
672
676
  "path": "src/cli/init.ts",
673
- "sha256": "51b1e38e25b642665567eff5a61987f86218cf3e0beb49cbb85238db033166b4"
677
+ "sha256": "0283ef8c97b467219a8e9f77015c18c83023c7068b445b3e29df7111ac18fdcb"
674
678
  },
675
679
  {
676
680
  "path": "src/cli/inspect.ts",
@@ -962,7 +966,7 @@
962
966
  },
963
967
  {
964
968
  "path": "src/codex/catalog.ts",
965
- "sha256": "29907ddc709e5a287a732bcb3716211faa07eb4faf5873fe2bbc73b9d6c12142"
969
+ "sha256": "e15f0b3c3827591cef846b255630586867703f0a1f37f846f0815f45ec2fd655"
966
970
  },
967
971
  {
968
972
  "path": "src/codex/catalog/account-models.ts",
@@ -1002,7 +1006,7 @@
1002
1006
  },
1003
1007
  {
1004
1008
  "path": "src/codex/catalog/provider-fetch.ts",
1005
- "sha256": "67c7916948b10ed77053217eb55eed71e4dd738370c5c7dd9f0fbaa4a0113737"
1009
+ "sha256": "533f6a92a1d9372119384d7211dd0e621e612d073ff8b2b395657f09bd3a98a9"
1006
1010
  },
1007
1011
  {
1008
1012
  "path": "src/codex/catalog/reserve.ts",
@@ -1010,7 +1014,7 @@
1010
1014
  },
1011
1015
  {
1012
1016
  "path": "src/codex/catalog/sync.ts",
1013
- "sha256": "7edb13998aa9eac65bb9deea7ee1dc387909eefc6997759865fb7639936abc9f"
1017
+ "sha256": "e775d8715527a47175f45006f21cec4c03e71ab6224acc7841f62a236f7cdf4a"
1014
1018
  },
1015
1019
  {
1016
1020
  "path": "src/codex/cli-install-provenance.ts",
@@ -1454,12 +1458,16 @@
1454
1458
  },
1455
1459
  {
1456
1460
  "path": "src/config.ts",
1457
- "sha256": "5fa1f245d0342663a5c0844c69252475a4130d73f01c5ac6592e9e9e772a3233"
1461
+ "sha256": "07e17c807d8e8546fbbffc746279bfab8caf76667c80c51968f4b9add14b03d5"
1458
1462
  },
1459
1463
  {
1460
1464
  "path": "src/config/atomic-write.ts",
1461
1465
  "sha256": "96e61002678c6f425518aece2e3fb4fd3ae49e25c375d337eecd676ae5d859c6"
1462
1466
  },
1467
+ {
1468
+ "path": "src/config/initialize.ts",
1469
+ "sha256": "12e07b539b32fbd7b4d96f0a92c3ba8c520c46aaea41a15f604d2f965daddde4"
1470
+ },
1463
1471
  {
1464
1472
  "path": "src/config/paths.ts",
1465
1473
  "sha256": "c95e06d52ea41a77d5edcab36d259d167e688a294362121949ad712b9a8a39b1"
@@ -1490,7 +1498,7 @@
1490
1498
  },
1491
1499
  {
1492
1500
  "path": "src/config/rebase-provenance.ts",
1493
- "sha256": "8ee5ee9c8110c71b09fdaeb806bdd4a8b8ce6e4a8ad447fbf06fc484c26f8fda"
1501
+ "sha256": "97dc1c17ccb9298ac963b67c1cd2f15d5ec853648c03b0505492941986e72e0f"
1494
1502
  },
1495
1503
  {
1496
1504
  "path": "src/config/subagent-models.ts",
@@ -2414,7 +2422,7 @@
2414
2422
  },
2415
2423
  {
2416
2424
  "path": "src/lib/windows-secret-acl.ts",
2417
- "sha256": "1ebb5a92b28a0ea6b4d019f823a4646393d5da05fe6331bc1ad5b95a7c1c147c"
2425
+ "sha256": "361952975f0379faaf3fae33088bac8386bb55e90be95bf2d051a4d2abbfcb27"
2418
2426
  },
2419
2427
  {
2420
2428
  "path": "src/lib/windows-service-mutation-lock.ts",
@@ -2770,7 +2778,7 @@
2770
2778
  },
2771
2779
  {
2772
2780
  "path": "src/providers/quota.ts",
2773
- "sha256": "53f8f308cc1ad1ae30ade207cdfab1600a08af6b5744ba89a68cff6be6389432"
2781
+ "sha256": "d7e0f344e54af750358f2a92b3af9f278e8d88e11c5d695311e7527c55c239af"
2774
2782
  },
2775
2783
  {
2776
2784
  "path": "src/providers/registry.ts",
@@ -2918,7 +2926,7 @@
2918
2926
  },
2919
2927
  {
2920
2928
  "path": "src/responses/state.ts",
2921
- "sha256": "32178760039b27dcaef8d7288fc066bc81a1ee173b756cd3c4b3c9e97864a7cd"
2929
+ "sha256": "f90df1f12e7606ce18c078277ddc43697851d23eac799fd81ca2fc4471e96e5f"
2922
2930
  },
2923
2931
  {
2924
2932
  "path": "src/responses/task-input.ts",
@@ -3066,19 +3074,19 @@
3066
3074
  },
3067
3075
  {
3068
3076
  "path": "src/server/chat-completions.ts",
3069
- "sha256": "d82c27416999a4b57723f7a5aafaa36cfc261c083b10a0482cae9ca5ea7c129a"
3077
+ "sha256": "afeb50f4a401eb7b444274fb2dd23ea578f08546eedb028ac94b5791cc054fef"
3070
3078
  },
3071
3079
  {
3072
3080
  "path": "src/server/chat-native-sse.ts",
3073
- "sha256": "2a183d786d0e5430a80192ed878aa32bed99d7ac53a92c6fe8ee9eccd0a56f65"
3081
+ "sha256": "ddf56df7b59df2d5d818c7b394f11dc3d0948adf00e14fbf4350c652dc9829bb"
3074
3082
  },
3075
3083
  {
3076
3084
  "path": "src/server/chat-native.ts",
3077
- "sha256": "f48d57bb19656e5a6fb8075a67485f5acaec68d76817cd1b29752ed6cb01d734"
3085
+ "sha256": "11bbb0a33b2b21a0fe7022e103ba5f1bf4adc7fe5d77c3d1159243eb94695d70"
3078
3086
  },
3079
3087
  {
3080
3088
  "path": "src/server/claude-messages.ts",
3081
- "sha256": "056a68fdb75af41a62b2d737df63d17b9e6375f22b6f8c1769f955cb68869fdb"
3089
+ "sha256": "5b674921b624b0195565f3abcb1279ec8db4e2fbb905a24b98d956adb4836ba3"
3082
3090
  },
3083
3091
  {
3084
3092
  "path": "src/server/direct-local-http.ts",
@@ -3122,7 +3130,7 @@
3122
3130
  },
3123
3131
  {
3124
3132
  "path": "src/server/index.ts",
3125
- "sha256": "310eea35bb2b472b162cbff9d74440fa7fdae194f033d0e6512dbf8ad2ff0ae2"
3133
+ "sha256": "451299336416e2ff2f3910cb50802fa0ecee2773f0d389b2110cf6b2f97137e0"
3126
3134
  },
3127
3135
  {
3128
3136
  "path": "src/server/lifecycle.ts",
@@ -3154,7 +3162,7 @@
3154
3162
  },
3155
3163
  {
3156
3164
  "path": "src/server/management/agent-settings-routes.ts",
3157
- "sha256": "da47ec85abb598ea480191e8f46ef23832e48f035579aebbbaff6f721be7ba68"
3165
+ "sha256": "6994b27581e2b82c7741b66e77ca038072c4475093c9db73aede22401692d52b"
3158
3166
  },
3159
3167
  {
3160
3168
  "path": "src/server/management/api-access.ts",
@@ -3210,7 +3218,7 @@
3210
3218
  },
3211
3219
  {
3212
3220
  "path": "src/server/management/logs-usage-routes.ts",
3213
- "sha256": "bc8b1a0c0a491443fc40ca0cc4176442bce9c1b0ee95e7798c3a9a83878ec911"
3221
+ "sha256": "e62eb449357833d13ef32575e02416f95ff9c76764c69ce2a0d4edb9c07bc2fe"
3214
3222
  },
3215
3223
  {
3216
3224
  "path": "src/server/management/model-routes.ts",
@@ -3348,9 +3356,13 @@
3348
3356
  "path": "src/server/request-log-conversation.ts",
3349
3357
  "sha256": "97666ff58b3483f29d387bbdf130d0eea8cd357ad0f1c9e40807b8fd1a4b0db1"
3350
3358
  },
3359
+ {
3360
+ "path": "src/server/request-log-cursor.ts",
3361
+ "sha256": "4ccac2e6a8741d3a745fcd6fddcab182bd6f7193887ef1473c8d2f27db05baf5"
3362
+ },
3351
3363
  {
3352
3364
  "path": "src/server/request-log.ts",
3353
- "sha256": "9918e39e25c4f0004ce560e0fd758c21b5e44525260c6fec0f1a2e72c88c87bd"
3365
+ "sha256": "18164c5562e3b06036c023d76b2320fc8c47fef0d6ba534db6038ab34b19b5a3"
3354
3366
  },
3355
3367
  {
3356
3368
  "path": "src/server/responses-custom-tool-repair.ts",
@@ -3414,7 +3426,7 @@
3414
3426
  },
3415
3427
  {
3416
3428
  "path": "src/server/responses/agent-task-recovery.ts",
3417
- "sha256": "c4f6d898fb097ad83b0a69de8b95a01f4a4873c416f6cd6b443b2b20e77d7b69"
3429
+ "sha256": "6041f65c4af7b9e42d041153da1a55a90cd4c7f62a6a48de1c5856761525b2e9"
3418
3430
  },
3419
3431
  {
3420
3432
  "path": "src/server/responses/codex-auth-error.ts",
@@ -3426,7 +3438,7 @@
3426
3438
  },
3427
3439
  {
3428
3440
  "path": "src/server/responses/codex-ws-exchange.ts",
3429
- "sha256": "a0fed1edf95466fb433fbaf7f8250b384bf90bb133c7425539cc1781168af2e5"
3441
+ "sha256": "fcabbf25487ea9f2ab9a4afc1af304964a359bff2948496633041e6403316ac5"
3430
3442
  },
3431
3443
  {
3432
3444
  "path": "src/server/responses/codex-ws-metadata.ts",
@@ -3458,7 +3470,7 @@
3458
3470
  },
3459
3471
  {
3460
3472
  "path": "src/server/responses/compact.ts",
3461
- "sha256": "687b041f879eaf6a676270508cd8b52ca8e744b984e9ed192abdb0b95645d3b6"
3473
+ "sha256": "953c907e6dbad4a7389380400f1302fa013564612dc8e2c426a8bcc7aac5e37c"
3462
3474
  },
3463
3475
  {
3464
3476
  "path": "src/server/responses/context-overflow.ts",
@@ -3466,7 +3478,7 @@
3466
3478
  },
3467
3479
  {
3468
3480
  "path": "src/server/responses/core.ts",
3469
- "sha256": "31cb091db2d14317e12ae16342fa7e264d4243d65d1a78a65787e56cc017eaf5"
3481
+ "sha256": "820be393b77d862ce99ae06ccb2b4f6ae5afb086f617e06f0453fed04941ceae"
3470
3482
  },
3471
3483
  {
3472
3484
  "path": "src/server/responses/empty-completion-guard.ts",
@@ -3598,7 +3610,7 @@
3598
3610
  },
3599
3611
  {
3600
3612
  "path": "src/storage/cleanup.ts",
3601
- "sha256": "93ce034c31eb85f1f179fc55cad30ccec9408f0eb47fb1b051fe8989f668ff42"
3613
+ "sha256": "9af868bf0e722b5b5a60ce6766a486ca03c705c8b07491cdee9cbddbbacf5af7"
3602
3614
  },
3603
3615
  {
3604
3616
  "path": "src/storage/policy-job.ts",
@@ -3670,7 +3682,7 @@
3670
3682
  },
3671
3683
  {
3672
3684
  "path": "src/types/config.ts",
3673
- "sha256": "ea6d2d64f91a5e1be026f8f7182bd28a5823db9005f0bbe803ed05cbed0358de"
3685
+ "sha256": "760afeb7a5c0553f1941dc28350db566b0fdd6c36aadec55e9bd6d2cb3e5b348"
3674
3686
  },
3675
3687
  {
3676
3688
  "path": "src/types/provider.ts",
@@ -3786,7 +3798,7 @@
3786
3798
  },
3787
3799
  {
3788
3800
  "path": "src/usage/log.ts",
3789
- "sha256": "891e5f077334e6703aefc6ef600fa0b45c5e91a1e0416416e550e1775a6c3a60"
3801
+ "sha256": "07f4e9c90a5be6fda0576b2c9de5c8575df87a93c011cfc065303056b4afd763"
3790
3802
  },
3791
3803
  {
3792
3804
  "path": "src/usage/model-identity.ts",
@@ -715,18 +715,22 @@ function sanitizedAclError(diagnostics: string, cause: unknown): NodeJS.ErrnoExc
715
715
  return error;
716
716
  }
717
717
 
718
- function previousTimeoutError(retryConsumed: boolean): NodeJS.ErrnoException {
718
+ type TimeoutMemoRefusalError = NodeJS.ErrnoException & {
719
+ aclFailureOrigin: "timeout_memo_refusal";
720
+ };
721
+
722
+ function previousTimeoutError(retryConsumed: boolean): TimeoutMemoRefusalError {
719
723
  if (retryConsumed) {
720
724
  const error = new Error(
721
725
  "ACL hardening skipped — the previous timeout recovery was already consumed",
722
726
  ) as NodeJS.ErrnoException;
723
727
  error.code = "EACLRETRYEXHAUSTED";
724
- return error;
728
+ return Object.assign(error, { aclFailureOrigin: "timeout_memo_refusal" as const });
725
729
  }
726
- return sanitizedAclError(
730
+ return Object.assign(sanitizedAclError(
727
731
  "ACL hardening skipped — previous attempt timed out",
728
732
  Object.assign(new Error("timeout"), { code: "ETIMEDOUT" }),
729
- );
733
+ ), { aclFailureOrigin: "timeout_memo_refusal" as const });
730
734
  }
731
735
 
732
736
  /** Consume, but never reset, the single explicit recovery attempt for this key. */
@@ -2607,11 +2607,22 @@ function parseAntigravityQuotaSummary(body: Record<string, unknown> | null): Pro
2607
2607
  }
2608
2608
 
2609
2609
  const ANTIGRAVITY_ACCOUNT_QUOTA_BASE = "https://daily-cloudcode-pa.googleapis.com";
2610
- let antigravityOutboundDependencies: ProviderOutboundDependencies = {};
2610
+ const ANTIGRAVITY_QUOTA_SUMMARY_URL = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:retrieveUserQuotaSummary`;
2611
+ const ANTIGRAVITY_QUOTA_MODELS_URL = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:fetchAvailableModels`;
2611
2612
 
2612
- /** Test seam: inject resolver/pinned transport for the per-account Antigravity probe. */
2613
+ /** Only these fixed accounting destinations may use transparent Fake-IP DNS. */
2614
+ export function isCanonicalAntigravityQuotaUrl(name: string, url: string): boolean {
2615
+ return name === "google-antigravity"
2616
+ && (url === ANTIGRAVITY_QUOTA_SUMMARY_URL || url === ANTIGRAVITY_QUOTA_MODELS_URL);
2617
+ }
2618
+
2619
+ let antigravityOutboundDependencies: ProviderOutboundDependencies = {
2620
+ isCanonicalUrl: isCanonicalAntigravityQuotaUrl,
2621
+ };
2622
+
2623
+ /** Test seam: inject resolver/pinned transport for provider and per-account probes. */
2613
2624
  export function setAntigravityAccountQuotaTransportForTests(dependencies: ProviderOutboundDependencies | null): void {
2614
- antigravityOutboundDependencies = dependencies ?? {};
2625
+ antigravityOutboundDependencies = { ...dependencies, isCanonicalUrl: isCanonicalAntigravityQuotaUrl };
2615
2626
  }
2616
2627
 
2617
2628
  /**
@@ -2622,7 +2633,7 @@ export function setAntigravityAccountQuotaTransportForTests(dependencies: Provid
2622
2633
  * A redirect or non-2xx yields null (unavailable), never a partial row.
2623
2634
  */
2624
2635
  export async function fetchAntigravityUsageQuota(accessToken: string, projectId: string): Promise<ProviderQuota | null> {
2625
- const summaryUrl = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:retrieveUserQuotaSummary`;
2636
+ const summaryUrl = ANTIGRAVITY_QUOTA_SUMMARY_URL;
2626
2637
  try {
2627
2638
  const summaryResponse = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, summaryUrl, {
2628
2639
  headers: {
@@ -2644,7 +2655,7 @@ export async function fetchAntigravityUsageQuota(accessToken: string, projectId:
2644
2655
  // Fallback to fetchAvailableModels on error
2645
2656
  }
2646
2657
 
2647
- const url = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:fetchAvailableModels`;
2658
+ const url = ANTIGRAVITY_QUOTA_MODELS_URL;
2648
2659
  const response = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
2649
2660
  headers: {
2650
2661
  Accept: "application/json",
@@ -2662,7 +2673,7 @@ export async function fetchAntigravityUsageQuota(accessToken: string, projectId:
2662
2673
  return { customWindows, updatedAt: Date.now() };
2663
2674
  }
2664
2675
 
2665
- async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig): Promise<ProviderQuotaReport | null> {
2676
+ async function fetchAntigravityQuota(provider: string): Promise<ProviderQuotaReport | null> {
2666
2677
  const credential = getCredential("google-antigravity");
2667
2678
  if (!credential?.projectId) return null;
2668
2679
  let accessToken: string;
@@ -2671,13 +2682,12 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2671
2682
  } catch {
2672
2683
  return null;
2673
2684
  }
2674
- const baseUrl = (config.baseUrl || ANTIGRAVITY_ACCOUNT_QUOTA_BASE).replace(/\/+$/, "");
2675
2685
 
2676
- // The summary probe is pinned to Google's own host through the provider-outbound
2686
+ // Both probes are pinned to Google's own host through the provider-outbound
2677
2687
  // transport, mirroring `fetchAntigravityUsageQuota` above: a configured `baseUrl` is a
2678
- // routing choice for requests, not a second source of Google's accounting, and this
2679
- // request carries the account bearer.
2680
- const summaryUrl = `${ANTIGRAVITY_ACCOUNT_QUOTA_BASE}/v1internal:retrieveUserQuotaSummary`;
2688
+ // routing choice for requests, not a second source of Google's accounting, and these
2689
+ // requests carry the account bearer.
2690
+ const summaryUrl = ANTIGRAVITY_QUOTA_SUMMARY_URL;
2681
2691
  try {
2682
2692
  const summaryResponse = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, summaryUrl, {
2683
2693
  headers: {
@@ -2701,8 +2711,8 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2701
2711
  // Fallback on network/fetch error
2702
2712
  }
2703
2713
 
2704
- const response = await fetch(`${baseUrl}/v1internal:fetchAvailableModels`, {
2705
- method: "POST",
2714
+ const url = ANTIGRAVITY_QUOTA_MODELS_URL;
2715
+ const response = await providerOutboundPost("google-antigravity", { baseUrl: ANTIGRAVITY_ACCOUNT_QUOTA_BASE }, url, {
2706
2716
  headers: {
2707
2717
  Accept: "application/json",
2708
2718
  "Content-Type": "application/json",
@@ -2711,7 +2721,8 @@ async function fetchAntigravityQuota(provider: string, config: OcxProviderConfig
2711
2721
  },
2712
2722
  body: JSON.stringify({ project: credential.projectId }),
2713
2723
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
2714
- });
2724
+ }, antigravityOutboundDependencies);
2725
+ if (await providerRedirectError(response, url)) return null;
2715
2726
  if (!response.ok) return null;
2716
2727
  const customWindows = antigravityWindowsFromModels(asRecord(await readQuotaJson(response)));
2717
2728
  if (customWindows.length === 0) return null;
@@ -2783,7 +2794,7 @@ async function maybeFetchProviderQuota(
2783
2794
  }
2784
2795
  if (provider.authMode === "oauth" && explicitAccountReader(name)) return await fetchExplicitCurrentQuota(name, provider, config);
2785
2796
  if (provider.authMode === "oauth" && name === "anthropic") return fetchAnthropicQuota(name);
2786
- if (provider.authMode === "oauth" && name === "google-antigravity") return fetchAntigravityQuota(name, provider);
2797
+ if (provider.authMode === "oauth" && name === "google-antigravity") return await fetchAntigravityQuota(name);
2787
2798
  if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
2788
2799
  // Passive providers (meta-muse): Meta publishes no quota endpoint, so there is no
2789
2800
  // probe to run — the row is the active account's last in-band observation.