lazycodex-ai 4.16.1 → 4.16.2

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 (86) hide show
  1. package/dist/cli/index.js +13 -13
  2. package/dist/cli-node/index.js +13 -13
  3. package/package.json +1 -1
  4. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  5. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  6. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  7. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  8. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  9. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  10. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  11. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  12. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  13. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  14. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  15. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  16. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +1 -1
  17. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  18. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
  20. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  21. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -1
  23. package/packages/omo-codex/plugin/components/teammode/AGENTS.md +1 -1
  24. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +3 -3
  27. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  28. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -0
  30. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +1 -0
  33. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -0
  34. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +2 -0
  35. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +1 -0
  36. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  37. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -0
  39. package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +1 -1
  40. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  41. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  61. package/packages/omo-codex/plugin/package.json +1 -1
  62. package/packages/omo-codex/plugin/scripts/auto-update.mjs +64 -17
  63. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +186 -20
  64. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +51 -3
  65. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +33 -5
  66. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
  67. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  68. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +1 -1
  69. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  70. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
  71. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
  72. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +3 -3
  73. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +1 -0
  74. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -0
  75. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -0
  76. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +2 -0
  77. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +1 -1
  78. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
  79. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
  80. package/packages/omo-codex/plugin/test/auto-update.test.mjs +35 -1
  81. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +275 -19
  82. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +33 -0
  83. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +4 -2
  84. package/packages/shared-skills/skills/review-work/SKILL.md +3 -1
  85. package/packages/shared-skills/skills/start-work/SKILL.md +2 -0
  86. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -1
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Enforcing Unlimited Goal Budget",
10
+ "statusMessage": "(OmO 4.16.2) Enforcing Unlimited Goal Budget",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Recommending Git Bash MCP",
10
+ "statusMessage": "(OmO 4.16.2) Recommending Git Bash MCP",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/scripts/auto-update.mjs\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Checking Auto Update",
10
+ "statusMessage": "(OmO 4.16.2) Checking Auto Update",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\scripts\\auto-update.mjs\" hook session-start"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 4.16.1) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 4.16.2) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/codegraph/dist/cli.js\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Checking CodeGraph Bootstrap",
10
+ "statusMessage": "(OmO 4.16.2) Checking CodeGraph Bootstrap",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\codegraph\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook session-start",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Loading Project Rules",
10
+ "statusMessage": "(OmO 4.16.2) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/telemetry/dist/cli.js\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Recording Session Telemetry",
10
+ "statusMessage": "(OmO 4.16.2) Recording Session Telemetry",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\telemetry\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/start-work-continuation/dist/cli.js\" hook stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Checking Start-Work Continuation",
10
+ "statusMessage": "(OmO 4.16.2) Checking Start-Work Continuation",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\start-work-continuation\\dist\\cli.js\" hook stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/start-work-continuation/dist/cli.js\" hook subagent-stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Checking Start-Work Continuation",
10
+ "statusMessage": "(OmO 4.16.2) Checking Start-Work Continuation",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\start-work-continuation\\dist\\cli.js\" hook subagent-stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Verifying LazyCodex Executor Evidence",
10
+ "statusMessage": "(OmO 4.16.2) Verifying LazyCodex Executor Evidence",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lazycodex-executor-verify\\dist\\cli.js\" hook subagent-stop"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ultrawork/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 4.16.1) Checking Ultrawork Trigger",
10
+ "statusMessage": "(OmO 4.16.2) Checking Ultrawork Trigger",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ultrawork\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Checking Ulw-Loop Steering",
10
+ "statusMessage": "(OmO 4.16.2) Checking Ulw-Loop Steering",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 4.16.1) Loading Project Rules",
10
+ "statusMessage": "(OmO 4.16.2) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/omo-codex-plugin",
3
- "version": "4.16.1",
3
+ "version": "4.16.2",
4
4
  "description": "Aggregate Codex plugin root for OMO components.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -39,6 +39,44 @@ const DEFAULT_RETRY_INTERVAL_MS = 30 * 60 * 1_000;
