@bitkyc08/opencodex 2.36.0-preview.20260829 → 2.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-Cy7Z_pl0.css +1 -0
- package/gui/dist/assets/index-DO8liQVL.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +2 -1
- package/src/AGENTS.md +2 -1
- package/src/adapters/agentrouter.ts +50 -0
- package/src/adapters/anthropic.ts +1 -51
- package/src/adapters/cursor/call-id.ts +76 -8
- package/src/adapters/cursor/checkpoint-store.ts +6 -1
- package/src/adapters/cursor/cursor-errors.ts +44 -0
- package/src/adapters/cursor/native-exec.ts +13 -0
- package/src/adapters/cursor/protobuf-request.ts +651 -29
- package/src/adapters/cursor/tool-result-normalize.ts +3 -3
- package/src/adapters/cursor/transport-retry.ts +5 -1
- package/src/adapters/cursor.ts +15 -1
- package/src/adapters/empty-tool-output-annotation.ts +43 -0
- package/src/adapters/exec-tool-result-normalize.ts +70 -5
- package/src/adapters/google.ts +22 -2
- package/src/adapters/kiro.ts +26 -2
- package/src/adapters/ollama-native-url.ts +111 -0
- package/src/adapters/ollama-native.ts +1131 -0
- package/src/adapters/openai-chat.ts +30 -7
- package/src/adapters/openai-responses.ts +72 -4
- package/src/adapters/registry.ts +7 -0
- package/src/adapters/xai-web-search.ts +58 -0
- package/src/claude/desktop-3p.ts +21 -1
- package/src/claude/desktop-policy.ts +149 -0
- package/src/cli/account.ts +16 -2
- package/src/cli/claude-desktop.ts +13 -3
- package/src/cli/combo.ts +8 -5
- package/src/cli/doctor.ts +77 -11
- package/src/cli/help.ts +1 -1
- package/src/cli/index.ts +16 -0
- package/src/cli/models.ts +20 -3
- package/src/cli/registry.ts +2 -1
- package/src/cli/status.ts +140 -2
- package/src/cli/storage.ts +10 -1
- package/src/codex/account-runtime-state.ts +39 -5
- package/src/codex/account-store.ts +393 -13
- package/src/codex/account-usability.ts +11 -4
- package/src/codex/app-server-processes.ts +46 -5
- package/src/codex/auth-context.ts +160 -32
- package/src/codex/catalog/bundled.ts +7 -5
- package/src/codex/catalog/metadata.ts +1 -1
- package/src/codex/catalog/parsing.ts +57 -1
- package/src/codex/catalog/provider-fetch.ts +61 -4
- package/src/codex/catalog/sync.ts +4 -3
- package/src/codex/convergence.ts +3 -2
- package/src/codex/data/upstream-models.json +40 -8
- package/src/codex/inject-coordination.ts +111 -14
- package/src/codex/integration-record.ts +12 -2
- package/src/codex/main-account.ts +225 -1
- package/src/codex/model-entitlements.ts +339 -27
- package/src/codex/prompt-layers.ts +346 -7
- package/src/codex/prompt-text-probe.ts +272 -21
- package/src/codex/routing.ts +693 -132
- package/src/codex/runtime.ts +12 -0
- package/src/codex/subagent-model-fallback.ts +62 -24
- package/src/codex/user-identity.ts +33 -25
- package/src/combos/index.ts +1 -0
- package/src/combos/reset-window.ts +46 -0
- package/src/combos/resolve.ts +84 -2
- package/src/combos/types.ts +5 -2
- package/src/config/atomic-write.ts +104 -22
- package/src/config/provider-validation.ts +11 -0
- package/src/config.ts +75 -3
- package/src/generated/compatibility-version.json +207 -131
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/catalog.ts +71 -0
- package/src/grok/effort.ts +83 -0
- package/src/grok/inject.ts +952 -127
- package/src/grok/models.ts +56 -0
- package/src/grok/status.ts +21 -8
- package/src/grok/sync.ts +10 -18
- package/src/images/loop.ts +6 -3
- package/src/integrations/native/ownership-preflight.ts +4 -1
- package/src/lab/fabric/producer-isolate.ts +36 -3
- package/src/lib/destination-policy.ts +93 -7
- package/src/lib/redact.ts +6 -1
- package/src/lib/shadow-call.ts +38 -3
- package/src/lib/test-home-guard.ts +18 -3
- package/src/lib/upstream-retry.ts +43 -6
- package/src/lib/windows-secret-acl.ts +66 -0
- package/src/lib/windows-text.ts +28 -2
- package/src/lib/windows-user-principal.ts +35 -23
- package/src/oauth/account-quota-rank.ts +107 -0
- package/src/oauth/anthropic-routing.ts +125 -30
- package/src/oauth/chatgpt.ts +5 -1
- package/src/oauth/generic-account-failover.ts +114 -7
- package/src/oauth/index.ts +15 -8
- package/src/oauth/store.ts +16 -0
- package/src/providers/account-quota-disk.ts +79 -0
- package/src/providers/command-code-efforts.ts +24 -0
- package/src/providers/derive.ts +6 -0
- package/src/providers/key-failover.ts +33 -1
- package/src/providers/kiro-usage.ts +272 -0
- package/src/providers/ollama-show.ts +311 -0
- package/src/providers/openai-sidecar.ts +5 -0
- package/src/providers/quota-routing-cache.ts +32 -0
- package/src/providers/quota-types.ts +36 -0
- package/src/providers/quota-wire.ts +102 -0
- package/src/providers/quota.ts +208 -147
- package/src/providers/registry.ts +68 -8
- package/src/providers/slug-codec.ts +12 -4
- package/src/providers/vercel-gateway-routing.ts +108 -0
- package/src/router.ts +22 -12
- package/src/server/auth-cors.ts +26 -0
- package/src/server/catalog-download.ts +73 -0
- package/src/server/chat-native.ts +12 -2
- package/src/server/gui-static.ts +4 -1
- package/src/server/index.ts +132 -9
- package/src/server/management/agent-settings-routes.ts +38 -5
- package/src/server/management/codex-prompt-routes.ts +7 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/management/config-routes.ts +9 -1
- package/src/server/management/context.ts +5 -0
- package/src/server/management/model-routes.ts +16 -6
- package/src/server/management/native-integration-routes.ts +12 -17
- package/src/server/management/oauth-account-routes.ts +13 -0
- package/src/server/management/provider-routes.ts +32 -5
- package/src/server/management/routing-profile-routes.ts +15 -0
- package/src/server/management/shadow-call-validation.ts +29 -0
- package/src/server/management-api.ts +7 -3
- package/src/server/request-log.ts +3 -5
- package/src/server/responses/agent-task-recovery-cache.ts +8 -0
- package/src/server/responses/agent-task-recovery.ts +52 -20
- package/src/server/responses/codex-auth-error.ts +26 -0
- package/src/server/responses/compact.ts +345 -10
- package/src/server/responses/core.ts +736 -108
- package/src/server/responses/empty-completion-guard.ts +16 -0
- package/src/server/responses/fetch-helpers.ts +42 -0
- package/src/server/responses/policy-fallback.ts +11 -6
- package/src/server/responses-undeclared-tool-guard.ts +16 -3
- package/src/server/startup-health-cache.ts +59 -13
- package/src/service-manager-probe.ts +115 -9
- package/src/service.ts +139 -40
- package/src/storage/cleanup.ts +10 -0
- package/src/storage/storage-mutation-coordinator.ts +14 -3
- package/src/tray/windows-tray.ps1 +10 -4
- package/src/tray/windows.ts +30 -2
- package/src/types/config.ts +27 -14
- package/src/types/provider.ts +54 -0
- package/src/types/tools.ts +13 -3
- package/src/types.ts +4 -0
- package/src/usage/summary.ts +421 -177
- package/src/vision/anthropic-describe.ts +3 -3
- package/src/vision/describe.ts +5 -3
- package/src/web-search/anthropic-executor.ts +9 -2
- package/src/web-search/exa-executor.ts +3 -3
- package/src/web-search/executor.ts +8 -3
- package/src/web-search/gemini-executor.ts +3 -3
- package/src/web-search/loop.ts +11 -3
- package/src/web-search/xai-executor.ts +3 -3
- package/gui/dist/assets/index-DQ-Ie18T.css +0 -1
- package/gui/dist/assets/index-DX0RGj3e.js +0 -112
|
@@ -100,41 +100,236 @@ function resolveCodexBinary(): string | null {
|
|
|
100
100
|
/** 8 MiB is far above any real prompt and far below anything that hurts the server. */
|
|
101
101
|
const MAX_PROBE_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
interface ProbeCommand {
|
|
104
|
+
binary: string;
|
|
105
|
+
args: string[];
|
|
106
|
+
cwd: string;
|
|
107
|
+
timeoutMs: number;
|
|
108
|
+
promptStateFingerprint: string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface PromptProbeFlight {
|
|
112
|
+
key: string;
|
|
113
|
+
controller: AbortController;
|
|
114
|
+
result: Promise<string | null>;
|
|
115
|
+
closed: Promise<void>;
|
|
116
|
+
waiters: number;
|
|
117
|
+
joinable: boolean;
|
|
118
|
+
resultSettled: boolean;
|
|
119
|
+
settled: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface PromptProbeExecution {
|
|
123
|
+
result: Promise<string | null>;
|
|
124
|
+
closed: Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type SharedPromptProbeOutcome =
|
|
128
|
+
| { kind: "output"; raw: string }
|
|
129
|
+
| { kind: "failed" }
|
|
130
|
+
| { kind: "busy" };
|
|
131
|
+
|
|
132
|
+
let activePromptProbe: PromptProbeFlight | null = null;
|
|
133
|
+
let probeCommandForTests: { binary: string; args: string[] } | null = null;
|
|
134
|
+
let probeSpawnAttemptsForTests = 0;
|
|
135
|
+
let probeCloseBarrierForTests: Promise<void> | null = null;
|
|
136
|
+
|
|
137
|
+
function commandKey(command: ProbeCommand): string {
|
|
138
|
+
return JSON.stringify([
|
|
139
|
+
command.binary,
|
|
140
|
+
command.args,
|
|
141
|
+
command.cwd,
|
|
142
|
+
command.timeoutMs,
|
|
143
|
+
command.promptStateFingerprint,
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function completedExecution(value: string | null): PromptProbeExecution {
|
|
148
|
+
return { result: Promise.resolve(value), closed: Promise.resolve() };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runProbe(
|
|
152
|
+
command: ProbeCommand,
|
|
153
|
+
signal: AbortSignal,
|
|
154
|
+
onStopping: () => void,
|
|
155
|
+
): PromptProbeExecution {
|
|
156
|
+
if (signal.aborted) return completedExecution(null);
|
|
157
|
+
let resolveResult!: (value: string | null) => void;
|
|
158
|
+
let resolveClosed!: () => void;
|
|
159
|
+
const result = new Promise<string | null>(resolve => { resolveResult = resolve; });
|
|
160
|
+
const closed = new Promise<void>(resolve => { resolveClosed = resolve; });
|
|
161
|
+
let resultSettled = false;
|
|
162
|
+
let closeSettled = false;
|
|
163
|
+
|
|
164
|
+
const finishResult = (value: string | null) => {
|
|
165
|
+
if (resultSettled) return;
|
|
166
|
+
resultSettled = true;
|
|
167
|
+
resolveResult(value);
|
|
168
|
+
};
|
|
169
|
+
const finishClosed = () => {
|
|
170
|
+
if (closeSettled) return;
|
|
171
|
+
closeSettled = true;
|
|
172
|
+
resolveClosed();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
try {
|
|
105
176
|
// A probe must never hang OR balloon the management API: it is bounded in
|
|
106
177
|
// time AND in bytes, and every failure degrades to "unavailable" rather than
|
|
107
178
|
// an error page.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
179
|
+
let child: ReturnType<typeof spawn>;
|
|
180
|
+
try {
|
|
181
|
+
if (probeCommandForTests) probeSpawnAttemptsForTests += 1;
|
|
182
|
+
child = spawn(command.binary, command.args, {
|
|
183
|
+
cwd: command.cwd,
|
|
184
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
185
|
+
});
|
|
186
|
+
} catch {
|
|
187
|
+
finishResult(null);
|
|
188
|
+
finishClosed();
|
|
189
|
+
return { result, closed };
|
|
190
|
+
}
|
|
112
191
|
const chunks: Buffer[] = [];
|
|
113
192
|
let size = 0;
|
|
114
193
|
let settled = false;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
194
|
+
let stopping = false;
|
|
195
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
196
|
+
|
|
197
|
+
const finish = (value: string | null) => {
|
|
118
198
|
if (settled) return;
|
|
119
199
|
settled = true;
|
|
120
|
-
clearTimeout(timer);
|
|
200
|
+
if (timer) clearTimeout(timer);
|
|
201
|
+
signal.removeEventListener("abort", onAbort);
|
|
202
|
+
finishResult(value);
|
|
203
|
+
finishClosed();
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Keep the flight admitted until `close`: kill() only requests termination
|
|
207
|
+
// and does not prove the exact child has released its process and stdio.
|
|
208
|
+
const terminate = () => {
|
|
209
|
+
if (settled || stopping) return;
|
|
210
|
+
stopping = true;
|
|
211
|
+
onStopping();
|
|
212
|
+
if (timer) clearTimeout(timer);
|
|
213
|
+
signal.removeEventListener("abort", onAbort);
|
|
121
214
|
child.stdout?.destroy();
|
|
122
|
-
|
|
123
|
-
|
|
215
|
+
// The caller is bounded even if OS termination later fails. Admission is
|
|
216
|
+
// retained separately by `closed`, and later probes fail soft while this
|
|
217
|
+
// exact child remains unproven terminal.
|
|
218
|
+
finishResult(null);
|
|
219
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
220
|
+
try {
|
|
221
|
+
child.kill("SIGKILL");
|
|
222
|
+
} catch {
|
|
223
|
+
// Exact child state is ambiguous. Keep admission non-joinable until its
|
|
224
|
+
// own `close` proves terminal instead of targeting a reusable numeric PID.
|
|
225
|
+
}
|
|
124
226
|
};
|
|
125
|
-
const
|
|
227
|
+
const onAbort = () => terminate();
|
|
228
|
+
|
|
229
|
+
timer = setTimeout(terminate, command.timeoutMs);
|
|
230
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
126
231
|
child.stdout?.on("data", (chunk: Buffer) => {
|
|
127
232
|
size += chunk.length;
|
|
128
|
-
if (size > MAX_PROBE_OUTPUT_BYTES) {
|
|
233
|
+
if (size > MAX_PROBE_OUTPUT_BYTES) { terminate(); return; }
|
|
129
234
|
chunks.push(chunk);
|
|
130
235
|
});
|
|
131
|
-
child.on("error", () =>
|
|
236
|
+
child.on("error", () => {
|
|
237
|
+
// No PID means spawn itself failed, so there is no live child to drain.
|
|
238
|
+
if (child.pid === undefined) {
|
|
239
|
+
finish(null);
|
|
240
|
+
}
|
|
241
|
+
else terminate();
|
|
242
|
+
});
|
|
132
243
|
child.on("close", code => {
|
|
133
244
|
// Decode once, at the end: `String(chunk)` per chunk corrupts any UTF-8
|
|
134
245
|
// character that straddles a chunk boundary.
|
|
135
|
-
|
|
246
|
+
const recordClose = () => {
|
|
247
|
+
finish(!stopping && code === 0 ? Buffer.concat(chunks).toString("utf8") : null);
|
|
248
|
+
};
|
|
249
|
+
const barrier = probeCloseBarrierForTests;
|
|
250
|
+
if (barrier) void barrier.then(recordClose, recordClose);
|
|
251
|
+
else recordClose();
|
|
136
252
|
});
|
|
253
|
+
// Close the race between the pre-spawn check and listener registration.
|
|
254
|
+
if (signal.aborted) terminate();
|
|
255
|
+
} catch {
|
|
256
|
+
finishResult(null);
|
|
257
|
+
finishClosed();
|
|
258
|
+
}
|
|
259
|
+
return { result, closed };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function startPromptProbeFlight(command: ProbeCommand): PromptProbeFlight {
|
|
263
|
+
const controller = new AbortController();
|
|
264
|
+
const flight: PromptProbeFlight = {
|
|
265
|
+
key: commandKey(command),
|
|
266
|
+
controller,
|
|
267
|
+
result: Promise.resolve(null),
|
|
268
|
+
closed: Promise.resolve(),
|
|
269
|
+
waiters: 0,
|
|
270
|
+
joinable: true,
|
|
271
|
+
resultSettled: false,
|
|
272
|
+
settled: false,
|
|
273
|
+
};
|
|
274
|
+
const execution = runProbe(command, controller.signal, () => {
|
|
275
|
+
flight.joinable = false;
|
|
137
276
|
});
|
|
277
|
+
flight.result = execution.result
|
|
278
|
+
.catch(() => null)
|
|
279
|
+
.finally(() => {
|
|
280
|
+
flight.resultSettled = true;
|
|
281
|
+
});
|
|
282
|
+
flight.closed = execution.closed
|
|
283
|
+
.finally(() => {
|
|
284
|
+
flight.settled = true;
|
|
285
|
+
if (activePromptProbe === flight) activePromptProbe = null;
|
|
286
|
+
});
|
|
287
|
+
activePromptProbe = flight;
|
|
288
|
+
return flight;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function runSharedPromptProbe(
|
|
292
|
+
command: ProbeCommand,
|
|
293
|
+
signal?: AbortSignal,
|
|
294
|
+
): Promise<SharedPromptProbeOutcome> {
|
|
295
|
+
const key = commandKey(command);
|
|
296
|
+
if (signal?.aborted) return { kind: "failed" };
|
|
297
|
+
const active = activePromptProbe;
|
|
298
|
+
if (!active) {
|
|
299
|
+
const raw = await waitForPromptProbeFlight(startPromptProbeFlight(command), signal);
|
|
300
|
+
return raw === null ? { kind: "failed" } : { kind: "output", raw };
|
|
301
|
+
}
|
|
302
|
+
if (active.key === key && active.joinable && !active.controller.signal.aborted) {
|
|
303
|
+
const raw = await waitForPromptProbeFlight(active, signal);
|
|
304
|
+
return raw === null ? { kind: "failed" } : { kind: "output", raw };
|
|
305
|
+
}
|
|
306
|
+
// A different or terminating flight still owns the sole process slot. Never
|
|
307
|
+
// wait unboundedly for an unproven close and never launch beside it.
|
|
308
|
+
return { kind: "busy" };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function waitForPromptProbeFlight(flight: PromptProbeFlight, signal?: AbortSignal): Promise<string | null> {
|
|
312
|
+
if (signal?.aborted) {
|
|
313
|
+
if (flight.waiters === 0 && !flight.settled) flight.controller.abort();
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
flight.waiters += 1;
|
|
317
|
+
let onAbort: (() => void) | undefined;
|
|
318
|
+
try {
|
|
319
|
+
if (!signal) return await flight.result;
|
|
320
|
+
const aborted = new Promise<null>(resolve => {
|
|
321
|
+
onAbort = () => resolve(null);
|
|
322
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
323
|
+
if (signal.aborted) onAbort();
|
|
324
|
+
});
|
|
325
|
+
return await Promise.race([flight.result, aborted]);
|
|
326
|
+
} finally {
|
|
327
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
328
|
+
flight.waiters = Math.max(0, flight.waiters - 1);
|
|
329
|
+
if (flight.waiters === 0 && !flight.resultSettled) {
|
|
330
|
+
flight.controller.abort(new DOMException("All prompt probe callers cancelled", "AbortError"));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
138
333
|
}
|
|
139
334
|
|
|
140
335
|
/** Pull every `<tag>...</tag>` section out of the rendered developer messages. */
|
|
@@ -182,20 +377,44 @@ export const extractSectionsForTests = extractSections;
|
|
|
182
377
|
* `cwd` matters: AGENTS.md and environment context are directory-dependent, so a
|
|
183
378
|
* probe from the wrong place would describe a prompt the user never sees.
|
|
184
379
|
*/
|
|
185
|
-
export async function probePromptText(
|
|
380
|
+
export async function probePromptText(
|
|
381
|
+
timeoutMs = 15_000,
|
|
382
|
+
signal?: AbortSignal,
|
|
383
|
+
promptStateFingerprint: string | null = null,
|
|
384
|
+
): Promise<PromptTextProbe> {
|
|
186
385
|
// The probe runs in CODEX_HOME, never in a caller-supplied directory. A `cwd`
|
|
187
386
|
// parameter let an authenticated request read any readable folder's AGENTS.md,
|
|
188
387
|
// and it also described a prompt that depends on where Codex happened to run.
|
|
189
388
|
// The global home is the one context this page can honestly report on.
|
|
190
389
|
const codexHome = resolveCodexHomeDir();
|
|
191
|
-
|
|
390
|
+
if (signal?.aborted) {
|
|
391
|
+
return { ok: false, codexHome, layers: {}, detail: "prompt probe cancelled" };
|
|
392
|
+
}
|
|
393
|
+
const binary = probeCommandForTests?.binary ?? resolveCodexBinary();
|
|
192
394
|
if (!binary) {
|
|
193
395
|
return { ok: false, codexHome, layers: {}, detail: "codex binary not found" };
|
|
194
396
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
397
|
+
const command: ProbeCommand = {
|
|
398
|
+
binary,
|
|
399
|
+
args: probeCommandForTests?.args ?? ["debug", "prompt-input"],
|
|
400
|
+
cwd: codexHome,
|
|
401
|
+
timeoutMs,
|
|
402
|
+
promptStateFingerprint,
|
|
403
|
+
};
|
|
404
|
+
const outcome = await runSharedPromptProbe(command, signal);
|
|
405
|
+
if (outcome.kind !== "output") {
|
|
406
|
+
return {
|
|
407
|
+
ok: false,
|
|
408
|
+
codexHome,
|
|
409
|
+
layers: {},
|
|
410
|
+
detail: signal?.aborted
|
|
411
|
+
? "prompt probe cancelled"
|
|
412
|
+
: outcome.kind === "busy"
|
|
413
|
+
? "another prompt probe is still finishing; retry shortly"
|
|
414
|
+
: "codex debug prompt-input failed",
|
|
415
|
+
};
|
|
198
416
|
}
|
|
417
|
+
const raw = outcome.raw;
|
|
199
418
|
const sections = extractSections(raw);
|
|
200
419
|
if (sections.size === 0) {
|
|
201
420
|
// Zero sections from a zero-exit probe means the output did not parse, which
|
|
@@ -236,3 +455,35 @@ export async function probePromptText(timeoutMs = 15_000): Promise<PromptTextPro
|
|
|
236
455
|
}
|
|
237
456
|
return { ok: true, codexHome, layers };
|
|
238
457
|
}
|
|
458
|
+
|
|
459
|
+
/** Test-only command seam; production always resolves the installed Codex binary. */
|
|
460
|
+
export function setPromptTextProbeCommandForTests(command: { binary: string; args: string[] } | null): void {
|
|
461
|
+
probeCommandForTests = command ? { binary: command.binary, args: [...command.args] } : null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Test-only process-start counter for proving admission without timing guesses. */
|
|
465
|
+
export function promptTextProbeSpawnAttemptsForTests(): number {
|
|
466
|
+
return probeSpawnAttemptsForTests;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Test-only close barrier for proving admission is not released at process exit. */
|
|
470
|
+
export function setPromptTextProbeCloseBarrierForTests(barrier: Promise<void> | null): void {
|
|
471
|
+
probeCloseBarrierForTests = barrier;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** Test-only fail-closed drain so one failed lifecycle case cannot poison another. */
|
|
475
|
+
export async function resetPromptTextProbeForTests(): Promise<void> {
|
|
476
|
+
const active = activePromptProbe;
|
|
477
|
+
if (active && !active.settled) {
|
|
478
|
+
active.controller.abort(new DOMException("Prompt probe test reset", "AbortError"));
|
|
479
|
+
const drained = await Promise.race([
|
|
480
|
+
active.closed.then(() => true),
|
|
481
|
+
Bun.sleep(2_000).then(() => false),
|
|
482
|
+
]);
|
|
483
|
+
if (!drained) throw new Error("prompt probe child did not close during test reset");
|
|
484
|
+
}
|
|
485
|
+
if (activePromptProbe === active) activePromptProbe = null;
|
|
486
|
+
probeCommandForTests = null;
|
|
487
|
+
probeSpawnAttemptsForTests = 0;
|
|
488
|
+
probeCloseBarrierForTests = null;
|
|
489
|
+
}
|