@bitkyc08/opencodex 2.55.0 → 2.56.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 (167) hide show
  1. package/gui/dist/assets/{index-VuoiWj9J.js → index-D4zuyIxQ.js} +1 -1
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +2 -1
  4. package/src/adapters/base.ts +21 -0
  5. package/src/adapters/cursor/transport-retry.ts +46 -1
  6. package/src/adapters/cursor.ts +4 -0
  7. package/src/adapters/kiro/adapter.ts +42 -1
  8. package/src/adapters/kiro-retry.ts +23 -4
  9. package/src/adapters/openai-chat/errors.ts +116 -0
  10. package/src/adapters/openai-chat/messages.ts +346 -0
  11. package/src/adapters/openai-chat/passthrough.ts +146 -0
  12. package/src/adapters/openai-chat/response-events.ts +117 -0
  13. package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
  14. package/src/adapters/openai-chat/tool-schema.ts +477 -0
  15. package/src/adapters/openai-chat/wire.ts +50 -0
  16. package/src/adapters/openai-chat.ts +33 -1445
  17. package/src/adapters/openai-responses/canonical-forward.ts +202 -0
  18. package/src/adapters/openai-responses/image-gen.ts +406 -0
  19. package/src/adapters/openai-responses/internal.ts +3 -0
  20. package/src/adapters/openai-responses/passthrough.ts +611 -0
  21. package/src/adapters/openai-responses/prompt-cache.ts +83 -0
  22. package/src/adapters/openai-responses/reasoning.ts +220 -0
  23. package/src/adapters/openai-responses/request-strips.ts +185 -0
  24. package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
  25. package/src/adapters/openai-responses/tool-schema.ts +293 -0
  26. package/src/adapters/openai-responses/web-search.ts +156 -0
  27. package/src/adapters/openai-responses.ts +4 -2625
  28. package/src/bridge/errors.ts +34 -0
  29. package/src/bridge/internal.ts +174 -0
  30. package/src/bridge/response-json.ts +624 -0
  31. package/src/bridge/sse.ts +1444 -0
  32. package/src/bridge.ts +5 -2204
  33. package/src/chat/inbound.ts +12 -1
  34. package/src/codex/account-lifecycle.ts +3 -0
  35. package/src/codex/account-store.ts +71 -9
  36. package/src/codex/auth-api/account-list.ts +507 -0
  37. package/src/codex/auth-api/http.ts +32 -0
  38. package/src/codex/auth-api/login-flow.ts +554 -0
  39. package/src/codex/auth-api/login-state.ts +64 -0
  40. package/src/codex/auth-api/main-account-probe.ts +331 -0
  41. package/src/codex/auth-api/pool-mode-gate.ts +274 -0
  42. package/src/codex/auth-api/pool-quota-probe.ts +512 -0
  43. package/src/codex/auth-api/reset-credit-service.ts +422 -0
  44. package/src/codex/auth-api/routes.ts +425 -0
  45. package/src/codex/auth-api/runtime-config.ts +48 -0
  46. package/src/codex/auth-api.ts +27 -3118
  47. package/src/codex/auth-context.ts +95 -28
  48. package/src/codex/catalog/auto-review.ts +507 -0
  49. package/src/codex/catalog/build-entries.ts +981 -0
  50. package/src/codex/catalog/combo-member.ts +375 -0
  51. package/src/codex/catalog/derive-entry.ts +229 -0
  52. package/src/codex/catalog/effort.ts +0 -1
  53. package/src/codex/catalog/gated-native-warn.ts +63 -0
  54. package/src/codex/catalog/gather-capture.ts +533 -0
  55. package/src/codex/catalog/model-hints.ts +691 -0
  56. package/src/codex/catalog/model-visibility.ts +304 -0
  57. package/src/codex/catalog/provider-fetch.ts +52 -2942
  58. package/src/codex/catalog/provider-models.ts +685 -0
  59. package/src/codex/catalog/restore.ts +132 -0
  60. package/src/codex/catalog/retained-sync.ts +706 -0
  61. package/src/codex/catalog/routed-gather.ts +858 -0
  62. package/src/codex/catalog/subagent-roster.ts +176 -0
  63. package/src/codex/catalog/sync.ts +52 -2698
  64. package/src/codex/inject/config-toml.ts +563 -0
  65. package/src/codex/inject/remove.ts +192 -0
  66. package/src/codex/inject/restore.ts +540 -0
  67. package/src/codex/inject/routing-classify.ts +109 -0
  68. package/src/codex/inject/routing-target.ts +125 -0
  69. package/src/codex/inject.ts +81 -1436
  70. package/src/codex/lineage.ts +458 -0
  71. package/src/codex/pool-refresh-backoff.ts +152 -0
  72. package/src/codex/routing/active-account.ts +194 -0
  73. package/src/codex/routing/cooldown-math.ts +275 -0
  74. package/src/codex/routing/health-store.ts +402 -0
  75. package/src/codex/routing/probe-lease.ts +358 -0
  76. package/src/codex/routing/selection.ts +703 -0
  77. package/src/codex/routing/thread-affinity.ts +538 -0
  78. package/src/codex/routing.ts +353 -2234
  79. package/src/codex/shim-fingerprint.ts +223 -0
  80. package/src/codex/shim-inspect.ts +175 -0
  81. package/src/codex/shim-probe.ts +367 -0
  82. package/src/codex/shim-restore-lock.ts +169 -0
  83. package/src/codex/shim-state-file.ts +151 -0
  84. package/src/codex/shim-templates.ts +265 -0
  85. package/src/codex/shim.ts +48 -1268
  86. package/src/config/diagnostics.ts +705 -0
  87. package/src/config/feature-flags.ts +55 -0
  88. package/src/config/live-reconcile.ts +403 -0
  89. package/src/config/load-degrade.ts +880 -0
  90. package/src/config/mutation-lock.ts +244 -0
  91. package/src/config/openai-tier-backup.ts +268 -0
  92. package/src/config/persist-unlocked.ts +92 -0
  93. package/src/config/proxy-env.ts +188 -0
  94. package/src/config/salvage.ts +244 -0
  95. package/src/config/schema/config-schema.ts +640 -0
  96. package/src/config/schema/leaf-validators.ts +855 -0
  97. package/src/config/warn-memo.ts +28 -0
  98. package/src/config.ts +234 -4481
  99. package/src/generated/compatibility-version.json +539 -39
  100. package/src/lib/request-execution-budget.ts +69 -20
  101. package/src/lib/spend-reservation-ledger.ts +940 -0
  102. package/src/lib/upstream-retry.ts +55 -11
  103. package/src/lib/workflow-budget.ts +553 -30
  104. package/src/providers/quota/account-cache.ts +441 -0
  105. package/src/providers/quota/antigravity.ts +295 -0
  106. package/src/providers/quota/report-cache.ts +320 -0
  107. package/src/providers/quota/vendor-probes-key.ts +1243 -0
  108. package/src/providers/quota/vendor-probes-oauth.ts +590 -0
  109. package/src/providers/quota.ts +324 -3079
  110. package/src/providers/registry/entries-core.ts +1221 -0
  111. package/src/providers/registry/entries-extended.ts +1204 -0
  112. package/src/providers/registry/model-seeds.ts +908 -0
  113. package/src/providers/registry/types.ts +352 -0
  114. package/src/providers/registry.ts +24 -3536
  115. package/src/responses/continuation-ownership.ts +29 -0
  116. package/src/responses/state/replay-fingerprint.ts +80 -0
  117. package/src/responses/state/snapshot-codec.ts +104 -0
  118. package/src/responses/state/spill-failure.ts +118 -0
  119. package/src/responses/state/spill-queue.ts +665 -0
  120. package/src/responses/state/temp-recovery.ts +257 -0
  121. package/src/responses/state.ts +82 -1143
  122. package/src/routing/identity-domains.ts +449 -0
  123. package/src/routing/probe-lease.ts +511 -0
  124. package/src/server/index/bounded-request.ts +88 -0
  125. package/src/server/index/live-sideband.ts +565 -0
  126. package/src/server/index/serve-options.ts +1766 -0
  127. package/src/server/index/startup-warnings.ts +213 -0
  128. package/src/server/index/websocket-handler.ts +335 -0
  129. package/src/server/index.ts +40 -2547
  130. package/src/server/management/route-registry.ts +26 -23
  131. package/src/server/management/shared.ts +8 -5
  132. package/src/server/management/workflow-budget-routes.ts +133 -0
  133. package/src/server/management-api.ts +12 -0
  134. package/src/server/request-log-conversation.ts +9 -7
  135. package/src/server/request-log.ts +245 -1
  136. package/src/server/responses/account-change-state.ts +233 -0
  137. package/src/server/responses/adapter-continuation.ts +514 -0
  138. package/src/server/responses/adapter-delivery.ts +214 -0
  139. package/src/server/responses/adapter-dispatch.ts +971 -0
  140. package/src/server/responses/compact.ts +59 -4
  141. package/src/server/responses/completion-policy.ts +33 -0
  142. package/src/server/responses/core-auth.ts +527 -0
  143. package/src/server/responses/core-codex-account.ts +859 -0
  144. package/src/server/responses/core-combo-failure.ts +210 -0
  145. package/src/server/responses/core-combo.ts +707 -0
  146. package/src/server/responses/core-errors.ts +152 -0
  147. package/src/server/responses/core-lifetime.ts +95 -0
  148. package/src/server/responses/core-normalize.ts +350 -0
  149. package/src/server/responses/core-opaque-recovery.ts +380 -0
  150. package/src/server/responses/core-options.ts +159 -0
  151. package/src/server/responses/core-replay.ts +225 -0
  152. package/src/server/responses/core.ts +192 -8893
  153. package/src/server/responses/passthrough-delivery.ts +856 -0
  154. package/src/server/responses/passthrough-dispatch.ts +1476 -0
  155. package/src/server/responses/passthrough-execution.ts +54 -0
  156. package/src/server/responses/request-prepare.ts +970 -0
  157. package/src/server/responses/request-send-budget.ts +164 -0
  158. package/src/server/responses/request-sidecar-auth.ts +149 -0
  159. package/src/server/responses/request-transport.ts +744 -0
  160. package/src/server/responses/response-effects.ts +157 -0
  161. package/src/server/responses/run-turn-execution.ts +448 -0
  162. package/src/server/responses/sidecar-execution.ts +469 -0
  163. package/src/server/responses-image-gen-repair.ts +1 -1
  164. package/src/server/workflow-refusal.ts +84 -0
  165. package/src/types/config.ts +30 -0
  166. package/src/usage/log.ts +146 -0
  167. package/src/usage/summary.ts +171 -21
