oh-my-opencode 4.5.12 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +2189 -529
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +1077 -563
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +22 -18
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +23 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
- package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -9,15 +9,15 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
|
|
|
9
9
|
|
|
10
10
|
| OpenCode example | Codex tool to use |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| `call_omo_agent(subagent_type="explore", ...)` | `spawn_agent(agent_type="explorer", task_name="...", message="...")` |
|
|
13
|
-
| `call_omo_agent(subagent_type="librarian", ...)` | `spawn_agent(agent_type="librarian", task_name="...", message="...")` |
|
|
14
|
-
| `task(subagent_type="plan", ...)` | `spawn_agent(agent_type="plan", task_name="...", message="...")` |
|
|
15
|
-
| `task(subagent_type="oracle", ...)` for final verification | `spawn_agent(agent_type="codex-ultrawork-reviewer", task_name="...", message="...")` |
|
|
16
|
-
| `task(category="...", ...)` for implementation or QA | `spawn_agent(agent_type="worker", task_name="...", message="...")` |
|
|
12
|
+
| `call_omo_agent(subagent_type="explore", ...)` | `spawn_agent(agent_type="explorer", task_name="...", message="...", fork_turns="none")` |
|
|
13
|
+
| `call_omo_agent(subagent_type="librarian", ...)` | `spawn_agent(agent_type="librarian", task_name="...", message="...", fork_turns="none")` |
|
|
14
|
+
| `task(subagent_type="plan", ...)` | `spawn_agent(agent_type="plan", task_name="...", message="...", fork_turns="none")` |
|
|
15
|
+
| `task(subagent_type="oracle", ...)` for final verification | `spawn_agent(agent_type="codex-ultrawork-reviewer", task_name="...", message="...", fork_turns="none")` |
|
|
16
|
+
| `task(category="...", ...)` for implementation or QA | `spawn_agent(agent_type="worker", task_name="...", message="...", fork_turns="none")` |
|
|
17
17
|
| `background_output(task_id="...")` | `wait_agent(...)` to wait for subagent completion and mailbox updates |
|
|
18
18
|
| `team_*(...)` | Use Codex native subagents plus `send_message`, `followup_task`, `wait_agent`, and `close_agent` |
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Codex full-history forks inherit the parent agent type, model, and reasoning effort, so role-specific spawns with `agent_type` must use a non-full-history fork mode such as `fork_turns="none"`. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
|
|
21
21
|
|
|
22
22
|
<identity>
|
|
23
23
|
You are Prometheus - Strategic Planning Consultant.
|
|
@@ -26,7 +26,7 @@ Named after the Titan who brought fire to humanity, you bring foresight and stru
|
|
|
26
26
|
**YOU ARE A PLANNER. NOT AN IMPLEMENTER. NOT A CODE WRITER.**
|
|
27
27
|
|
|
28
28
|
When user says "do X", "fix X", "build X" - interpret as "create a work plan for X". No exceptions.
|
|
29
|
-
Your only outputs: questions, research, work plans (
|
|
29
|
+
Your only outputs: questions, research, work plans (`.omo/plans/<slug>.md`), drafts (`.omo/drafts/*.md`).
|
|
30
30
|
</identity>
|
|
31
31
|
|
|
32
32
|
<mission>
|
|
@@ -68,7 +68,7 @@ This is your north star quality metric.
|
|
|
68
68
|
- Spawning read-only subagents for research
|
|
69
69
|
|
|
70
70
|
### Allowed (plan artifacts only)
|
|
71
|
-
- Writing/editing files in
|
|
71
|
+
- Writing/editing files in `.omo/plans/<slug>.md`
|
|
72
72
|
- Writing/editing files in `.omo/drafts/*.md`
|
|
73
73
|
|
|
74
74
|
### Forbidden (mutating, plan-executing)
|
|
@@ -185,7 +185,7 @@ ANY NO -> Ask the specific unclear question.
|
|
|
185
185
|
Spawn the metis agent to analyze the planning session for contradictions, ambiguity, missing constraints, and execution risks:
|
|
186
186
|
|
|
187
187
|
```
|
|
188
|
-
spawn_agent(agent_type="metis", task_name="gap-analysis",
|
|
188
|
+
spawn_agent(agent_type="metis", task_name="gap-analysis", fork_turns="none",
|
|
189
189
|
message="Review this planning session. Goal: {summary}. Discussed: {key points}. Understanding: {interpretation}. Research: {findings}. Identify: contradictions, ambiguity, missing constraints, execution risks, scope creep areas, missing acceptance criteria.")
|
|
190
190
|
```
|
|
191
191
|
|
|
@@ -233,7 +233,7 @@ Self-review checklist:
|
|
|
233
233
|
**Defaults Applied**: [default]: [assumption]
|
|
234
234
|
**Decisions Needed**: [question requiring user input] (if any)
|
|
235
235
|
|
|
236
|
-
Plan saved to: plans/{slug}.md
|
|
236
|
+
Plan saved to: .omo/plans/{slug}.md
|
|
237
237
|
```
|
|
238
238
|
|
|
239
239
|
If "Decisions Needed" exists, wait for user response and update plan.
|
|
@@ -253,8 +253,8 @@ Only activated when user selects "High Accuracy Review".
|
|
|
253
253
|
Spawn the momus agent with the plan file path:
|
|
254
254
|
|
|
255
255
|
```
|
|
256
|
-
spawn_agent(agent_type="momus", task_name="plan-review",
|
|
257
|
-
message="Review this plan: plans/{slug}.md")
|
|
256
|
+
spawn_agent(agent_type="momus", task_name="plan-review", fork_turns="none",
|
|
257
|
+
message="Review this plan: .omo/plans/{slug}.md")
|
|
258
258
|
```
|
|
259
259
|
|
|
260
260
|
Handle the three-verdict response:
|
|
@@ -270,13 +270,13 @@ Handle the three-verdict response:
|
|
|
270
270
|
|
|
271
271
|
After plan is complete (direct or Momus-approved):
|
|
272
272
|
1. Delete draft: remove `.omo/drafts/{name}.md`
|
|
273
|
-
2. Guide user: "Plan saved to
|
|
273
|
+
2. Guide user: "Plan saved to `.omo/plans/{slug}.md`. Spawn a worker agent to begin execution."
|
|
274
274
|
</phases>
|
|
275
275
|
|
|
276
276
|
<plan_template>
|
|
277
277
|
## Plan Structure
|
|
278
278
|
|
|
279
|
-
Generate to:
|
|
279
|
+
Generate to: `.omo/plans/{slug}.md`
|
|
280
280
|
|
|
281
281
|
**Single Plan Mandate**: No matter how large the task, EVERYTHING goes into ONE plan. Never split into "Phase 1, Phase 2". 50+ TODOs is fine.
|
|
282
282
|
|
|
@@ -308,7 +308,7 @@ Generate to: `plans/{slug}.md`
|
|
|
308
308
|
> ZERO HUMAN INTERVENTION - all verification is agent-executed.
|
|
309
309
|
- Test decision: [TDD / tests-after / none] + framework
|
|
310
310
|
- QA policy: Every task has agent-executed scenarios
|
|
311
|
-
- Evidence: evidence/task-{N}-{slug}.{ext}
|
|
311
|
+
- Evidence: .omo/evidence/task-{N}-{slug}.{ext}
|
|
312
312
|
|
|
313
313
|
## Execution Strategy
|
|
314
314
|
### Parallel Execution Waves
|
|
@@ -346,13 +346,13 @@ Wave 2: [dependent tasks]
|
|
|
346
346
|
Tool: [bash / curl / tmux / playwright]
|
|
347
347
|
Steps: [exact actions with specific data]
|
|
348
348
|
Expected: [concrete, binary pass/fail]
|
|
349
|
-
Evidence: evidence/task-{N}-{slug}.{ext}
|
|
349
|
+
Evidence: .omo/evidence/task-{N}-{slug}.{ext}
|
|
350
350
|
|
|
351
351
|
Scenario: [Failure/edge case]
|
|
352
352
|
Tool: [same]
|
|
353
353
|
Steps: [trigger error condition]
|
|
354
354
|
Expected: [graceful failure with correct error message/code]
|
|
355
|
-
Evidence: evidence/task-{N}-{slug}-error.{ext}
|
|
355
|
+
Evidence: .omo/evidence/task-{N}-{slug}-error.{ext}
|
|
356
356
|
```
|
|
357
357
|
|
|
358
358
|
**Commit**: YES/NO | Message: `type(scope): desc` | Files: [paths]
|
|
@@ -5,17 +5,30 @@ import test from "node:test";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const mcpPackageManifestPaths = ["../../lsp-tools-mcp/package.json", "../../ast-grep-mcp/package.json", "../../git-bash-mcp/package.json"];
|
|
9
|
+
const mcpPackageManifestExists = await Promise.all(mcpPackageManifestPaths.map(exists));
|
|
8
10
|
|
|
9
11
|
async function readJson(relativePath) {
|
|
10
12
|
return JSON.parse(await readFile(join(root, relativePath), "utf8"));
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
async function exists(relativePath) {
|
|
16
|
+
try {
|
|
17
|
+
await stat(join(root, relativePath));
|
|
18
|
+
return true;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
async function readComponentHookManifests() {
|
|
14
26
|
const components = await readdir(join(root, "components"), { withFileTypes: true });
|
|
15
27
|
const manifests = [];
|
|
16
28
|
for (const entry of components) {
|
|
17
29
|
if (!entry.isDirectory()) continue;
|
|
18
30
|
const source = join("components", entry.name, "hooks", "hooks.json");
|
|
31
|
+
if (!(await exists(source))) continue;
|
|
19
32
|
manifests.push({ source, hooks: await readJson(source) });
|
|
20
33
|
}
|
|
21
34
|
return manifests.sort((left, right) => left.source.localeCompare(right.source));
|
|
@@ -57,6 +70,18 @@ function findSpawnAgentTypes(content) {
|
|
|
57
70
|
return [...agentTypes].sort();
|
|
58
71
|
}
|
|
59
72
|
|
|
73
|
+
function findRoleSpecificSpawnsWithoutForkTurnsNone(content) {
|
|
74
|
+
const missingForkTurns = [];
|
|
75
|
+
const regex = /spawn_agent\(agent_type="([^"]+)"[^)]*\)/g;
|
|
76
|
+
for (const match of content.matchAll(regex)) {
|
|
77
|
+
const call = match[0];
|
|
78
|
+
if (!call.includes('fork_turns="none"')) {
|
|
79
|
+
missingForkTurns.push(call);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return missingForkTurns;
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
test("#given aggregate plugin manifest #when inspected #then it owns the omo namespace", async () => {
|
|
61
86
|
// given
|
|
62
87
|
const manifest = await readJson(".codex-plugin/plugin.json");
|
|
@@ -99,6 +124,7 @@ test("#given isolated components #when hooks are inspected #then commands stay i
|
|
|
99
124
|
"components/telemetry/dist/cli.js",
|
|
100
125
|
"components/ulw-loop/dist/cli.js",
|
|
101
126
|
"components/ultrawork/dist/cli.js",
|
|
127
|
+
"scripts/auto-update.mjs",
|
|
102
128
|
];
|
|
103
129
|
|
|
104
130
|
// then
|
|
@@ -106,6 +132,7 @@ test("#given isolated components #when hooks are inspected #then commands stay i
|
|
|
106
132
|
assert.match(text, new RegExp(marker.replaceAll("/", "\\/")));
|
|
107
133
|
}
|
|
108
134
|
assert.doesNotMatch(text, /codex-(comment-checker|lsp|rules|telemetry|ulw-loop|ultrawork)@/);
|
|
135
|
+
assert.equal(await exists("scripts/migrate-codex-config.mjs"), true);
|
|
109
136
|
});
|
|
110
137
|
|
|
111
138
|
test("#given aggregate hook commands #when inspected #then every command exposes a Codex status message", async () => {
|
|
@@ -154,7 +181,7 @@ test("#given hook status messages #when inspected #then labels describe OMO resp
|
|
|
154
181
|
assert.deepEqual(genericStatusMessages, []);
|
|
155
182
|
});
|
|
156
183
|
|
|
157
|
-
test("#given aggregate OMO plugin is enabled #when hooks are inspected #then ulw-loop
|
|
184
|
+
test("#given aggregate OMO plugin is enabled #when hooks are inspected #then shell guidance and ulw-loop guard are registered", async () => {
|
|
158
185
|
// given
|
|
159
186
|
const hooks = await readJson("hooks/hooks.json");
|
|
160
187
|
const text = JSON.stringify(hooks);
|
|
@@ -163,9 +190,31 @@ test("#given aggregate OMO plugin is enabled #when hooks are inspected #then ulw
|
|
|
163
190
|
const preToolUseGroups = hooks.hooks.PreToolUse;
|
|
164
191
|
|
|
165
192
|
// then
|
|
193
|
+
assert.match(text, /components\/git-bash\/dist\/cli\.js/);
|
|
194
|
+
assert.match(text, /Recommending Git Bash Mcp/);
|
|
195
|
+
assert.match(text, /hook post-compact/);
|
|
196
|
+
assert.match(text, /Resetting Git Bash Mcp Reminder/);
|
|
166
197
|
assert.match(text, /components\/ulw-loop\/dist\/cli\.js/);
|
|
167
198
|
assert.match(text, /hook pre-tool-use/);
|
|
168
|
-
assert.deepEqual(preToolUseGroups.map((group) => group.matcher), ["^create_goal$"]);
|
|
199
|
+
assert.deepEqual(preToolUseGroups.map((group) => group.matcher), ["^Bash$", "^create_goal$"]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("#given aggregate SessionStart hooks #when inspected #then LazyCodex auto-update is registered", async () => {
|
|
203
|
+
// given
|
|
204
|
+
const hooks = await readJson("hooks/hooks.json");
|
|
205
|
+
const text = JSON.stringify(hooks);
|
|
206
|
+
|
|
207
|
+
// when
|
|
208
|
+
const sessionStartCommands = collectCommandHooks(hooks, "hooks/hooks.json")
|
|
209
|
+
.filter(({ eventName }) => eventName === "SessionStart")
|
|
210
|
+
.map(({ handler }) => handler.command);
|
|
211
|
+
const autoUpdateGroup = hooks.hooks.SessionStart.find((group) => JSON.stringify(group).includes("scripts/auto-update.mjs"));
|
|
212
|
+
|
|
213
|
+
// then
|
|
214
|
+
assert.equal(autoUpdateGroup?.matcher, "^startup$");
|
|
215
|
+
assert.match(text, /scripts\/auto-update\.mjs/);
|
|
216
|
+
assert.match(text, /Checking Auto Update/);
|
|
217
|
+
assert(sessionStartCommands.some((command) => command.includes("scripts/auto-update.mjs")));
|
|
169
218
|
});
|
|
170
219
|
|
|
171
220
|
test("#given aggregate MCP config #when inspected #then code MCPs reference package runtimes without package names", async () => {
|
|
@@ -178,17 +227,19 @@ test("#given aggregate MCP config #when inspected #then code MCPs reference pack
|
|
|
178
227
|
// when
|
|
179
228
|
const lspServer = mcp.mcpServers.lsp;
|
|
180
229
|
const astGrepServer = mcp.mcpServers.ast_grep;
|
|
230
|
+
const gitBashServer = mcp.mcpServers.git_bash;
|
|
181
231
|
const codeMcpNames = Object.keys(mcp.mcpServers)
|
|
182
|
-
.filter((name) => name === "lsp" || name === "ast_grep")
|
|
232
|
+
.filter((name) => name === "lsp" || name === "ast_grep" || name === "git_bash")
|
|
183
233
|
.sort();
|
|
184
234
|
const componentLocalMcpSources = lspSources.filter((name) => name.startsWith("lazy-mcp") || name === "lazy-lsp-mcp.ts");
|
|
185
235
|
|
|
186
236
|
// then
|
|
187
|
-
assert.deepEqual(codeMcpNames, ["ast_grep", "lsp"]);
|
|
237
|
+
assert.deepEqual(codeMcpNames, ["ast_grep", "git_bash", "lsp"]);
|
|
188
238
|
assert.equal(packageJson.workspaces.includes("components/lsp/packages/lsp-tools-mcp"), false);
|
|
189
239
|
assert.equal(packageJson.workspaces.includes("components/ast-grep/packages/ast-grep-mcp"), false);
|
|
190
240
|
assert.deepEqual(packageJson.dependencies, { "@oh-my-opencode/shared-skills": "file:../../shared-skills" });
|
|
191
241
|
assert.match(bundledMcpBuildScript, /ast-grep-mcp/);
|
|
242
|
+
assert.match(bundledMcpBuildScript, /git-bash-mcp/);
|
|
192
243
|
assert.doesNotMatch(packageJson.scripts.build, /--workspaces/);
|
|
193
244
|
assert.equal(lspServer.command, "node");
|
|
194
245
|
assert.deepEqual(lspServer.args, ["../../lsp-tools-mcp/dist/cli.js", "mcp"]);
|
|
@@ -196,25 +247,37 @@ test("#given aggregate MCP config #when inspected #then code MCPs reference pack
|
|
|
196
247
|
assert.equal(astGrepServer.command, "node");
|
|
197
248
|
assert.deepEqual(astGrepServer.args, ["../../ast-grep-mcp/dist/cli.js", "mcp"]);
|
|
198
249
|
assert.equal(astGrepServer.cwd, ".");
|
|
250
|
+
assert.equal(gitBashServer.command, "node");
|
|
251
|
+
assert.deepEqual(gitBashServer.args, ["../../git-bash-mcp/dist/cli.js", "mcp"]);
|
|
252
|
+
assert.equal(gitBashServer.cwd, ".");
|
|
199
253
|
assert.deepEqual(componentLocalMcpSources, []);
|
|
200
254
|
});
|
|
201
255
|
|
|
202
|
-
test(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
256
|
+
test(
|
|
257
|
+
"#given package-level MCP CLIs #when package metadata is inspected #then bin names use the omo prefix",
|
|
258
|
+
{ skip: mcpPackageManifestExists.some((exists) => !exists) },
|
|
259
|
+
async () => {
|
|
260
|
+
// given
|
|
261
|
+
const [lspPackageJson, astGrepPackageJson, gitBashPackageJson] = await Promise.all(
|
|
262
|
+
mcpPackageManifestPaths.map((path) => readJson(path)),
|
|
263
|
+
);
|
|
209
264
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
265
|
+
// when
|
|
266
|
+
const binNames = [
|
|
267
|
+
...Object.keys(lspPackageJson.bin ?? {}),
|
|
268
|
+
...Object.keys(astGrepPackageJson.bin ?? {}),
|
|
269
|
+
...Object.keys(gitBashPackageJson.bin ?? {}),
|
|
270
|
+
].sort();
|
|
271
|
+
|
|
272
|
+
// then
|
|
273
|
+
assert.deepEqual(binNames, ["omo-ast-grep", "omo-git-bash", "omo-lsp"]);
|
|
274
|
+
for (const name of binNames) {
|
|
275
|
+
assert.match(name, /^omo-/);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
);
|
|
216
279
|
|
|
217
|
-
test("#given aggregate plugin build script #when inspected #then telemetry sync
|
|
280
|
+
test("#given aggregate plugin build script #when inspected #then hook status and telemetry sync run before workspace builds", async () => {
|
|
218
281
|
// given
|
|
219
282
|
const packageJson = await readJson("package.json");
|
|
220
283
|
const telemetrySyncScript = await readFile(join(root, "..", "scripts", "sync-telemetry-component.mjs"), "utf8");
|
|
@@ -225,7 +288,7 @@ test("#given aggregate plugin build script #when inspected #then telemetry sync
|
|
|
225
288
|
// then
|
|
226
289
|
assert.equal(
|
|
227
290
|
buildScript,
|
|
228
|
-
"node scripts/build-bundled-mcp-runtimes.mjs && node scripts/sync-skills.mjs && node ../scripts/sync-telemetry-component.mjs && node scripts/build-components.mjs",
|
|
291
|
+
"node scripts/sync-hook-status-messages.mjs && node scripts/build-bundled-mcp-runtimes.mjs && node scripts/sync-skills.mjs && node ../scripts/sync-telemetry-component.mjs && node scripts/build-components.mjs",
|
|
229
292
|
);
|
|
230
293
|
assert.match(telemetrySyncScript, /syncTelemetryComponent/);
|
|
231
294
|
});
|
|
@@ -247,11 +310,18 @@ test("#given component directories #when scanned #then only intentional resource
|
|
|
247
310
|
const expectedComponentManifests = new Map([["rules", { hooks: "./hooks/hooks.json" }]]);
|
|
248
311
|
|
|
249
312
|
// when
|
|
250
|
-
const componentNames =
|
|
313
|
+
const componentNames = [];
|
|
314
|
+
for (const entry of components) {
|
|
315
|
+
if (!entry.isDirectory()) continue;
|
|
316
|
+
if (!(await exists(join("components", entry.name, "package.json")))) continue;
|
|
317
|
+
componentNames.push(entry.name);
|
|
318
|
+
}
|
|
319
|
+
componentNames.sort();
|
|
251
320
|
|
|
252
321
|
// then
|
|
253
322
|
assert.deepEqual(componentNames, [
|
|
254
323
|
"comment-checker",
|
|
324
|
+
"git-bash",
|
|
255
325
|
"lsp",
|
|
256
326
|
"rules",
|
|
257
327
|
"start-work-continuation",
|
|
@@ -300,6 +370,65 @@ test("#given bundled Codex agents #when components/ultrawork/agents directory is
|
|
|
300
370
|
}
|
|
301
371
|
});
|
|
302
372
|
|
|
373
|
+
test("#given planner agent prompt #when inspected #then generated artifacts stay under .omo", async () => {
|
|
374
|
+
const prompt = await readFile(join(root, "components", "ultrawork", "agents", "plan.toml"), "utf8");
|
|
375
|
+
|
|
376
|
+
assert.match(prompt, /\.omo\/plans\/<slug>\.md/);
|
|
377
|
+
assert.match(prompt, /\.omo\/evidence\/task-<N>-<slug>\.<ext>/);
|
|
378
|
+
assert.doesNotMatch(prompt, /(?<!\.omo\/)plans\/<slug>\.md/);
|
|
379
|
+
assert.doesNotMatch(prompt, /(?<!\.omo\/)evidence\/task-/);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("#given reviewer agent prompt #when inspected #then default model is ChatGPT-account compatible", async () => {
|
|
383
|
+
const prompt = await readFile(
|
|
384
|
+
join(root, "components", "ultrawork", "agents", "codex-ultrawork-reviewer.toml"),
|
|
385
|
+
"utf8",
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
assert.match(prompt, /^model\s*=\s*"gpt-5\.5"$/m);
|
|
389
|
+
assert.match(prompt, /^model_reasoning_effort\s*=\s*"xhigh"$/m);
|
|
390
|
+
assert.doesNotMatch(prompt, /^model\s*=\s*"gpt-5\.2"$/m);
|
|
391
|
+
assert.match(prompt, /ChatGPT account/);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("#given bundled model catalog #when inspected #then default verifier and worker roles are pinned", async () => {
|
|
395
|
+
const catalog = JSON.parse(await readFile(join(root, "model-catalog.json"), "utf8"));
|
|
396
|
+
|
|
397
|
+
assert.equal(catalog.current.model, "gpt-5.5");
|
|
398
|
+
assert.equal(catalog.current.model_context_window, 400000);
|
|
399
|
+
assert.equal(catalog.current.model_reasoning_effort, "high");
|
|
400
|
+
assert.equal(catalog.current.plan_mode_reasoning_effort, "xhigh");
|
|
401
|
+
assert.deepEqual(catalog.roles.default, catalog.current);
|
|
402
|
+
assert.deepEqual(catalog.roles.verifier, {
|
|
403
|
+
model: "gpt-5.5",
|
|
404
|
+
model_reasoning_effort: "xhigh",
|
|
405
|
+
});
|
|
406
|
+
assert.deepEqual(catalog.roles.worker, {
|
|
407
|
+
model: "gpt-5.4",
|
|
408
|
+
model_reasoning_effort: "high",
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("#given Codex-facing orchestration surfaces #when inspected #then retired ChatGPT-account model names are not recommended", async () => {
|
|
413
|
+
const promptFiles = [
|
|
414
|
+
join(root, "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
415
|
+
join(root, "components", "ulw-loop", "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
416
|
+
join(root, "components", "ultrawork", "README.md"),
|
|
417
|
+
join(root, "components", "ultrawork", "CHANGELOG.md"),
|
|
418
|
+
join(root, "components", "rules", "src", "post-compact-budget.ts"),
|
|
419
|
+
];
|
|
420
|
+
|
|
421
|
+
const staleReferences = [];
|
|
422
|
+
for (const promptPath of promptFiles) {
|
|
423
|
+
const content = await readFile(promptPath, "utf8");
|
|
424
|
+
if (/gpt-5\.(?:2|3-codex)/i.test(content)) {
|
|
425
|
+
staleReferences.push(`${basename(dirname(promptPath))}/${basename(promptPath)}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
assert.deepEqual(staleReferences, []);
|
|
430
|
+
});
|
|
431
|
+
|
|
303
432
|
test("#given synced skills with Codex compatibility guidance #when a bundled agent_type is referenced #then a matching TOML is bundled", async () => {
|
|
304
433
|
const skillsDir = join(root, "skills");
|
|
305
434
|
const skillEntries = await readdir(skillsDir, { withFileTypes: true });
|
|
@@ -328,3 +457,42 @@ test("#given synced skills with Codex compatibility guidance #when a bundled age
|
|
|
328
457
|
assert.equal(basename(tomlPath), `${agentType}.toml`);
|
|
329
458
|
}
|
|
330
459
|
});
|
|
460
|
+
|
|
461
|
+
test('#given synced skills and bundled rules #when role-specific agents are spawned #then they set fork_turns="none"', async () => {
|
|
462
|
+
const skillsDir = join(root, "skills");
|
|
463
|
+
const skillEntries = await readdir(skillsDir, { withFileTypes: true });
|
|
464
|
+
const promptFiles = skillEntries
|
|
465
|
+
.filter((entry) => entry.isDirectory())
|
|
466
|
+
.map((entry) => join(skillsDir, entry.name, "SKILL.md"));
|
|
467
|
+
promptFiles.push(join(root, "components", "rules", "bundled-rules", "hephaestus.md"));
|
|
468
|
+
|
|
469
|
+
const missingForkTurns = [];
|
|
470
|
+
for (const promptPath of promptFiles) {
|
|
471
|
+
const content = await readFile(promptPath, "utf8");
|
|
472
|
+
for (const call of findRoleSpecificSpawnsWithoutForkTurnsNone(content)) {
|
|
473
|
+
missingForkTurns.push(`${basename(dirname(promptPath))}/${basename(promptPath)}: ${call}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
assert.deepEqual(missingForkTurns, []);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("#given long-running orchestration prompts #when waiting on child agents #then parent liveness is surfaced", async () => {
|
|
481
|
+
const promptFiles = [
|
|
482
|
+
join(root, "skills", "ulw-loop", "SKILL.md"),
|
|
483
|
+
join(root, "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
484
|
+
join(root, "skills", "review-work", "SKILL.md"),
|
|
485
|
+
join(root, "skills", "start-work", "SKILL.md"),
|
|
486
|
+
join(root, "components", "rules", "bundled-rules", "hephaestus.md"),
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
const missingLivenessGuidance = [];
|
|
490
|
+
for (const promptPath of promptFiles) {
|
|
491
|
+
const content = await readFile(promptPath, "utf8");
|
|
492
|
+
if (!content.includes("active subagent count") || !content.includes("last heartbeat")) {
|
|
493
|
+
missingLivenessGuidance.push(`${basename(dirname(promptPath))}/${basename(promptPath)}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
assert.deepEqual(missingLivenessGuidance, []);
|
|
498
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
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 { resolveAutoUpdatePlan, runAutoUpdateCheck } from "../scripts/auto-update.mjs";
|
|
8
|
+
|
|
9
|
+
test("#given auto update is disabled #when resolving plan #then no command is scheduled", () => {
|
|
10
|
+
const plan = resolveAutoUpdatePlan({
|
|
11
|
+
env: { LAZYCODEX_AUTO_UPDATE_DISABLED: "1" },
|
|
12
|
+
now: 1_000,
|
|
13
|
+
lastCheckedAt: 0,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
assert.equal(plan.shouldRun, false);
|
|
17
|
+
assert.equal(plan.reason, "disabled");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("#given stale state #when resolving plan #then installer update command is scheduled", () => {
|
|
21
|
+
const plan = resolveAutoUpdatePlan({
|
|
22
|
+
env: {},
|
|
23
|
+
now: 90_000_000,
|
|
24
|
+
lastCheckedAt: 0,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
assert.equal(plan.shouldRun, true);
|
|
28
|
+
assert.deepEqual(plan.command, "npx");
|
|
29
|
+
assert.deepEqual(plan.args, ["--yes", "lazycodex-ai@latest", "install", "--no-tui", "--skip-auth"]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("#given recent state #when resolving plan #then update is throttled", () => {
|
|
33
|
+
const plan = resolveAutoUpdatePlan({
|
|
34
|
+
env: {},
|
|
35
|
+
now: 90_000_000,
|
|
36
|
+
lastCheckedAt: 89_999_000,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.equal(plan.shouldRun, false);
|
|
40
|
+
assert.equal(plan.reason, "throttled");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("#given test command override #when running check #then records state and launches command", async () => {
|
|
44
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-auto-update-"));
|
|
45
|
+
const logPath = join(root, "spawn.log");
|
|
46
|
+
const statePath = join(root, "state.json");
|
|
47
|
+
const codexHome = join(root, "codex-home");
|
|
48
|
+
|
|
49
|
+
const result = await runAutoUpdateCheck({
|
|
50
|
+
env: {
|
|
51
|
+
CODEX_HOME: codexHome,
|
|
52
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json"),
|
|
53
|
+
LAZYCODEX_AUTO_UPDATE_STATE_PATH: statePath,
|
|
54
|
+
LAZYCODEX_AUTO_UPDATE_INTERVAL_MS: "0",
|
|
55
|
+
LAZYCODEX_AUTO_UPDATE_COMMAND: process.execPath,
|
|
56
|
+
LAZYCODEX_AUTO_UPDATE_ARGS_JSON: JSON.stringify(["-e", `require("node:fs").writeFileSync(${JSON.stringify(logPath)}, "ok")`]),
|
|
57
|
+
LAZYCODEX_AUTO_UPDATE_WAIT: "1",
|
|
58
|
+
},
|
|
59
|
+
now: 123_456,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.equal(result.started, true);
|
|
63
|
+
assert.equal(JSON.parse(await readFile(statePath, "utf8")).lastCheckedAt, 123_456);
|
|
64
|
+
assert.equal(await readFile(logPath, "utf8"), "ok");
|
|
65
|
+
assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /model = "gpt-5\.5"/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("#given active lock #when running check #then skips concurrent update", async () => {
|
|
69
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-auto-update-lock-"));
|
|
70
|
+
const statePath = join(root, "state.json");
|
|
71
|
+
const lockPath = join(root, "state.json.lock");
|
|
72
|
+
const codexHome = join(root, "codex-home");
|
|
73
|
+
await writeFile(lockPath, "locked\n");
|
|
74
|
+
|
|
75
|
+
const result = await runAutoUpdateCheck({
|
|
76
|
+
env: {
|
|
77
|
+
CODEX_HOME: codexHome,
|
|
78
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json"),
|
|
79
|
+
LAZYCODEX_AUTO_UPDATE_STATE_PATH: statePath,
|
|
80
|
+
LAZYCODEX_AUTO_UPDATE_LOCK_PATH: lockPath,
|
|
81
|
+
LAZYCODEX_AUTO_UPDATE_INTERVAL_MS: "0",
|
|
82
|
+
LAZYCODEX_AUTO_UPDATE_LOCK_STALE_MS: "600000",
|
|
83
|
+
},
|
|
84
|
+
now: 123_456,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
assert.equal(result.started, false);
|
|
88
|
+
assert.equal(result.reason, "locked");
|
|
89
|
+
assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /model_context_window = 400000/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("#given throttled updater and stale Codex config #when running check #then config migration still runs", async () => {
|
|
93
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-auto-update-migration-"));
|
|
94
|
+
const statePath = join(root, "state.json");
|
|
95
|
+
const codexHome = join(root, "codex-home");
|
|
96
|
+
await writeFile(statePath, JSON.stringify({ lastCheckedAt: 99_999 }, null, 2));
|
|
97
|
+
await mkdir(codexHome, { recursive: true });
|
|
98
|
+
await writeFile(
|
|
99
|
+
join(codexHome, "config.toml"),
|
|
100
|
+
[
|
|
101
|
+
'model = "gpt-5.2"',
|
|
102
|
+
"model_context_window = 272000",
|
|
103
|
+
'model_reasoning_effort = "low"',
|
|
104
|
+
'plan_mode_reasoning_effort = "medium"',
|
|
105
|
+
"",
|
|
106
|
+
"[features]",
|
|
107
|
+
"plugins = true",
|
|
108
|
+
"",
|
|
109
|
+
].join("\n"),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const result = await runAutoUpdateCheck({
|
|
113
|
+
env: {
|
|
114
|
+
CODEX_HOME: codexHome,
|
|
115
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json"),
|
|
116
|
+
LAZYCODEX_AUTO_UPDATE_STATE_PATH: statePath,
|
|
117
|
+
},
|
|
118
|
+
now: 100_000,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const content = await readFile(join(codexHome, "config.toml"), "utf8");
|
|
122
|
+
assert.equal(result.started, false);
|
|
123
|
+
assert.equal(result.reason, "throttled");
|
|
124
|
+
assert.match(content, /model = "gpt-5\.5"/);
|
|
125
|
+
assert.match(content, /model_context_window = 400000/);
|
|
126
|
+
assert.match(content, /model_reasoning_effort = "high"/);
|
|
127
|
+
assert.match(content, /plan_mode_reasoning_effort = "xhigh"/);
|
|
128
|
+
assert.doesNotMatch(content, /gpt-5\.2/);
|
|
129
|
+
});
|