@bitkyc08/opencodex 2.82.0 → 2.83.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 (85) hide show
  1. package/gui/dist/assets/{App-Ds_qL7o8.js → App-Ba8uG696.js} +4 -4
  2. package/gui/dist/assets/{Tray-BqHF5Trd.js → Tray-IImbNPRy.js} +1 -1
  3. package/gui/dist/assets/{index-D3FBCNiP.js → index-BHRZdJuv.js} +12 -12
  4. package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-DiJO6sYY.js} +1 -1
  5. package/gui/dist/index.html +1 -1
  6. package/package.json +1 -1
  7. package/src/adapters/cursor/catalog.ts +2 -2
  8. package/src/adapters/cursor/effort-map.ts +1 -1
  9. package/src/adapters/devin/live-models.ts +2 -2
  10. package/src/adapters/google-truncation.ts +22 -0
  11. package/src/adapters/google.ts +7 -3
  12. package/src/adapters/ollama-native.ts +18 -14
  13. package/src/adapters/openai-responses/passthrough.ts +2 -0
  14. package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
  15. package/src/adapters/openai-responses/reasoning.ts +11 -1
  16. package/src/bridge/errors.ts +7 -1
  17. package/src/claude/context-windows.ts +3 -3
  18. package/src/claude/desktop-picker.ts +1 -1
  19. package/src/cli/account-auth.ts +20 -12
  20. package/src/cli/resolve.ts +1 -1
  21. package/src/clients/config-export/model-metadata.ts +16 -0
  22. package/src/clients/config-export/zcode-store.ts +46 -11
  23. package/src/clients/config-export/zcode.ts +3 -5
  24. package/src/clients/config-export.ts +1 -1
  25. package/src/codex/auth-api/pool-mode-gate.ts +4 -5
  26. package/src/codex/auth-context.ts +8 -3
  27. package/src/codex/catalog/native-models.ts +1 -1
  28. package/src/codex/catalog/provider-models.ts +25 -0
  29. package/src/codex/desktop-app/darwin.ts +1 -1
  30. package/src/codex/desktop-app/handoff.ts +1 -1
  31. package/src/codex/desktop-app/linux.ts +1 -1
  32. package/src/codex/desktop-app/lock.ts +1 -1
  33. package/src/codex/desktop-app/types.ts +1 -1
  34. package/src/codex/desktop-app/windows.ts +1 -1
  35. package/src/codex/desktop-app-restart.ts +1 -1
  36. package/src/codex/main-account.ts +6 -7
  37. package/src/generated/compatibility-version.json +91 -75
  38. package/src/grok/inject.ts +245 -221
  39. package/src/grok/reset-coupon-ledger.ts +223 -57
  40. package/src/grok/reset-coupons.ts +13 -6
  41. package/src/integrations/cursor-detect.ts +1 -1
  42. package/src/lib/errors.ts +25 -0
  43. package/src/lib/redact.ts +2 -2
  44. package/src/oauth/index.ts +1 -1
  45. package/src/oauth/meta-muse-device.ts +1 -1
  46. package/src/oauth/types.ts +1 -1
  47. package/src/providers/anthropic-fast.ts +1 -1
  48. package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
  49. package/src/providers/devin-cli-authmode-migration.ts +1 -1
  50. package/src/providers/devin-provider-merge-migration.ts +1 -1
  51. package/src/providers/github-copilot-auto.ts +169 -0
  52. package/src/providers/model-presets.ts +1 -1
  53. package/src/providers/muse-subscription-usage.ts +1 -1
  54. package/src/providers/registry/entries-core.ts +6 -6
  55. package/src/providers/registry/entries-extended.ts +4 -0
  56. package/src/providers/registry/model-seeds.ts +3 -3
  57. package/src/providers/xai-fast-model.ts +1 -1
  58. package/src/server/inbound-body-admission.ts +24 -7
  59. package/src/server/index/link-listener.ts +30 -17
  60. package/src/server/index/serve-options.ts +8 -2
  61. package/src/server/index.ts +1 -1
  62. package/src/server/lifecycle.ts +31 -4
  63. package/src/server/management/grok-coupon-routes.ts +277 -52
  64. package/src/server/management/link-routes.ts +42 -20
  65. package/src/server/management/route-registry.ts +2 -2
  66. package/src/server/relay-eager.ts +2 -0
  67. package/src/server/relay.ts +18 -7
  68. package/src/server/request-log-failure-attribution.ts +3 -0
  69. package/src/server/request-log-terminal-status.ts +186 -0
  70. package/src/server/request-log.ts +15 -118
  71. package/src/server/responses/adapter-continuation.ts +9 -1
  72. package/src/server/responses/adapter-dispatch.ts +65 -6
  73. package/src/server/responses/compact.ts +3 -2
  74. package/src/server/responses/core-opaque-recovery.ts +5 -5
  75. package/src/server/responses/core.ts +6 -6
  76. package/src/server/responses/empty-completion-guard.ts +18 -2
  77. package/src/server/responses/jev-model-invoke.ts +1 -1
  78. package/src/server/responses/passthrough-dispatch.ts +115 -60
  79. package/src/server/responses/passthrough-execution.ts +3 -3
  80. package/src/server/responses/request-transport.ts +125 -5
  81. package/src/server/responses/run-turn-execution.ts +13 -5
  82. package/src/server/responses/sidecar-execution.ts +5 -0
  83. package/src/server/responses/spend-storage-error.ts +33 -0
  84. package/src/types/request.ts +5 -0
  85. package/src/usage/expected-prices.ts +1 -1
