oh-my-opencode 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.
- package/dist/cli/index.js +13 -13
- package/dist/cli-node/index.js +13 -13
- package/dist/features/background-agent/parent-wake-dedupe.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-flush-runner.d.ts +2 -0
- package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -0
- package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
- package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
- package/dist/index.js +63 -32
- package/dist/skills/review-work/SKILL.md +3 -1
- package/dist/skills/start-work/SKILL.md +2 -0
- package/dist/skills/ulw-research/SKILL.md +3 -1
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -1
- package/packages/omo-codex/plugin/components/teammode/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +3 -3
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +1 -0
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -0
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +64 -17
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +186 -20
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +51 -3
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +33 -5
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +3 -3
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +1 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +1 -1
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +35 -1
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +275 -19
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +33 -0
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +4 -2
- package/packages/shared-skills/skills/review-work/SKILL.md +3 -1
- package/packages/shared-skills/skills/start-work/SKILL.md +2 -0
- package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -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({
|
|
130
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
2
|
+
* Runtime migration for `[features.multi_agent_v2]`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
27
|
-
|
|
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(
|
|
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) =>
|
|
37
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
6
|
-
|
|
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) =>
|
|
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 {
|
|
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({
|
|
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(
|
|
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
|
|
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
|
|
|
@@ -52,7 +52,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
52
52
|
|
|
53
53
|
Role-specific behavior must be described in a self-contained \`message\`. Use \`fork_context: false\` to start the child with only the initial prompt (no parent history); use \`fork_context: true\` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's \`message\`. OMO installs these selectable agent roles into \`~/.codex/agents/\`: \`explorer\`, \`librarian\`, \`plan\`, \`momus\`, \`metis\`, \`lazycodex-code-reviewer\`, \`lazycodex-qa-executor\`, and \`lazycodex-gate-reviewer\` - pass the matching name as \`agent_type\` so the child gets that role's model and instructions. If the spawn tool exposes no \`agent_type\` parameter, omit it and describe the role inside \`message\`. If a code block below conflicts with this section, this section wins.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If \`multi_agent_v1.*\` tools exist, use the table above as written. If instead a flat \`spawn_agent\` with a required \`task_name\` exists (\`multi_agent_v2\`), rewrite every \`multi_agent_v1.*\` example: \`multi_agent_v1.spawn_agent({...,"fork_context":false})\` becomes \`spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})\` (\`"all"\` only when full parent history is truly required); \`send_input\` becomes \`send_message\`; do not call \`close_agent\`/\`resume_agent\` (finished agents end on their own; \`followup_task\` re-tasks one, \`interrupt_agent\` stops one); \`wait_agent\` takes only \`timeout_ms\` and returns on any child mailbox activity. \`agent_type\` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
56
56
|
|
|
57
57
|
When translating \`load_skills=[...]\`, include the requested skill names in the spawned agent's \`message\`. If a code block below conflicts with this section, this section wins.
|
|
58
58
|
|
|
@@ -18,7 +18,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
18
18
|
|
|
19
19
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
22
22
|
|
|
23
23
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
24
24
|
|
|
@@ -19,7 +19,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
19
19
|
|
|
20
20
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
23
23
|
|
|
24
24
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
19
19
|
|
|
20
20
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
23
23
|
|
|
24
24
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
25
25
|
|
|
@@ -18,7 +18,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
18
18
|
|
|
19
19
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
22
22
|
|
|
23
23
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
24
24
|
|
|
@@ -19,7 +19,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
19
19
|
|
|
20
20
|
Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` - pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
|
|
23
23
|
|
|
24
24
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
25
25
|
|
|
@@ -19,7 +19,7 @@ Use a TEAM when EITHER holds:
|
|
|
19
19
|
- one task still needs exploration, yet its GOAL is already clear - parallel investigation under
|
|
20
20
|
a fixed objective.
|
|
21
21
|
|
|
22
|
-
Use plain subagents (`$ulw` / `multi_agent_v1.spawn_agent`) - NOT a team - when EITHER holds:
|
|
22
|
+
Use plain subagents (`$ulw` / `multi_agent_v1.spawn_agent` / flat `spawn_agent` on `multi_agent_v2`) - NOT a team - when EITHER holds:
|
|
23
23
|
- the work IS perfectly isolated, so there is no coordination cost worth paying; or
|
|
24
24
|
- the GOAL is still ambiguous, where one mind should resolve direction before any fan-out.
|
|
25
25
|
|
|
@@ -39,7 +39,7 @@ merge), not the keystrokes.
|
|
|
39
39
|
## Compose by part, ownership, or perspective - not by job title
|
|
40
40
|
|
|
41
41
|
A team is ALWAYS two or more members - never a single-member team. One worker on an isolated
|
|
42
|
-
job is a subagent
|
|
42
|
+
job is a spawned subagent, not a team; if you end up with a single member,
|
|
43
43
|
either split off a second distinct slice or drop the team and use a subagent.
|
|
44
44
|
|
|
45
45
|
Compose the team from what you actually KNOW about the work. Ground the split in real knowledge
|
|
@@ -110,7 +110,7 @@ do not treat the intended mutation as complete; retry after the named command fi
|
|
|
110
110
|
lose in the sidebar without this link.
|
|
111
111
|
|
|
112
112
|
Every team member is a real Codex thread created with `codex_app.create_thread` - this is strict,
|
|
113
|
-
not a preference. NEVER substitute `multi_agent_v1.spawn_agent
|
|
113
|
+
not a preference. NEVER substitute a spawned in-process subagent (`multi_agent_v1.spawn_agent` or flat `spawn_agent`)
|
|
114
114
|
for a team member: a spawned agent is an ephemeral helper that does not show up as a team thread,
|
|
115
115
|
cannot carry the `[team name] <member name>` title, and cannot be inspected, titled, archived, or
|
|
116
116
|
re-opened with the `codex_app.*` thread tools - which defeats the entire point of a durable team.
|
|
@@ -266,6 +266,7 @@ Every `multi_agent_v1.spawn_agent` message is self-contained and starts with
|
|
|
266
266
|
handoff. Use `fork_context: false` unless full history is truly
|
|
267
267
|
required; paste only the context the child needs. Full-history forks can
|
|
268
268
|
make the child continue old parent context instead of the delegated task.
|
|
269
|
+
If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: `fork_context: false` becomes `fork_turns: "none"`, `send_input` becomes `send_message`, finished agents end on their own (no `close_agent`; `followup_task` re-tasks, `interrupt_agent` stops), and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity.
|
|
269
270
|
|
|
270
271
|
# TOML-backed subagent routing compatibility
|
|
271
272
|
Treat TOML-backed role routing as **routing-unverified**. The
|
|
@@ -46,4 +46,6 @@ The full workflow may mention OpenCode-style orchestration examples. In Codex, t
|
|
|
46
46
|
| Wait for background result | `multi_agent_v1.wait_agent(...)` |
|
|
47
47
|
| Clean up finished worker | `multi_agent_v1.close_agent(...)` |
|
|
48
48
|
|
|
49
|
+
Flat `spawn_agent` requiring `task_name` instead (`multi_agent_v2`)? Rewrite rows: add `"task_name"`, `"fork_context":false` → `"fork_turns":"none"`, `wait_agent` takes only `timeout_ms`, no `close_agent` — finished agents end on their own.
|
|
50
|
+
|
|
49
51
|
When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`.
|
|
@@ -67,6 +67,8 @@ Fan out read-only research before deciding. Every spawn names DELIVERABLE / SCOP
|
|
|
67
67
|
multi_agent_v1.spawn_agent({"message":"TASK: act as an explorer. DELIVERABLE: ... SCOPE: ... VERIFY: ...","agent_type":"explorer","fork_context":false})
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
If your tool list has a flat `spawn_agent` with a required `task_name` instead of `multi_agent_v1.*` (`multi_agent_v2`), rewrite: add `"task_name":"<lowercase_digits_underscores>"`, replace `"fork_context":false` with `"fork_turns":"none"`, and `wait_agent` takes only `timeout_ms`, returning on any child mailbox activity (finished agents end on their own).
|
|
71
|
+
|
|
70
72
|
Spawn every independent child for the current wave first. After the wave
|
|
71
73
|
is launched, use `multi_agent_v1.wait_agent` for each child until each
|
|
72
74
|
reaches terminal status. A timeout is not terminal status. Do not start dependent planning, drafting, approval-gate work, or final handoff until each child result is integrated or recorded as inconclusive.
|