@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.
- package/gui/dist/assets/index-CCfD72yq.js +115 -0
- package/gui/dist/assets/index-J96sug5C.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/openai-responses.ts +14 -0
- package/src/chat/outbound.ts +286 -35
- package/src/claude/compatibility.ts +192 -0
- package/src/claude/model-info.ts +14 -2
- package/src/cli/account-extended.ts +24 -1
- package/src/cli/init.ts +70 -13
- package/src/codex/catalog/provider-fetch.ts +38 -9
- package/src/codex/catalog/sync.ts +34 -2
- package/src/codex/catalog.ts +1 -1
- package/src/config/initialize.ts +132 -0
- package/src/config/rebase-provenance.ts +26 -0
- package/src/config.ts +50 -1
- package/src/generated/compatibility-version.json +41 -29
- package/src/lib/windows-secret-acl.ts +8 -4
- package/src/providers/quota.ts +26 -15
- package/src/responses/state.ts +48 -3
- package/src/server/chat-completions.ts +32 -36
- package/src/server/chat-native-sse.ts +23 -3
- package/src/server/chat-native.ts +15 -8
- package/src/server/claude-messages.ts +27 -0
- package/src/server/index.ts +5 -1
- package/src/server/management/agent-settings-routes.ts +101 -14
- package/src/server/management/logs-usage-routes.ts +9 -2
- package/src/server/request-log-cursor.ts +84 -0
- package/src/server/request-log.ts +46 -3
- package/src/server/responses/agent-task-recovery.ts +50 -14
- package/src/server/responses/codex-ws-exchange.ts +79 -0
- package/src/server/responses/compact.ts +39 -33
- package/src/server/responses/core.ts +43 -27
- package/src/storage/cleanup.ts +49 -35
- package/src/types/config.ts +4 -0
- package/src/usage/log.ts +22 -0
- package/gui/dist/assets/index-B7_K1Hsj.js +0 -115
- 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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
3077
|
+
"sha256": "afeb50f4a401eb7b444274fb2dd23ea578f08546eedb028ac94b5791cc054fef"
|
|
3070
3078
|
},
|
|
3071
3079
|
{
|
|
3072
3080
|
"path": "src/server/chat-native-sse.ts",
|
|
3073
|
-
"sha256": "
|
|
3081
|
+
"sha256": "ddf56df7b59df2d5d818c7b394f11dc3d0948adf00e14fbf4350c652dc9829bb"
|
|
3074
3082
|
},
|
|
3075
3083
|
{
|
|
3076
3084
|
"path": "src/server/chat-native.ts",
|
|
3077
|
-
"sha256": "
|
|
3085
|
+
"sha256": "11bbb0a33b2b21a0fe7022e103ba5f1bf4adc7fe5d77c3d1159243eb94695d70"
|
|
3078
3086
|
},
|
|
3079
3087
|
{
|
|
3080
3088
|
"path": "src/server/claude-messages.ts",
|
|
3081
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
-
|
|
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. */
|
package/src/providers/quota.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
//
|
|
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
|
|
2679
|
-
//
|
|
2680
|
-
const summaryUrl =
|
|
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
|
|
2705
|
-
|
|
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
|
|
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.
|