@@ -0,0 +1,257 @@
1
+ import { opendirSync, lstatSync, unlinkSync } from "node:fs";
2
+ import { uptime } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { getConfigDir, resolveWriteTarget } from "../../config";
5
+
6
+ const STALE_TEMP_GRACE_MS = 15 * 60 * 1_000;
7
+ const STALE_TEMP_MAX_ENTRIES = 4_096;
8
+ const STALE_TEMP_MAX_CLEANUPS = 512;
9
+ /** Absorbs `os.uptime()` granularity only. It is deliberately NOT the safety margin:
10
+ * the unconditional 15-minute grace above is (see the boot floor in the scan loop). */
11
+ const BOOT_FLOOR_SKEW_MS = 60 * 1_000;
12
+ /** Per-tick budget for the periodic reclaim. Smaller than the startup budget because the
13
+ * periodic pass runs synchronously on the serving process's event loop every 60 s. */
14
+ const PERIODIC_TEMP_MAX_ENTRIES = 512;
15
+ const PERIODIC_TEMP_MAX_CLEANUPS = 64;
16
+ /** Wall-clock ceiling for one periodic scan. An entry cap bounds syscalls, not time: on a
17
+ * network-mounted config dir each `lstat` can cost 10-20 ms, which would stall in-flight
18
+ * streams. Reclaim is idempotent, so a truncated tick simply resumes on the next one. */
19
+ const PERIODIC_TEMP_SCAN_DEADLINE_MS = 25;
20
+ const RESPONSE_STATE_TEMP_NAME = /^responses-state\.json\.ocx\.(\d+)\.(\d+)\.tmp$/;
21
+
22
+ export interface ResponseStateTempRecoveryResult {
23
+ matched: number;
24
+ removed: number;
25
+ failed: number;
26
+ bytesRemoved: number;
27
+ /** Entries that passed EVERY gate and would be reclaimed. In a dry run nothing is
28
+ * unlinked, so this is the only honest count to show an operator: `matched` is
29
+ * incremented before the file-type, age, boot-floor, and liveness gates. */
30
+ eligible: number;
31
+ /** Total size of the `eligible` entries. */
32
+ eligibleBytes: number;
33
+ /** The scan stopped on a budget (entry cap, cleanup cap, or deadline) rather than reaching
34
+ * the end of the directory, so the counts below describe a prefix of the backlog and not
35
+ * the backlog. `eligible > removed + failed` cannot express this: outside a dry run every
36
+ * eligible entry is unlinked or failed on the same iteration, so the two are always equal
37
+ * and a comparison between them is dead code. */
38
+ truncated: boolean;
39
+ }
40
+
41
+ interface ResponseStateTempRecoveryIO {
42
+ now: () => number;
43
+ /** Approximate epoch ms of the current boot; see the boot floor in the scan loop. */
44
+ bootTime: () => number;
45
+ list: (dir: string) => Iterable<string>;
46
+ inspect: (path: string) => { isFile: boolean; mtimeMs: number; size: number };
47
+ isProcessAlive: (pid: number) => boolean;
48
+ unlink: (path: string) => void;
49
+ }
50
+
51
+ export type ResponseStateTempRecoveryOptions = Partial<ResponseStateTempRecoveryIO> & {
52
+ maxEntries?: number;
53
+ maxCleanups?: number;
54
+ /** Wall-clock ceiling for the scan, or null/undefined for no deadline (startup path). */
55
+ deadlineMs?: number | null;
56
+ /** Report only: apply every gate, count what would be reclaimed, unlink nothing. */
57
+ dryRun?: boolean;
58
+ };
59
+
60
+ function processIsAlive(pid: number): boolean {
61
+ if (pid === process.pid) return true;
62
+ try {
63
+ process.kill(pid, 0);
64
+ return true;
65
+ } catch (error) {
66
+ // EPERM means the process exists but cannot be signalled. Unknown platform errors
67
+ // are also protected; cleanup should prefer a false negative over touching a live writer.
68
+ return (error as NodeJS.ErrnoException).code !== "ESRCH";
69
+ }
70
+ }
71
+
72
+ const responseStateTempRecoveryIO: ResponseStateTempRecoveryIO = {
73
+ now: Date.now,
74
+ bootTime: () => Date.now() - uptime() * 1_000,
75
+ list: function* list(dir) {
76
+ const handle = opendirSync(dir);
77
+ try {
78
+ for (let entry = handle.readSync(); entry; entry = handle.readSync()) yield entry.name;
79
+ } finally {
80
+ handle.closeSync();
81
+ }
82
+ },
83
+ inspect: path => {
84
+ const stat = lstatSync(path);
85
+ return { isFile: stat.isFile() && !stat.isSymbolicLink(), mtimeMs: stat.mtimeMs, size: stat.size };
86
+ },
87
+ isProcessAlive: processIsAlive,
88
+ unlink: unlinkSync,
89
+ };
90
+
91
+ /**
92
+ * Recover only abandoned response-state atomic-write files. The exact basename,
93
+ * regular-file check, age gate, and PID liveness check protect unrelated/active files.
94
+ * Cleanup is capped and best-effort because continuation state is only a cache. Removal
95
+ * deliberately uses unlink only: path-based truncation could follow a replacement symlink.
96
+ */
97
+ export function recoverStaleResponseStateTemps(
98
+ dir = getConfigDir(),
99
+ options: ResponseStateTempRecoveryOptions = {},
100
+ ): ResponseStateTempRecoveryResult {
101
+ const {
102
+ maxEntries = STALE_TEMP_MAX_ENTRIES,
103
+ maxCleanups = STALE_TEMP_MAX_CLEANUPS,
104
+ deadlineMs = null,
105
+ dryRun = false,
106
+ ...overrides
107
+ } = options;
108
+ const io = { ...responseStateTempRecoveryIO, ...overrides };
109
+ const result: ResponseStateTempRecoveryResult = {
110
+ matched: 0,
111
+ removed: 0,
112
+ failed: 0,
113
+ bytesRemoved: 0,
114
+ eligible: 0,
115
+ eligibleBytes: 0,
116
+ truncated: false,
117
+ };
118
+ const startedAt = io.now();
119
+ // One probe per scan, not one per entry. A non-finite or future-dated boot is anomalous, and
120
+ // clamping it to "now" would be the WORST response: the floor would then retire the liveness
121
+ // probe for every file older than the skew, which is every file past the grace. Disable it
122
+ // instead -- an absent floor only costs a missed reclaim, never a wrong one.
123
+ const rawBoot = io.bootTime();
124
+ const bootMs = Number.isFinite(rawBoot) && rawBoot <= startedAt ? rawBoot : Number.NEGATIVE_INFINITY;
125
+ let names: Iterable<string>;
126
+ try { names = io.list(dir); } catch { return result; }
127
+ let iterator: Iterator<string>;
128
+ try { iterator = names[Symbol.iterator](); } catch { return result; }
129
+ let scanned = 0;
130
+ // Every early exit runs through this. The production `list` is a generator that closes its
131
+ // directory handle in a `finally`, and a `finally` does NOT run when the consumer simply
132
+ // stops calling `next()` -- only `return()` resumes the generator to completion. Breaking
133
+ // out of the loop directly therefore leaked one directory handle per truncated scan, and the
134
+ // periodic reclaim truncates on purpose (entry cap, cleanup cap, deadline), so on a slow
135
+ // filesystem that is a leak per tick, forever.
136
+ const stopScan = (): ResponseStateTempRecoveryResult => {
137
+ try { iterator.return?.(); } catch { /* closing is best-effort; never fail a reclaim on it */ }
138
+ return result;
139
+ };
140
+ for (;;) {
141
+ let next: IteratorResult<string>;
142
+ try { next = iterator.next(); } catch { return result; }
143
+ if (next.done) break;
144
+ const name = next.value;
145
+ scanned += 1;
146
+ // A dry run performs no cleanups, so bounding it by the cleanup budget would truncate
147
+ // the very report an operator uses to size the problem.
148
+ if (scanned > maxEntries) { result.truncated = true; return stopScan(); }
149
+ if (!dryRun && result.removed + result.failed >= maxCleanups) { result.truncated = true; return stopScan(); }
150
+ if (deadlineMs !== null && io.now() - startedAt > deadlineMs) { result.truncated = true; return stopScan(); }
151
+ const match = RESPONSE_STATE_TEMP_NAME.exec(name);
152
+ if (!match) continue;
153
+ result.matched += 1;
154
+ const pid = Number(match[1]);
155
+ const sequence = Number(match[2]);
156
+ if (!Number.isSafeInteger(pid) || pid <= 0 || !Number.isSafeInteger(sequence) || sequence <= 0) continue;
157
+ const path = join(dir, name);
158
+ let file: ReturnType<ResponseStateTempRecoveryIO["inspect"]>;
159
+ try { file = io.inspect(path); } catch { continue; }
160
+ if (!file.isFile || io.now() - file.mtimeMs < STALE_TEMP_GRACE_MS) continue;
161
+ // Boot floor. After a reboot the original writer's pid is routinely reused, which makes
162
+ // the liveness skip PERMANENT: the 15-minute grace above is a lower bound and never
163
+ // expires it, so the file is skipped on every future pass forever. A temp older than
164
+ // this boot cannot be owned by the pid we would probe, so the probe is vacuous and we
165
+ // retire it. This does NOT claim the file is provably dead: under a shared-volume
166
+ // container, suspend-excluding uptime, or a network config dir the computed boot can
167
+ // land after the real one. The unconditional 15-minute grace above remains the safety
168
+ // floor, and this process's own temps are never touched.
169
+ const predatesBoot = file.mtimeMs < bootMs - BOOT_FLOOR_SKEW_MS;
170
+ if (pid === process.pid) continue;
171
+ if (!predatesBoot && io.isProcessAlive(pid)) continue;
172
+
173
+ result.eligible += 1;
174
+ result.eligibleBytes += file.size;
175
+ if (dryRun) continue;
176
+
177
+ try {
178
+ io.unlink(path);
179
+ result.removed += 1;
180
+ result.bytesRemoved += file.size;
181
+ } catch (error) {
182
+ // Another proxy sharing this config dir may have won the race. A file that is already
183
+ // gone is reclaimed, not a failure -- reporting it as one would surface "in use or
184
+ // locked" to an operator for a file nobody holds.
185
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
186
+ result.removed += 1;
187
+ continue;
188
+ }
189
+ // Locked files remain for a later startup. Do not truncate by path: a same-user
190
+ // replacement could turn that fallback into an arbitrary symlink-target write.
191
+ result.failed += 1;
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+
197
+ /**
198
+ * Literal config dir plus the snapshot's resolved dir. Atomic writes place their temp beside
199
+ * the RESOLVED target, so a symlinked snapshot (dotfiles-managed config dir) strands temps in
200
+ * the link's real directory where a scan of the literal dir would never see them. The two
201
+ * collapse to one when nothing is symlinked.
202
+ */
203
+ function responseStateSweepDirectories(): Set<string> {
204
+ const path = join(getConfigDir(), "responses-state.json");
205
+ let resolvedDir = dirname(path);
206
+ try {
207
+ resolvedDir = dirname(resolveWriteTarget(path));
208
+ } catch {
209
+ /* unresolvable link: sweep the literal dir only */
210
+ }
211
+ return new Set([dirname(path), resolvedDir]);
212
+ }
213
+
214
+ export function reclaimAbandonedResponseStateTemps(
215
+ options: ResponseStateTempRecoveryOptions = {},
216
+ ): ResponseStateTempRecoveryResult {
217
+ const total: ResponseStateTempRecoveryResult = {
218
+ matched: 0, removed: 0, failed: 0, bytesRemoved: 0, eligible: 0, eligibleBytes: 0, truncated: false,
219
+ };
220
+ // The try encloses responseStateSweepDirectories() deliberately: recoverStaleResponseStateTemps
221
+ // already swallows its own enumeration failures, so a catch around only that call would be
222
+ // unreachable. snapshotPath()/getConfigDir() are the paths that can genuinely throw.
223
+ try {
224
+ for (const dir of responseStateSweepDirectories()) {
225
+ const result = recoverStaleResponseStateTemps(dir, options);
226
+ total.matched += result.matched;
227
+ total.removed += result.removed;
228
+ total.failed += result.failed;
229
+ total.bytesRemoved += result.bytesRemoved;
230
+ total.eligible += result.eligible;
231
+ total.eligibleBytes += result.eligibleBytes;
232
+ // Truncation anywhere makes the whole total a prefix.
233
+ total.truncated ||= result.truncated;
234
+ }
235
+ } catch {
236
+ /* best-effort: disk reclaim must never destabilize the caller */
237
+ }
238
+ return total;
239
+ }
240
+
241
+ /**
242
+ * Report-only counterpart for `ocx doctor`: applies every selection gate and unlinks
243
+ * nothing. It runs the SAME predicate as the reclaim, so the report and the subsequent
244
+ * removal cannot disagree about which files are reclaimable.
245
+ */
246
+ export function inspectAbandonedResponseStateTemps(): ResponseStateTempRecoveryResult {
247
+ return reclaimAbandonedResponseStateTemps({ dryRun: true });
248
+ }
249
+
250
+ /** Sweeper adapter: narrows the reclaim to the `() => number` the liveness tick expects. */
251
+ export function sweepAbandonedResponseStateTemps(): number {
252
+ return reclaimAbandonedResponseStateTemps({
253
+ maxEntries: PERIODIC_TEMP_MAX_ENTRIES,
254
+ maxCleanups: PERIODIC_TEMP_MAX_CLEANUPS,
255
+ deadlineMs: PERIODIC_TEMP_SCAN_DEADLINE_MS,
256
+ }).removed;
257
+ }