@@ -1,9 +1,10 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { atomicWriteFile } from "../config/atomic-write";
1
+ import { closeSync, fsyncSync, openSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { atomicWriteFileStreamed } from "../config/atomic-write";
4
+ import { withConfigMutationLockSync } from "../config/mutation-lock";
4
5
  import { getConfigDir } from "../config/paths";
5
6
 
6
- export type GrokResetCouponOperationKind = "execute" | "replay" | "identity-mismatch" | "capacity";
7
+ export type GrokResetCouponOperationKind = "execute" | "replay" | "identity-mismatch" | "token-mismatch" | "capacity";
7
8
 
8
9
  export interface GrokResetCouponOperationIdentity {
9
10
  accountId: string;
@@ -16,6 +17,12 @@ export interface GrokResetCouponOperationRecord {
16
17
  operationId: string;
17
18
  accountId?: string;
18
19
  tokenId?: string;
20
+ /** Token expiry (ms) captured at claim time as journal metadata. Remaining
21
+ * availability or expiry cannot establish this operation's outcome. */
22
+ tokenValidityEnd?: number;
23
+ /** When the attempt was marked (ms) — set only on an "attempted" replay so
24
+ * the route can defer inspection while the attempt may still be in flight. */
25
+ attemptedAt?: number;
19
26
  code?: string;
20
27
  settledAt?: number;
21
28
  }
@@ -23,14 +30,18 @@ export interface GrokResetCouponOperationRecord {
23
30
  interface GrokResetCouponOperationState {
24
31
  accountId: string;
25
32
  tokenId?: string;
26
- status: "open" | "settled" | "failed";
33
+ // "attempted" sits between open and settled: the spend call may have fired,
34
+ // so the operation must never execute again. Its replay carries no code —
35
+ // the route reconciles it against upstream instead of trusting a status.
36
+ status: "open" | "attempted" | "settled" | "failed";
27
37
  code?: string;
38
+ tokenValidityEnd?: number;
28
39
  createdAt: number;
29
40
  updatedAt: number;
30
41
  }
31
42
 
32
43
  interface GrokResetCouponLedger {
33
- version: 1;
44
+ version: 2;
34
45
  operations: Record<string, GrokResetCouponOperationState>;
35
46
  }
36
47
 
@@ -40,95 +51,249 @@ export function grokCouponJournalPath(customDir?: string): string {
40
51
  }
41
52
 
42
53
  function readGrokCouponLedger(filePath: string): GrokResetCouponLedger {
43
- if (!existsSync(filePath)) {
44
- return { version: 1, operations: {} };
54
+ let raw: string;
55
+ try { raw = readFileSync(filePath, "utf-8"); }
56
+ catch (error) {
57
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return { version: 2, operations: {} };
58
+ throw new Error("Grok coupon ledger is unavailable; existing records were preserved.");
45
59
  }
46
60
  try {
47
- const raw = readFileSync(filePath, "utf-8");
48
- const parsed = JSON.parse(raw) as GrokResetCouponLedger;
49
- return parsed && parsed.version === 1 && parsed.operations && typeof parsed.operations === "object"
50
- ? parsed
51
- : { version: 1, operations: {} };
61
+ const parsed = JSON.parse(raw) as Omit<GrokResetCouponLedger, "version"> & { version: unknown };
62
+ if (!parsed || (parsed.version !== 1 && parsed.version !== 2) || !parsed.operations
63
+ || typeof parsed.operations !== "object" || Array.isArray(parsed.operations)) throw new Error();
64
+ for (const op of Object.values(parsed.operations)) {
65
+ if (!op || typeof op.accountId !== "string" || !op.accountId
66
+ || !["open", "attempted", "settled", "failed"].includes(op.status)
67
+ || !Number.isFinite(op.createdAt) || !Number.isFinite(op.updatedAt)
68
+ || (op.tokenId !== undefined && typeof op.tokenId !== "string")
69
+ || (op.code !== undefined && typeof op.code !== "string")
70
+ || (op.tokenValidityEnd !== undefined && !Number.isFinite(op.tokenValidityEnd))) throw new Error();
71
+ }
72
+ if (parsed.version === 1) {
73
+ for (const op of Object.values(parsed.operations)) {
74
+ // Old opens (and transport failures recorded as failed) may already
75
+ // have dispatched. Only version-two opens prove pre-dispatch state.
76
+ if (op.status === "open" || (op.status === "failed" && op.code === "redeem_failed")) {
77
+ op.status = "attempted";
78
+ delete op.code;
79
+ }
80
+ }
81
+ }
82
+ return { version: 2, operations: parsed.operations };
52
83
  } catch {
53
- return { version: 1, operations: {} };
84
+ throw new Error("Grok coupon ledger is unreadable; existing records were preserved.");
54
85
  }
55
86
  }
56
87
 
57
88
  function writeGrokCouponLedger(filePath: string, ledger: GrokResetCouponLedger, now = Date.now()): void {
58
- // Prune settled/failed operations older than 30 days to avoid unbounded growth
89
+ // Prune every record past the 30-day window — including "open" and
90
+ // "attempted". Idempotency retention is explicitly bounded to 30 days;
91
+ // operations are not retry authorities after that window.
59
92
  const retentionCutoff = now - 30 * 24 * 60 * 60_000;
60
93
  ledger.operations = Object.fromEntries(
61
- Object.entries(ledger.operations).filter(
62
- ([, op]) => op.status === "open" || op.updatedAt > retentionCutoff,
63
- ),
94
+ Object.entries(ledger.operations).filter(([, op]) => op.updatedAt > retentionCutoff),
64
95
  );
65
- atomicWriteFile(filePath, JSON.stringify(ledger, null, 2));
96
+ atomicWriteFileStreamed(filePath, descriptor => writeFileSync(descriptor, JSON.stringify(ledger, null, 2)), {
97
+ afterRename: target => {
98
+ // The shared streamed writer flushes the temp. A spend claim must also
99
+ // refuse on a POSIX directory-sync failure before dispatch, rather than
100
+ // relying on that writer's best-effort parent sync. Windows has no
101
+ // portable directory descriptor and retains the writer's platform contract.
102
+ if (process.platform === "win32") return;
103
+ const descriptor = openSync(dirname(target), "r");
104
+ try { fsyncSync(descriptor); }
105
+ finally { closeSync(descriptor); }
106
+ },
107
+ });
66
108
  }
67
109
 
68
110
  const MAX_GROK_RESET_COUPON_OPERATION_IDS = 256;
69
111
 
112
+ /** Resolve an omitted-account retry from its durable identity, within retention. */
113
+ export function getGrokResetCouponOperationAccountId(
114
+ operationId: string,
115
+ now = Date.now(),
116
+ journalPath?: string,
117
+ ): string | undefined {
118
+ return withConfigMutationLockSync(() => {
119
+ const record = readGrokCouponLedger(journalPath ?? grokCouponJournalPath()).operations[operationId];
120
+ if (!record || record.updatedAt <= now - 30 * 24 * 60 * 60_000) return undefined;
121
+ return record.accountId;
122
+ });
123
+ }
124
+
70
125
  export function openGrokResetCouponOperation(
71
126
  identity: GrokResetCouponOperationIdentity,
72
127
  now = Date.now(),
73
128
  journalPath?: string,
74
129
  ): GrokResetCouponOperationRecord {
75
- const filePath = journalPath ?? grokCouponJournalPath();
76
- const ledger = readGrokCouponLedger(filePath);
130
+ // Read→decide→write under the shared config mutation lock: two concurrent
131
+ // coupon requests on the same home must not lose an operation record — the
132
+ // ledger exists precisely to make an irreversible spend replay-safe.
133
+ return withConfigMutationLockSync(() => {
134
+ const filePath = journalPath ?? grokCouponJournalPath();
135
+ const ledger = readGrokCouponLedger(filePath);
77
136
 
78
- if (Object.keys(ledger.operations).length >= MAX_GROK_RESET_COUPON_OPERATION_IDS) {
79
- return { kind: "capacity", operationId: identity.operationId };
80
- }
137
+ // Prune BEFORE the capacity check: expiry is enforced on write, so a ledger
138
+ // full of stale records would otherwise reject every new operation forever
139
+ // (the prune path inside writeGrokCouponLedger is never reached when all
140
+ // openings are rejected).
141
+ const retentionCutoff = now - 30 * 24 * 60 * 60_000;
142
+ ledger.operations = Object.fromEntries(
143
+ Object.entries(ledger.operations).filter(([, op]) => op.updatedAt > retentionCutoff),
144
+ );
81
145
 
82
- const existing = ledger.operations[identity.operationId];
83
- if (existing) {
84
- if (existing.accountId !== identity.accountId) {
85
- return { kind: "identity-mismatch", operationId: identity.operationId };
86
- }
87
- if (existing.status !== "open") {
88
- // Durably settled already: replay the recorded outcome instead of
89
- // trusting upstream idempotency for an irreversible spend.
146
+ const existing = ledger.operations[identity.operationId];
147
+ if (existing) {
148
+ if (existing.accountId !== identity.accountId) {
149
+ return { kind: "identity-mismatch", operationId: identity.operationId };
150
+ }
151
+ if (identity.tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== identity.tokenId) {
152
+ return { kind: "token-mismatch", operationId: identity.operationId };
153
+ }
154
+ if (existing.status !== "open") {
155
+ // Non-open: replay the recorded outcome. "attempted" records carry no
156
+ // code — the caller must reconcile them against upstream, never re-run.
157
+ return {
158
+ kind: "replay",
159
+ operationId: identity.operationId,
160
+ accountId: existing.accountId,
161
+ tokenId: existing.tokenId,
162
+ tokenValidityEnd: existing.tokenValidityEnd,
163
+ attemptedAt: existing.status === "attempted" ? existing.updatedAt : undefined,
164
+ code: existing.code,
165
+ settledAt: existing.updatedAt,
166
+ };
167
+ }
90
168
  return {
91
- kind: "replay",
169
+ kind: "execute",
92
170
  operationId: identity.operationId,
93
171
  accountId: existing.accountId,
94
172
  tokenId: existing.tokenId,
95
- code: existing.code,
96
- settledAt: existing.updatedAt,
97
173
  };
98
174
  }
175
+
176
+ if (Object.keys(ledger.operations).length >= MAX_GROK_RESET_COUPON_OPERATION_IDS) {
177
+ return { kind: "capacity", operationId: identity.operationId };
178
+ }
179
+
180
+ ledger.operations[identity.operationId] = {
181
+ accountId: identity.accountId,
182
+ ...(identity.tokenId === undefined ? {} : { tokenId: identity.tokenId }),
183
+ status: "open",
184
+ createdAt: now,
185
+ updatedAt: now,
186
+ };
187
+ writeGrokCouponLedger(filePath, ledger, now);
99
188
  return {
100
189
  kind: "execute",
101
190
  operationId: identity.operationId,
102
- accountId: existing.accountId,
103
- tokenId: existing.tokenId,
191
+ accountId: identity.accountId,
192
+ tokenId: identity.tokenId,
104
193
  };
105
- }
194
+ });
195
+ }
106
196
 
107
- ledger.operations[identity.operationId] = {
108
- accountId: identity.accountId,
109
- ...(identity.tokenId === undefined ? {} : { tokenId: identity.tokenId }),
110
- status: "open",
111
- createdAt: now,
112
- updatedAt: now,
113
- };
114
- writeGrokCouponLedger(filePath, ledger, now);
115
- return {
116
- kind: "execute",
117
- operationId: identity.operationId,
118
- accountId: identity.accountId,
119
- tokenId: identity.tokenId,
120
- };
197
+ /**
198
+ * Flip an open operation to `attempted` BEFORE the upstream spend call. The
199
+ * write happens while nothing is irreversible yet: if it throws, the caller
200
+ * aborts without spending; if a later settle write dies instead, the
201
+ * operation still refuses blind re-execution — the next open returns a
202
+ * code-less replay the route resolves via the upstream remaining-resets list.
203
+ */
204
+ export function markGrokResetCouponAttempt(
205
+ operationId: string,
206
+ tokenId: string,
207
+ now = Date.now(),
208
+ journalPath?: string,
209
+ tokenValidityEnd?: number,
210
+ ): boolean {
211
+ return withConfigMutationLockSync(() => {
212
+ const filePath = journalPath ?? grokCouponJournalPath();
213
+ const ledger = readGrokCouponLedger(filePath);
214
+ const existing = ledger.operations[operationId];
215
+ if (!existing || existing.status !== "open"
216
+ || (existing.tokenId !== undefined && existing.tokenId !== tokenId)) return false;
217
+ existing.status = "attempted";
218
+ existing.tokenId = tokenId;
219
+ if (tokenValidityEnd !== undefined) existing.tokenValidityEnd = tokenValidityEnd;
220
+ existing.updatedAt = now;
221
+ writeGrokCouponLedger(filePath, ledger, now);
222
+ return true;
223
+ });
224
+ }
225
+
226
+ type GrokResetCouponSettlement = { operationId: string; accountId: string; code: string } & (
227
+ | { tokenId?: string; status: "failed"; expectedStatus: "open" }
228
+ | { tokenId: string; status: "success"; expectedStatus: "attempted" }
229
+ );
230
+
231
+ type GrokResetCouponPreflightResult =
232
+ | { kind: "recorded" | "changed" }
233
+ | { kind: "replay"; code: string; tokenId?: string; settledAt: number };
234
+
235
+ const UNRESOLVED_COUPON_CODES = new Set(["redeem_failed", "attempt_unresolved", "attempt_in_progress", "attempt_reconcile_failed", "operation_state_changed"]);
236
+
237
+ /** The durable definitive outcome for this account and token, if the operation has one. */
238
+ function terminalReplay(
239
+ ledger: GrokResetCouponLedger, operationId: string, accountId: string, tokenId: string | undefined,
240
+ ): Extract<GrokResetCouponPreflightResult, { kind: "replay" }> | null {
241
+ const existing = ledger.operations[operationId];
242
+ if (!existing || existing.accountId !== accountId) return null;
243
+ if (tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== tokenId) return null;
244
+ if ((existing.status !== "settled" && existing.status !== "failed") || !existing.code || UNRESOLVED_COUPON_CODES.has(existing.code)) return null;
245
+ return { kind: "replay", code: existing.code, tokenId: existing.tokenId, settledAt: existing.updatedAt };
121
246
  }
122
247
 
248
+ /** Settle a refusal or inspect its winner without releasing the mutation transaction. */
249
+ export function settleGrokResetCouponPreflightRefusal(
250
+ settlement: Extract<GrokResetCouponSettlement, { status: "failed" }>,
251
+ now = Date.now(),
252
+ journalPath?: string,
253
+ ): GrokResetCouponPreflightResult {
254
+ return withConfigMutationLockSync(() => {
255
+ const filePath = journalPath ?? grokCouponJournalPath();
256
+ const ledger = readGrokCouponLedger(filePath);
257
+ if (recordSettlement(ledger, filePath, settlement, now)) return { kind: "recorded" };
258
+ return terminalReplay(ledger, settlement.operationId, settlement.accountId, settlement.tokenId) ?? { kind: "changed" };
259
+ });
260
+ }
261
+
262
+ /**
263
+ * After a lost claim: the definitive winner to replay, or null while the
264
+ * operation is still unresolved. Terminal states never revert, so reading after
265
+ * the failed claim cannot replay an outcome that is later replaced.
266
+ */
267
+ export function readGrokResetCouponTerminalReplay(
268
+ operationId: string, accountId: string, tokenId?: string, journalPath?: string,
269
+ ): Extract<GrokResetCouponPreflightResult, { kind: "replay" }> | null {
270
+ return withConfigMutationLockSync(() => {
271
+ const filePath = journalPath ?? grokCouponJournalPath();
272
+ return terminalReplay(readGrokCouponLedger(filePath), operationId, accountId, tokenId);
273
+ });
274
+ }
275
+
276
+ /** A preflight refusal cannot replace a claim or a terminal result. */
123
277
  export function recordGrokResetCouponSettlement(
124
- settlement: { operationId: string; tokenId?: string; code: string; status: "success" | "failed" },
278
+ settlement: GrokResetCouponSettlement,
125
279
  now = Date.now(),
126
280
  journalPath?: string,
127
- ): void {
128
- const filePath = journalPath ?? grokCouponJournalPath();
129
- const ledger = readGrokCouponLedger(filePath);
281
+ ): boolean {
282
+ return withConfigMutationLockSync(() => {
283
+ const filePath = journalPath ?? grokCouponJournalPath();
284
+ const ledger = readGrokCouponLedger(filePath);
285
+ return recordSettlement(ledger, filePath, settlement, now);
286
+ });
287
+ }
288
+
289
+ function recordSettlement(
290
+ ledger: GrokResetCouponLedger, filePath: string, settlement: GrokResetCouponSettlement, now: number,
291
+ ): boolean {
130
292
  const existing = ledger.operations[settlement.operationId];
131
- if (!existing) return;
293
+ if (!existing || existing.status !== settlement.expectedStatus
294
+ || existing.accountId !== settlement.accountId
295
+ || (settlement.tokenId !== undefined && existing.tokenId !== undefined && existing.tokenId !== settlement.tokenId)
296
+ || (settlement.status === "success" && existing.tokenId !== settlement.tokenId)) return false;
132
297
 
133
298
  existing.status = settlement.status === "success" ? "settled" : "failed";
134
299
  existing.code = settlement.code;
@@ -136,4 +301,5 @@ export function recordGrokResetCouponSettlement(
136
301
  existing.updatedAt = now;
137
302
 
138
303
  writeGrokCouponLedger(filePath, ledger, now);
304
+ return true;
139
305
  }
@@ -230,6 +230,17 @@ function buildGrokHeaders(accessToken: string): Record<string, string> {
230
230
  /**
231
231
  * Reads available Grok reset tokens for the authenticated xAI account.
232
232
  */
233
+ function assertConfirmedCouponStatus(decoded: ReturnType<typeof decodeGrpcWebResponse>): void {
234
+ const rawStatus = decoded.trailers?.metadata["grpc-status"];
235
+ // The shared decoder defaults a missing/malformed status to zero. Coupon
236
+ // availability and an irreversible redemption need affirmative confirmation.
237
+ if (rawStatus === undefined || !/^\d+$/.test(rawStatus) || !Number.isSafeInteger(Number(rawStatus))) {
238
+ throw new Error("Grok coupon response has no confirmed gRPC status");
239
+ }
240
+ const status = Number(rawStatus);
241
+ if (status !== 0) throw new GrpcWebError(status, decoded.statusMessage ?? "Unknown gRPC error");
242
+ }
243
+
233
244
  export async function getGrokRemainingResets(options: GetRemainingResetsOptions): Promise<{ tokens: GrokResetCoupon[] }> {
234
245
  const fetchImpl = options.fetchFn ?? globalThis.fetch;
235
246
  const endpoint = options.endpoint ?? GROK_GET_REMAINING_RESETS_ENDPOINT;
@@ -251,9 +262,7 @@ export async function getGrokRemainingResets(options: GetRemainingResetsOptions)
251
262
  const rawBytes = new Uint8Array(await res.arrayBuffer());
252
263
  const decoded = decodeGrpcWebResponse(rawBytes);
253
264
 
254
- if (decoded.status !== 0) {
255
- throw new GrpcWebError(decoded.status, decoded.statusMessage ?? "Unknown gRPC error");
256
- }
265
+ assertConfirmedCouponStatus(decoded);
257
266
 
258
267
  if (decoded.messages.length === 0) {
259
268
  return { tokens: [] };
@@ -285,9 +294,7 @@ export async function redeemGrokResetCoupon(options: RedeemResetOptions): Promis
285
294
  const rawBytes = new Uint8Array(await res.arrayBuffer());
286
295
  const decoded = decodeGrpcWebResponse(rawBytes);
287
296
 
288
- if (decoded.status !== 0) {
289
- throw new GrpcWebError(decoded.status, decoded.statusMessage ?? "Unknown gRPC error");
290
- }
297
+ assertConfirmedCouponStatus(decoded);
291
298
 
292
299
  return {
293
300
  success: true,
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Detection is read-only and injectable: the proxy never writes anything into a Cursor
11
11
  * install, its state database, or its keychain entries (the T20 exclusion in
12
- * devlog/_plan/260822_senpi_cursor_transfer/090), and the tests run against a temp tree.
12
+ * devlog/_fin/260822_senpi_cursor_transfer/090), and the tests run against a temp tree.
13
13
  */
14
14
  import { existsSync, readdirSync, readFileSync } from "node:fs";
15
15
  import { homedir } from "node:os";
package/src/lib/errors.ts CHANGED
@@ -17,6 +17,7 @@ export const ENCRYPTED_FUNCTION_OUTPUT_REJECTION =
17
17
  * string for a client to be able to tell this apart from a provider rate limit.
18
18
  */
19
19
  export const SEND_BUDGET_EXHAUSTED_CODE = "request_send_budget_exhausted";
20
+ export const SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE = "spend_ledger_storage_unavailable";
20
21
 
21
22
  /** First nonblank string across the canonical upstream error paths, in priority order. */
22
23
  export function upstreamErrorMessageFromPayload(payload: unknown): string | undefined {
@@ -357,6 +358,9 @@ export function isUpstreamResetReplayRefusedMessage(text: string): boolean {
357
358
 
358
359
  export function classifyError(status: number, type: string, message: string): OcxErrorPayload {
359
360
  const text = message.toLowerCase();
361
+ if (type === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) {
362
+ return { message, type: "server_error", code: SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE };
363
+ }
360
364
  if (type === "previous_response_not_found") {
361
365
  return { message, type: "invalid_request_error", code: "previous_response_not_found" };
362
366
  }
@@ -639,6 +643,24 @@ export function adapterFailureFromMessage(message: string): { httpStatus: number
639
643
  return { httpStatus, error };
640
644
  }
641
645
 
646
+ /** Narrow structured classes carried through a synthesized bare-error terminal. */
647
+ export function recognizedUpstreamError(error: { type?: unknown; code?: unknown }):
648
+ { type: string; code: string; httpStatus: 429 | 503 } | undefined {
649
+ const code = typeof error.code === "string" ? error.code : undefined;
650
+ // An explicit unknown code must not gain a recognized class from its type or copy.
651
+ if (code !== undefined && !["rate_limit_exceeded", "rate_limit_error", "server_is_overloaded", "overloaded_error"].includes(code)) return undefined;
652
+ if (code === "rate_limit_exceeded" || code === "rate_limit_error"
653
+ || (code === undefined && error.type === "rate_limit_error")) {
654
+ return { type: "rate_limit_error", code: code ?? "rate_limit_exceeded", httpStatus: 429 };
655
+ }
656
+ if (code === "server_is_overloaded" || code === "overloaded_error"
657
+ || (code === undefined && error.type === "overloaded_error")) {
658
+ return { type: error.type === "overloaded_error" ? "overloaded_error" : "server_error",
659
+ code: code ?? "server_is_overloaded", httpStatus: 503 };
660
+ }
661
+ return undefined;
662
+ }
663
+
642
664
  /** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
643
665
  export function httpStatusFromTerminalError(error: {
644
666
  type?: string;
@@ -646,10 +668,13 @@ export function httpStatusFromTerminalError(error: {
646
668
  message?: string;
647
669
  } | undefined): number {
648
670
  if (!error) return 502;
671
+ if (error.code === SPEND_LEDGER_STORAGE_UNAVAILABLE_CODE) return 503;
649
672
  if (error.code === "client_closed_request" || error.code === "client_cancelled") return 499;
650
673
  if (isCyberPolicyCode(error.code) || (error.message ? isCyberPolicyMessage(error.message) : false)) {
651
674
  return 400;
652
675
  }
676
+ const recognized = recognizedUpstreamError(error);
677
+ if (recognized) return recognized.httpStatus;
653
678
  if (error.type === "rate_limit_error" || error.code === "rate_limit_exceeded") return 429;
654
679
  if (error.type === "authentication_error" || error.code === "invalid_api_key") return 401;
655
680
  if (
package/src/lib/redact.ts CHANGED
@@ -7,7 +7,7 @@ export const REDACTED_SECRET = "[REDACTED]";
7
7
  * credentials over an unsafe channel (e.g. plaintext non-loopback HTTP) rather than
8
8
  * re-deriving a narrower local list.
9
9
  */
10
- export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn|exa[-_]?api[-_]?key)$/i;
10
+ export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|copilot[-_]?session[-_]?token|session[-_]?token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn|exa[-_]?api[-_]?key)$/i;
11
11
 
12
12
  /**
13
13
  * Colon-labelled credential headers echoed back inside an error body
@@ -40,7 +40,7 @@ export const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cook
40
40
  // Every letter position also accepts \u0001, the placeholder the fold emits for
41
41
  // an unresolved HTML named reference: `author&ii;zation` is the label with one
42
42
  // character we cannot name, and that is still the label.
43
- const CREDENTIAL_HEADER_LABEL_RAW = "x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|exa[_-]?api[_-]?key|exaApiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token";
43
+ const CREDENTIAL_HEADER_LABEL_RAW = "copilot[_-]?session[_-]?token|session[_-]?token|sessionToken|x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|exa[_-]?api[_-]?key|exaApiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token";
44
44
 
45
45
  const CREDENTIAL_HEADER_LABEL = CREDENTIAL_HEADER_LABEL_RAW
46
46
  .replace(/(?<![\[\\])([A-Za-z])(?![\]\-])/g, "[$1\u0001]");
@@ -360,7 +360,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
360
360
  * Removed provider ids that still name a live successor.
361
361
  *
362
362
  * `devin-cli` was merged into `devin` (import-first login absorbed the CLI
363
- * credential import; devlog/_plan/260913_devin_provider_merge). The id can
363
+ * credential import; devlog/_fin/260913_devin_provider_merge). The id can
364
364
  * still arrive here from a saved config row or a stored credential slot that
365
365
  * the startup migration has not rekeyed yet, and from a user typing the old
366
366
  * name at `ocx login`. It is deliberately NOT an OAUTH_PROVIDERS entry:
@@ -6,7 +6,7 @@
6
6
  * resulting account token into the `LLM|` Model API key our request path sends as a
7
7
  * bearer.
8
8
  *
9
- * Protocol source: devlog/_plan/260912_muse_device_oauth/001_reference_measurements.md.
9
+ * Protocol source: devlog/_fin/260912_muse_device_oauth/001_reference_measurements.md.
10
10
  * It is second-party, not vendor documentation, so every response is parsed defensively
11
11
  * and every failure names a kind instead of throwing a bare string.
12
12
  *
@@ -30,7 +30,7 @@ export interface KiroOAuthMetadata {
30
30
  *
31
31
  * The Model API is authenticated by the `LLM|` key in `access`; this token authenticates
32
32
  * the Meta ACCOUNT and exists only to mint that key and to read subscription usage
33
- * (devlog/_plan/260912_muse_device_oauth/002 A). Keeping it out of `access` is what lets
33
+ * (devlog/_fin/260912_muse_device_oauth/002 A). Keeping it out of `access` is what lets
34
34
  * every request path stay unchanged.
35
35
  *
36
36
  * It must never be added to `OAuthAccountSummary` (src/oauth/index.ts:1803) or to
@@ -12,7 +12,7 @@
12
12
  * account rate limit and must keep flowing into the existing wait/rotation handling, so only an
13
13
  * error message that names fast mode or the speed parameter, or a fast-pool header reporting
14
14
  * zero remaining, counts. Live probe strings (2026-09-23,
15
- * devlog/_plan/260923_anthropic_fast_speed/020_probe-evidence.md):
15
+ * devlog/_fin/260923_anthropic_fast_speed/020_probe-evidence.md):
16
16
  * 429 "Usage credits are required for fast mode."
17
17
  * 400 "Fast mode is not enabled for your organization. ..."
18
18
  * 400 "'<model>' does not support the `speed` parameter. ..."
@@ -7,7 +7,7 @@
7
7
  * ten minutes (`b5o = 600000`) and mints a new one afterwards; this journal keeps
8
8
  * the same window.
9
9
  *
10
- * Rules (devlog/_plan/260923_claude_reset_grants/010_plan.md, "Ledger contract"):
10
+ * Rules (devlog/_fin/260923_claude_reset_grants/010_plan.md, "Ledger contract"):
11
11
  * - Every read-modify-write runs synchronously inside a cross-process
12
12
  * `BEGIN IMMEDIATE` lock on a sibling SQLite file; nothing awaits inside it,
13
13
  * and the upstream claim always runs after the open record is on disk.
@@ -4,7 +4,7 @@
4
4
  * `devin-cli` used to be a local provider whose adapter spawned `devin acp`;
5
5
  * it later became an account provider on the shared `devin` adapter, and the
6
6
  * registry id itself has since merged into `devin` outright
7
- * (devlog/_plan/260913_devin_provider_merge).
7
+ * (devlog/_fin/260913_devin_provider_merge).
8
8
  *
9
9
  * Only the adapter repair still lives here. The `authMode` half — rewriting
10
10
  * `"local"` to `"oauth"` on the registry-id row — moved into
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Merge the retired `devin-cli` provider id into `devin` at startup
3
- * (devlog/_plan/260913_devin_provider_merge).
3
+ * (devlog/_fin/260913_devin_provider_merge).
4
4
  *
5
5
  * The two registry entries were the same adapter, api-server, and credential
6
6
  * shape — only the login path differed — so `devin-cli` was removed from the