@yansigit/opencodex 2.36.1-dev.20260829.47 → 2.36.1-dev.20260829.50
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/{ApiKeys-DAuYQsGJ.js → ApiKeys-BLPOvK4z.js} +1 -1
- package/gui/dist/assets/{Claude-bN_80RT8.js → Claude-BbHv3EU7.js} +1 -1
- package/gui/dist/assets/{CodexSet-D3dWX_ay.js → CodexSet-hgu5hES-.js} +1 -1
- package/gui/dist/assets/{FileIntegrationPage-BGyzkPs2.js → FileIntegrationPage-RCmDvvsx.js} +1 -1
- package/gui/dist/assets/{Grok-CuaKd1TT.js → Grok-DH-ggRqA.js} +1 -1
- package/gui/dist/assets/{Integrations-C7NGQoxR.js → Integrations-D6m5E2-T.js} +2 -2
- package/gui/dist/assets/{IntegrationsOverview-DjVorYO5.js → IntegrationsOverview-C1rbnXV7.js} +1 -1
- package/gui/dist/assets/{Logs-B21a-9xe.js → Logs-Bqb0vYRk.js} +1 -1
- package/gui/dist/assets/{Models-CN7Q3kuK.js → Models-gMnzHf0c.js} +1 -1
- package/gui/dist/assets/{NumberStepper-BWg90tEs.js → NumberStepper-awaoZNlj.js} +1 -1
- package/gui/dist/assets/{Providers-CP_i-7LS.js → Providers-Bo6wuQMa.js} +1 -1
- package/gui/dist/assets/{RestoreDialog-3M0NqCiD.js → RestoreDialog-DX7SGWIk.js} +1 -1
- package/gui/dist/assets/{Startup-BVPFoP2s.js → Startup-B1htiLU9.js} +1 -1
- package/gui/dist/assets/{Storage-BdsG9ypz.js → Storage-B3MfosmS.js} +1 -1
- package/gui/dist/assets/{Subagents-CguR_bGQ.js → Subagents-L6Q2V9PZ.js} +1 -1
- package/gui/dist/assets/{Usage-C0kKi9_H.js → Usage-xbZnkDoj.js} +1 -1
- package/gui/dist/assets/{codex-stale-banner-D4gvSWnO.js → codex-stale-banner-W4FhikN7.js} +1 -1
- package/gui/dist/assets/{data-surface-BtO3lwam.js → data-surface-B8PX8rjm.js} +1 -1
- package/gui/dist/assets/{data-surface-BCycbObp.js → data-surface-BbBMp0Wj.js} +1 -1
- package/gui/dist/assets/{index-CU1jE0st.js → index-JEC_6CUW.js} +3 -3
- package/gui/dist/assets/{model-display-D2elXkuE.js → model-display-BvLWy9D5.js} +1 -1
- package/gui/dist/assets/{provider-payload-DfkXWE7v.js → provider-payload-BRfSsonS.js} +1 -1
- package/gui/dist/assets/{section-tabs-BP7gEPK6.js → section-tabs-B8sLMiz6.js} +1 -1
- package/gui/dist/assets/shared-DI18-uF7.js +69 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/base.ts +26 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +34 -41
- package/src/adapters/cursor/envelope-echo.ts +128 -0
- package/src/adapters/cursor/request-builder.ts +19 -12
- package/src/adapters/cursor/tool-definitions.ts +2 -1
- package/src/adapters/cursor/tool-result-normalize.ts +23 -31
- package/src/adapters/cursor.ts +21 -2
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +71 -2
- package/src/adapters/google.ts +19 -4
- package/src/adapters/kiro-constants.ts +12 -0
- package/src/adapters/kiro.ts +128 -11
- package/src/adapters/openai-chat.ts +16 -2
- package/src/adapters/openai-responses.ts +15 -2
- package/src/adapters/tool-catalog-nudge.ts +2 -1
- package/src/adapters/xai-web-search.ts +10 -14
- package/src/claude/outbound.ts +14 -3
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +93 -16
- package/src/cli/account-extended.ts +262 -36
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +40 -10
- package/src/cli/agent.ts +8 -1
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +535 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/config-command.ts +7 -3
- package/src/cli/dispatch.ts +195 -27
- package/src/cli/doctor.ts +100 -1
- package/src/cli/help.ts +11 -2
- package/src/cli/index.ts +19 -3
- package/src/cli/init.ts +2 -2
- package/src/cli/inspect.ts +230 -0
- package/src/cli/observe.ts +11 -3
- package/src/cli/provider.ts +58 -74
- package/src/cli/registry.ts +34 -2
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +16 -0
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/usage-report.ts +52 -2
- package/src/cli/version-skew.ts +46 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/catalog/provider-fetch.ts +4 -0
- package/src/codex/convergence.ts +110 -15
- package/src/codex/transition-state.ts +12 -3
- package/src/compatibility/openai-responses.ts +9 -1
- package/src/config.ts +209 -11
- package/src/generated/compatibility-version.json +115 -75
- package/src/integrations/ownership-policy.ts +24 -5
- package/src/integrations/ownership.ts +36 -2
- package/src/integrations/state.ts +40 -7
- package/src/integrations/writer.ts +21 -3
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/errors.ts +25 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/oauth/index.ts +143 -82
- package/src/oauth/login-cli.ts +53 -11
- package/src/oauth/store.ts +16 -6
- package/src/providers/alibaba-region-startup.ts +31 -11
- package/src/providers/api-keys.ts +58 -34
- package/src/providers/key-failover.ts +59 -46
- package/src/providers/label.ts +34 -1
- package/src/providers/model-rename-startup.ts +25 -7
- package/src/providers/openai-tier-startup.ts +38 -14
- package/src/responses/turn-termination.ts +107 -0
- package/src/server/index.ts +10 -0
- package/src/server/management/context.ts +10 -3
- package/src/server/management/logs-usage-routes.ts +24 -43
- package/src/server/management/model-routes.ts +284 -165
- package/src/server/management/oauth-account-routes.ts +93 -15
- package/src/server/management/provider-routes.ts +200 -87
- package/src/server/management/route-registry.ts +317 -0
- package/src/server/proxy-liveness.ts +27 -4
- package/src/server/request-log.ts +37 -17
- package/src/server/responses/core.ts +80 -4
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/service.ts +34 -0
- package/src/storage/policy-input.ts +166 -0
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +115 -221
- package/src/usage/log.ts +32 -6
- package/src/usage/summary.ts +22 -34
- package/gui/dist/assets/shared-BdYGV-yJ.js +0 -69
package/src/storage/policy.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Privacy: logs never include host paths, digests of file contents, or secrets.
|
|
9
9
|
*/
|
|
10
10
|
import { resolveCodexHomeDir } from "../codex/home";
|
|
11
|
-
import { loadConfig,
|
|
11
|
+
import { loadConfig, mutatePersistedConfig } from "../config";
|
|
12
12
|
import type { StorageCleanupPolicy } from "../types";
|
|
13
13
|
import {
|
|
14
14
|
computePreviewDigest,
|
|
@@ -22,9 +22,21 @@ import {
|
|
|
22
22
|
type CleanupResult,
|
|
23
23
|
type ExecuteCleanupOptions,
|
|
24
24
|
} from "./cleanup";
|
|
25
|
+
import {
|
|
26
|
+
computeNextRun,
|
|
27
|
+
normalizeStorageCleanupPolicy,
|
|
28
|
+
} from "./policy-input";
|
|
29
|
+
export {
|
|
30
|
+
computeNextRun,
|
|
31
|
+
defaultStorageCleanupPolicy,
|
|
32
|
+
isValidPolicyTarget,
|
|
33
|
+
normalizeStorageCleanupPolicy,
|
|
34
|
+
parseStorageCleanupPolicyInput,
|
|
35
|
+
DEFAULT_ARCHIVED_BYTES_OVER,
|
|
36
|
+
DEFAULT_REMOVE_OLDEST_PERCENT,
|
|
37
|
+
type PolicySchedule,
|
|
38
|
+
} from "./policy-input";
|
|
25
39
|
|
|
26
|
-
export const DEFAULT_ARCHIVED_BYTES_OVER = 5 * 1024 ** 3; // 5 GiB
|
|
27
|
-
export const DEFAULT_REMOVE_OLDEST_PERCENT = 25;
|
|
28
40
|
export const BUSY_DEFER_MS = 15 * 60 * 1000;
|
|
29
41
|
|
|
30
42
|
/** Optional sink so background policy writes stay synced with the live server config. */
|
|
@@ -36,8 +48,12 @@ export function setStorageCleanupPolicyLiveSink(
|
|
|
36
48
|
livePolicySink = sink ?? undefined;
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
51
|
+
function adoptStorageCleanupPolicy(policy: StorageCleanupPolicy): void {
|
|
52
|
+
livePolicySink?.(policy);
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
export type PolicyRunReason = "startup" | "schedule" | "manual";
|
|
56
|
+
export type PolicyMetadataPersistenceError = "missing" | "invalid" | "conflict" | "write_failed";
|
|
41
57
|
|
|
42
58
|
export type PolicySkipReason =
|
|
43
59
|
| "disabled"
|
|
@@ -54,6 +70,7 @@ export interface PolicyRunResult {
|
|
|
54
70
|
freedBytes?: number;
|
|
55
71
|
removed?: number;
|
|
56
72
|
trashDir?: string;
|
|
73
|
+
metadataPersistenceError?: PolicyMetadataPersistenceError;
|
|
57
74
|
policy: StorageCleanupPolicy;
|
|
58
75
|
}
|
|
59
76
|
|
|
@@ -76,206 +93,20 @@ export interface PolicyRunDeps {
|
|
|
76
93
|
onPolicyLoaded?: () => void;
|
|
77
94
|
}
|
|
78
95
|
|
|
79
|
-
/** Canonical defaults — enabled is always false. */
|
|
80
|
-
export function defaultStorageCleanupPolicy(): StorageCleanupPolicy {
|
|
81
|
-
return {
|
|
82
|
-
enabled: false,
|
|
83
|
-
trigger: { archivedBytesOver: DEFAULT_ARCHIVED_BYTES_OVER },
|
|
84
|
-
target: { removeOldestPercent: DEFAULT_REMOVE_OLDEST_PERCENT },
|
|
85
|
-
schedule: "manual",
|
|
86
|
-
mode: "quarantine",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isFiniteNonNegInt(n: unknown): n is number {
|
|
91
|
-
return typeof n === "number" && Number.isFinite(n) && n >= 0 && Math.floor(n) === n;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isFinitePositiveInt(n: unknown): n is number {
|
|
95
|
-
return typeof n === "number" && Number.isFinite(n) && n > 0 && Math.floor(n) === n;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** True when target has exactly one of reduceToBytes / removeOldestPercent. */
|
|
99
|
-
export function isValidPolicyTarget(target: StorageCleanupPolicy["target"]): boolean {
|
|
100
|
-
if (!target || typeof target !== "object") return false;
|
|
101
|
-
const reduce = (target as { reduceToBytes?: unknown }).reduceToBytes;
|
|
102
|
-
const percent = (target as { removeOldestPercent?: unknown }).removeOldestPercent;
|
|
103
|
-
const hasReduce = reduce !== undefined;
|
|
104
|
-
const hasPercent = percent !== undefined;
|
|
105
|
-
if (hasReduce === hasPercent) return false; // none or both
|
|
106
|
-
if (hasReduce) return isFiniteNonNegInt(reduce);
|
|
107
|
-
return typeof percent === "number" && Number.isFinite(percent) && percent > 0 && percent <= 100;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Normalize a partial/unknown policy into a complete StorageCleanupPolicy.
|
|
112
|
-
* Never flips enabled to true unless the input explicitly sets enabled: true.
|
|
113
|
-
*/
|
|
114
|
-
export function normalizeStorageCleanupPolicy(raw: unknown): StorageCleanupPolicy {
|
|
115
|
-
const base = defaultStorageCleanupPolicy();
|
|
116
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
|
|
117
|
-
const o = raw as Record<string, unknown>;
|
|
118
|
-
|
|
119
|
-
// only explicit true enables — and never when a present target is malformed
|
|
120
|
-
let enabled = o.enabled === true;
|
|
121
|
-
|
|
122
|
-
let archivedBytesOver = base.trigger.archivedBytesOver;
|
|
123
|
-
if (o.trigger && typeof o.trigger === "object" && !Array.isArray(o.trigger)) {
|
|
124
|
-
const t = (o.trigger as { archivedBytesOver?: unknown }).archivedBytesOver;
|
|
125
|
-
if (isFiniteNonNegInt(t)) archivedBytesOver = t;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let target: StorageCleanupPolicy["target"] = base.target;
|
|
129
|
-
if (Object.prototype.hasOwnProperty.call(o, "target")) {
|
|
130
|
-
if (o.target && typeof o.target === "object" && !Array.isArray(o.target)) {
|
|
131
|
-
const candidate = o.target as StorageCleanupPolicy["target"];
|
|
132
|
-
if (isValidPolicyTarget(candidate)) {
|
|
133
|
-
const reduce = (candidate as { reduceToBytes?: number }).reduceToBytes;
|
|
134
|
-
const percent = (candidate as { removeOldestPercent?: number }).removeOldestPercent;
|
|
135
|
-
target = reduce !== undefined
|
|
136
|
-
? { reduceToBytes: reduce }
|
|
137
|
-
: { removeOldestPercent: Math.min(100, Math.max(1, Math.floor(percent!))) };
|
|
138
|
-
} else {
|
|
139
|
-
// Fail closed: malformed persisted target must not become delete-oldest 25%.
|
|
140
|
-
enabled = false;
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
enabled = false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const schedule: PolicySchedule =
|
|
148
|
-
o.schedule === "startup" || o.schedule === "daily" || o.schedule === "weekly" || o.schedule === "manual"
|
|
149
|
-
? o.schedule
|
|
150
|
-
: base.schedule;
|
|
151
|
-
|
|
152
|
-
const mode: CleanupMode = o.mode === "permanent" ? "permanent" : "quarantine";
|
|
153
|
-
|
|
154
|
-
let lastRun: StorageCleanupPolicy["lastRun"];
|
|
155
|
-
if (o.lastRun && typeof o.lastRun === "object" && !Array.isArray(o.lastRun)) {
|
|
156
|
-
const lr = o.lastRun as Record<string, unknown>;
|
|
157
|
-
if (
|
|
158
|
-
isFinitePositiveInt(lr.at)
|
|
159
|
-
&& isFiniteNonNegInt(lr.freedBytes)
|
|
160
|
-
&& isFiniteNonNegInt(lr.removed)
|
|
161
|
-
) {
|
|
162
|
-
lastRun = { at: lr.at, freedBytes: lr.freedBytes, removed: lr.removed };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
let nextRun: number | undefined;
|
|
167
|
-
if (o.nextRun === undefined || o.nextRun === null) {
|
|
168
|
-
nextRun = undefined;
|
|
169
|
-
} else if (isFinitePositiveInt(o.nextRun)) {
|
|
170
|
-
nextRun = o.nextRun;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
enabled,
|
|
175
|
-
trigger: { archivedBytesOver },
|
|
176
|
-
target,
|
|
177
|
-
schedule,
|
|
178
|
-
mode,
|
|
179
|
-
...(lastRun ? { lastRun } : {}),
|
|
180
|
-
...(nextRun !== undefined ? { nextRun } : {}),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Validate a PUT body. Returns a normalized policy or an error string.
|
|
186
|
-
* Does not invent enabled=true.
|
|
187
|
-
*/
|
|
188
|
-
export function parseStorageCleanupPolicyInput(
|
|
189
|
-
raw: unknown,
|
|
190
|
-
previous?: StorageCleanupPolicy,
|
|
191
|
-
): { ok: true; policy: StorageCleanupPolicy } | { ok: false; error: string } {
|
|
192
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
193
|
-
return { ok: false, error: "body must be a JSON object" };
|
|
194
|
-
}
|
|
195
|
-
const o = raw as Record<string, unknown>;
|
|
196
|
-
const prev = previous ?? defaultStorageCleanupPolicy();
|
|
197
|
-
|
|
198
|
-
if (o.enabled !== undefined && typeof o.enabled !== "boolean") {
|
|
199
|
-
return { ok: false, error: "enabled must be a boolean" };
|
|
200
|
-
}
|
|
201
|
-
if (o.mode !== undefined && o.mode !== "quarantine" && o.mode !== "permanent") {
|
|
202
|
-
return { ok: false, error: "mode must be quarantine or permanent" };
|
|
203
|
-
}
|
|
204
|
-
if (
|
|
205
|
-
o.schedule !== undefined
|
|
206
|
-
&& o.schedule !== "startup"
|
|
207
|
-
&& o.schedule !== "daily"
|
|
208
|
-
&& o.schedule !== "weekly"
|
|
209
|
-
&& o.schedule !== "manual"
|
|
210
|
-
) {
|
|
211
|
-
return { ok: false, error: "schedule must be startup, daily, weekly, or manual" };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const merged: Record<string, unknown> = {
|
|
215
|
-
...prev,
|
|
216
|
-
...o,
|
|
217
|
-
trigger: o.trigger !== undefined ? o.trigger : prev.trigger,
|
|
218
|
-
target: o.target !== undefined ? o.target : prev.target,
|
|
219
|
-
// Preserve run metadata unless the client explicitly sends replacements.
|
|
220
|
-
lastRun: o.lastRun !== undefined ? o.lastRun : prev.lastRun,
|
|
221
|
-
nextRun: o.nextRun !== undefined ? o.nextRun : prev.nextRun,
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
if (o.trigger !== undefined) {
|
|
225
|
-
if (!o.trigger || typeof o.trigger !== "object" || Array.isArray(o.trigger)) {
|
|
226
|
-
return { ok: false, error: "trigger must be an object" };
|
|
227
|
-
}
|
|
228
|
-
const bytes = (o.trigger as { archivedBytesOver?: unknown }).archivedBytesOver;
|
|
229
|
-
if (!isFiniteNonNegInt(bytes)) {
|
|
230
|
-
return { ok: false, error: "trigger.archivedBytesOver must be a non-negative integer" };
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (o.target !== undefined) {
|
|
235
|
-
if (!isValidPolicyTarget(o.target as StorageCleanupPolicy["target"])) {
|
|
236
|
-
return {
|
|
237
|
-
ok: false,
|
|
238
|
-
error: "target must set exactly one of reduceToBytes (non-negative int) or removeOldestPercent (1-100)",
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Clients must not clear lastRun/nextRun via null unless intentional — accept omit only.
|
|
244
|
-
const policy = normalizeStorageCleanupPolicy(merged);
|
|
245
|
-
// Recompute nextRun only when schedule newly becomes timed, or no valid nextRun exists.
|
|
246
|
-
// Do not reset nextRun on every PUT that merely re-sends an unchanged schedule.
|
|
247
|
-
if (
|
|
248
|
-
(policy.schedule === "daily" || policy.schedule === "weekly")
|
|
249
|
-
&& o.nextRun === undefined
|
|
250
|
-
&& (policy.nextRun === undefined || (o.schedule !== undefined && o.schedule !== prev.schedule))
|
|
251
|
-
) {
|
|
252
|
-
policy.nextRun = computeNextRun(policy.schedule, Date.now());
|
|
253
|
-
}
|
|
254
|
-
if (policy.schedule === "manual" || policy.schedule === "startup") {
|
|
255
|
-
if (o.nextRun === undefined) delete policy.nextRun;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return { ok: true, policy };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
96
|
export function readStorageCleanupPolicyFromConfig(): StorageCleanupPolicy {
|
|
262
97
|
return normalizeStorageCleanupPolicy(loadConfig().storageCleanupPolicy);
|
|
263
98
|
}
|
|
264
99
|
|
|
265
100
|
export function writeStorageCleanupPolicyToConfig(policy: StorageCleanupPolicy): StorageCleanupPolicy {
|
|
266
101
|
const normalized = normalizeStorageCleanupPolicy(policy);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
export function computeNextRun(schedule: PolicySchedule, now: number): number | undefined {
|
|
276
|
-
if (schedule === "daily") return now + 24 * 60 * 60 * 1000;
|
|
277
|
-
if (schedule === "weekly") return now + 7 * 24 * 60 * 60 * 1000;
|
|
278
|
-
return undefined;
|
|
102
|
+
const outcome = mutatePersistedConfig(fresh => {
|
|
103
|
+
const changed = JSON.stringify(fresh.storageCleanupPolicy) !== JSON.stringify(normalized);
|
|
104
|
+
fresh.storageCleanupPolicy = normalized;
|
|
105
|
+
return { changed, value: structuredClone(normalized) };
|
|
106
|
+
});
|
|
107
|
+
if (outcome.status === "unavailable") throw new Error(`storage cleanup policy persistence unavailable: ${outcome.reason}`);
|
|
108
|
+
adoptStorageCleanupPolicy(outcome.value);
|
|
109
|
+
return outcome.value;
|
|
279
110
|
}
|
|
280
111
|
|
|
281
112
|
export function isPolicyDue(
|
|
@@ -401,6 +232,21 @@ export type PolicyRunMetadataPatch = {
|
|
|
401
232
|
lastRun?: StorageCleanupPolicy["lastRun"];
|
|
402
233
|
};
|
|
403
234
|
|
|
235
|
+
/** Apply only run-owned fields while preserving the supplied policy settings. */
|
|
236
|
+
function applyPolicyRunMetadata(
|
|
237
|
+
policy: StorageCleanupPolicy,
|
|
238
|
+
patch: PolicyRunMetadataPatch,
|
|
239
|
+
): StorageCleanupPolicy {
|
|
240
|
+
let next =
|
|
241
|
+
patch.nextRun === "defer_busy"
|
|
242
|
+
? deferBusy(policy, patch.now)
|
|
243
|
+
: advanceNextRun(policy, patch.now);
|
|
244
|
+
if (patch.lastRun) {
|
|
245
|
+
next = { ...next, lastRun: patch.lastRun };
|
|
246
|
+
}
|
|
247
|
+
return next;
|
|
248
|
+
}
|
|
249
|
+
|
|
404
250
|
/**
|
|
405
251
|
* Reload the latest persisted policy and write only run-owned metadata
|
|
406
252
|
* (`lastRun` / `nextRun`). Preserves concurrent edits to enabled, trigger,
|
|
@@ -412,17 +258,58 @@ export function commitPolicyRunMetadata(
|
|
|
412
258
|
patch: PolicyRunMetadataPatch,
|
|
413
259
|
): StorageCleanupPolicy {
|
|
414
260
|
const latest = normalizeStorageCleanupPolicy(load());
|
|
415
|
-
|
|
416
|
-
patch.nextRun === "defer_busy"
|
|
417
|
-
? deferBusy(latest, patch.now)
|
|
418
|
-
: advanceNextRun(latest, patch.now);
|
|
419
|
-
if (patch.lastRun) {
|
|
420
|
-
next = { ...next, lastRun: patch.lastRun };
|
|
421
|
-
}
|
|
261
|
+
const next = applyPolicyRunMetadata(latest, patch);
|
|
422
262
|
save(next);
|
|
423
263
|
return next;
|
|
424
264
|
}
|
|
425
265
|
|
|
266
|
+
type PolicyRunMetadataCommit = {
|
|
267
|
+
policy: StorageCleanupPolicy;
|
|
268
|
+
persistenceError?: PolicyMetadataPersistenceError;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/** Attach the durable metadata outcome without replacing cleanup status or metrics. */
|
|
272
|
+
function withMetadataCommit(
|
|
273
|
+
result: Omit<PolicyRunResult, "policy" | "metadataPersistenceError">,
|
|
274
|
+
committed: PolicyRunMetadataCommit,
|
|
275
|
+
): PolicyRunResult {
|
|
276
|
+
return {
|
|
277
|
+
...result,
|
|
278
|
+
policy: committed.policy,
|
|
279
|
+
...(committed.persistenceError ? { metadataPersistenceError: committed.persistenceError } : {}),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Recompute run-owned metadata from the latest config inside the mutation lock. */
|
|
284
|
+
function commitPolicyRunMetadataToConfig(
|
|
285
|
+
patch: PolicyRunMetadataPatch,
|
|
286
|
+
fallbackPolicy: StorageCleanupPolicy,
|
|
287
|
+
): PolicyRunMetadataCommit {
|
|
288
|
+
const unavailable = (reason: PolicyMetadataPersistenceError): PolicyRunMetadataCommit => {
|
|
289
|
+
console.warn(`[storage-policy] metadata_persist_failed reason=${reason}`);
|
|
290
|
+
return {
|
|
291
|
+
policy: applyPolicyRunMetadata(fallbackPolicy, patch),
|
|
292
|
+
persistenceError: reason,
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
try {
|
|
296
|
+
const outcome = mutatePersistedConfig(config => {
|
|
297
|
+
const before = JSON.stringify(config.storageCleanupPolicy);
|
|
298
|
+
const next = applyPolicyRunMetadata(
|
|
299
|
+
normalizeStorageCleanupPolicy(config.storageCleanupPolicy),
|
|
300
|
+
patch,
|
|
301
|
+
);
|
|
302
|
+
config.storageCleanupPolicy = next;
|
|
303
|
+
return { changed: JSON.stringify(next) !== before, value: structuredClone(next) };
|
|
304
|
+
});
|
|
305
|
+
if (outcome.status === "unavailable") return unavailable(outcome.reason);
|
|
306
|
+
adoptStorageCleanupPolicy(outcome.value);
|
|
307
|
+
return { policy: outcome.value };
|
|
308
|
+
} catch {
|
|
309
|
+
return unavailable("write_failed");
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
426
313
|
function logPolicyEvent(message: string): void {
|
|
427
314
|
console.log(`[storage-policy] ${message}`);
|
|
428
315
|
}
|
|
@@ -434,10 +321,15 @@ function logPolicyEvent(message: string): void {
|
|
|
434
321
|
export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
|
|
435
322
|
const now = deps.now ?? Date.now();
|
|
436
323
|
const load = deps.loadPolicy ?? readStorageCleanupPolicyFromConfig;
|
|
437
|
-
const save = deps.savePolicy ?? writeStorageCleanupPolicyToConfig;
|
|
438
324
|
const execute = deps.execute ?? executeArchivedCleanup;
|
|
439
|
-
|
|
440
325
|
const policy = normalizeStorageCleanupPolicy(load());
|
|
326
|
+
// Injected stores retain the existing load/save contract. The production path
|
|
327
|
+
// recomputes metadata from the latest persisted policy inside the config lock.
|
|
328
|
+
const commitMetadata = deps.savePolicy !== undefined
|
|
329
|
+
? (patch: PolicyRunMetadataPatch): PolicyRunMetadataCommit => ({
|
|
330
|
+
policy: commitPolicyRunMetadata(load, deps.savePolicy!, patch),
|
|
331
|
+
})
|
|
332
|
+
: (patch: PolicyRunMetadataPatch) => commitPolicyRunMetadataToConfig(patch, policy);
|
|
441
333
|
deps.onPolicyLoaded?.();
|
|
442
334
|
|
|
443
335
|
if (typeof deps.holdAfterLoadMs === "number" && Number.isFinite(deps.holdAfterLoadMs) && deps.holdAfterLoadMs > 0) {
|
|
@@ -454,15 +346,15 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
|
|
|
454
346
|
|
|
455
347
|
const selection = selectPolicyPreview(policy, deps.codexHome);
|
|
456
348
|
if (selection.archivedBytes <= policy.trigger.archivedBytesOver) {
|
|
457
|
-
const
|
|
349
|
+
const committed = commitMetadata({ now, nextRun: "advance" });
|
|
458
350
|
logPolicyEvent("skip under_threshold");
|
|
459
|
-
return { ok: true, skipped: "under_threshold",
|
|
351
|
+
return withMetadataCommit({ ok: true, skipped: "under_threshold" }, committed);
|
|
460
352
|
}
|
|
461
353
|
|
|
462
354
|
if (selection.count === 0) {
|
|
463
|
-
const
|
|
355
|
+
const committed = commitMetadata({ now, nextRun: "advance" });
|
|
464
356
|
logPolicyEvent("skip nothing_selected");
|
|
465
|
-
return { ok: true, skipped: "nothing_selected",
|
|
357
|
+
return withMetadataCommit({ ok: true, skipped: "nothing_selected" }, committed);
|
|
466
358
|
}
|
|
467
359
|
|
|
468
360
|
const result = execute({
|
|
@@ -476,25 +368,28 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
|
|
|
476
368
|
});
|
|
477
369
|
|
|
478
370
|
if (!result.ok && result.error === "codex_busy") {
|
|
479
|
-
const
|
|
371
|
+
const committed = commitMetadata({ now, nextRun: "defer_busy" });
|
|
480
372
|
logPolicyEvent("defer codex_busy");
|
|
481
|
-
return {
|
|
373
|
+
return withMetadataCommit({
|
|
374
|
+
ok: false,
|
|
375
|
+
deferred: "codex_busy",
|
|
376
|
+
error: "codex_busy",
|
|
377
|
+
}, committed);
|
|
482
378
|
}
|
|
483
379
|
|
|
484
380
|
if (!result.ok) {
|
|
485
381
|
// Non-busy failure: still advance schedule so we do not tight-loop.
|
|
486
|
-
const
|
|
382
|
+
const committed = commitMetadata({ now, nextRun: "advance" });
|
|
487
383
|
logPolicyEvent(`fail ${result.error ?? "cleanup_failed"}`);
|
|
488
|
-
return {
|
|
384
|
+
return withMetadataCommit({
|
|
489
385
|
ok: false,
|
|
490
386
|
error: result.error,
|
|
491
387
|
mode: result.mode,
|
|
492
388
|
...(result.trashDir ? { trashDir: result.trashDir } : {}),
|
|
493
|
-
|
|
494
|
-
};
|
|
389
|
+
}, committed);
|
|
495
390
|
}
|
|
496
391
|
|
|
497
|
-
const
|
|
392
|
+
const committed = commitMetadata({
|
|
498
393
|
now,
|
|
499
394
|
nextRun: "advance",
|
|
500
395
|
lastRun: {
|
|
@@ -506,14 +401,13 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
|
|
|
506
401
|
logPolicyEvent(
|
|
507
402
|
`ok mode=${result.mode} removed=${result.count} freedBytes=${result.bytes}`,
|
|
508
403
|
);
|
|
509
|
-
return {
|
|
404
|
+
return withMetadataCommit({
|
|
510
405
|
ok: true,
|
|
511
406
|
mode: result.mode,
|
|
512
407
|
freedBytes: result.bytes,
|
|
513
408
|
removed: result.count,
|
|
514
409
|
...(result.trashDir ? { trashDir: result.trashDir } : {}),
|
|
515
|
-
|
|
516
|
-
};
|
|
410
|
+
}, committed);
|
|
517
411
|
}
|
|
518
412
|
|
|
519
413
|
/** Startup / schedule tick entry — swallows unexpected errors. */
|
package/src/usage/log.ts
CHANGED
|
@@ -8,22 +8,27 @@ import { sanitizeLogMetadataString } from "../lib/redact";
|
|
|
8
8
|
import { usageDisplayTotalTokens } from "./totals";
|
|
9
9
|
import type { AttemptTierOutcome, OcxUsage } from "../types";
|
|
10
10
|
import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
|
|
11
|
-
import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
11
|
+
import { ACCOUNT_LOG_LABEL_RE, CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
12
12
|
import type { AgentKind } from "../server/effort-policy";
|
|
13
13
|
|
|
14
14
|
export type UsageStatus = "reported" | "unreported" | "unsupported" | "estimated";
|
|
15
|
-
export type
|
|
15
|
+
export type UsageAccountLogLabel = "main" | `p${string}` | `o${string}`;
|
|
16
|
+
export type CodexUsageAccountLogLabel = UsageAccountLogLabel;
|
|
16
17
|
|
|
17
18
|
export const OAUTH_ACCOUNT_LOG_LABEL_RE = /^[a-f0-9]{8,64}(?:-\d+)?$/;
|
|
18
19
|
|
|
19
20
|
export function isPersistableAccountLogLabel(value: unknown): value is string {
|
|
20
21
|
return (
|
|
21
|
-
value === "
|
|
22
|
-
|
|
22
|
+
typeof value === "string"
|
|
23
|
+
&& (ACCOUNT_LOG_LABEL_RE.test(value) || OAUTH_ACCOUNT_LOG_LABEL_RE.test(value))
|
|
23
24
|
);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export function isCodexUsageAccountLogLabel(value: unknown): value is
|
|
27
|
+
export function isCodexUsageAccountLogLabel(value: unknown): value is UsageAccountLogLabel {
|
|
28
|
+
return typeof value === "string" && ACCOUNT_LOG_LABEL_RE.test(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isCodexPoolAccountLogLabel(value: unknown): value is "main" | `p${string}` {
|
|
27
32
|
return value === "main" || (typeof value === "string" && CODEX_ACCOUNT_LOG_LABEL_RE.test(value));
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -63,6 +68,13 @@ export interface PersistedUsageAttempt {
|
|
|
63
68
|
sendCount: number;
|
|
64
69
|
recoveryKinds: AttemptRecoveryKind[];
|
|
65
70
|
usageStatus: UsageStatus;
|
|
71
|
+
/**
|
|
72
|
+
* True when the proxy answered this turn locally and issued no upstream request. It travels on
|
|
73
|
+
* the attempt itself rather than as a `finishRequestAttempt` argument because that function is
|
|
74
|
+
* called from six places, and a new parameter would silently default to the wrong answer at any
|
|
75
|
+
* one of them that was missed. Absent on ordinary attempts so old rows keep their exact shape.
|
|
76
|
+
*/
|
|
77
|
+
locallyAnswered?: boolean;
|
|
66
78
|
/** Stable non-PII identity for the account that served this attempt. */
|
|
67
79
|
accountLogLabel?: string;
|
|
68
80
|
inputTokenEstimate?: number;
|
|
@@ -200,8 +212,20 @@ function isEstimatedUsageProvider(providerOrAdapter: string): boolean {
|
|
|
200
212
|
|| providerOrAdapter === "cursor" || providerOrAdapter.startsWith("cursor-");
|
|
201
213
|
}
|
|
202
214
|
|
|
203
|
-
export function usageForFinalLog(
|
|
215
|
+
export function usageForFinalLog(
|
|
216
|
+
provider: string,
|
|
217
|
+
usage: OcxUsage | undefined,
|
|
218
|
+
/**
|
|
219
|
+
* True when the proxy answered this turn locally and issued no upstream request. Such a turn's
|
|
220
|
+
* zero counts are EXACT, so the provider-wide estimated marking must not apply: Kiro and Cursor
|
|
221
|
+
* are marked estimated because their adapters can only guess a real inference's usage, and a
|
|
222
|
+
* turn with no inference has nothing to guess. Without this, a no-send turn is indistinguishable
|
|
223
|
+
* from a real one whose usage frame never arrived.
|
|
224
|
+
*/
|
|
225
|
+
locallyAnswered = false,
|
|
226
|
+
): OcxUsage | undefined {
|
|
204
227
|
if (!usage) return undefined;
|
|
228
|
+
if (locallyAnswered) return usage;
|
|
205
229
|
if (usage.estimated || isEstimatedUsageProvider(provider)) return { ...usage, estimated: true };
|
|
206
230
|
return usage;
|
|
207
231
|
}
|
|
@@ -239,6 +263,8 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
|
|
|
239
263
|
"key-429",
|
|
240
264
|
"rate-limit-429",
|
|
241
265
|
"anthropic-oauth-429",
|
|
266
|
+
"cursor-oauth-auth",
|
|
267
|
+
"cursor-oauth-429",
|
|
242
268
|
"oauth-account-429",
|
|
243
269
|
"image-413",
|
|
244
270
|
"opaque-blob-rejection",
|
package/src/usage/summary.ts
CHANGED
|
@@ -94,7 +94,6 @@ export interface UsageProvider {
|
|
|
94
94
|
|
|
95
95
|
export interface UsageAccount {
|
|
96
96
|
accountLogLabel: string;
|
|
97
|
-
provider?: string;
|
|
98
97
|
ambiguous: boolean;
|
|
99
98
|
requests: number;
|
|
100
99
|
attemptCount: number;
|
|
@@ -685,25 +684,22 @@ function legacyCodexAccountLabel(provider: string): string | null {
|
|
|
685
684
|
return suffix ?? LEGACY_AMBIGUOUS_ACCOUNT_LABEL;
|
|
686
685
|
}
|
|
687
686
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
return
|
|
687
|
+
/**
|
|
688
|
+
* An explicitly stamped label of EITHER family is authoritative for any provider (#2699).
|
|
689
|
+
*
|
|
690
|
+
* No `o`-label branch is needed here: `isCodexUsageAccountLogLabel` now accepts both families,
|
|
691
|
+
* and adding a second predicate call would be a no-op guarded by a comment claiming otherwise.
|
|
692
|
+
*
|
|
693
|
+
* The legacy fallback stays openai-only on purpose. It infers an account from the PROVIDER
|
|
694
|
+
* string, and inferring for a non-Codex row would merge unrelated accounts under one label --
|
|
695
|
+
* so an unlabeled xai row is dropped from the account table rather than guessed at.
|
|
696
|
+
*/
|
|
697
|
+
function accountLabelForAttribution(provider: string, explicit: unknown): string | null {
|
|
698
|
+
if (isCodexUsageAccountLogLabel(explicit)) return explicit;
|
|
699
|
+
return legacyCodexAccountLabel(provider);
|
|
701
700
|
}
|
|
702
701
|
|
|
703
|
-
function buildAccounts(
|
|
704
|
-
entries: PersistedUsageEntry[],
|
|
705
|
-
fallbackAccountByProvider?: Record<string, string>,
|
|
706
|
-
): UsageAccount[] {
|
|
702
|
+
function buildAccounts(entries: PersistedUsageEntry[]): UsageAccount[] {
|
|
707
703
|
const byLabel = new Map<string, UsageAccount>();
|
|
708
704
|
const requestIds = new Map<string, Set<string>>();
|
|
709
705
|
|
|
@@ -716,13 +712,12 @@ function buildAccounts(
|
|
|
716
712
|
totalTokens?: number;
|
|
717
713
|
estimate: ReturnType<typeof estimateRequestCost>;
|
|
718
714
|
}): void => {
|
|
719
|
-
const label = accountLabelForAttribution(input.provider, input.accountLogLabel
|
|
715
|
+
const label = accountLabelForAttribution(input.provider, input.accountLogLabel);
|
|
720
716
|
if (!label) return;
|
|
721
717
|
let row = byLabel.get(label);
|
|
722
718
|
if (!row) {
|
|
723
719
|
row = {
|
|
724
720
|
accountLogLabel: label,
|
|
725
|
-
provider: baseProviderLabel(input.provider),
|
|
726
721
|
ambiguous: label === LEGACY_AMBIGUOUS_ACCOUNT_LABEL,
|
|
727
722
|
requests: 0,
|
|
728
723
|
attemptCount: 0,
|
|
@@ -823,7 +818,6 @@ export function summarizeUsage(
|
|
|
823
818
|
range: UsageRange,
|
|
824
819
|
now: number,
|
|
825
820
|
surface: UsageSurface = "all",
|
|
826
|
-
fallbackAccountByProvider?: Record<string, string>,
|
|
827
821
|
): UsageSummary {
|
|
828
822
|
const { since } = rangeWindow(range, now);
|
|
829
823
|
const filteredEntries = entries.filter(entry => {
|
|
@@ -853,7 +847,7 @@ export function summarizeUsage(
|
|
|
853
847
|
days: buildDayGrid(range, since, now, filteredEntries),
|
|
854
848
|
models: buildModels(filteredEntries, totals.totalTokens),
|
|
855
849
|
providers: buildProviders(filteredEntries, totals.totalTokens),
|
|
856
|
-
accounts: buildAccounts(filteredEntries
|
|
850
|
+
accounts: buildAccounts(filteredEntries),
|
|
857
851
|
};
|
|
858
852
|
}
|
|
859
853
|
|
|
@@ -885,14 +879,12 @@ function normalizeFilterValue(input: string | null | undefined): string | null {
|
|
|
885
879
|
*/
|
|
886
880
|
export function projectUsageSummary<T extends UsageSummary>(
|
|
887
881
|
summary: T,
|
|
888
|
-
filter: { provider?: string | null; model?: string | null
|
|
882
|
+
filter: { provider?: string | null; model?: string | null },
|
|
889
883
|
entries?: PersistedUsageEntry[],
|
|
890
|
-
fallbackAccountByProvider?: Record<string, string>,
|
|
891
884
|
): T & { filter?: UsageFilterEcho } {
|
|
892
885
|
const provider = normalizeFilterValue(filter.provider);
|
|
893
886
|
const model = normalizeFilterValue(filter.model);
|
|
894
|
-
|
|
895
|
-
if (provider === null && model === null && account === null) return summary;
|
|
887
|
+
if (provider === null && model === null) return summary;
|
|
896
888
|
|
|
897
889
|
// Re-summarise from the entries the summary was built from, rather than
|
|
898
890
|
// projecting over its rows.
|
|
@@ -909,13 +901,9 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
909
901
|
//
|
|
910
902
|
// The entries are already in hand on every path that filters, so the honest
|
|
911
903
|
// computation is also the simple one.
|
|
912
|
-
const matches = (rowProvider: string, rowModel: string
|
|
904
|
+
const matches = (rowProvider: string, rowModel: string): boolean => {
|
|
913
905
|
if (provider !== null && baseProviderLabel(rowProvider).toLowerCase() !== provider) return false;
|
|
914
906
|
if (model !== null && rowModel.toLowerCase() !== model) return false;
|
|
915
|
-
if (account !== null && rowAccount !== undefined) {
|
|
916
|
-
const effectiveAccount = accountLabelForAttribution(rowProvider, rowAccount, fallbackAccountByProvider);
|
|
917
|
-
if (!effectiveAccount || effectiveAccount.toLowerCase() !== account) return false;
|
|
918
|
-
}
|
|
919
907
|
return true;
|
|
920
908
|
};
|
|
921
909
|
|
|
@@ -931,10 +919,10 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
931
919
|
const filtered: PersistedUsageEntry[] = [];
|
|
932
920
|
for (const entry of source) {
|
|
933
921
|
if (!entry.attempts?.length) {
|
|
934
|
-
if (matches(entry.provider, antigravityUsageModel(entry.provider, entry.model)
|
|
922
|
+
if (matches(entry.provider, antigravityUsageModel(entry.provider, entry.model))) filtered.push(entry);
|
|
935
923
|
continue;
|
|
936
924
|
}
|
|
937
|
-
const attempts = entry.attempts.filter(a => matches(a.provider, antigravityUsageModel(a.provider, a.model)
|
|
925
|
+
const attempts = entry.attempts.filter(a => matches(a.provider, antigravityUsageModel(a.provider, a.model)));
|
|
938
926
|
if (attempts.length === 0) continue;
|
|
939
927
|
// A combo is still counted once per participating model, so a filtered
|
|
940
928
|
// request count can exceed the number of distinct requests. That is the
|
|
@@ -943,7 +931,7 @@ export function projectUsageSummary<T extends UsageSummary>(
|
|
|
943
931
|
filtered.push({ ...entry, attempts });
|
|
944
932
|
}
|
|
945
933
|
|
|
946
|
-
const projected = summarizeUsage(filtered, summary.range, summary.generatedAt, summary.surface
|
|
934
|
+
const projected = summarizeUsage(filtered, summary.range, summary.generatedAt, summary.surface);
|
|
947
935
|
// matched reflects usage inside the requested WINDOW, not anywhere in the
|
|
948
936
|
// log: summarizeUsage applies the range and surface predicates, and the CLI
|
|
949
937
|
// uses this flag to decide between a table and "no usage recorded".
|