39
39
 
40
40
  export { resolveLazyCodexUpdatePlan };
41
41
 
42
+ /**
43
+ * Read Codex SessionStart stdin JSON and extract the effective session model.
44
+ * @param {NodeJS.ReadableStream | null | undefined} stdin
45
+ * @returns {Promise<string | null>}
46
+ */
47
+ export async function readSessionModelFromStdin(stdin = process.stdin) {
48
+ if (!stdin || typeof stdin.on !== "function") return null;
49
+ if (stdin.isTTY) return null;
50
+
51
+ const raw = await new Promise((resolve) => {
52
+ let data = "";
53
+ let settled = false;
54
+ const finish = () => {
55
+ if (settled) return;
56
+ settled = true;
57
+ resolve(data);
58
+ };
59
+ stdin.setEncoding("utf8");
60
+ stdin.on("data", (chunk) => {
61
+ data += chunk;
62
+ });
63
+ stdin.once("end", finish);
64
+ stdin.once("error", finish);
65
+ // Hooks normally close stdin quickly; avoid hanging migration forever.
66
+ setTimeout(finish, 250).unref?.();
67
+ });
68
+
69
+ const trimmed = raw.trim();
70
+ if (!trimmed) return null;
71
+ try {
72
+ const payload = JSON.parse(trimmed);
73
+ if (typeof payload?.model === "string" && payload.model.trim()) return payload.model.trim();
74
+ return null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
42
80
  export function resolveAutoUpdatePlan({ env = process.env, now = Date.now(), lastCheckedAt, lastAttemptedAt, lastStatus, installFlow } = {}) {
43
81
  if (env.LAZYCODEX_AUTO_UPDATE_DISABLED === "1" || env.OMO_CODEX_AUTO_UPDATE_DISABLED === "1") {
44
82
  return { shouldRun: false, reason: "disabled" };
@@ -126,8 +164,13 @@ export async function runLazyCodexManualUpdate({ env = process.env, dryRun = fal
126
164
  return 0;
127
165
  }
128
166
 
129
- export async function runAutoUpdateCheck({ env = process.env, now = Date.now() } = {}) {
130
- const migrationNotices = await runConfigMigration({ env });
167
+ export async function runAutoUpdateCheck({
168
+ env = process.env,
169
+ now = Date.now(),
170
+ sessionModel = null,
171
+ requireSessionModel = false,
172
+ } = {}) {
173
+ const migrationNotices = await runConfigMigration({ env, sessionModel, requireSessionModel });
131
174
  const statePath = resolveStatePath(env);
132
175
  const notices = [...migrationNotices];
133
176
  const state = await settlePendingNotice({ env, now, statePath, state: await readState(statePath), notices });
@@ -251,11 +294,11 @@ function resolveUpdateContext({ env }) {
251
294
  return { currentVersion, latestVersion, shouldUpdate: plan.shouldUpdate };
252
295
  }
253
296
 
254
- async function runConfigMigration({ env }) {
297
+ async function runConfigMigration({ env, sessionModel = null, requireSessionModel = false }) {
255
298
  if (env.LAZYCODEX_CONFIG_MIGRATION_DISABLED === "1" || env.OMO_CODEX_CONFIG_MIGRATION_DISABLED === "1") return [];
256
299
  try {
257
300
  await migrateOmoSotConfig({ env, seed: true });
258
- const result = await migrateCodexConfig({ env });
301
+ const result = await migrateCodexConfig({ env, sessionModel, requireSessionModel });
259
302
  if (result.modeChanged.length === 0) return [];
260
303
  return [
261
304
  "[LazyCodex] Removed unsupported Codex root multi_agent_mode from config.toml. Tell the user LazyCodex cleaned up a stale OMO-managed setting so Codex uses its supported per-turn multiAgentMode API.",
@@ -267,18 +310,22 @@ async function runConfigMigration({ env }) {
267
310
  }
268
311
 
269
312
  if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
270
- runAutoUpdateCheck()
271
- .then(({ notices }) => {
272
- if (notices.length === 0) return;
273
- console.log(JSON.stringify({
274
- hookSpecificOutput: {
275
- hookEventName: "SessionStart",
276
- additionalContext: notices.join("\n\n"),
277
- },
278
- }));
279
- })
280
- .catch((error) => {
281
- console.error(error instanceof Error ? error.message : String(error));
282
- process.exit(0);
313
+ (async () => {
314
+ const sessionModel = await readSessionModelFromStdin(process.stdin);
315
+ const { notices } = await runAutoUpdateCheck({
316
+ sessionModel,
317
+ // Hook CLI path: only force-disable when SessionStart provided the active model.
318
+ requireSessionModel: true,
283
319
  });
320
+ if (notices.length === 0) return;
321
+ console.log(JSON.stringify({
322
+ hookSpecificOutput: {
323
+ hookEventName: "SessionStart",
324
+ additionalContext: notices.join("\n\n"),
325
+ },
326
+ }));
327
+ })().catch((error) => {
328
+ console.error(error instanceof Error ? error.message : String(error));
329
+ process.exit(0);
330
+ });
284
331
  }
@@ -1,20 +1,31 @@
1
1
  /**
2
- * Runtime migration: force `[features.multi_agent_v2]` to `enabled = false`.
2
+ * Runtime migration for `[features.multi_agent_v2]`.
3
3
  *
4
- * Runs on every Codex SessionStart (via auto-update's config migration) so
5
- * multi-agent V2 stays off regardless of how it was turned on: an
6
- * installer-forced `enabled = true`, a missing `enabled` key the runtime
7
- * would resolve per model, or the `[features]` boolean shorthand
8
- * `multi_agent_v2 = true` (removed here because a boolean key and a
9
- * `[features.multi_agent_v2]` table for the same name are conflicting TOML).
4
+ * Historical behavior (openai/codex#26753): force `enabled = false` on every
5
+ * SessionStart because enabling V2 made every turn 400 with encrypted
6
+ * spawn_agent parameters on models that were not configured for encrypted
7
+ * tool use. OpenAI closed that as NOT_PLANNED (V2 under development).
10
8
  *
11
- * Upstream basis: openai/codex#26753 with the flag on, EVERY turn fails
12
- * with a 400 ("spawn_agent declares encrypted parameters but is not
13
- * configured for encrypted tool use by this model"), even on prompts that
14
- * never touch subagents. OpenAI closed it NOT_PLANNED stating V2 is under
15
- * development, not recommended, and bug reports are not accepted. Same
16
- * failure class still being reported (openai/codex#27205).
9
+ * GPT-5.6 models that declare `multi_agent_version: "v2"` in the Codex model
10
+ * catalog invert that failure mode: forcing `enabled = false` makes every
11
+ * turn 400 with a reserved `collaboration.spawn_agent` schema mismatch
12
+ * (lazycodex#118 / oh-my-openagent#6002 / openai/codex#31097), and
13
+ * `hide_spawn_agent_metadata = false` (written by OMO installers <= 4.15.x)
14
+ * mismatches the same reserved schema by re-adding agent_type/model
15
+ * properties to spawn_agent. For those models this guard clears the managed
16
+ * disable and the stale metadata override, leaving V2 unset so Codex can
17
+ * follow model metadata.
18
+ *
19
+ * When the selected model is unknown or declares V1, keep the #26753
20
+ * force-disable path.
21
+ *
22
+ * Opt out of the whole migration with LAZYCODEX_CONFIG_MIGRATION_DISABLED=1
23
+ * (or OMO_CODEX_CONFIG_MIGRATION_DISABLED=1).
17
24
  */
25
+ import { readFileSync } from "node:fs";
26
+ import { homedir } from "node:os";
27
+ import { join } from "node:path";
28
+
18
29
  const MANAGED_COMMENT_MARKER = "openai/codex#26753";
19
30
  const MANAGED_DISABLE_COMMENT = [
20
31
  "# Managed by LazyCodex: multi_agent_v2 is re-disabled on every Codex session start",
@@ -23,26 +34,157 @@ const MANAGED_DISABLE_COMMENT = [
23
34
  "",
24
35
  ].join("\n");
25
36
 
26
- export function forceDisableMultiAgentV2(config) {
27
- let result = removeFeaturesShorthand(config);
37
+ /**
38
+ * @param {string} config
39
+ * @param {{
40
+ * multiAgentVersion?: string | null,
41
+ * sessionModel?: string | null,
42
+ * requireSessionModel?: boolean,
43
+ * env?: NodeJS.ProcessEnv,
44
+ * modelsCachePath?: string,
45
+ * }} [options]
46
+ */
47
+ export function forceDisableMultiAgentV2(config, options = {}) {
48
+ // Always normalize the legacy `[features]` boolean shorthand first: leaving
49
+ // `multi_agent_v2 = true|false` in place while a later guard appends the
50
+ // `[features.multi_agent_v2]` table would define the same name as both a
51
+ // scalar and a table, which Codex rejects as invalid TOML.
52
+ const normalized = removeFeaturesShorthand(config);
53
+ const sessionModel = normalizeModel(options.sessionModel);
54
+ const multiAgentVersion =
55
+ options.multiAgentVersion !== undefined
56
+ ? options.multiAgentVersion
57
+ : resolveMultiAgentVersionFromConfig(normalized, options);
58
+
59
+ if (prefersMultiAgentV2(multiAgentVersion, sessionModel)) {
60
+ return clearMultiAgentV2DisableForReservedSchema(normalized);
61
+ }
62
+
63
+ // SessionStart can run with an override model (`codex -m gpt-5.6-terra`) while
64
+ // config.toml still lists a different default. If we cannot see the effective
65
+ // session model, do not force-disable — writing enabled=false would break a
66
+ // GPT-5.6 reserved collaboration.spawn_agent session.
67
+ if (options.requireSessionModel === true && !sessionModel) {
68
+ return normalized;
69
+ }
70
+
71
+ // Unknown catalog entry for an explicit session model: skip force-disable
72
+ // rather than assume the legacy encrypted-V2 failure mode.
73
+ if (sessionModel && multiAgentVersion == null) {
74
+ return normalized;
75
+ }
76
+
77
+ return forceDisableLegacyEncryptedV2(normalized);
78
+ }
79
+
80
+ /**
81
+ * True when the effective model should run MultiAgentV2: the catalog says
82
+ * "v2", or the catalog is unavailable but the model is a GPT-5.6 family
83
+ * model (which reserves the collaboration.spawn_agent schema).
84
+ * @param {"v1" | "v2" | null | undefined} multiAgentVersion
85
+ * @param {string | null | undefined} sessionModel
86
+ */
87
+ export function prefersMultiAgentV2(multiAgentVersion, sessionModel) {
88
+ return multiAgentVersion === "v2" || (multiAgentVersion == null && isGpt56Family(normalizeModel(sessionModel)));
89
+ }
90
+
91
+ /**
92
+ * Resolve the effective model against Codex `models_cache.json`.
93
+ * Prefers SessionStart `model` over the root `model` in config.toml.
94
+ * @param {string} config
95
+ * @param {{ sessionModel?: string | null, env?: NodeJS.ProcessEnv, modelsCachePath?: string }} [options]
96
+ * @returns {"v1" | "v2" | null}
97
+ */
98
+ export function resolveMultiAgentVersionFromConfig(config, options = {}) {
99
+ const model = normalizeModel(options.sessionModel) || readRootModel(config);
100
+ if (!model) return null;
101
+ return resolveMultiAgentVersionForModel(model, options);
102
+ }
103
+
104
+ /**
105
+ * @param {string} model
106
+ * @param {{ env?: NodeJS.ProcessEnv, modelsCachePath?: string }} [options]
107
+ * @returns {"v1" | "v2" | null}
108
+ */
109
+ export function resolveMultiAgentVersionForModel(model, options = {}) {
110
+ const cachePath =
111
+ options.modelsCachePath?.trim() ||
112
+ join(options.env?.CODEX_HOME?.trim() || join(homedir(), ".codex"), "models_cache.json");
113
+
114
+ try {
115
+ const cache = JSON.parse(readFileSync(cachePath, "utf8"));
116
+ const models = Array.isArray(cache?.models) ? cache.models : [];
117
+ const entry = models.find((item) => item?.slug === model || item?.id === model);
118
+ const version = entry?.multi_agent_version;
119
+ if (version === "v1" || version === "v2") return version;
120
+ return null;
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ export function readRootModel(config) {
127
+ const double = config.match(/^\s*model\s*=\s*"([^"]+)"/m);
128
+ if (double) return double[1];
129
+ const single = config.match(/^\s*model\s*=\s*'([^']+)'/m);
130
+ return single?.[1] ?? null;
131
+ }
132
+
133
+ function normalizeModel(value) {
134
+ if (typeof value !== "string") return null;
135
+ const trimmed = value.trim();
136
+ return trimmed.length > 0 ? trimmed : null;
137
+ }
138
+
139
+ function isGpt56Family(model) {
140
+ return typeof model === "string" && /^gpt-5\.6\b/i.test(model);
141
+ }
142
+
143
+ function clearMultiAgentV2DisableForReservedSchema(config) {
144
+ // `config` arrives shorthand-normalized from forceDisableMultiAgentV2.
145
+ const result = removeManagedDisableComments(config);
146
+
28
147
  const section = findSection(result, "[features.multi_agent_v2]");
148
+ if (!section) return result;
149
+
150
+ // Two settings poison the reserved collaboration.spawn_agent schema on V2
151
+ // models (verified against codex-cli 0.144.1 + gpt-5.6-sol):
152
+ // - `enabled = false` forces the legacy V1 tool surface on some Codex
153
+ // versions (#6002's failure shape);
154
+ // - `hide_spawn_agent_metadata = false` (written by OMO installers
155
+ // <= 4.15.x to expose agent_type) re-adds agent_type/model/... to
156
+ // spawn_agent, mismatching the reserved schema -> HTTP 400 every turn.
157
+ // Remove both; `hide_spawn_agent_metadata = true` matches the Codex
158
+ // default and is left alone.
159
+ const cleared = section.text
160
+ .replace(/^\s*enabled\s*=\s*false[ \t]*(?:#[^\n]*)?\n?/gm, "")
161
+ .replace(/^\s*hide_spawn_agent_metadata\s*=\s*false[ \t]*(?:#[^\n]*)?\n?/gm, "");
162
+ if (cleared === section.text) return result;
163
+ return result.slice(0, section.start) + cleared + result.slice(section.end);
164
+ }
165
+
166
+ // `config` arrives shorthand-normalized from forceDisableMultiAgentV2.
167
+ function forceDisableLegacyEncryptedV2(config) {
168
+ const section = findSection(config, "[features.multi_agent_v2]");
29
169
 
30
170
  if (!section) {
31
- return ensureManagedComment(appendDisabledSection(result));
171
+ return ensureManagedComment(appendDisabledSection(config));
32
172
  }
33
173
 
34
174
  const enabledTruePattern = /^(\s*)enabled\s*=\s*true[ \t]*(#[^\n]*)?$/m;
35
175
  if (enabledTruePattern.test(section.text)) {
36
- const patched = section.text.replace(enabledTruePattern, (_match, indent, comment) => comment ? `${indent}enabled = false ${comment}` : `${indent}enabled = false`);
37
- return ensureManagedComment(result.slice(0, section.start) + patched + result.slice(section.end));
176
+ const patched = section.text.replace(enabledTruePattern, (_match, indent, comment) =>
177
+ comment ? `${indent}enabled = false ${comment}` : `${indent}enabled = false`,
178
+ );
179
+ return ensureManagedComment(config.slice(0, section.start) + patched + config.slice(section.end));
38
180
  }
39
181
 
40
- if (/^\s*enabled\s*=\s*false[ \t]*(?:#[^\n]*)?$/m.test(section.text)) return result;
182
+ if (/^\s*enabled\s*=\s*false[ \t]*(?:#[^\n]*)?$/m.test(section.text)) return config;
41
183
 
42
184
  const headerEnd = section.text.indexOf("\n");
43
185
  const insertAt = headerEnd === -1 ? section.text.length : headerEnd + 1;
44
186
  const patched = `${section.text.slice(0, insertAt)}${headerEnd === -1 ? "\n" : ""}enabled = false\n${section.text.slice(insertAt)}`;
45
- return ensureManagedComment(result.slice(0, section.start) + patched + result.slice(section.end));
187
+ return ensureManagedComment(config.slice(0, section.start) + patched + config.slice(section.end));
46
188
  }
47
189
 
48
190
  function ensureManagedComment(config) {
@@ -52,6 +194,30 @@ function ensureManagedComment(config) {
52
194
  return config.slice(0, section.start) + MANAGED_DISABLE_COMMENT + config.slice(section.start);
53
195
  }
54
196
 
197
+ function removeManagedDisableComments(config) {
198
+ if (!config.includes(MANAGED_COMMENT_MARKER) && !config.includes("Managed by LazyCodex: multi_agent_v2")) {
199
+ return config;
200
+ }
201
+
202
+ const lines = config.split("\n");
203
+ const kept = [];
204
+ for (const line of lines) {
205
+ const trimmed = line.trim();
206
+ if (
207
+ trimmed.startsWith("#") &&
208
+ (trimmed.includes(MANAGED_COMMENT_MARKER) ||
209
+ trimmed.includes("Managed by LazyCodex: multi_agent_v2") ||
210
+ trimmed.includes("because enabling it fails every turn with HTTP 400") ||
211
+ trimmed.includes("LAZYCODEX_CONFIG_MIGRATION_DISABLED=1") ||
212
+ trimmed.includes("OMO_CODEX_CONFIG_MIGRATION_DISABLED=1"))
213
+ ) {
214
+ continue;
215
+ }
216
+ kept.push(line);
217
+ }
218
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n");
219
+ }
220
+
55
221
  function removeFeaturesShorthand(config) {
56
222
  const section = findSection(config, "[features]");
57
223
  if (!section) return config;
@@ -1,9 +1,39 @@
1
+ import { prefersMultiAgentV2, resolveMultiAgentVersionFromConfig } from "./multi-agent-v2-guard.mjs";
2
+
1
3
  const CODEX_AGENTS_HEADER = "[agents]";
2
4
  const CODEX_MULTI_AGENT_V2_HEADER = "[features.multi_agent_v2]";
3
5
  const CODEX_SUBAGENT_THREAD_LIMIT = "1000";
4
6
 
5
- export function ensureSubagentConcurrencyLimit(config) {
6
- return ensureMultiAgentV2ThreadLimit(ensureAgentsMaxThreads(config));
7
+ /**
8
+ * Ensure subagent concurrency limits without writing settings that conflict
9
+ * with MultiAgentV2. When the selected model prefers V2 (catalog `v2`, or a
10
+ * GPT-5.6 family session model with the catalog unavailable) or V2 is already
11
+ * enabled in config, skip `agents.max_threads` because Codex rejects that key
12
+ * while features.multi_agent_v2 is enabled.
13
+ *
14
+ * @param {string} config
15
+ * @param {{ multiAgentVersion?: string | null, sessionModel?: string | null, env?: NodeJS.ProcessEnv, modelsCachePath?: string }} [options]
16
+ */
17
+ export function ensureSubagentConcurrencyLimit(config, options = {}) {
18
+ const multiAgentVersion =
19
+ options.multiAgentVersion !== undefined
20
+ ? options.multiAgentVersion
21
+ : resolveMultiAgentVersionFromConfig(config, options);
22
+ const v2Preferred = prefersMultiAgentV2(multiAgentVersion, options.sessionModel) || isMultiAgentV2Enabled(config);
23
+
24
+ let result = config;
25
+ if (!v2Preferred) {
26
+ result = ensureAgentsMaxThreads(result);
27
+ } else {
28
+ result = removeAgentsMaxThreads(result);
29
+ }
30
+ return ensureMultiAgentV2ThreadLimit(result);
31
+ }
32
+
33
+ function isMultiAgentV2Enabled(config) {
34
+ const section = findSection(config, CODEX_MULTI_AGENT_V2_HEADER);
35
+ if (!section) return false;
36
+ return /^\s*enabled\s*=\s*true[ \t]*(?:#[^\n]*)?$/m.test(section.text);
7
37
  }
8
38
 
9
39
  function ensureAgentsMaxThreads(config) {
@@ -12,6 +42,22 @@ function ensureAgentsMaxThreads(config) {
12
42
  return replaceOrInsertSetting(config, section, "max_threads", CODEX_SUBAGENT_THREAD_LIMIT);
13
43
  }
14
44
 
45
+ function removeAgentsMaxThreads(config) {
46
+ const section = findSection(config, CODEX_AGENTS_HEADER);
47
+ if (!section) return config;
48
+ if (!/^\s*max_threads\s*=/m.test(section.text)) return config;
49
+
50
+ const patched = section.text.replace(/^\s*max_threads\s*=\s*[^\n]*\n?/m, "");
51
+ const bodyLines = patched
52
+ .split("\n")
53
+ .slice(1)
54
+ .filter((line) => line.trim() !== "");
55
+ if (bodyLines.length === 0) {
56
+ return config.slice(0, section.start) + config.slice(section.end).replace(/^\n+/, "");
57
+ }
58
+ return config.slice(0, section.start) + patched + config.slice(section.end);
59
+ }
60
+
15
61
  function ensureMultiAgentV2ThreadLimit(config) {
16
62
  const section = findSection(config, CODEX_MULTI_AGENT_V2_HEADER);
17
63
  if (!section) {
@@ -26,7 +72,9 @@ function ensureMultiAgentV2ThreadLimit(config) {
26
72
  function replaceOrInsertSetting(config, section, key, value) {
27
73
  const pattern = new RegExp(`^(\\s*)${escapeRegExp(key)}\\s*=\\s*[^\\n#]*(#[^\\n]*)?$`, "m");
28
74
  if (pattern.test(section.text)) {
29
- const patched = section.text.replace(pattern, (_match, indent, comment) => comment ? `${indent}${key} = ${value} ${comment}` : `${indent}${key} = ${value}`);
75
+ const patched = section.text.replace(pattern, (_match, indent, comment) =>
76
+ comment ? `${indent}${key} = ${value} ${comment}` : `${indent}${key} = ${value}`,
77
+ );
30
78
  return config.slice(0, section.start) + patched + config.slice(section.end);
31
79
  }
32
80
 
@@ -8,7 +8,10 @@ import { FALLBACK_CATALOG, readModelCatalog } from "./migrate-codex-config/catal
8
8
  import { configPaths } from "./migrate-codex-config/config-paths.mjs";
9
9
  import { removeStaleContext7PlaceholderMcpServer } from "./migrate-codex-config/context7-placeholder-guard.mjs";
10
10
  import { removeUnsupportedRootMultiAgentMode } from "./migrate-codex-config/multi-agent-mode-guard.mjs";
11
- import { forceDisableMultiAgentV2 } from "./migrate-codex-config/multi-agent-v2-guard.mjs";
11
+ import {
12
+ forceDisableMultiAgentV2,
13
+ resolveMultiAgentVersionFromConfig,
14
+ } from "./migrate-codex-config/multi-agent-v2-guard.mjs";
12
15
  import { ensureCodexReasoningConfig as applyReasoningProfile, readRootSettings } from "./migrate-codex-config/root-settings.mjs";
13
16
  import { readState, resolveStatePath, writeState } from "./migrate-codex-config/state.mjs";
14
17
  import { ensureSubagentConcurrencyLimit } from "./migrate-codex-config/subagent-limit-guard.mjs";
@@ -19,7 +22,12 @@ export function ensureCodexReasoningConfig(config, profile = FALLBACK_CATALOG.cu
19
22
  return applyReasoningProfile(config, profile);
20
23
  }
21
24
 
22
- export async function migrateCodexConfig({ env = process.env, cwd = process.cwd() } = {}) {
25
+ export async function migrateCodexConfig({
26
+ env = process.env,
27
+ cwd = process.cwd(),
28
+ sessionModel = null,
29
+ requireSessionModel = false,
30
+ } = {}) {
23
31
  const catalog = await readModelCatalog(env);
24
32
  const statePath = resolveStatePath(env);
25
33
  const state = await readState(statePath);
@@ -31,6 +39,9 @@ export async function migrateCodexConfig({ env = process.env, cwd = process.cwd(
31
39
  const result = await migrateConfigFile(configPath, {
32
40
  catalog,
33
41
  previousState: state.files?.[configPath],
42
+ env,
43
+ sessionModel,
44
+ requireSessionModel,
34
45
  });
35
46
  if (result.changed) changed.push(configPath);
36
47
  if (result.multiAgentModeChanged) modeChanged.push(configPath);
@@ -44,7 +55,16 @@ export async function migrateCodexConfig({ env = process.env, cwd = process.cwd(
44
55
  return { changed, modeChanged };
45
56
  }
46
57
 
47
- export async function migrateConfigFile(configPath, { catalog = FALLBACK_CATALOG, previousState } = {}) {
58
+ export async function migrateConfigFile(
59
+ configPath,
60
+ {
61
+ catalog = FALLBACK_CATALOG,
62
+ previousState,
63
+ env = process.env,
64
+ sessionModel = null,
65
+ requireSessionModel = false,
66
+ } = {},
67
+ ) {
48
68
  const before = await readConfig(configPath);
49
69
  const decision = shouldApplyCatalog(before, catalog, previousState);
50
70
 
@@ -56,7 +76,12 @@ export async function migrateConfigFile(configPath, { catalog = FALLBACK_CATALOG
56
76
  reasoningApplied = config !== before;
57
77
  }
58
78
 
59
- const afterMultiAgentGuard = forceDisableMultiAgentV2(config);
79
+ const multiAgentOptions = { env, sessionModel, requireSessionModel };
80
+ const multiAgentVersion = resolveMultiAgentVersionFromConfig(config, multiAgentOptions);
81
+ const afterMultiAgentGuard = forceDisableMultiAgentV2(config, {
82
+ ...multiAgentOptions,
83
+ multiAgentVersion,
84
+ });
60
85
  const multiAgentChanged = afterMultiAgentGuard !== config;
61
86
  if (multiAgentChanged) config = afterMultiAgentGuard;
62
87
 
@@ -68,7 +93,10 @@ export async function migrateConfigFile(configPath, { catalog = FALLBACK_CATALOG
68
93
  const context7PlaceholderChanged = afterContext7PlaceholderGuard !== config;
69
94
  if (context7PlaceholderChanged) config = afterContext7PlaceholderGuard;
70
95
 
71
- const afterSubagentLimit = ensureSubagentConcurrencyLimit(config);
96
+ const afterSubagentLimit = ensureSubagentConcurrencyLimit(config, {
97
+ ...multiAgentOptions,
98
+ multiAgentVersion,
99
+ });
72
100
  const subagentLimitChanged = afterSubagentLimit !== config;
73
101
  if (subagentLimitChanged) config = afterSubagentLimit;
74
102