@bitkyc08/opencodex 2.7.42 → 2.7.43

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 (143) hide show
  1. package/README.md +8 -1
  2. package/bin/ocx.mjs +13 -14
  3. package/gui/dist/assets/index-Czw-jpTU.css +1 -0
  4. package/gui/dist/assets/index-cmds12BG.js +67 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +1 -1
  7. package/src/AGENTS.md +28 -0
  8. package/src/adapters/anthropic.ts +15 -6
  9. package/src/adapters/cursor/native-exec-shell.ts +18 -6
  10. package/src/adapters/cursor/protobuf-events.ts +24 -2
  11. package/src/adapters/cursor/protobuf-request.ts +1 -2
  12. package/src/adapters/cursor/tool-definitions.ts +68 -29
  13. package/src/adapters/google-wire-compiler.ts +4 -0
  14. package/src/adapters/google.ts +128 -2
  15. package/src/adapters/identity.ts +12 -2
  16. package/src/adapters/kiro.ts +49 -6
  17. package/src/adapters/mimo-free.ts +2 -0
  18. package/src/adapters/openai-responses.ts +246 -59
  19. package/src/claude/agents-inject.ts +5 -0
  20. package/src/claude/inbound.ts +26 -9
  21. package/src/cli/account-auth.ts +1 -1
  22. package/src/cli/agent-driven.ts +37 -0
  23. package/src/cli/claude.ts +3 -3
  24. package/src/cli/doctor.ts +23 -18
  25. package/src/cli/help.ts +42 -6
  26. package/src/cli/index.ts +88 -19
  27. package/src/cli/interactive-confirm.ts +129 -0
  28. package/src/cli/opencode.ts +701 -0
  29. package/src/cli/provider-runtime.ts +3 -0
  30. package/src/cli/provider.ts +31 -10
  31. package/src/cli/star-prompt.ts +55 -16
  32. package/src/cli/status.ts +47 -13
  33. package/src/codex/account-id.ts +34 -0
  34. package/src/codex/account-lifecycle.ts +4 -1
  35. package/src/codex/account-namespace-match.ts +63 -0
  36. package/src/codex/account-namespaces.ts +149 -0
  37. package/src/codex/account-pause.ts +20 -0
  38. package/src/codex/account-usability.ts +6 -1
  39. package/src/codex/app-server-processes.ts +511 -0
  40. package/src/codex/auth-api.ts +293 -34
  41. package/src/codex/auth-collision.ts +2 -1
  42. package/src/codex/auth-context.ts +60 -17
  43. package/src/codex/catalog/parsing.ts +16 -1
  44. package/src/codex/catalog/provider-fetch.ts +48 -12
  45. package/src/codex/catalog/sync.ts +18 -3
  46. package/src/codex/catalog.ts +1 -1
  47. package/src/codex/history-provider.ts +145 -1
  48. package/src/codex/inject.ts +114 -14
  49. package/src/codex/main-account.ts +2 -8
  50. package/src/codex/pool-rotation.ts +186 -0
  51. package/src/codex/quota.ts +15 -0
  52. package/src/codex/routing.ts +695 -106
  53. package/src/codex/shim.ts +4 -1
  54. package/src/codex/subagent-defaults.ts +550 -0
  55. package/src/codex/subagent-model-fallback.ts +2 -0
  56. package/src/codex/sync.ts +3 -0
  57. package/src/config.ts +566 -25
  58. package/src/images/artifacts.ts +516 -0
  59. package/src/images/fulfill-video.ts +163 -0
  60. package/src/images/fulfill.ts +111 -0
  61. package/src/images/index.ts +4 -0
  62. package/src/images/loop.ts +789 -0
  63. package/src/images/plan.ts +133 -0
  64. package/src/images/synthetic-tool.ts +133 -0
  65. package/src/images/types.ts +41 -0
  66. package/src/images/xai-client.ts +141 -0
  67. package/src/images/xai-video-client.ts +163 -0
  68. package/src/lib/admin-secrets.ts +25 -0
  69. package/src/lib/config-ownership.ts +327 -0
  70. package/src/lib/crash-guard.ts +2 -0
  71. package/src/lib/destination-policy.ts +122 -4
  72. package/src/lib/pinned-http.ts +151 -0
  73. package/src/lib/process-control.ts +2 -2
  74. package/src/lib/provider-outbound.ts +164 -0
  75. package/src/lib/provider-url.ts +14 -0
  76. package/src/lib/proxy-env.ts +18 -0
  77. package/src/lib/windows-elevation.ts +81 -3
  78. package/src/lib/windows-secret-acl.ts +189 -12
  79. package/src/lib/winsw.ts +2 -0
  80. package/src/oauth/anthropic-routing.ts +570 -0
  81. package/src/oauth/health.ts +6 -0
  82. package/src/oauth/index.ts +283 -72
  83. package/src/oauth/key-providers.ts +17 -6
  84. package/src/oauth/kimi.ts +2 -0
  85. package/src/oauth/kiro-credentials.ts +322 -10
  86. package/src/oauth/kiro.ts +423 -43
  87. package/src/oauth/login-cli.ts +32 -5
  88. package/src/oauth/store.ts +54 -4
  89. package/src/oauth/types.ts +11 -0
  90. package/src/providers/alibaba-region-migration.ts +16 -3
  91. package/src/providers/antigravity-models.ts +3 -0
  92. package/src/providers/api-keys.ts +13 -6
  93. package/src/providers/derive.ts +6 -0
  94. package/src/providers/key-failover.ts +24 -4
  95. package/src/providers/quota.ts +233 -29
  96. package/src/providers/registry.ts +11 -3
  97. package/src/responses/parser.ts +11 -0
  98. package/src/responses/state.ts +22 -8
  99. package/src/responses/tool-groups.ts +19 -0
  100. package/src/router.ts +14 -4
  101. package/src/server/auth-cors.ts +110 -22
  102. package/src/server/claude-messages.ts +8 -1
  103. package/src/server/gui-static.ts +30 -6
  104. package/src/server/images.ts +303 -9
  105. package/src/server/index.ts +77 -9
  106. package/src/server/lifecycle.ts +25 -1
  107. package/src/server/management/agent-settings-routes.ts +28 -4
  108. package/src/server/management/combo-routes.ts +7 -0
  109. package/src/server/management/config-routes.ts +5 -2
  110. package/src/server/management/logs-usage-routes.ts +167 -3
  111. package/src/server/management/oauth-account-routes.ts +163 -17
  112. package/src/server/management/provider-routes.ts +35 -7
  113. package/src/server/management/system-restart.ts +172 -0
  114. package/src/server/management/system-routes.ts +33 -10
  115. package/src/server/management-api.ts +2 -2
  116. package/src/server/management-auth.ts +216 -0
  117. package/src/server/proxy-liveness.ts +14 -3
  118. package/src/server/responses/compact.ts +21 -13
  119. package/src/server/responses/core.ts +583 -152
  120. package/src/server/responses-image-gen-repair.ts +118 -0
  121. package/src/server/responses-item-id-repair.ts +10 -85
  122. package/src/server/sse-payload-rewrite.ts +116 -0
  123. package/src/server/system-env.ts +28 -10
  124. package/src/service.ts +49 -2
  125. package/src/storage/cleanup-job.ts +57 -0
  126. package/src/storage/cleanup.ts +1504 -28
  127. package/src/storage/policy-job.ts +366 -0
  128. package/src/storage/policy-scheduler.ts +40 -0
  129. package/src/storage/policy-worker.ts +53 -0
  130. package/src/storage/policy.ts +522 -0
  131. package/src/storage/restore-job.ts +242 -0
  132. package/src/storage/restore-worker.ts +52 -0
  133. package/src/storage/storage-mutation-coordinator.ts +109 -0
  134. package/src/tray/windows.ts +2 -0
  135. package/src/types.ts +96 -1
  136. package/src/update/index.ts +36 -18
  137. package/src/update/job.ts +103 -12
  138. package/src/update/npm-invocation.d.mts +23 -0
  139. package/src/update/npm-invocation.mjs +94 -0
  140. package/src/usage/debug.ts +2 -0
  141. package/src/usage/log.ts +4 -0
  142. package/gui/dist/assets/index-Bl_VBGoI.js +0 -65
  143. package/gui/dist/assets/index-DfVGuN88.css +0 -1
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Single-flight controller for trash restore runs.
3
+ *
4
+ * Heavy work (file moves, SQLite reconcile) runs in a Bun Worker so the proxy
5
+ * event loop stays responsive while the management API awaits the outcome.
6
+ *
7
+ * Restore shares the CODEX_HOME storage-mutation coordinator with manual cleanup
8
+ * and (Phase 3) policy-driven cleanup. Concurrent callers receive
9
+ * `storage_mutation_busy` (409) instead of queueing.
10
+ */
11
+ import { resolveCodexHomeDir } from "../codex/home";
12
+ import { restoreTrashEntry, type RestoreResult, type RestoreTestHooks } from "./cleanup";
13
+ import {
14
+ resetStorageMutationCoordinatorForTests,
15
+ setStorageMutationCoordinatorTestHooks,
16
+ withStorageMutationSlot,
17
+ type StorageMutationCoordinatorTestHooks,
18
+ } from "./storage-mutation-coordinator";
19
+
20
+ export interface RestoreJobTestHooks extends StorageMutationCoordinatorTestHooks {
21
+ /**
22
+ * When true, run on the main thread via dynamic import + optional sleep.
23
+ * Responsiveness tests must leave this unset so work stays in a Worker.
24
+ */
25
+ runInProcess?: boolean;
26
+ /** Expose GET /api/storage/trash/restore/test-stream for responsiveness tests. */
27
+ enableTestStream?: boolean;
28
+ /** Forwarded to restoreTrashEntry _test hooks. */
29
+ restoreTest?: RestoreTestHooks;
30
+ }
31
+
32
+ const WORKER_TIMEOUT_MS = 10 * 60 * 1000;
33
+
34
+ const WORKER_REJECTION_CODES: Record<string, RestoreResult["error"]> = {
35
+ restore_worker_timeout: "restore_worker_timeout",
36
+ aborted: "restore_worker_aborted",
37
+ worker_failed: "restore_worker_failed",
38
+ };
39
+
40
+ /** Map a rejected worker promise to a precise restore outcome (exported for tests). */
41
+ export function restoreResultFromWorkerRejection(err: unknown, trashId: string): RestoreResult {
42
+ const raw = err instanceof Error ? err.message : String(err);
43
+ const error = WORKER_REJECTION_CODES[raw] ?? "restore_worker_failed";
44
+ const detail =
45
+ error === "restore_worker_failed" && raw !== "worker_failed" && raw.trim().length > 0
46
+ ? raw
47
+ : undefined;
48
+ console.error(
49
+ `[storage] trash restore worker failed (${error}) trashId=${trashId}${detail ? `: ${detail}` : ""}`,
50
+ );
51
+ return {
52
+ ok: false,
53
+ count: 0,
54
+ bytes: 0,
55
+ restoredPaths: [],
56
+ error,
57
+ ...(detail ? { message: detail } : {}),
58
+ };
59
+ }
60
+
61
+ let activeWorker: Worker | null = null;
62
+ let testHooks: RestoreJobTestHooks | null = null;
63
+ let cancelActiveRun: (() => void) | null = null;
64
+
65
+ export function setRestoreTrashJobTestHooks(hooks: RestoreJobTestHooks | null): void {
66
+ testHooks = hooks;
67
+ setStorageMutationCoordinatorTestHooks(hooks);
68
+ }
69
+
70
+ export function resetRestoreTrashJobForTests(): void {
71
+ if (activeWorker) {
72
+ try { activeWorker.terminate(); } catch { /* */ }
73
+ activeWorker = null;
74
+ }
75
+ cancelActiveRun?.();
76
+ cancelActiveRun = null;
77
+ testHooks = null;
78
+ resetStorageMutationCoordinatorForTests();
79
+ }
80
+
81
+ /** Terminate an in-flight worker during process shutdown. */
82
+ export function abortRestoreTrashJob(): void {
83
+ if (activeWorker) {
84
+ try { activeWorker.terminate(); } catch { /* */ }
85
+ activeWorker = null;
86
+ }
87
+ cancelActiveRun?.();
88
+ cancelActiveRun = null;
89
+ }
90
+
91
+ /** Test-only SSE/text stream served from the proxy while a worker is blocked. */
92
+ export function getRestoreTrashTestStreamResponse(): Response | null {
93
+ if (!testHooks?.enableTestStream) return null;
94
+ const encoder = new TextEncoder();
95
+ return new Response(
96
+ new ReadableStream({
97
+ async start(controller) {
98
+ for (let i = 0; i < 8; i++) {
99
+ controller.enqueue(encoder.encode(`chunk-${i}\n`));
100
+ await Bun.sleep(50);
101
+ }
102
+ controller.close();
103
+ },
104
+ }),
105
+ { headers: { "content-type": "text/plain; charset=utf-8" } },
106
+ );
107
+ }
108
+
109
+ function runInWorker(opts: {
110
+ trashId: string;
111
+ codexHome?: string;
112
+ busyTimeoutMs?: number;
113
+ blockMs?: number;
114
+ restoreTest?: RestoreTestHooks;
115
+ }): Promise<RestoreResult> {
116
+ return new Promise((resolve, reject) => {
117
+ const requestId = crypto.randomUUID();
118
+ let settled = false;
119
+ const worker = new Worker(new URL("./restore-worker.ts", import.meta.url).href);
120
+ activeWorker = worker;
121
+
122
+ const timer = setTimeout(() => {
123
+ if (settled) return;
124
+ settled = true;
125
+ cancelActiveRun = null;
126
+ try { worker.terminate(); } catch { /* */ }
127
+ if (activeWorker === worker) activeWorker = null;
128
+ reject(new Error("restore_worker_timeout"));
129
+ }, WORKER_TIMEOUT_MS);
130
+
131
+ const finish = (fn: () => void) => {
132
+ if (settled) return;
133
+ settled = true;
134
+ cancelActiveRun = null;
135
+ clearTimeout(timer);
136
+ if (activeWorker === worker) activeWorker = null;
137
+ try { worker.terminate(); } catch { /* */ }
138
+ fn();
139
+ };
140
+
141
+ cancelActiveRun = () => {
142
+ finish(() => reject(new Error("aborted")));
143
+ };
144
+
145
+ worker.onmessage = (event: MessageEvent<unknown>) => {
146
+ const data = event.data;
147
+ if (!data || typeof data !== "object") return;
148
+ const msg = data as Record<string, unknown>;
149
+ if (msg.requestId !== requestId) return;
150
+ if (msg.type === "done" && msg.result && typeof msg.result === "object") {
151
+ finish(() => resolve(msg.result as RestoreResult));
152
+ return;
153
+ }
154
+ if (msg.type === "error") {
155
+ const message = typeof msg.message === "string" ? msg.message : "worker_failed";
156
+ finish(() => reject(new Error(message)));
157
+ }
158
+ };
159
+
160
+ worker.onerror = (err: ErrorEvent) => {
161
+ finish(() => reject(err.error instanceof Error ? err.error : new Error(err.message || "worker_failed")));
162
+ };
163
+
164
+ worker.postMessage({
165
+ type: "run",
166
+ requestId,
167
+ trashId: opts.trashId,
168
+ ...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
169
+ ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
170
+ ...(opts.blockMs !== undefined ? { blockMs: opts.blockMs } : {}),
171
+ ...(opts.restoreTest ? { restoreTest: opts.restoreTest } : {}),
172
+ env: {
173
+ ...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),
174
+ ...(process.env.OPENCODEX_HOME ? { OPENCODEX_HOME: process.env.OPENCODEX_HOME } : {}),
175
+ },
176
+ });
177
+ });
178
+ }
179
+
180
+ async function executeRestore(opts: {
181
+ trashId: string;
182
+ codexHome?: string;
183
+ busyTimeoutMs?: number;
184
+ _test?: RestoreTestHooks;
185
+ }): Promise<RestoreResult> {
186
+ const blockMs = testHooks?.blockMs;
187
+ const restoreTest = opts._test ?? testHooks?.restoreTest;
188
+
189
+ if (testHooks?.runInProcess) {
190
+ if (typeof blockMs === "number" && blockMs > 0) await Bun.sleep(blockMs);
191
+ return restoreTrashEntry(opts.trashId, {
192
+ ...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
193
+ ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
194
+ ...(restoreTest ? { _test: restoreTest } : {}),
195
+ });
196
+ }
197
+
198
+ try {
199
+ return await runInWorker({
200
+ trashId: opts.trashId,
201
+ ...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
202
+ ...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
203
+ ...(typeof blockMs === "number" && blockMs > 0 ? { blockMs } : {}),
204
+ ...(restoreTest ? { restoreTest } : {}),
205
+ });
206
+ } catch (err) {
207
+ return restoreResultFromWorkerRejection(err, opts.trashId);
208
+ }
209
+ }
210
+
211
+ function busyRestoreResult(): RestoreResult {
212
+ return {
213
+ ok: false,
214
+ count: 0,
215
+ bytes: 0,
216
+ restoredPaths: [],
217
+ error: "storage_mutation_busy",
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Run trash restore off the event loop under the shared storage-mutation gate.
223
+ * Returns `storage_mutation_busy` when cleanup or another restore is in flight.
224
+ */
225
+ export async function runRestoreTrashEntryJob(
226
+ trashId: string,
227
+ options?: {
228
+ codexHome?: string;
229
+ busyTimeoutMs?: number;
230
+ _test?: RestoreTestHooks;
231
+ },
232
+ ): Promise<RestoreResult> {
233
+ const codexHome = options?.codexHome ?? resolveCodexHomeDir();
234
+ const result = await withStorageMutationSlot("restore", codexHome, () =>
235
+ executeRestore({ trashId, ...options }),
236
+ );
237
+ if (result && typeof result === "object" && "ok" in result && result.ok === false
238
+ && "error" in result && result.error === "storage_mutation_busy") {
239
+ return busyRestoreResult();
240
+ }
241
+ return result as RestoreResult;
242
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Worker-thread entry for trash restore runs.
3
+ * Keeps file moves and SQLite reconcile off the proxy event loop.
4
+ */
5
+ import { restoreTrashEntry, type RestoreResult, type RestoreTestHooks } from "./cleanup";
6
+
7
+ interface RunMessage {
8
+ type: "run";
9
+ requestId: string;
10
+ trashId: string;
11
+ codexHome?: string;
12
+ busyTimeoutMs?: number;
13
+ /** Test-only: block before restore so responsiveness tests can probe /healthz. */
14
+ blockMs?: number;
15
+ restoreTest?: RestoreTestHooks;
16
+ /** Env snapshot — Workers may not see parent mutations on all platforms. */
17
+ env?: { CODEX_HOME?: string; OPENCODEX_HOME?: string };
18
+ }
19
+
20
+ function isRunMessage(data: unknown): data is RunMessage {
21
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
22
+ const o = data as Record<string, unknown>;
23
+ return o.type === "run" && typeof o.requestId === "string" && typeof o.trashId === "string";
24
+ }
25
+
26
+ declare const self: Worker;
27
+
28
+ self.onmessage = async (event: MessageEvent<unknown>) => {
29
+ if (!isRunMessage(event.data)) return;
30
+ const { requestId, trashId, codexHome, busyTimeoutMs, blockMs, restoreTest, env } = event.data;
31
+ try {
32
+ if (env?.CODEX_HOME) process.env.CODEX_HOME = env.CODEX_HOME;
33
+ if (env?.OPENCODEX_HOME) process.env.OPENCODEX_HOME = env.OPENCODEX_HOME;
34
+ if (typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0) {
35
+ await Bun.sleep(Math.floor(blockMs));
36
+ }
37
+ const result = restoreTrashEntry(trashId, {
38
+ ...(codexHome ? { codexHome } : {}),
39
+ ...(busyTimeoutMs !== undefined ? { busyTimeoutMs } : {}),
40
+ ...(restoreTest ? { _test: restoreTest } : {}),
41
+ });
42
+ self.postMessage({ type: "done", requestId, result });
43
+ } catch (err) {
44
+ self.postMessage({
45
+ type: "error",
46
+ requestId,
47
+ message: err instanceof Error ? err.message : "worker_failed",
48
+ });
49
+ }
50
+ };
51
+
52
+ export type { RestoreResult };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Single-flight gate for CODEX_HOME storage mutations (cleanup, restore, policy).
3
+ *
4
+ * Manual cleanup, trash restore, and (Phase 3) policy-driven cleanup share one
5
+ * in-flight slot per resolved CODEX_HOME. A second caller receives
6
+ * `storage_mutation_busy` immediately — no per-operation queues.
7
+ */
8
+ import { resolve } from "node:path";
9
+ import { resolveCodexHomeDir } from "../codex/home";
10
+
11
+ export type StorageMutationKind = "cleanup" | "restore" | "policy";
12
+
13
+ export type StorageMutationBusyError = "storage_mutation_busy";
14
+
15
+ export interface StorageMutationCoordinatorTestHooks {
16
+ /** Block after acquiring the slot, before mutation work (race tests). */
17
+ blockMs?: number;
18
+ }
19
+
20
+ interface ActiveSlot {
21
+ kind: StorageMutationKind;
22
+ startedAt: number;
23
+ }
24
+
25
+ const slots = new Map<string, ActiveSlot>();
26
+ let testHooks: StorageMutationCoordinatorTestHooks | null = null;
27
+
28
+ function slotKey(codexHome?: string): string {
29
+ return resolve(codexHome ?? resolveCodexHomeDir());
30
+ }
31
+
32
+ export function setStorageMutationCoordinatorTestHooks(
33
+ hooks: StorageMutationCoordinatorTestHooks | null,
34
+ ): void {
35
+ testHooks = hooks;
36
+ }
37
+
38
+ export function resetStorageMutationCoordinatorForTests(): void {
39
+ testHooks = null;
40
+ slots.clear();
41
+ }
42
+
43
+ export function getActiveStorageMutation(
44
+ codexHome?: string,
45
+ ): { kind: StorageMutationKind; startedAt: number } | null {
46
+ const active = slots.get(slotKey(codexHome));
47
+ return active ? { kind: active.kind, startedAt: active.startedAt } : null;
48
+ }
49
+
50
+ export function tryBeginStorageMutation(
51
+ kind: StorageMutationKind,
52
+ codexHome?: string,
53
+ ): { acquired: true } | { acquired: false; error: StorageMutationBusyError } {
54
+ const key = slotKey(codexHome);
55
+ if (slots.has(key)) {
56
+ return { acquired: false, error: "storage_mutation_busy" };
57
+ }
58
+ slots.set(key, { kind, startedAt: Date.now() });
59
+ return { acquired: true };
60
+ }
61
+
62
+ export function endStorageMutation(codexHome?: string): void {
63
+ slots.delete(slotKey(codexHome));
64
+ }
65
+
66
+ async function applyCoordinatorBlock(): Promise<void> {
67
+ const blockMs = testHooks?.blockMs;
68
+ if (typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0) {
69
+ await Bun.sleep(Math.floor(blockMs));
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Phase 3 policy worker integration — wrap policy-driven cleanup FS/DB work.
75
+ * Returns `{ ok: false, error: 'storage_mutation_busy' }` when another mutation
76
+ * holds the CODEX_HOME slot.
77
+ */
78
+ export async function runPolicyStorageMutation<T>(
79
+ codexHome: string | undefined,
80
+ work: () => T | Promise<T>,
81
+ ): Promise<T | { ok: false; error: StorageMutationBusyError }> {
82
+ const gate = tryBeginStorageMutation("policy", codexHome);
83
+ if (!gate.acquired) {
84
+ return { ok: false, error: "storage_mutation_busy" };
85
+ }
86
+ try {
87
+ await applyCoordinatorBlock();
88
+ return await work();
89
+ } finally {
90
+ endStorageMutation(codexHome);
91
+ }
92
+ }
93
+
94
+ export async function withStorageMutationSlot<T>(
95
+ kind: StorageMutationKind,
96
+ codexHome: string | undefined,
97
+ work: () => T | Promise<T>,
98
+ ): Promise<T | { ok: false; error: StorageMutationBusyError }> {
99
+ const gate = tryBeginStorageMutation(kind, codexHome);
100
+ if (!gate.acquired) {
101
+ return { ok: false, error: "storage_mutation_busy" };
102
+ }
103
+ try {
104
+ await applyCoordinatorBlock();
105
+ return await work();
106
+ } finally {
107
+ endStorageMutation(codexHome);
108
+ }
109
+ }
@@ -6,6 +6,7 @@ import { join, resolve } from "node:path";
6
6
  import { expandUserPath, getConfigDir } from "../config";
7
7
  import { durableBunPath } from "../lib/bun-runtime";
8
8
  import { hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
9
+ import { recordOwnedConfigPath } from "../lib/config-ownership";
9
10
 
10
11
  const RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
11
12
  const RUN_PARENT_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion";
@@ -476,6 +477,7 @@ export function installWindowsTray(startNow = true): WindowsTrayStatus {
476
477
  for (const path of [entry.bun, entry.cli, sourceScript, ...iconPairs.map(pair => pair.source)]) {
477
478
  if (!existsSync(path)) throw new Error(`Cannot install the tray because a required file is missing: ${path}`);
478
479
  }
480
+ recordOwnedConfigPath(getConfigDir(), trayStatePath());
479
481
  if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
480
482
  const runCommand = buildWindowsTrayRunCommand(entry);
481
483
  const runValue = windowsTrayRunValue(entry.opencodexHome);
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { KiroOAuthMetadata } from "./oauth/types";
2
+
1
3
  export interface OcxParsedRequest {
2
4
  modelId: string;
3
5
  previousResponseId?: string;
@@ -23,6 +25,8 @@ export interface OcxParsedRequest {
23
25
  * derived from the parent thread id.
24
26
  */
25
27
  _cursorIsolateConversation?: boolean;
28
+ /** Account-scoped, non-secret Kiro request metadata selected with the OAuth access token. */
29
+ _kiroAuthContext?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
26
30
  /** Provider-private continuation metadata resolved from the Responses previous_response_id chain. */
27
31
  _providerContinuation?: OcxProviderContinuationState;
28
32
  /**
@@ -31,6 +35,8 @@ export interface OcxParsedRequest {
31
35
  * executes searches via the gpt-5.4-mini sidecar (see src/web-search). Absent when not requested.
32
36
  */
33
37
  _webSearch?: Record<string, unknown>;
38
+ /** Hosted image_generation tool config stashed for the image bridge sidecar (see src/images). */
39
+ _imageGeneration?: { toolNames: Set<string>; originalTool?: Record<string, unknown> };
34
40
  /**
35
41
  * True when Codex requested structured output (`text.format` = json_schema/json_object). The
36
42
  * web-search tool_result is then rendered as compact JSON instead of markdown prose, so its
@@ -152,6 +158,10 @@ export interface OcxTool {
152
158
  loadedFromToolSearch?: boolean;
153
159
  /** Synthetic web_search tool: the model's call is executed by the gpt-5.4-mini sidecar, not relayed to Codex. */
154
160
  webSearch?: boolean;
161
+ /** Synthetic image_gen tool: the model's call is executed by the xAI image bridge sidecar, not relayed to Codex. */
162
+ imageGeneration?: boolean;
163
+ /** Synthetic video_gen tool: executed by the xAI video bridge sidecar. */
164
+ videoGeneration?: boolean;
155
165
  }
156
166
 
157
167
  /**
@@ -432,6 +442,11 @@ export interface OcxClaudeCodeConfig {
432
442
  * free. Only ocx-*.md files are owned/pruned. Default: enabled.
433
443
  */
434
444
  injectAgents?: boolean;
445
+ /**
446
+ * Optional Claude Code effort pinned in every generated ocx-* subagent
447
+ * definition. Unset inherits the parent session effort.
448
+ */
449
+ subagentEffort?: "low" | "medium" | "high" | "xhigh" | "max";
435
450
  /** Claude-originated web-search override. Unset fields inherit the global sidecar settings. */
436
451
  webSearchSidecar?: { backend?: "openai" | "anthropic"; model?: string };
437
452
  /** Claude-originated vision override. Unset fields inherit the global sidecar settings. */
@@ -459,6 +474,25 @@ export interface OcxClaudeDesktopProfile {
459
474
  appliedAt?: string;
460
475
  }
461
476
 
477
+ /**
478
+ * Opt-in archived-session auto-cleanup policy (issue #42 Phase 3).
479
+ * Persisted under `OcxConfig.storageCleanupPolicy`. Default `enabled: false`.
480
+ */
481
+ export interface StorageCleanupPolicy {
482
+ /** When false/unset, the engine never mutates. Default false. */
483
+ enabled: boolean;
484
+ /** Run when archived session bytes exceed this threshold. */
485
+ trigger: { archivedBytesOver: number };
486
+ /** Either shrink archives toward a byte floor, or remove the oldest N%. */
487
+ target: { reduceToBytes?: number } | { removeOldestPercent?: number };
488
+ schedule: "startup" | "daily" | "weekly" | "manual";
489
+ /** Default quarantine. Permanent only when explicitly set. */
490
+ mode: "quarantine" | "permanent";
491
+ lastRun?: { at: number; freedBytes: number; removed: number };
492
+ /** Epoch ms when the next scheduled evaluation is due. */
493
+ nextRun?: number;
494
+ }
495
+
462
496
  /** 사용자가 대시보드에서 직접 추가한 커스텀 모델 정의. */
463
497
  export interface OcxCustomModel {
464
498
  /** 고유 ID (crypto.randomUUID()) */
@@ -501,6 +535,11 @@ export interface OcxConfig {
501
535
  */
502
536
  subagentModelFallbackPollMs?: number;
503
537
  injectionModel?: string;
538
+ /**
539
+ * Opt in to synchronizing the selected injection model into Codex's native
540
+ * sub-agent defaults. Only meaningful while `injectionModel` is set.
541
+ */
542
+ syncCodexSubagentDefaults?: boolean;
504
543
  /**
505
544
  * Optional reasoning effort the delegation prompt tells the agent to pass in spawn_agent calls
506
545
  * (`reasoning_effort` argument). Only meaningful while `injectionModel` is set; validated against
@@ -613,6 +652,12 @@ export interface OcxConfig {
613
652
  shutdownTimeoutMs?: number;
614
653
  /** Advertise supports_websockets so Codex opens the WS endpoint. Default false; set true to opt in. */
615
654
  websockets?: boolean;
655
+ /**
656
+ * Opt-in auto-cleanup policy for archived Codex sessions (issue #42 Phase 3).
657
+ * Default OFF (`enabled` false / unset). Never enabled implicitly.
658
+ * See `src/storage/policy.ts`.
659
+ */
660
+ storageCleanupPolicy?: StorageCleanupPolicy;
616
661
  /** Generated API keys for external access to the proxy's /v1/responses endpoint. */
617
662
  apiKeys?: Array<{ id: string; name: string; key: string; createdAt: string }>;
618
663
  /** Auto-start/sync the proxy from the Codex shim before launching Codex. Default true. */
@@ -640,12 +685,38 @@ export interface OcxConfig {
640
685
  search?: OcxSearchConfig;
641
686
  /** Codex multi-account pool. */
642
687
  codexAccounts?: CodexAccount[];
688
+ /** Account ids administratively excluded from future pool selection until resumed. */
689
+ pausedCodexAccountIds?: string[];
690
+ /**
691
+ * Public model-selector namespaces bound to one Codex account. Values are stored account ids;
692
+ * `"@main"` selects the Codex Desktop/main auth.json account. Account display aliases
693
+ * are intentionally separate from these selectors.
694
+ */
695
+ codexAccountNamespaces?: Record<string, string>;
643
696
  /** Active pool account id for next session. undefined = main (passthrough as-is). */
644
697
  activeCodexAccountId?: string;
645
698
  /** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
646
699
  autoSwitchThreshold?: number;
700
+ /** New-session account rotation strategy for the Codex pool. Default quota (today's behaviour). */
701
+ accountPoolStrategy?: OcxAccountPoolRotationStrategy;
702
+ /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
703
+ accountPoolStickyLimit?: number;
647
704
  /** Consecutive non-2xx upstream responses before switching future new threads. Default 3. 0 = disabled. */
648
705
  upstreamFailoverThreshold?: number;
706
+ /**
707
+ * Opt-in Anthropic OAuth account pool (#294). Default OFF.
708
+ * Failover on 429 + sticky affinity; new sessions may pick lowest known 5h usage.
709
+ * Experimental — see docs and GUI warning before enabling.
710
+ */
711
+ anthropicAccountPool?: {
712
+ enabled?: boolean;
713
+ /** Usage % threshold for new-session auto-pick. Default 80. 0 = disabled (affinity/active only). */
714
+ autoSwitchThreshold?: number;
715
+ /** New-session rotation strategy. Default quota (today's behaviour). */
716
+ strategy?: OcxAccountPoolRotationStrategy;
717
+ /** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
718
+ stickyLimit?: number;
719
+ };
649
720
  /** Virtual `combo/<id>` models spanning concrete provider/model targets (issue #133). */
650
721
  combos?: Record<string, OcxComboConfig>;
651
722
  /** Background proactive token refresh ("Token Guardian"). Off by default; see OcxTokenGuardianConfig. */
@@ -654,6 +725,8 @@ export interface OcxConfig {
654
725
  corsAllowOrigins?: string[];
655
726
  }
656
727
 
728
+ export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-first";
729
+
657
730
  export type OcxComboStrategy = "failover" | "round-robin";
658
731
  export type OcxComboDefaultEffort = "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
659
732
 
@@ -714,8 +787,24 @@ export interface OcxTokenGuardianConfig {
714
787
  export interface OcxImagesConfig {
715
788
  /** Optional custom API-key provider for /v1/images relays. Built-in OpenAI tiers remain automatic. */
716
789
  provider?: string;
717
- /** Upstream timeout (ms) for one /v1/images relay. Default 300000 generation is slow. */
790
+ /** Upstream timeout (ms) for one image generation/edit call (bridge xAI + /v1/images relay). Default 60000 for the bridge; relay may use a higher default (300000). */
718
791
  timeoutMs?: number;
792
+ /** Master switch for the image bridge. Default false — set true to enable paid xAI Grok Imagine generation. */
793
+ bridgeEnabled?: boolean;
794
+ /** xAI image model id. Default "grok-imagine-image-quality" (see DEFAULT_MODEL in images/plan.ts). */
795
+ bridgeModel?: string;
796
+ /** Max image-generation loop iterations before forced-final. Default 3; clamped to [0, 10]. */
797
+ maxRounds?: number;
798
+ /** Max files retained under artifacts/. Oldest deleted when exceeded. Default 200. */
799
+ artifactsKeepCount?: number;
800
+ /** Master switch for the video bridge. Default false — must be explicitly opted in. */
801
+ videoBridgeEnabled?: boolean;
802
+ /** Model for xAI video generation. Default "grok-imagine-video". */
803
+ videoBridgeModel?: string;
804
+ /** Max video-gen rounds before forced-final. Default 2 (video is slower than image). */
805
+ videoMaxRounds?: number;
806
+ /** Per-video generation timeout (ms) including polling. Default 300000 (5 min). */
807
+ videoTimeoutMs?: number;
719
808
  }
720
809
 
721
810
  export interface OcxSearchConfig {
@@ -817,6 +906,12 @@ export interface OcxProviderConfig {
817
906
  */
818
907
  codexAccountMode?: CodexAccountMode;
819
908
  apiKey?: string;
909
+ /**
910
+ * Key-auth header style for Anthropic-compatible providers.
911
+ * Defaults to the native Anthropic `x-api-key`; gateways may require
912
+ * `Authorization: Bearer <key>` instead.
913
+ */
914
+ apiKeyTransport?: "x-api-key" | "bearer";
820
915
  /**
821
916
  * Multi-key pool (API-key twin of OAuth multiauth). `apiKey` always mirrors the ACTIVE
822
917
  * entry so routing stays single-key; managed via /api/providers/keys. A legacy bare