oh-my-opencode 4.16.2 → 4.16.3
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/.agents/skills/pre-publish-review/SKILL.md +3 -0
- package/README.md +2 -2
- package/dist/agents/hephaestus/agent.d.ts +1 -1
- package/dist/agents/hephaestus/gpt-5-6.d.ts +2 -0
- package/dist/agents/momus-gpt-5-6.d.ts +1 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/cli/index.js +800 -536
- package/dist/cli-node/index.js +800 -536
- package/dist/features/background-agent/error-classifier.d.ts +5 -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 +416 -37
- package/dist/shared/live-server-route.d.ts +1 -1
- package/dist/skills/review-work/SKILL.md +1 -1
- package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
- package/dist/skills/ulw-research/SKILL.md +2 -2
- package/dist/skills/visual-qa/SKILL.md +1 -1
- package/dist/tui.js +125 -13
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +398 -246
- 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/gpt-5.6.md +93 -0
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +43 -24
- 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/rules/src/codex-hook.ts +2 -1
- package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +15 -6
- package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +7 -2
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +2 -2
- package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +32 -0
- package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +106 -0
- 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/teammode/AGENTS.md +12 -9
- 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 +136 -76
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +94 -23
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +94 -14
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-transport.mjs +55 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +72 -18
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +23 -0
- 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/agents/explorer.toml +24 -41
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-executor.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +21 -49
- package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +17 -29
- package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +20 -50
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +18 -39
- 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/ulw-plan/references/full-workflow.md +1 -1
- 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/references/full-workflow.md +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-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +9 -2
- package/packages/omo-codex/plugin/scripts/entry-guard.mjs +26 -0
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +3 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +39 -4
- package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +24 -4
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +3 -3
- package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +2 -2
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +2 -2
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -4
- package/packages/omo-codex/plugin/scripts/sync-version.mjs +4 -2
- 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 +136 -76
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +94 -23
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +94 -14
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-transport.mjs +55 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +72 -18
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +2 -2
- package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +47 -1
- package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +4 -2
- package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +33 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -9
- package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +71 -0
- package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +32 -0
- package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +9 -2
- package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +345 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +436 -284
- package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +111 -1
- package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
- package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
- /package/packages/omo-codex/plugin/components/rules/bundled-rules/{hephaestus.md → hephaestus/gpt-5.5.md} +0 -0
|
@@ -85,7 +85,7 @@ Runs in parallel; ALL must APPROVE; surface results and wait for the user's expl
|
|
|
85
85
|
- UNCLEAR: run the high-accuracy review AUTOMATICALLY before presenting (unless Classify=Trivial), then present a brief that LEADS with the derived approach and the adopted defaults; still wait for the user's explicit okay.
|
|
86
86
|
|
|
87
87
|
### High-accuracy review (dual review)
|
|
88
|
-
The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.
|
|
88
|
+
The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Codex CLI review on gpt-5.6-sol at xhigh reasoning, run in a disposable isolated workspace and `CODEX_HOME` with the harness's normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. Momus runs at Ultra and may take substantially longer than other agents. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled). Keep Momus in flight and wait for its terminal result: elapsed time alone never justifies cancelling, duplicating, replacing, or treating it as failed. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
|
|
89
89
|
|
|
90
90
|
The draft must record the native Momus session/result, the independent Codex CLI review command/result, and the fix/retry summary. Do not say "high-accuracy review completed" unless both receipts exist and both final verdicts are unconditional approval.
|
|
91
91
|
|
|
@@ -15,7 +15,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
15
15
|
| `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
|
|
16
16
|
| `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
|
|
17
17
|
| `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
|
|
18
|
-
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent
|
|
18
|
+
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
|
|
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
|
|
|
@@ -69,7 +69,7 @@ Observation candidates and claim candidates travel back from workers as message
|
|
|
69
69
|
|
|
70
70
|
## Run the swarm as a cooperating team
|
|
71
71
|
|
|
72
|
-
Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (`codex_app` threads); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
|
|
72
|
+
Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (MultiAgentV2 agents, or `codex_app` threads as its fallback); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
|
|
73
73
|
|
|
74
74
|
- **One member per axis — by part, ownership, or perspective, never a job title.** Each Phase 0 axis is one member owning one concrete slice: a codebase part, a source territory, or a question lens. No two members share an angle. "Backend researcher" or "the web person" gives no real boundary and invites overlap — name what the member owns.
|
|
75
75
|
- **Many teammates by default.** Prefer a larger roster, usually 5-8 teammates, whenever the axes can be made distinct. Add at least one skeptic or red-team perspective for hyperdebate/ultradebate: cross-critique claims, evidence quality, synthesis structure, and visual-report choices before they reach the final deliverable.
|
|
@@ -15,7 +15,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
15
15
|
| `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
|
|
16
16
|
| `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
|
|
17
17
|
| `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
|
|
18
|
-
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent
|
|
18
|
+
| `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
|
|
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
|
|
|
@@ -271,7 +271,7 @@ If any page fails, you are not done: fix it, re-capture the full set, re-dispatc
|
|
|
271
271
|
|
|
272
272
|
Run this step IN ADDITION to Steps 1-4 when the original user task has a concrete visual target: "clone this site", "move this Figma design to code", "rebuild this screen", "make it look exactly like X", or "build this Imagen/Stitch/generated mockup and overview". For these tasks the normal dual-oracle is necessary but NOT sufficient. After it returns, run the following TWO additional MANDATORY verifications and LOOP until BOTH pass.
|
|
273
273
|
|
|
274
|
-
1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.
|
|
274
|
+
1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.6-sol` with xhigh reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
|
|
275
275
|
|
|
276
276
|
```
|
|
277
277
|
node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>
|
|
@@ -16,9 +16,50 @@ const agentSchemaKeys = new Set([
|
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
const lazycodexAgentInvariants = new Map([
|
|
19
|
+
[
|
|
20
|
+
"explorer.toml",
|
|
21
|
+
{
|
|
22
|
+
model: "gpt-5.6-terra",
|
|
23
|
+
effort: "medium",
|
|
24
|
+
includes: [/Read-only/, /working tree/, /rg/],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"librarian.toml",
|
|
29
|
+
{
|
|
30
|
+
model: "gpt-5.6-terra",
|
|
31
|
+
effort: "medium",
|
|
32
|
+
includes: [/Read-only/, /SHA-pinned GitHub permalink/, /external/],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"metis.toml",
|
|
37
|
+
{
|
|
38
|
+
model: "gpt-5.6-sol",
|
|
39
|
+
effort: "high",
|
|
40
|
+
includes: [/pre-planning analyst/i, /contradictions/, /Read-only/],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"momus.toml",
|
|
45
|
+
{
|
|
46
|
+
model: "gpt-5.6-sol",
|
|
47
|
+
effort: "ultra",
|
|
48
|
+
includes: [/plan reviewer/i, /OKAY, ITERATE, or REJECT/, /Read-only/],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
"plan.toml",
|
|
53
|
+
{
|
|
54
|
+
model: "gpt-5.6-sol",
|
|
55
|
+
effort: "xhigh",
|
|
56
|
+
includes: [/strategic planning consultant/i, /\.omo\/plans\/<slug>\.md/, /never implements/i],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
19
59
|
[
|
|
20
60
|
"lazycodex-executor.toml",
|
|
21
61
|
{
|
|
62
|
+
model: "gpt-5.6-sol",
|
|
22
63
|
effort: "high",
|
|
23
64
|
includes: [/EVIDENCE_RECORDED: <path>/, /scenario/i, /artifact/i],
|
|
24
65
|
},
|
|
@@ -26,6 +67,7 @@ const lazycodexAgentInvariants = new Map([
|
|
|
26
67
|
[
|
|
27
68
|
"lazycodex-clone-fidelity-reviewer.toml",
|
|
28
69
|
{
|
|
70
|
+
model: "gpt-5.6-sol",
|
|
29
71
|
effort: "xhigh",
|
|
30
72
|
includes: [/recommendation/, /blockers/, /\.omo\/evidence\/<goal>-clone-fidelity\.md/],
|
|
31
73
|
},
|
|
@@ -33,6 +75,7 @@ const lazycodexAgentInvariants = new Map([
|
|
|
33
75
|
[
|
|
34
76
|
"lazycodex-code-reviewer.toml",
|
|
35
77
|
{
|
|
78
|
+
model: "gpt-5.6-sol",
|
|
36
79
|
effort: "xhigh",
|
|
37
80
|
includes: [/codeQualityStatus/, /recommendation/, /\.omo\/evidence\/<goal>-code-review\.md/],
|
|
38
81
|
},
|
|
@@ -40,6 +83,7 @@ const lazycodexAgentInvariants = new Map([
|
|
|
40
83
|
[
|
|
41
84
|
"lazycodex-qa-executor.toml",
|
|
42
85
|
{
|
|
86
|
+
model: "gpt-5.6-terra",
|
|
43
87
|
effort: "medium",
|
|
44
88
|
includes: [/not_applicable/, /surfaceEvidence/, /adversarialCases/],
|
|
45
89
|
},
|
|
@@ -47,6 +91,7 @@ const lazycodexAgentInvariants = new Map([
|
|
|
47
91
|
[
|
|
48
92
|
"lazycodex-gate-reviewer.toml",
|
|
49
93
|
{
|
|
94
|
+
model: "gpt-5.6-sol",
|
|
50
95
|
effort: "xhigh",
|
|
51
96
|
includes: [/APPROVE\/REJECT/, /blockers/, /\.omo\/evidence\/<goal>-gate-review\.md/],
|
|
52
97
|
},
|
|
@@ -109,7 +154,8 @@ test("#given lazycodex agent prompts #when inspected #then each role pins model
|
|
|
109
154
|
for (const [fileName, invariant] of lazycodexAgentInvariants) {
|
|
110
155
|
const prompt = await readFile(join(agentsDir, fileName), "utf8");
|
|
111
156
|
|
|
112
|
-
|
|
157
|
+
const escapedModel = invariant.model.replace(/\./g, "\\.");
|
|
158
|
+
assert.match(prompt, new RegExp(`^model\\s*=\\s*"${escapedModel}"$`, "m"));
|
|
113
159
|
assert.match(prompt, new RegExp(`^model_reasoning_effort\\s*=\\s*"${invariant.effort}"$`, "m"));
|
|
114
160
|
assert.doesNotMatch(prompt, /^tools\s*=/m);
|
|
115
161
|
assert.doesNotMatch(prompt, /^blocking\s*=/m);
|
|
@@ -34,7 +34,8 @@ test('#given synced skills and bundled rules #when role-specific agents are spaw
|
|
|
34
34
|
const promptFiles = skillEntries
|
|
35
35
|
.filter((entry) => entry.isDirectory())
|
|
36
36
|
.map((entry) => join(skillsDir, entry.name, "SKILL.md"));
|
|
37
|
-
promptFiles.push(join(root, "components", "rules", "bundled-rules", "hephaestus.md"));
|
|
37
|
+
promptFiles.push(join(root, "components", "rules", "bundled-rules", "hephaestus", "gpt-5.5.md"));
|
|
38
|
+
promptFiles.push(join(root, "components", "rules", "bundled-rules", "hephaestus", "gpt-5.6.md"));
|
|
38
39
|
|
|
39
40
|
const missingForkContext = [];
|
|
40
41
|
for (const promptPath of promptFiles) {
|
|
@@ -53,7 +54,8 @@ test("#given long-running orchestration prompts #when waiting on child agents #t
|
|
|
53
54
|
join(root, "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
54
55
|
join(root, "skills", "review-work", "SKILL.md"),
|
|
55
56
|
join(root, "skills", "start-work", "SKILL.md"),
|
|
56
|
-
join(root, "components", "rules", "bundled-rules", "hephaestus.md"),
|
|
57
|
+
join(root, "components", "rules", "bundled-rules", "hephaestus", "gpt-5.5.md"),
|
|
58
|
+
join(root, "components", "rules", "bundled-rules", "hephaestus", "gpt-5.6.md"),
|
|
57
59
|
];
|
|
58
60
|
|
|
59
61
|
const missingLivenessGuidance = [];
|
|
@@ -224,3 +224,36 @@ test("#given completed pending update #when hook session-start runs as CLI #then
|
|
|
224
224
|
assert.equal(repeat.status, 0);
|
|
225
225
|
assert.equal(repeat.stdout, "");
|
|
226
226
|
});
|
|
227
|
+
|
|
228
|
+
test("#given the script reached through a symlinked directory #when hook session-start runs as CLI #then the entry guard still fires", async () => {
|
|
229
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-restart-notice-symlink-"));
|
|
230
|
+
const env = autoUpdateEnv(root, {
|
|
231
|
+
LAZYCODEX_CURRENT_VERSION: "1.0.1",
|
|
232
|
+
LAZYCODEX_LATEST_VERSION: "1.0.1",
|
|
233
|
+
LAZYCODEX_CONFIG_MIGRATION_DISABLED: "1",
|
|
234
|
+
});
|
|
235
|
+
await writeFile(env.LAZYCODEX_AUTO_UPDATE_STATE_PATH, JSON.stringify({
|
|
236
|
+
lastCheckedAt: Date.now() - 1_000,
|
|
237
|
+
lastStatus: "success",
|
|
238
|
+
pendingNotice: { fromVersion: "1.0.0", toVersion: "1.0.1", startedAt: 1 },
|
|
239
|
+
}));
|
|
240
|
+
// Simulate a symlinked plugin cache dir: argv[1] spells the symlink, while
|
|
241
|
+
// import.meta.url resolves to the real path. Seen in the wild; the old
|
|
242
|
+
// `pathToFileURL(process.argv[1])` guard never matched and the hook was a
|
|
243
|
+
// silent no-op.
|
|
244
|
+
const { symlink } = await import("node:fs/promises");
|
|
245
|
+
const { dirname } = await import("node:path");
|
|
246
|
+
const linkDir = join(root, "linked-scripts");
|
|
247
|
+
await symlink(dirname(SCRIPT_PATH), linkDir, "dir");
|
|
248
|
+
const linkedScript = join(linkDir, "auto-update.mjs");
|
|
249
|
+
|
|
250
|
+
const result = spawnSync(process.execPath, [linkedScript, "hook", "session-start"], {
|
|
251
|
+
encoding: "utf8",
|
|
252
|
+
env: { ...process.env, ...env },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
assert.equal(result.status, 0);
|
|
256
|
+
const lines = result.stdout.split("\n").filter((line) => line.length > 0);
|
|
257
|
+
assert.equal(lines.length, 1, `expected one SessionStart JSON line, got stdout=${JSON.stringify(result.stdout)} stderr=${JSON.stringify(result.stderr)}`);
|
|
258
|
+
assert.equal(JSON.parse(lines[0]).hookSpecificOutput.hookEventName, "SessionStart");
|
|
259
|
+
});
|
|
@@ -756,7 +756,7 @@ test("#given global config with forced multi_agent_v2 #when full migration runs
|
|
|
756
756
|
test("#given enabled = true with an inline comment #when forcing disable #then flips to false and preserves the comment", () => {
|
|
757
757
|
const config = ["[features.multi_agent_v2]", "enabled = true # tuned by me", ""].join("\n");
|
|
758
758
|
|
|
759
|
-
const result = forceDisableMultiAgentV2(config, { multiAgentVersion:
|
|
759
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
|
|
760
760
|
|
|
761
761
|
assert.match(result, /^enabled = false # tuned by me$/m);
|
|
762
762
|
assert.doesNotMatch(result, /enabled = true/);
|
|
@@ -767,7 +767,7 @@ test("#given enabled = true with an inline comment #when forcing disable #then f
|
|
|
767
767
|
test("#given a section header with an inline comment #when forcing disable #then patches in place without duplicating the table", () => {
|
|
768
768
|
const config = ["[features.multi_agent_v2] # pinned by me", "enabled = true", ""].join("\n");
|
|
769
769
|
|
|
770
|
-
const result = forceDisableMultiAgentV2(config, { multiAgentVersion:
|
|
770
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
|
|
771
771
|
|
|
772
772
|
assert.equal((result.match(/\[features\.multi_agent_v2\]/g) ?? []).length, 1);
|
|
773
773
|
assert.match(result, /enabled = false/);
|
|
@@ -779,19 +779,19 @@ test("#given an already-guarded commented config #when re-running #then output i
|
|
|
779
779
|
const configA = ["[features.multi_agent_v2]", "enabled = true # tuned by me", ""].join("\n");
|
|
780
780
|
const configB = ["[features.multi_agent_v2] # pinned by me", "enabled = true", ""].join("\n");
|
|
781
781
|
|
|
782
|
-
const firstA = forceDisableMultiAgentV2(configA, { multiAgentVersion:
|
|
783
|
-
const rerunA = forceDisableMultiAgentV2(firstA, { multiAgentVersion:
|
|
782
|
+
const firstA = forceDisableMultiAgentV2(configA, { multiAgentVersion: "v1" });
|
|
783
|
+
const rerunA = forceDisableMultiAgentV2(firstA, { multiAgentVersion: "v1" });
|
|
784
784
|
assert.equal(rerunA, firstA);
|
|
785
785
|
|
|
786
|
-
const firstB = forceDisableMultiAgentV2(configB, { multiAgentVersion:
|
|
787
|
-
const rerunB = forceDisableMultiAgentV2(firstB, { multiAgentVersion:
|
|
786
|
+
const firstB = forceDisableMultiAgentV2(configB, { multiAgentVersion: "v1" });
|
|
787
|
+
const rerunB = forceDisableMultiAgentV2(firstB, { multiAgentVersion: "v1" });
|
|
788
788
|
assert.equal(rerunB, firstB);
|
|
789
789
|
});
|
|
790
790
|
|
|
791
791
|
test("#given user-disabled with an inline comment #when forcing disable #then returns config unchanged", () => {
|
|
792
792
|
const config = ["[features.multi_agent_v2]", "enabled = false # I turned this off myself", ""].join("\n");
|
|
793
793
|
|
|
794
|
-
const result = forceDisableMultiAgentV2(config, { multiAgentVersion:
|
|
794
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
|
|
795
795
|
|
|
796
796
|
assert.equal(result, config);
|
|
797
797
|
});
|
|
@@ -799,7 +799,7 @@ test("#given user-disabled with an inline comment #when forcing disable #then re
|
|
|
799
799
|
test("#given [features] shorthand true with an inline comment #when forcing disable #then removes the shorthand and appends one disabled table", () => {
|
|
800
800
|
const config = ["[features]", "plugins = true", "multi_agent_v2 = true # legacy", ""].join("\n");
|
|
801
801
|
|
|
802
|
-
const result = forceDisableMultiAgentV2(config, { multiAgentVersion:
|
|
802
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
|
|
803
803
|
|
|
804
804
|
assert.doesNotMatch(result, /^\s*multi_agent_v2\s*=/m);
|
|
805
805
|
assert.equal((result.match(/\[features\.multi_agent_v2\]/g) ?? []).length, 1);
|
|
@@ -816,7 +816,7 @@ test("#given a following section header with an inline comment #when inserting e
|
|
|
816
816
|
"",
|
|
817
817
|
].join("\n");
|
|
818
818
|
|
|
819
|
-
const result = forceDisableMultiAgentV2(config, { multiAgentVersion:
|
|
819
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: "v1" });
|
|
820
820
|
|
|
821
821
|
assert.match(result, /\[features\.multi_agent_v2\]\nenabled = false\n/);
|
|
822
822
|
assert.match(result, /\[mcp_servers\.x\][^\n]*\nenabled = true/);
|
|
@@ -1034,6 +1034,41 @@ test("#given legacy shorthand and no session model on hook path #when full migra
|
|
|
1034
1034
|
assert.equal("enabled" in parsed.features.multi_agent_v2, false);
|
|
1035
1035
|
});
|
|
1036
1036
|
|
|
1037
|
+
test("#given SoT migration failure #when hook migration runs #then config.toml repair still happens", async () => {
|
|
1038
|
+
const { runAutoUpdateCheck } = await import("../scripts/auto-update.mjs");
|
|
1039
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-sot-isolation-"));
|
|
1040
|
+
const codexHome = join(root, "codex-home");
|
|
1041
|
+
await mkdir(codexHome, { recursive: true });
|
|
1042
|
+
const configPath = join(codexHome, "config.toml");
|
|
1043
|
+
await writeFile(
|
|
1044
|
+
configPath,
|
|
1045
|
+
['model = "gpt-5.6-sol"', "", "[features.multi_agent_v2]", "enabled = false", ""].join("\n"),
|
|
1046
|
+
);
|
|
1047
|
+
await writeFile(
|
|
1048
|
+
join(codexHome, "models_cache.json"),
|
|
1049
|
+
JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v2" }] }),
|
|
1050
|
+
);
|
|
1051
|
+
// HOME pointing at a FILE makes migrateOmoSotConfig's mkdir(~/.omo) throw.
|
|
1052
|
+
const brokenHome = join(root, "not-a-dir");
|
|
1053
|
+
await writeFile(brokenHome, "");
|
|
1054
|
+
|
|
1055
|
+
await runAutoUpdateCheck({
|
|
1056
|
+
env: {
|
|
1057
|
+
CODEX_HOME: codexHome,
|
|
1058
|
+
HOME: brokenHome,
|
|
1059
|
+
USERPROFILE: brokenHome,
|
|
1060
|
+
LAZYCODEX_AUTO_UPDATE_DISABLED: "1",
|
|
1061
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json"),
|
|
1062
|
+
LAZYCODEX_AUTO_UPDATE_STATE_PATH: join(root, "state.json"),
|
|
1063
|
+
},
|
|
1064
|
+
sessionModel: "gpt-5.6-sol",
|
|
1065
|
+
requireSessionModel: true,
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
const content = await readFile(configPath, "utf8");
|
|
1069
|
+
assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1037
1072
|
test("#given config default gpt-5.5 #when full migration gets SessionStart gpt-5.6-terra #then clears disable using session model", async () => {
|
|
1038
1073
|
const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-session-model-"));
|
|
1039
1074
|
const codexHome = join(root, "codex-home");
|
|
@@ -1078,6 +1113,47 @@ test("#given config default gpt-5.5 #when full migration gets SessionStart gpt-5
|
|
|
1078
1113
|
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
1079
1114
|
});
|
|
1080
1115
|
|
|
1116
|
+
test("#given no session model and no root model #when forcing disable #then leaves the enable state untouched", () => {
|
|
1117
|
+
const config = ["[features]", "plugins = true", "", "[features.multi_agent_v2]", "enabled = true", ""].join("\n");
|
|
1118
|
+
|
|
1119
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null, sessionModel: null });
|
|
1120
|
+
|
|
1121
|
+
assert.match(result, /enabled = true/);
|
|
1122
|
+
assert.doesNotMatch(result, /enabled = false/);
|
|
1123
|
+
assert.doesNotMatch(result, /openai\/codex#26753/);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
test("#given no session model and no root model #when config has no multi_agent_v2 section #then does not append a disable", () => {
|
|
1127
|
+
const config = ["[features]", "plugins = true", ""].join("\n");
|
|
1128
|
+
|
|
1129
|
+
const result = forceDisableMultiAgentV2(config, { multiAgentVersion: null, sessionModel: null });
|
|
1130
|
+
|
|
1131
|
+
assert.equal(result, config);
|
|
1132
|
+
assert.doesNotMatch(result, /\[features\.multi_agent_v2\]/);
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
test("#given user-modified config without root model #when full non-hook migration runs #then writes no disable and no new agents.max_threads", async () => {
|
|
1136
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-desktop-no-model-"));
|
|
1137
|
+
const codexHome = join(root, "codex-home");
|
|
1138
|
+
await mkdir(codexHome, { recursive: true });
|
|
1139
|
+
const configPath = join(codexHome, "config.toml");
|
|
1140
|
+
// Codex Desktop sessions select the model in the UI; config.toml has no root
|
|
1141
|
+
// `model`, so migration cannot prove the session is not a GPT-5.6
|
|
1142
|
+
// reserved-schema model (#6002).
|
|
1143
|
+
await writeFile(configPath, ['model_reasoning_effort = "high"', "", "[features]", "plugins = true", ""].join("\n"));
|
|
1144
|
+
|
|
1145
|
+
await migrateCodexConfig({
|
|
1146
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
1147
|
+
cwd: root,
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
const content = await readFile(configPath, "utf8");
|
|
1151
|
+
assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
|
|
1152
|
+
assert.doesNotMatch(content, /openai\/codex#26753/);
|
|
1153
|
+
assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
|
|
1154
|
+
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1081
1157
|
async function canCreateSymlink(type) {
|
|
1082
1158
|
const root = await mkdtemp(join(tmpdir(), "lazycodex-symlink-capability-"));
|
|
1083
1159
|
const target = join(root, "target");
|
|
@@ -1103,3 +1179,64 @@ async function canCreateSymlink(type) {
|
|
|
1103
1179
|
return false;
|
|
1104
1180
|
}
|
|
1105
1181
|
}
|
|
1182
|
+
|
|
1183
|
+
test("#given model_catalog_json declares a v2 model as v1 #when full migration runs #then keeps the managed disable and max_threads", async () => {
|
|
1184
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-catalog-override-"));
|
|
1185
|
+
const codexHome = join(root, "codex-home");
|
|
1186
|
+
await mkdir(codexHome, { recursive: true });
|
|
1187
|
+
const configPath = join(codexHome, "config.toml");
|
|
1188
|
+
const catalogPath = join(root, "custom-catalog.json");
|
|
1189
|
+
// Codex Desktop user forces gpt-5.6-sol to v1 via an explicit replacement catalog.
|
|
1190
|
+
await writeFile(configPath, [
|
|
1191
|
+
'model = "gpt-5.6-sol"',
|
|
1192
|
+
`model_catalog_json = "${catalogPath}"`,
|
|
1193
|
+
"",
|
|
1194
|
+
"[agents]",
|
|
1195
|
+
"max_threads = 1000",
|
|
1196
|
+
"max_depth = 2",
|
|
1197
|
+
"",
|
|
1198
|
+
"[features.multi_agent_v2]",
|
|
1199
|
+
"enabled = false",
|
|
1200
|
+
"max_concurrent_threads_per_session = 1000",
|
|
1201
|
+
"",
|
|
1202
|
+
].join("\n"));
|
|
1203
|
+
// models_cache.json STILL says v2 (stale), but the explicit catalog wins and says v1.
|
|
1204
|
+
await writeFile(join(codexHome, "models_cache.json"), JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v2" }] }));
|
|
1205
|
+
await writeFile(catalogPath, JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v1" }] }));
|
|
1206
|
+
|
|
1207
|
+
await migrateCodexConfig({
|
|
1208
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
1209
|
+
cwd: root,
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
const content = await readFile(configPath, "utf8");
|
|
1213
|
+
assert.match(content, /enabled = false/, "explicit v1 catalog must keep the managed disable");
|
|
1214
|
+
assert.match(content, /max_threads = 1000/, "explicit v1 catalog must keep agents.max_threads");
|
|
1215
|
+
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
1216
|
+
assert.match(content, /max_depth = 2/);
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
test("#given model_catalog_json declares a model as v2 #when full migration runs #then clears the managed disable", async () => {
|
|
1220
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-multi-agent-v2-catalog-v2-"));
|
|
1221
|
+
const codexHome = join(root, "codex-home");
|
|
1222
|
+
await mkdir(codexHome, { recursive: true });
|
|
1223
|
+
const configPath = join(codexHome, "config.toml");
|
|
1224
|
+
const catalogPath = join(root, "custom-catalog.json");
|
|
1225
|
+
await writeFile(configPath, [
|
|
1226
|
+
'model = "custom-model"',
|
|
1227
|
+
`model_catalog_json = "${catalogPath}"`,
|
|
1228
|
+
"",
|
|
1229
|
+
"[features.multi_agent_v2]",
|
|
1230
|
+
"enabled = false",
|
|
1231
|
+
"",
|
|
1232
|
+
].join("\n"));
|
|
1233
|
+
await writeFile(catalogPath, JSON.stringify({ models: [{ slug: "custom-model", multi_agent_version: "v2" }] }));
|
|
1234
|
+
|
|
1235
|
+
await migrateCodexConfig({
|
|
1236
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
1237
|
+
cwd: root,
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
const content = await readFile(configPath, "utf8");
|
|
1241
|
+
assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m, "explicit v2 catalog must clear the disable");
|
|
1242
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { migrateCodexConfig } from "../scripts/migrate-codex-config.mjs";
|
|
8
|
+
|
|
9
|
+
test("#given relative model_catalog_json declares gpt-5.6 model as v1 #when migrating #then resolves catalog from config directory", async () => {
|
|
10
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-v2-relative-catalog-"));
|
|
11
|
+
const codexHome = join(root, "codex-home");
|
|
12
|
+
await mkdir(codexHome, { recursive: true });
|
|
13
|
+
const configPath = join(codexHome, "config.toml");
|
|
14
|
+
await writeFile(
|
|
15
|
+
configPath,
|
|
16
|
+
[
|
|
17
|
+
'model = "gpt-5.6-sol"',
|
|
18
|
+
'model_catalog_json = "custom-catalog.json"',
|
|
19
|
+
"",
|
|
20
|
+
"[agents]",
|
|
21
|
+
"max_threads = 1000",
|
|
22
|
+
"",
|
|
23
|
+
"[features.multi_agent_v2]",
|
|
24
|
+
"enabled = false",
|
|
25
|
+
"max_concurrent_threads_per_session = 1000",
|
|
26
|
+
"",
|
|
27
|
+
].join("\n"),
|
|
28
|
+
);
|
|
29
|
+
await writeFile(join(codexHome, "models_cache.json"), JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v2" }] }));
|
|
30
|
+
await writeFile(join(codexHome, "custom-catalog.json"), JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v1" }] }));
|
|
31
|
+
|
|
32
|
+
await migrateCodexConfig({
|
|
33
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
34
|
+
cwd: root,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const content = await readFile(configPath, "utf8");
|
|
38
|
+
assert.match(content, /enabled = false/);
|
|
39
|
+
assert.match(content, /max_threads = 1000/);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("#given no SessionStart model and root gpt-5.6 model without catalog #when migrating #then clears stale V2 disable", async () => {
|
|
43
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-v2-root-gpt56-nocache-"));
|
|
44
|
+
const codexHome = join(root, "codex-home");
|
|
45
|
+
await mkdir(codexHome, { recursive: true });
|
|
46
|
+
const configPath = join(codexHome, "config.toml");
|
|
47
|
+
await writeFile(
|
|
48
|
+
configPath,
|
|
49
|
+
[
|
|
50
|
+
'model = "gpt-5.6-terra"',
|
|
51
|
+
"",
|
|
52
|
+
"[agents]",
|
|
53
|
+
"max_threads = 1000",
|
|
54
|
+
"",
|
|
55
|
+
"[features.multi_agent_v2]",
|
|
56
|
+
"enabled = false",
|
|
57
|
+
"max_concurrent_threads_per_session = 1000",
|
|
58
|
+
"",
|
|
59
|
+
].join("\n"),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await migrateCodexConfig({
|
|
63
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
64
|
+
cwd: root,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const content = await readFile(configPath, "utf8");
|
|
68
|
+
assert.doesNotMatch(content, /^\s*enabled\s*=\s*false/m);
|
|
69
|
+
assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
|
|
70
|
+
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
71
|
+
});
|
|
@@ -75,3 +75,35 @@ test("#given gpt-5.6 session model with no models_cache #when migrating #then do
|
|
|
75
75
|
assert.match(content, /max_depth = 4/);
|
|
76
76
|
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
77
77
|
});
|
|
78
|
+
|
|
79
|
+
test("#given config without any model #when migrating #then does not introduce agents.max_threads", async () => {
|
|
80
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-subagent-limit-no-model-"));
|
|
81
|
+
const configPath = join(root, "config.toml");
|
|
82
|
+
await writeFile(
|
|
83
|
+
configPath,
|
|
84
|
+
['model_reasoning_effort = "high"', "", "[agents]", "max_depth = 4", ""].join("\n"),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
await migrateConfigFile(configPath, { env: { CODEX_HOME: root } });
|
|
88
|
+
|
|
89
|
+
const content = await readFile(configPath, "utf8");
|
|
90
|
+
assert.doesNotMatch(content, /^\s*max_threads\s*=/m);
|
|
91
|
+
assert.match(content, /max_depth = 4/);
|
|
92
|
+
assert.match(content, /max_concurrent_threads_per_session = 1000/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("#given config without any model but an existing low cap #when migrating #then still raises the existing cap", async () => {
|
|
96
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-subagent-limit-no-model-raise-"));
|
|
97
|
+
const configPath = join(root, "config.toml");
|
|
98
|
+
await writeFile(
|
|
99
|
+
configPath,
|
|
100
|
+
['model_reasoning_effort = "high"', "", "[agents]", "max_threads = 6", "max_depth = 4", ""].join("\n"),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await migrateConfigFile(configPath, { env: { CODEX_HOME: root } });
|
|
104
|
+
|
|
105
|
+
const content = await readFile(configPath, "utf8");
|
|
106
|
+
assert.match(content, /max_threads = 1000/);
|
|
107
|
+
assert.doesNotMatch(content, /max_threads = 6/);
|
|
108
|
+
assert.match(content, /max_depth = 4/);
|
|
109
|
+
});
|
|
@@ -3,7 +3,7 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { insertCodexCompatibilityGuidance } from "../scripts/sync-skills.mjs";
|
|
6
|
+
import { codexHarnessToolCompatibility, insertCodexCompatibilityGuidance } from "../scripts/sync-skills.mjs";
|
|
7
7
|
|
|
8
8
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
9
|
|
|
@@ -207,7 +207,8 @@ test("#given synced aggregate Codex skills #when they describe background orches
|
|
|
207
207
|
["working progress message", /WORKING:/],
|
|
208
208
|
["blocked progress message", /BLOCKED:/],
|
|
209
209
|
["mailbox timeout framing", /timeout only means no new mailbox update arrived/],
|
|
210
|
-
|
|
210
|
+
// Skills route by session tool surface: the namespaced V1 tool or the flat V2 tool both count.
|
|
211
|
+
["wait-agent tool ref", /multi_agent_v1\.wait_agent|`wait_agent`/],
|
|
211
212
|
["explicit fallback conditions", /Fallback only when|Mark a file for retry only when/],
|
|
212
213
|
];
|
|
213
214
|
const bannedPatterns = [
|
|
@@ -265,3 +266,9 @@ test("#given PR and review skills #when synced for Codex #then worktree lifecycl
|
|
|
265
266
|
assert.match(reviewWork, /dedicated review worktree attached to that branch/);
|
|
266
267
|
assert.match(reviewWork, /Never\s+checkout, test, or edit the review branch in the main worktree/);
|
|
267
268
|
});
|
|
269
|
+
|
|
270
|
+
test("#given generated Codex compatibility guidance #when multi-agent lifecycle tools are mentioned #then optional tools are guarded by the active tools list", () => {
|
|
271
|
+
assert.match(codexHarnessToolCompatibility, /when exposed in the active tools list/, "send_input/close_agent must be marked optional (lazycodex#116)");
|
|
272
|
+
assert.match(codexHarnessToolCompatibility, /multi_agent_v1\.spawn_agent/);
|
|
273
|
+
assert.match(codexHarnessToolCompatibility, /multi_agent_v1\.wait_agent/);
|
|
274
|
+
});
|