sneakoscope 0.7.54 → 0.7.55

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.54",
4
+ "version": "0.7.55",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { codexRemoteControlStatus, formatCodexRemoteControlStatus } from '../core/codex-app.mjs';
3
+ import { forceGpt55CodexConfigArgs } from '../core/codex-model-guard.mjs';
3
4
 
4
5
  export async function codexAppRemoteControlCommand(args = [], opts = {}) {
5
6
  const controlArgs = argsBeforeSeparator(args);
@@ -27,7 +28,7 @@ export async function codexAppRemoteControlCommand(args = [], opts = {}) {
27
28
  return;
28
29
  }
29
30
 
30
- const passthrough = stripSeparator(args);
31
+ const passthrough = forceGpt55CodexConfigArgs(stripSeparator(args));
31
32
  const spawnFn = opts.spawn || spawn;
32
33
  const code = await spawnInherited(spawnFn, status.codex_cli.bin, ['remote-control', ...passthrough], {
33
34
  cwd: process.cwd(),
package/src/cli/main.mjs CHANGED
@@ -5,7 +5,7 @@ import readline from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import { projectRoot, readJson, writeJsonAtomic, writeTextAtomic, appendJsonlBounded, nowIso, exists, ensureDir, tmpdir, packageRoot, dirSize, formatBytes, which, runProcess, PACKAGE_VERSION, sksRoot, globalSksRoot, findProjectRoot, readStdin } from '../core/fsx.mjs';
7
7
  import { initProject, installSkills, normalizeInstallScope, sksCommandPrefix } from '../core/init.mjs';
8
- import { getCodexInfo, runCodexExec } from '../core/codex-adapter.mjs';
8
+ import { buildCodexExecArgs, getCodexInfo, runCodexExec } from '../core/codex-adapter.mjs';
9
9
  import { createMission, loadMission, findLatestMission, missionDir, setCurrent, stateFile } from '../core/mission.mjs';
10
10
  import { buildQuestionSchema, writeQuestions } from '../core/questions.mjs';
11
11
  import { sealContract, validateAnswers } from '../core/decision-contract.mjs';
@@ -2277,6 +2277,10 @@ async function selftest() {
2277
2277
  if (defaultFastHighPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="high"') throw new Error('selftest failed: default sks tmux launch is not fast-high');
2278
2278
  const forcedModelPlan = await buildTmuxLaunchPlan({ root: tmp, env: { SKS_CODEX_MODEL: 'gpt-5.4-mini', SKS_CODEX_FAST_HIGH: '0', SKS_CODEX_REASONING: 'medium' }, tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
2279
2279
  if (forcedModelPlan.codexArgs.includes('gpt-5.4-mini') || forcedModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 -c model_reasoning_effort="medium"') throw new Error('selftest failed: sks tmux launch allowed a non-GPT-5.5 model override');
2280
+ const explicitBadModelPlan = await buildTmuxLaunchPlan({ root: tmp, codexArgs: ['--profile', 'legacy-5.4', '--model', 'gpt-5.4-mini', '-c', 'model="gpt-5.4"', '-c', 'model_reasoning_effort="low"'], tmux: { ok: true, bin: 'tmux', version: '3.4' }, codex: { bin: 'codex', version: 'codex-cli 99.0.0' }, app: { ok: true } });
2281
+ if (explicitBadModelPlan.codexArgs.join(' ').includes('gpt-5.4') || explicitBadModelPlan.codexArgs.join(' ') !== '--model gpt-5.5 --profile legacy-5.4 -c model_reasoning_effort="low"') throw new Error('selftest failed: explicit tmux model override was not forced back to GPT-5.5');
2282
+ const codexExecArgs = buildCodexExecArgs({ root: tmp, prompt: 'model guard selftest', profile: 'legacy-5.4', extraArgs: ['--model=gpt-5.4-mini', '--config', 'model = "gpt-5.4"', '-c', 'model_reasoning_effort="medium"'] });
2283
+ if (codexExecArgs.join(' ').includes('gpt-5.4') || !codexExecArgs.includes('gpt-5.5') || codexExecArgs.includes('--model=gpt-5.4-mini')) throw new Error('selftest failed: codex exec args allowed a non-GPT-5.5 model override');
2280
2284
  const codexLbHome = path.join(tmp, 'codex-lb-home');
2281
2285
  await ensureDir(path.join(codexLbHome, '.codex'));
2282
2286
  const codexLbFakeBin = path.join(tmp, 'codex-lb-fake-bin');
@@ -2309,6 +2313,7 @@ async function selftest() {
2309
2313
  if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || codexLbConfig.includes('codex_hooks = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig)) throw new Error('selftest failed: codex-lb setup did not preserve Codex App Fast mode defaults, force GPT-5.5, or migrate the hooks feature flag');
2310
2314
  const codexLbLaunch = codexLaunchCommand(tmp, 'codex', []);
2311
2315
  if (!codexLbLaunch.includes('sks-codex-lb.env')) throw new Error('selftest failed: tmux launch command does not source codex-lb env file');
2316
+ if (!codexLbLaunch.includes("'--model' 'gpt-5.5'")) throw new Error('selftest failed: tmux launch command without args did not force GPT-5.5');
2312
2317
  if (!codexLbLaunch.includes('SKS_TMUX_LOGO_ANIMATION') || !codexLbLaunch.includes('SNEAKOSCOPE CODEX')) throw new Error('selftest failed: tmux launch command does not include the animated SKS logo intro');
2313
2318
  const madLaunchSource = await safeReadText(path.join(packageRoot(), 'src', 'cli', 'main.mjs'));
2314
2319
  if (!madLaunchSource.includes('const lb = await maybePromptCodexLbSetupForLaunch(args)') || !madLaunchSource.includes("const launchLb = lb.status === 'present'") || !madLaunchSource.includes('codexLbImmediateLaunchOpts(cleanArgs, launchLb')) throw new Error('selftest failed: MAD launch does not sync codex-lb auth and fresh-session launch options');
@@ -2353,7 +2358,7 @@ async function selftest() {
2353
2358
  if (!String(openClawAutoUpdate.stdout || '').includes('Codex CLI ready: 0.1.0 -> codex-cli 99.0.0')) throw new Error('selftest failed: OpenClaw mode did not auto-approve Codex CLI update before tmux launch');
2354
2359
  const remoteControlBin = path.join(tmp, 'remote-control-bin');
2355
2360
  await ensureDir(remoteControlBin);
2356
- await writeTextAtomic(path.join(remoteControlBin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 0.130.0"; exit 0; fi\nif [ "$1" = "remote-control" ]; then echo "remote-control $*"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
2361
+ await writeTextAtomic(path.join(remoteControlBin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 0.130.0"; exit 0; fi\nif [ "$1" = "remote-control" ]; then shift; for arg in "$@"; do if [ "$arg" = "--model" ]; then echo "remote-control rejects --model" >&2; exit 64; fi; done; echo "remote-control $*"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
2357
2362
  await fsp.chmod(path.join(remoteControlBin, 'codex'), 0o755);
2358
2363
  const remoteControlStatus = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--dry-run', '--json'], {
2359
2364
  cwd: globalCwd,
@@ -2364,6 +2369,14 @@ async function selftest() {
2364
2369
  if (remoteControlStatus.code !== 0) throw new Error(`selftest failed: Codex remote-control status exited ${remoteControlStatus.code}: ${remoteControlStatus.stderr}`);
2365
2370
  const remoteControlJson = JSON.parse(remoteControlStatus.stdout);
2366
2371
  if (!remoteControlJson.ok || remoteControlJson.min_version !== '0.130.0' || !String(remoteControlJson.command || '').includes('remote-control')) throw new Error('selftest failed: Codex remote-control status did not report 0.130.0 readiness');
2372
+ const remoteControlLaunch = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'codex-app', 'remote-control', '--', '--model', 'gpt-5.4-mini', '-c', 'model="gpt-5.4"', '--example'], {
2373
+ cwd: globalCwd,
2374
+ env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, PATH: remoteControlBin },
2375
+ timeoutMs: 15000,
2376
+ maxOutputBytes: 64 * 1024
2377
+ });
2378
+ const remoteControlLaunchText = `${remoteControlLaunch.stdout}\n${remoteControlLaunch.stderr}`;
2379
+ if (remoteControlLaunch.code !== 0 || remoteControlLaunchText.includes('gpt-5.4') || remoteControlLaunchText.includes('--model') || !remoteControlLaunchText.includes('-c model="gpt-5.5"')) throw new Error('selftest failed: Codex remote-control passthrough did not force GPT-5.5 with config syntax');
2367
2380
  const remoteControlOldBin = path.join(tmp, 'remote-control-old-bin');
2368
2381
  await ensureDir(remoteControlOldBin);
2369
2382
  await writeTextAtomic(path.join(remoteControlOldBin, 'codex'), '#!/bin/sh\nif [ "$1" = "--version" ]; then echo "codex-cli 0.129.0"; exit 0; fi\necho "unexpected codex $*" >&2\nexit 2\n');
@@ -2804,6 +2817,10 @@ async function selftest() {
2804
2817
  if (hookTeamState.phase !== 'TEAM_PARALLEL_ANALYSIS_SCOUTING' || hookTeamState.implementation_allowed === false || !hookTeamState.team_plan_ready) throw new Error('selftest failed: $Team hook did not prepare direct Team mission');
2805
2818
  if (!hookTeamState.pipeline_plan_ready || !(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), PIPELINE_PLAN_ARTIFACT)))) throw new Error('selftest failed: $Team hook did not write a pipeline plan');
2806
2819
  if (!(await exists(path.join(missionDir(hookTeamTmp, hookTeamState.mission_id), 'team-plan.json')))) throw new Error('selftest failed: Team plan was not created directly');
2820
+ const hookForbiddenModelResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team should be blocked before route work', model: 'gpt-5.5', metadata: { client: { modelId: 'gpt-5.4-mini' } } }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 128 * 1024 });
2821
+ if (hookForbiddenModelResult.code !== 0) throw new Error(`selftest failed: forbidden model hook exited ${hookForbiddenModelResult.code}: ${hookForbiddenModelResult.stderr}`);
2822
+ const hookForbiddenModelJson = JSON.parse(hookForbiddenModelResult.stdout);
2823
+ if (hookForbiddenModelJson.decision !== 'block' || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.5') || !String(hookForbiddenModelJson.reason || '').includes('gpt-5.4-mini')) throw new Error('selftest failed: hook did not block GPT-5.4 client model metadata');
2807
2824
  const hookTeamPendingResult = await runProcess(process.execPath, [hookBin, 'hook', 'user-prompt-submit'], { cwd: hookTeamTmp, input: JSON.stringify({ cwd: hookTeamTmp, prompt: '$Team 새 작업으로 넘어가' }), env: { SKS_DISABLE_UPDATE_CHECK: '1' }, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
2808
2825
  if (hookTeamPendingResult.code !== 0) throw new Error(`selftest failed: pending clarification hook exited ${hookTeamPendingResult.code}: ${hookTeamPendingResult.stderr}`);
2809
2826
  const hookTeamPendingJson = JSON.parse(hookTeamPendingResult.stdout);
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { exists, packageRoot, runProcess, which } from './fsx.mjs';
3
+ import { forceGpt55CodexArgs } from './codex-model-guard.mjs';
3
4
 
4
5
  export async function findCodexBinary() {
5
6
  const env = process.env.SKS_CODEX_BIN || process.env.DCODEX_CODEX_BIN || process.env.CODEX_BIN;
@@ -25,17 +26,22 @@ export async function getCodexInfo() {
25
26
  return { bin, version, available: Boolean(bin) };
26
27
  }
27
28
 
28
- export async function runCodexExec({ root, prompt, outputFile, json = true, profile = null, extraArgs = [], onStdout, onStderr, logDir = null, stdoutFile = null, stderrFile = null, maxBufferBytes = 256 * 1024, timeoutMs = null }) {
29
- const bin = await findCodexBinary();
30
- if (!bin) {
31
- return { code: 127, stdout: '', stderr: 'Codex CLI not found. Install @openai/codex or set SKS_CODEX_BIN.' };
32
- }
29
+ export function buildCodexExecArgs({ root, prompt, outputFile, json = true, profile = null, extraArgs = [] }) {
33
30
  const args = ['exec', '--cd', root];
34
31
  if (profile) args.push('--profile', profile);
35
32
  if (json) args.push('--json');
36
33
  if (outputFile) args.push('--output-last-message', outputFile);
37
- args.push(...extraArgs);
34
+ args.push(...forceGpt55CodexArgs(extraArgs));
38
35
  args.push(prompt);
36
+ return args;
37
+ }
38
+
39
+ export async function runCodexExec({ root, prompt, outputFile, json = true, profile = null, extraArgs = [], onStdout, onStderr, logDir = null, stdoutFile = null, stderrFile = null, maxBufferBytes = 256 * 1024, timeoutMs = null }) {
40
+ const bin = await findCodexBinary();
41
+ if (!bin) {
42
+ return { code: 127, stdout: '', stderr: 'Codex CLI not found. Install @openai/codex or set SKS_CODEX_BIN.' };
43
+ }
44
+ const args = buildCodexExecArgs({ root, prompt, outputFile, json, profile, extraArgs });
39
45
  const effectiveTimeoutMs = Number(timeoutMs || process.env.SKS_CODEX_TIMEOUT_MS || process.env.DCODEX_CODEX_TIMEOUT_MS || 30 * 60 * 1000);
40
46
  return runProcess(bin, args, {
41
47
  cwd: root,
@@ -0,0 +1,50 @@
1
+ export const REQUIRED_CODEX_MODEL = 'gpt-5.5';
2
+
3
+ const MODEL_VALUE_FLAGS = new Set(['--model', '-m']);
4
+ const CONFIG_VALUE_FLAGS = new Set(['-c', '--config']);
5
+
6
+ function isModelConfigOverride(value = '') {
7
+ return /^model\s*=/.test(String(value || '').trim());
8
+ }
9
+
10
+ function stripCodexModelOverrides(args = []) {
11
+ const out = [];
12
+ const input = Array.isArray(args) ? args : [];
13
+ for (let i = 0; i < input.length; i += 1) {
14
+ const arg = String(input[i]);
15
+ if (MODEL_VALUE_FLAGS.has(arg)) {
16
+ i += 1;
17
+ continue;
18
+ }
19
+ if (arg.startsWith('--model=') || arg.startsWith('-m=')) continue;
20
+ if (CONFIG_VALUE_FLAGS.has(arg)) {
21
+ const value = i + 1 < input.length ? String(input[i + 1]) : '';
22
+ if (isModelConfigOverride(value)) {
23
+ i += 1;
24
+ continue;
25
+ }
26
+ out.push(arg);
27
+ if (i + 1 < input.length) out.push(String(input[++i]));
28
+ continue;
29
+ }
30
+ if (arg.startsWith('-c=') || arg.startsWith('--config=')) {
31
+ const value = arg.slice(arg.indexOf('=') + 1);
32
+ if (isModelConfigOverride(value)) continue;
33
+ }
34
+ out.push(arg);
35
+ }
36
+ return out;
37
+ }
38
+
39
+ export function forceGpt55CodexArgs(args = []) {
40
+ return ['--model', REQUIRED_CODEX_MODEL, ...stripCodexModelOverrides(args)];
41
+ }
42
+
43
+ export function forceGpt55CodexConfigArgs(args = []) {
44
+ return ['-c', `model="${REQUIRED_CODEX_MODEL}"`, ...stripCodexModelOverrides(args)];
45
+ }
46
+
47
+ export function isForbiddenCodexModel(value = '') {
48
+ const model = String(value || '').trim().toLowerCase();
49
+ return /^gpt-5\./.test(model) && model !== REQUIRED_CODEX_MODEL;
50
+ }
package/src/core/fsx.mjs CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
 
8
- export const PACKAGE_VERSION = '0.7.54';
8
+ export const PACKAGE_VERSION = '0.7.55';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
@@ -6,6 +6,7 @@ import { checkDbOperation, dbBlockReason, handleMadSksUserConfirmation } from '.
6
6
  import { checkHarnessModification, harnessGuardBlockReason } from './harness-guard.mjs';
7
7
  import { activeRouteContext, evaluateStop, prepareRoute, promptPipelineContext as routePipelineContext, recordContext7Evidence, recordSubagentEvidence, routePrompt } from './pipeline.mjs';
8
8
  import { classifyToolError } from './evaluation.mjs';
9
+ import { REQUIRED_CODEX_MODEL, isForbiddenCodexModel } from './codex-model-guard.mjs';
9
10
 
10
11
  const TEAM_DIGEST_MAX_EVENTS = 4;
11
12
  const TEAM_DIGEST_MESSAGE_CHARS = 180;
@@ -91,7 +92,11 @@ export async function hookMain(name) {
91
92
  const root = await projectRoot(payload.cwd || process.cwd());
92
93
  const state = await loadState(root);
93
94
  const noQuestion = isNoQuestionRunning(state);
94
- if (name === 'user-prompt-submit') return hookUserPrompt(root, state, payload, noQuestion);
95
+ if (name === 'user-prompt-submit') {
96
+ const modelBlock = blockForbiddenClientModel(payload);
97
+ if (modelBlock) return modelBlock;
98
+ return hookUserPrompt(root, state, payload, noQuestion);
99
+ }
95
100
  if (name === 'pre-tool') return hookPreTool(root, state, payload, noQuestion);
96
101
  if (name === 'post-tool') return hookPostTool(root, state, payload, noQuestion);
97
102
  if (name === 'permission-request') return hookPermission(root, state, payload, noQuestion);
@@ -99,6 +104,43 @@ export async function hookMain(name) {
99
104
  return { continue: true };
100
105
  }
101
106
 
107
+ function blockForbiddenClientModel(payload = {}) {
108
+ const model = forbiddenClientModelFromPayload(payload);
109
+ if (!model || !isForbiddenCodexModel(model)) return null;
110
+ return {
111
+ decision: 'block',
112
+ reason: `SKS requires ${REQUIRED_CODEX_MODEL}; client payload requested ${model}. Switch the Codex client/session model to ${REQUIRED_CODEX_MODEL} and retry.`
113
+ };
114
+ }
115
+
116
+ function forbiddenClientModelFromPayload(payload = {}) {
117
+ const candidates = [
118
+ payload.model,
119
+ payload.model_id,
120
+ payload.modelId,
121
+ payload.client_model,
122
+ payload.clientModel,
123
+ ...clientModelCandidates(payload.client),
124
+ ...clientModelCandidates(payload.metadata),
125
+ ...clientModelCandidates(payload.context),
126
+ ...clientModelCandidates(payload.thread),
127
+ ...clientModelCandidates(payload.session)
128
+ ];
129
+ return candidates.find((value) => typeof value === 'string' && isForbiddenCodexModel(value)) || '';
130
+ }
131
+
132
+ function clientModelCandidates(value, depth = 0) {
133
+ if (!value || typeof value !== 'object' || depth > 4) return [];
134
+ const out = [];
135
+ for (const key of ['model', 'model_id', 'modelId', 'client_model', 'clientModel']) {
136
+ if (typeof value[key] === 'string') out.push(value[key]);
137
+ }
138
+ for (const key of ['client', 'metadata', 'context', 'thread', 'session']) {
139
+ out.push(...clientModelCandidates(value[key], depth + 1));
140
+ }
141
+ return out;
142
+ }
143
+
102
144
  async function hookUserPrompt(root, state, payload, noQuestion) {
103
145
  if (!noQuestion) {
104
146
  const prompt = extractUserPrompt(payload);
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
4
4
  import { exists, nowIso, packageRoot, readJson, runProcess, sha256, sksRoot, which, writeJsonAtomic } from './fsx.mjs';
5
5
  import { getCodexInfo } from './codex-adapter.mjs';
6
6
  import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
7
+ import { REQUIRED_CODEX_MODEL, forceGpt55CodexArgs } from './codex-model-guard.mjs';
7
8
  import { MIN_TEAM_REVIEWER_LANES } from './team-review-policy.mjs';
8
9
 
9
10
  export const SKS_TMUX_LOGO = [
@@ -114,7 +115,7 @@ const SKS_TMUX_LOGO_ANIMATION_STEPS = Object.freeze([
114
115
  { frame: 9, color: '51', bold: true, delay: '0.16' }
115
116
  ]);
116
117
 
117
- export const DEFAULT_SKS_CODEX_MODEL = 'gpt-5.5';
118
+ export const DEFAULT_SKS_CODEX_MODEL = REQUIRED_CODEX_MODEL;
118
119
  export const DEFAULT_SKS_CODEX_REASONING = 'high';
119
120
 
120
121
  export function defaultCodexLaunchArgs(env = process.env) {
@@ -200,7 +201,7 @@ export function tmuxStatusKind(tmux = {}) {
200
201
  }
201
202
 
202
203
  export function codexLaunchCommand(root, codexBin, codexArgs = []) {
203
- const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
204
+ const extraArgs = forceGpt55CodexArgs(codexArgs);
204
205
  return [
205
206
  sksLogoIntroCommand(codexBin),
206
207
  `printf '\\nProject: %s\\n' ${shellEscape(root)}`,
@@ -322,7 +323,7 @@ export async function buildTmuxLaunchPlan(opts = {}) {
322
323
  const codex = opts.codex || await getCodexInfo().catch(() => ({}));
323
324
  const tmux = opts.tmux || await tmuxReadiness(opts);
324
325
  const app = opts.app || await codexAppIntegrationStatus({ codex });
325
- const codexArgs = Array.isArray(opts.codexArgs) ? opts.codexArgs : defaultCodexLaunchArgs(opts.env || process.env);
326
+ const codexArgs = forceGpt55CodexArgs(Array.isArray(opts.codexArgs) ? opts.codexArgs : defaultCodexLaunchArgs(opts.env || process.env));
326
327
  return {
327
328
  root,
328
329
  session,