oh-my-codex 0.14.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +14 -8
  4. package/crates/omx-explore/src/main.rs +94 -1
  5. package/crates/omx-sparkshell/src/codex_bridge.rs +59 -12
  6. package/crates/omx-sparkshell/tests/execution.rs +48 -0
  7. package/dist/cli/__tests__/explore.test.js +33 -1
  8. package/dist/cli/__tests__/explore.test.js.map +1 -1
  9. package/dist/cli/__tests__/index.test.js +11 -2
  10. package/dist/cli/__tests__/index.test.js.map +1 -1
  11. package/dist/cli/__tests__/package-bin-contract.test.js +5 -0
  12. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  13. package/dist/cli/__tests__/question.test.js +78 -25
  14. package/dist/cli/__tests__/question.test.js.map +1 -1
  15. package/dist/cli/__tests__/setup-agents-overwrite.test.js +32 -7
  16. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  17. package/dist/cli/__tests__/setup-refresh.test.js +8 -6
  18. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  19. package/dist/cli/__tests__/sparkshell-cli.test.js +23 -0
  20. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  21. package/dist/cli/__tests__/uninstall.test.js +65 -5
  22. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  23. package/dist/cli/__tests__/update.test.js +360 -26
  24. package/dist/cli/__tests__/update.test.js.map +1 -1
  25. package/dist/cli/explore.d.ts.map +1 -1
  26. package/dist/cli/explore.js +18 -3
  27. package/dist/cli/explore.js.map +1 -1
  28. package/dist/cli/index.d.ts +2 -1
  29. package/dist/cli/index.d.ts.map +1 -1
  30. package/dist/cli/index.js +7 -2
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/cli/setup.d.ts.map +1 -1
  33. package/dist/cli/setup.js +25 -3
  34. package/dist/cli/setup.js.map +1 -1
  35. package/dist/cli/sparkshell.d.ts.map +1 -1
  36. package/dist/cli/sparkshell.js +11 -1
  37. package/dist/cli/sparkshell.js.map +1 -1
  38. package/dist/cli/team.d.ts.map +1 -1
  39. package/dist/cli/team.js +159 -394
  40. package/dist/cli/team.js.map +1 -1
  41. package/dist/cli/uninstall.d.ts.map +1 -1
  42. package/dist/cli/uninstall.js +3 -1
  43. package/dist/cli/uninstall.js.map +1 -1
  44. package/dist/cli/update.d.ts +37 -9
  45. package/dist/cli/update.d.ts.map +1 -1
  46. package/dist/cli/update.js +204 -26
  47. package/dist/cli/update.js.map +1 -1
  48. package/dist/config/__tests__/generator-idempotent.test.js +51 -14
  49. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  50. package/dist/config/__tests__/generator-notify.test.js +35 -10
  51. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  52. package/dist/config/generator.d.ts +1 -0
  53. package/dist/config/generator.d.ts.map +1 -1
  54. package/dist/config/generator.js +61 -7
  55. package/dist/config/generator.js.map +1 -1
  56. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts +2 -0
  57. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts.map +1 -0
  58. package/dist/hooks/__tests__/code-review-skill-contract.test.js +56 -0
  59. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -0
  60. package/dist/hooks/__tests__/deep-interview-contract.test.js +29 -0
  61. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  62. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts +2 -0
  63. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts.map +1 -0
  64. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js +43 -0
  65. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js.map +1 -0
  66. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts +2 -0
  67. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts.map +1 -0
  68. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js +38 -0
  69. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js.map +1 -0
  70. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
  71. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +16 -1
  72. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  73. package/dist/mcp/__tests__/bootstrap.test.js +5 -24
  74. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  75. package/dist/mcp/__tests__/state-server.test.js +126 -0
  76. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  77. package/dist/mcp/bootstrap.d.ts +1 -1
  78. package/dist/mcp/bootstrap.d.ts.map +1 -1
  79. package/dist/mcp/bootstrap.js +3 -11
  80. package/dist/mcp/bootstrap.js.map +1 -1
  81. package/dist/mcp/state-server.d.ts +17 -0
  82. package/dist/mcp/state-server.d.ts.map +1 -1
  83. package/dist/mcp/state-server.js +37 -0
  84. package/dist/mcp/state-server.js.map +1 -1
  85. package/dist/notifications/__tests__/index.test.js +0 -3
  86. package/dist/notifications/__tests__/index.test.js.map +1 -1
  87. package/dist/notifications/__tests__/session-status.test.js +90 -0
  88. package/dist/notifications/__tests__/session-status.test.js.map +1 -1
  89. package/dist/notifications/session-status.d.ts +2 -0
  90. package/dist/notifications/session-status.d.ts.map +1 -1
  91. package/dist/notifications/session-status.js +19 -4
  92. package/dist/notifications/session-status.js.map +1 -1
  93. package/dist/question/__tests__/deep-interview.test.js +10 -0
  94. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  95. package/dist/question/__tests__/renderer.test.js +157 -7
  96. package/dist/question/__tests__/renderer.test.js.map +1 -1
  97. package/dist/question/__tests__/state.test.js +21 -1
  98. package/dist/question/__tests__/state.test.js.map +1 -1
  99. package/dist/question/deep-interview.d.ts +3 -0
  100. package/dist/question/deep-interview.d.ts.map +1 -1
  101. package/dist/question/deep-interview.js +18 -1
  102. package/dist/question/deep-interview.js.map +1 -1
  103. package/dist/question/renderer.d.ts +3 -1
  104. package/dist/question/renderer.d.ts.map +1 -1
  105. package/dist/question/renderer.js +75 -13
  106. package/dist/question/renderer.js.map +1 -1
  107. package/dist/runtime/__tests__/run-outcome.test.js +38 -0
  108. package/dist/runtime/__tests__/run-outcome.test.js.map +1 -1
  109. package/dist/runtime/__tests__/run-state.test.d.ts +2 -0
  110. package/dist/runtime/__tests__/run-state.test.d.ts.map +1 -0
  111. package/dist/runtime/__tests__/run-state.test.js +37 -0
  112. package/dist/runtime/__tests__/run-state.test.js.map +1 -0
  113. package/dist/runtime/run-loop.d.ts +5 -1
  114. package/dist/runtime/run-loop.d.ts.map +1 -1
  115. package/dist/runtime/run-loop.js +8 -3
  116. package/dist/runtime/run-loop.js.map +1 -1
  117. package/dist/runtime/run-outcome.d.ts +18 -0
  118. package/dist/runtime/run-outcome.d.ts.map +1 -1
  119. package/dist/runtime/run-outcome.js +156 -7
  120. package/dist/runtime/run-outcome.js.map +1 -1
  121. package/dist/runtime/run-state.d.ts +5 -1
  122. package/dist/runtime/run-state.d.ts.map +1 -1
  123. package/dist/runtime/run-state.js +13 -3
  124. package/dist/runtime/run-state.js.map +1 -1
  125. package/dist/runtime/terminal-lifecycle.d.ts +11 -0
  126. package/dist/runtime/terminal-lifecycle.d.ts.map +1 -0
  127. package/dist/runtime/terminal-lifecycle.js +52 -0
  128. package/dist/runtime/terminal-lifecycle.js.map +1 -0
  129. package/dist/scripts/__tests__/codex-native-hook.test.js +346 -56
  130. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  131. package/dist/scripts/__tests__/postinstall.test.d.ts +2 -0
  132. package/dist/scripts/__tests__/postinstall.test.d.ts.map +1 -0
  133. package/dist/scripts/__tests__/postinstall.test.js +178 -0
  134. package/dist/scripts/__tests__/postinstall.test.js.map +1 -0
  135. package/dist/scripts/codex-native-hook.d.ts +1 -0
  136. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  137. package/dist/scripts/codex-native-hook.js +115 -56
  138. package/dist/scripts/codex-native-hook.js.map +1 -1
  139. package/dist/scripts/postinstall.d.ts +22 -0
  140. package/dist/scripts/postinstall.d.ts.map +1 -0
  141. package/dist/scripts/postinstall.js +105 -0
  142. package/dist/scripts/postinstall.js.map +1 -0
  143. package/dist/state/__tests__/operations.test.js +18 -0
  144. package/dist/state/__tests__/operations.test.js.map +1 -1
  145. package/dist/team/__tests__/role-router.test.js +6 -0
  146. package/dist/team/__tests__/role-router.test.js.map +1 -1
  147. package/dist/team/__tests__/runtime.test.js +108 -2
  148. package/dist/team/__tests__/runtime.test.js.map +1 -1
  149. package/dist/team/runtime.d.ts.map +1 -1
  150. package/dist/team/runtime.js +18 -4
  151. package/dist/team/runtime.js.map +1 -1
  152. package/dist/utils/__tests__/dep-versions.test.js +25 -8
  153. package/dist/utils/__tests__/dep-versions.test.js.map +1 -1
  154. package/dist/utils/__tests__/paths.test.js +45 -0
  155. package/dist/utils/__tests__/paths.test.js.map +1 -1
  156. package/dist/utils/paths.d.ts +2 -0
  157. package/dist/utils/paths.d.ts.map +1 -1
  158. package/dist/utils/paths.js +22 -7
  159. package/dist/utils/paths.js.map +1 -1
  160. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  161. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  162. package/package.json +2 -1
  163. package/prompts/architect.md +4 -0
  164. package/prompts/code-reviewer.md +3 -0
  165. package/skills/code-review/SKILL.md +94 -28
  166. package/skills/deep-interview/SKILL.md +90 -0
  167. package/src/scripts/__tests__/codex-native-hook.test.ts +438 -64
  168. package/src/scripts/__tests__/postinstall.test.ts +210 -0
  169. package/src/scripts/codex-native-hook.ts +136 -53
  170. package/src/scripts/postinstall-bootstrap.js +23 -0
  171. package/src/scripts/postinstall.ts +161 -0
  172. package/templates/model-instructions/explore-lightweight-AGENTS.md +11 -0
  173. package/templates/model-instructions/sparkshell-lightweight-AGENTS.md +10 -0
package/Cargo.lock CHANGED
@@ -32,11 +32,11 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
32
32
 
33
33
  [[package]]
34
34
  name = "omx-explore-harness"
35
- version = "0.14.0"
35
+ version = "0.14.1"
36
36
 
37
37
  [[package]]
38
38
  name = "omx-mux"
39
- version = "0.14.0"
39
+ version = "0.14.1"
40
40
  dependencies = [
41
41
  "serde",
42
42
  "serde_json",
@@ -44,7 +44,7 @@ dependencies = [
44
44
 
45
45
  [[package]]
46
46
  name = "omx-runtime"
47
- version = "0.14.0"
47
+ version = "0.14.1"
48
48
  dependencies = [
49
49
  "omx-mux",
50
50
  "omx-runtime-core",
@@ -53,7 +53,7 @@ dependencies = [
53
53
 
54
54
  [[package]]
55
55
  name = "omx-runtime-core"
56
- version = "0.14.0"
56
+ version = "0.14.1"
57
57
  dependencies = [
58
58
  "fs2",
59
59
  "serde",
@@ -62,7 +62,7 @@ dependencies = [
62
62
 
63
63
  [[package]]
64
64
  name = "omx-sparkshell"
65
- version = "0.14.0"
65
+ version = "0.14.1"
66
66
  dependencies = [
67
67
  "omx-mux",
68
68
  ]
package/Cargo.toml CHANGED
@@ -10,7 +10,7 @@ resolver = "2"
10
10
 
11
11
  [workspace.package]
12
12
 
13
- version = "0.14.0"
13
+ version = "0.14.1"
14
14
 
15
15
  edition = "2021"
16
16
  license = "MIT"
package/README.md CHANGED
@@ -12,7 +12,9 @@
12
12
  [![Discord](https://img.shields.io/discord/1452487457085063218?color=5865F2&logo=discord&logoColor=white&label=Discord)](https://discord.gg/PUwSMR9XNk)
13
13
 
14
14
  **Website:** https://yeachan-heo.github.io/oh-my-codex-website/
15
+
15
16
  **Docs:** [Getting Started](./docs/getting-started.html) · [Agents](./docs/agents.html) · [Skills](./docs/skills.html) · [Integrations](./docs/integrations.html) · [Demo](./DEMO.md) · [OpenClaw guide](./docs/openclaw-integration.md)
17
+
16
18
  **Community:** [Discord](https://discord.gg/PUwSMR9XNk) — shared OMX/community server for oh-my-codex and related tooling.
17
19
 
18
20
  OMX is a workflow layer for [OpenAI Codex CLI](https://github.com/openai/codex).
@@ -58,10 +60,11 @@ If you want the default OMX experience, start here:
58
60
 
59
61
  ```bash
60
62
  npm install -g @openai/codex oh-my-codex
61
- omx setup
62
63
  omx --madmax --high
63
64
  ```
64
65
 
66
+ On a real `oh-my-codex` version bump, the global npm install now launches the interactive `omx setup` refresh automatically when a TTY is available. If npm scripts are skipped or the install is non-interactive, run `omx setup` manually or use `omx update` to force the same refresh path later.
67
+
65
68
  Then work normally inside Codex:
66
69
 
67
70
  ```text
@@ -145,13 +148,14 @@ Most users should think of OMX as **better task routing + better workflow + bett
145
148
 
146
149
  ## Start here if you are new
147
150
 
148
- 1. Run `omx setup`
149
- 2. Run `omx doctor`
150
- 3. Run a real execution smoke test: `codex login status` and `omx exec --skip-git-repo-check -C . "Reply with exactly OMX-EXEC-OK"`
151
- 4. Launch with `omx --madmax --high`
152
- 5. Use `$deep-interview "..."` when the request or boundaries are still unclear
153
- 6. Use `$ralplan "..."` to approve the plan and review tradeoffs
154
- 7. Choose `$team` for coordinated parallel execution or `$ralph` for persistent completion loops
151
+ 1. Install or update OMX with `npm install -g @openai/codex oh-my-codex`
152
+ 2. Let the interactive `omx setup` refresh run automatically on real OMX version bumps, or run `omx setup` / `omx update` yourself when scripts are skipped or you want to rerun it
153
+ 3. Run `omx doctor`
154
+ 4. Run a real execution smoke test: `codex login status` and `omx exec --skip-git-repo-check -C . "Reply with exactly OMX-EXEC-OK"`
155
+ 5. Launch with `omx --madmax --high`
156
+ 6. Use `$deep-interview "..."` when the request or boundaries are still unclear
157
+ 7. Use `$ralplan "..."` to approve the plan and review tradeoffs
158
+ 8. Choose `$team` for coordinated parallel execution or `$ralph` for persistent completion loops
155
159
 
156
160
  ## Recommended workflow
157
161
 
@@ -190,6 +194,8 @@ These are operator/support surfaces:
190
194
  - `omx setup` installs prompts, skills, AGENTS scaffolding, `.codex/config.toml`, and OMX-managed native Codex hooks in `.codex/hooks.json`
191
195
  - setup refresh preserves non-OMX hook entries in `.codex/hooks.json` and only rewrites OMX-managed wrappers
192
196
  - `omx uninstall` removes OMX-managed wrappers from `.codex/hooks.json` but keeps the file when user hooks remain
197
+ - `omx update` checks npm immediately, installs the newest global OMX build, then reruns the same interactive setup refresh path
198
+ - fresh OMX-managed `gpt-5.4` config seeding now recommends `model_context_window = 250000` and `model_auto_compact_token_limit = 200000`, but only when those keys are missing
193
199
  - `omx doctor` verifies the install when something seems wrong; it does not prove that the active Codex profile can make an authenticated model call
194
200
  - `omx hud --watch` is a monitoring/status surface, not the primary user workflow
195
201
 
@@ -26,6 +26,7 @@ struct Args {
26
26
  cwd: PathBuf,
27
27
  prompt: String,
28
28
  prompt_file: PathBuf,
29
+ instructions_file: PathBuf,
29
30
  spark_model: String,
30
31
  fallback_model: String,
31
32
  }
@@ -155,6 +156,7 @@ where
155
156
  let mut cwd: Option<PathBuf> = None;
156
157
  let mut prompt: Option<String> = None;
157
158
  let mut prompt_file: Option<PathBuf> = None;
159
+ let mut instructions_file: Option<PathBuf> = None;
158
160
  let mut spark_model: Option<String> = None;
159
161
  let mut fallback_model: Option<String> = None;
160
162
 
@@ -166,6 +168,12 @@ where
166
168
  "--prompt-file" => {
167
169
  prompt_file = Some(PathBuf::from(next_required(&mut args, "--prompt-file")?))
168
170
  }
171
+ "--instructions-file" => {
172
+ instructions_file = Some(PathBuf::from(next_required(
173
+ &mut args,
174
+ "--instructions-file",
175
+ )?))
176
+ }
169
177
  "--model-spark" => spark_model = Some(next_required(&mut args, "--model-spark")?),
170
178
  "--model-fallback" => {
171
179
  fallback_model = Some(next_required(&mut args, "--model-fallback")?)
@@ -179,6 +187,8 @@ where
179
187
  cwd: cwd.ok_or_else(|| format!("missing --cwd\n{}", usage()))?,
180
188
  prompt: prompt.ok_or_else(|| format!("missing --prompt\n{}", usage()))?,
181
189
  prompt_file: prompt_file.ok_or_else(|| format!("missing --prompt-file\n{}", usage()))?,
190
+ instructions_file: instructions_file
191
+ .ok_or_else(|| format!("missing --instructions-file\n{}", usage()))?,
182
192
  spark_model: spark_model.ok_or_else(|| format!("missing --model-spark\n{}", usage()))?,
183
193
  fallback_model: fallback_model
184
194
  .ok_or_else(|| format!("missing --model-fallback\n{}", usage()))?,
@@ -198,7 +208,7 @@ where
198
208
  }
199
209
 
200
210
  fn usage() -> &'static str {
201
- "Usage: omx-explore --cwd <dir> --prompt <text> --prompt-file <explore-prompt.md> --model-spark <model> --model-fallback <model>"
211
+ "Usage: omx-explore --cwd <dir> --prompt <text> --prompt-file <explore-prompt.md> --instructions-file <AGENTS.md> --model-spark <model> --model-fallback <model>"
202
212
  }
203
213
 
204
214
  fn invoke_codex(args: &Args, model: &str, prompt_contract: &str) -> io::Result<AttemptResult> {
@@ -220,6 +230,11 @@ fn invoke_codex(args: &Args, model: &str, prompt_contract: &str) -> io::Result<A
220
230
  .arg("-c")
221
231
  .arg("model_reasoning_effort=\"low\"")
222
232
  .arg("-c")
233
+ .arg(format!(
234
+ "model_instructions_file=\"{}\"",
235
+ escape_toml_string(&args.instructions_file.display().to_string())
236
+ ))
237
+ .arg("-c")
223
238
  .arg("shell_environment_policy.inherit=all")
224
239
  .arg("--skip-git-repo-check")
225
240
  .arg("-o")
@@ -240,6 +255,10 @@ fn invoke_codex(args: &Args, model: &str, prompt_contract: &str) -> io::Result<A
240
255
  })
241
256
  }
242
257
 
258
+ fn escape_toml_string(value: &str) -> String {
259
+ value.replace('\\', "\\\\").replace('"', "\\\"")
260
+ }
261
+
243
262
  #[derive(Debug, Clone, PartialEq, Eq)]
244
263
  struct CodexLaunch {
245
264
  program: String,
@@ -982,6 +1001,8 @@ mod tests {
982
1001
  "find auth",
983
1002
  "--prompt-file",
984
1003
  "/tmp/explore.md",
1004
+ "--instructions-file",
1005
+ "/tmp/explore-agents.md",
985
1006
  "--model-spark",
986
1007
  "gpt-5.3-codex-spark",
987
1008
  "--model-fallback",
@@ -995,6 +1016,7 @@ mod tests {
995
1016
  assert_eq!(args.cwd, Path::new("/tmp/repo"));
996
1017
  assert_eq!(args.prompt, "find auth");
997
1018
  assert_eq!(args.prompt_file, Path::new("/tmp/explore.md"));
1019
+ assert_eq!(args.instructions_file, Path::new("/tmp/explore-agents.md"));
998
1020
  assert_eq!(args.spark_model, "gpt-5.3-codex-spark");
999
1021
  assert_eq!(args.fallback_model, "gpt-5.4");
1000
1022
  }
@@ -1618,6 +1640,8 @@ exit 17
1618
1640
  "find tests",
1619
1641
  "--prompt-file",
1620
1642
  prompt_file.to_str().expect("prompt path"),
1643
+ "--instructions-file",
1644
+ prompt_file.to_str().expect("instructions path"),
1621
1645
  "--model-spark",
1622
1646
  "spark-model",
1623
1647
  "--model-fallback",
@@ -1675,6 +1699,7 @@ printf '# Answer\nok\n' > "$output_path"
1675
1699
  cwd: repo.clone(),
1676
1700
  prompt: "find tests".to_string(),
1677
1701
  prompt_file,
1702
+ instructions_file: repo.join("AGENTS.md"),
1678
1703
  spark_model: "spark-model".to_string(),
1679
1704
  fallback_model: "fallback-model".to_string(),
1680
1705
  },
@@ -1696,6 +1721,74 @@ printf '# Answer\nok\n' > "$output_path"
1696
1721
  );
1697
1722
  }
1698
1723
 
1724
+ #[test]
1725
+ fn invoke_codex_injects_model_instructions_file_override() {
1726
+ let _guard = env_lock();
1727
+ let root = temp_allowlist_dir().expect("temp root");
1728
+ let repo = root.path.join("repo");
1729
+ create_dir_all(&repo).expect("create repo");
1730
+ let prompt_file = root.path.join("prompt.md");
1731
+ write(&prompt_file, "contract").expect("write prompt");
1732
+ let capture_path = root.path.join("argv.txt");
1733
+ let fake_codex = root.path.join("codex-stub");
1734
+ write_executable(
1735
+ &fake_codex,
1736
+ &format!(
1737
+ r#"#!/bin/sh
1738
+ set -eu
1739
+ output_path=""
1740
+ capture={}
1741
+ printf '' > "$capture"
1742
+ while [ "$#" -gt 0 ]; do
1743
+ printf '%s\n' "$1" >> "$capture"
1744
+ if [ "$1" = "-o" ]; then
1745
+ shift
1746
+ output_path="$1"
1747
+ fi
1748
+ shift
1749
+ done
1750
+ printf '# Answer\nok\n' > "$output_path"
1751
+ "#,
1752
+ shell_quote(&capture_path.display().to_string())
1753
+ ),
1754
+ )
1755
+ .expect("write fake codex");
1756
+
1757
+ unsafe {
1758
+ env::set_var(CODEX_BIN_ENV, &fake_codex);
1759
+ }
1760
+ let instructions_path = repo.join("custom instructions.md");
1761
+ let attempt = invoke_codex(
1762
+ &Args {
1763
+ cwd: repo.clone(),
1764
+ prompt: "find tests".to_string(),
1765
+ prompt_file,
1766
+ instructions_file: instructions_path.clone(),
1767
+ spark_model: "spark-model".to_string(),
1768
+ fallback_model: "fallback-model".to_string(),
1769
+ },
1770
+ "spark-model",
1771
+ "contract",
1772
+ )
1773
+ .expect("invoke codex");
1774
+ unsafe {
1775
+ env::remove_var(CODEX_BIN_ENV);
1776
+ }
1777
+
1778
+ assert_eq!(attempt.status_code, 0);
1779
+ let captured = read_to_string(&capture_path).expect("read capture");
1780
+ let expected = format!(
1781
+ "model_instructions_file=\"{}\"",
1782
+ escape_toml_string(&instructions_path.display().to_string())
1783
+ );
1784
+ assert!(
1785
+ captured.contains(&expected),
1786
+ "expected {:?} in {:?}",
1787
+ expected,
1788
+ captured
1789
+ );
1790
+ }
1791
+
1699
1792
  #[test]
1700
1793
  fn sanitize_explore_subprocess_env_blocks_bash_env_startup_hooks() {
1701
1794
  let _guard = env_lock();
@@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
9
9
 
10
10
  pub const DEFAULT_SUMMARY_TIMEOUT_MS: u64 = 60_000;
11
11
  pub const DEFAULT_SPARK_MODEL: &str = "gpt-5.3-codex-spark";
12
- pub const DEFAULT_FRONTIER_MODEL: &str = "gpt-5.4";
12
+ pub const DEFAULT_STANDARD_MODEL: &str = "gpt-5.4-mini";
13
13
 
14
14
  pub fn resolve_model() -> String {
15
15
  env::var("OMX_SPARKSHELL_MODEL")
@@ -33,11 +33,18 @@ pub fn resolve_fallback_model() -> String {
33
33
  .ok()
34
34
  .filter(|value| !value.trim().is_empty())
35
35
  .or_else(|| {
36
- env::var("OMX_DEFAULT_FRONTIER_MODEL")
36
+ env::var("OMX_DEFAULT_STANDARD_MODEL")
37
37
  .ok()
38
38
  .filter(|value| !value.trim().is_empty())
39
39
  })
40
- .unwrap_or_else(|| DEFAULT_FRONTIER_MODEL.to_string())
40
+ .unwrap_or_else(|| DEFAULT_STANDARD_MODEL.to_string())
41
+ }
42
+
43
+ pub fn resolve_instructions_file() -> Option<String> {
44
+ env::var("OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE")
45
+ .ok()
46
+ .map(|value| value.trim().to_string())
47
+ .filter(|value| !value.is_empty())
41
48
  }
42
49
 
43
50
  pub fn read_summary_timeout_ms() -> u64 {
@@ -125,6 +132,12 @@ fn run_codex_exec(
125
132
  .arg("read-only")
126
133
  .arg("-c")
127
134
  .arg("model_reasoning_effort=\"low\"")
135
+ .args(resolve_instructions_file().into_iter().flat_map(|path| {
136
+ [
137
+ "-c".to_string(),
138
+ format!("model_instructions_file=\"{}\"", escape_toml_string(&path)),
139
+ ]
140
+ }))
128
141
  .arg("--skip-git-repo-check")
129
142
  .arg("--color")
130
143
  .arg("never")
@@ -191,6 +204,10 @@ fn run_codex_exec(
191
204
  ))
192
205
  }
193
206
 
207
+ fn escape_toml_string(value: &str) -> String {
208
+ value.replace('\\', "\\\\").replace('"', "\\\"")
209
+ }
210
+
194
211
  fn normalize_summary(raw: &str) -> Option<String> {
195
212
  let mut summary = Vec::new();
196
213
  let mut failures = Vec::new();
@@ -271,8 +288,9 @@ fn render_section(name: &str, entries: &[String]) -> String {
271
288
  #[cfg(test)]
272
289
  mod tests {
273
290
  use super::{
274
- normalize_summary, read_summary_timeout_ms, resolve_fallback_model, resolve_model,
275
- DEFAULT_FRONTIER_MODEL, DEFAULT_SPARK_MODEL, DEFAULT_SUMMARY_TIMEOUT_MS,
291
+ normalize_summary, read_summary_timeout_ms, resolve_fallback_model,
292
+ resolve_instructions_file, resolve_model, DEFAULT_SPARK_MODEL, DEFAULT_STANDARD_MODEL,
293
+ DEFAULT_SUMMARY_TIMEOUT_MS,
276
294
  };
277
295
  use crate::test_support::env_lock;
278
296
  use std::env;
@@ -293,23 +311,23 @@ mod tests {
293
311
  }
294
312
 
295
313
  #[test]
296
- fn fallback_model_resolution_prefers_override_then_default_frontier() {
314
+ fn fallback_model_resolution_prefers_override_then_default_standard() {
297
315
  let _guard = env_lock();
298
316
  unsafe {
299
317
  env::remove_var("OMX_SPARKSHELL_FALLBACK_MODEL");
300
- env::remove_var("OMX_DEFAULT_FRONTIER_MODEL");
318
+ env::remove_var("OMX_DEFAULT_STANDARD_MODEL");
301
319
  }
302
- assert_eq!(resolve_fallback_model(), DEFAULT_FRONTIER_MODEL);
320
+ assert_eq!(resolve_fallback_model(), DEFAULT_STANDARD_MODEL);
303
321
 
304
322
  unsafe {
305
- env::set_var("OMX_DEFAULT_FRONTIER_MODEL", "frontier-a");
323
+ env::set_var("OMX_DEFAULT_STANDARD_MODEL", "standard-a");
306
324
  }
307
- assert_eq!(resolve_fallback_model(), "frontier-a");
325
+ assert_eq!(resolve_fallback_model(), "standard-a");
308
326
 
309
327
  unsafe {
310
- env::set_var("OMX_SPARKSHELL_FALLBACK_MODEL", "frontier-b");
328
+ env::set_var("OMX_SPARKSHELL_FALLBACK_MODEL", "standard-b");
311
329
  }
312
- assert_eq!(resolve_fallback_model(), "frontier-b");
330
+ assert_eq!(resolve_fallback_model(), "standard-b");
313
331
  }
314
332
 
315
333
  #[test]
@@ -347,6 +365,35 @@ mod tests {
347
365
  }
348
366
  }
349
367
 
368
+ #[test]
369
+ fn instructions_file_resolution_prefers_override_and_ignores_blank() {
370
+ let _guard = env_lock();
371
+ unsafe {
372
+ env::remove_var("OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE");
373
+ }
374
+ assert_eq!(resolve_instructions_file(), None);
375
+
376
+ unsafe {
377
+ env::set_var(
378
+ "OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE",
379
+ " /tmp/sparkshell-agents.md ",
380
+ );
381
+ }
382
+ assert_eq!(
383
+ resolve_instructions_file(),
384
+ Some("/tmp/sparkshell-agents.md".to_string())
385
+ );
386
+
387
+ unsafe {
388
+ env::set_var("OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE", " ");
389
+ }
390
+ assert_eq!(resolve_instructions_file(), None);
391
+
392
+ unsafe {
393
+ env::remove_var("OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE");
394
+ }
395
+ }
396
+
350
397
  #[test]
351
398
  fn timeout_defaults_for_zero_and_invalid_values() {
352
399
  let _guard = env_lock();
@@ -97,6 +97,54 @@ fn summary_mode_uses_codex_exec_and_model_override() {
97
97
  let _ = fs::remove_dir_all(temp);
98
98
  }
99
99
 
100
+ #[test]
101
+ fn summary_mode_injects_model_instructions_file_override() {
102
+ let temp = unique_temp_dir("codex-instructions-file");
103
+ let codex = temp.join("codex");
104
+ let args_log = temp.join("args.log");
105
+ let instructions_file = temp.join("sparkshell-lightweight-AGENTS.md");
106
+ write_executable(
107
+ &codex,
108
+ &format!(
109
+ "#!/bin/sh\nprintf '%s\n' \"$@\" > '{}'\nprintf '%s\n' '- summary: command produced long output'\n",
110
+ args_log.display()
111
+ ),
112
+ );
113
+ fs::write(&instructions_file, "# sparkshell instructions\n").expect("write instructions file");
114
+
115
+ let path = format!(
116
+ "{}:{}",
117
+ temp.display(),
118
+ env::var("PATH").unwrap_or_default()
119
+ );
120
+ let output = Command::new(sparkshell_bin())
121
+ .env("PATH", path)
122
+ .env("OMX_SPARKSHELL_LINES", "1")
123
+ .env(
124
+ "OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE",
125
+ instructions_file.display().to_string(),
126
+ )
127
+ .arg("sh")
128
+ .arg("-c")
129
+ .arg("printf 'one\ntwo\n'")
130
+ .output()
131
+ .expect("run sparkshell");
132
+
133
+ assert!(output.status.success());
134
+ let args = fs::read_to_string(args_log).expect("args log");
135
+ assert!(args.contains("model_reasoning_effort=\"low\""));
136
+ assert!(args.contains(&format!(
137
+ "model_instructions_file=\"{}\"",
138
+ instructions_file
139
+ .display()
140
+ .to_string()
141
+ .replace('\\', "\\\\")
142
+ .replace('"', "\\\"")
143
+ )));
144
+
145
+ let _ = fs::remove_dir_all(temp);
146
+ }
147
+
100
148
  #[test]
101
149
  fn summary_failure_falls_back_to_raw_output_with_notice() {
102
150
  let temp = unique_temp_dir("codex-fail");
@@ -578,12 +578,43 @@ describe('buildExploreHarnessArgs', () => {
578
578
  assert.deepEqual(args.slice(4), [
579
579
  '--prompt-file',
580
580
  '/pkg/prompts/explore-harness.md',
581
+ '--instructions-file',
582
+ '/pkg/templates/model-instructions/explore-lightweight-AGENTS.md',
581
583
  '--model-spark',
582
584
  'spark-model',
583
585
  '--model-fallback',
584
- 'gpt-5.4',
586
+ 'gpt-5.4-mini',
585
587
  ]);
586
588
  });
589
+ it('honors configured env overrides for fallback model and instructions file', async () => {
590
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-explore-config-env-'));
591
+ await writeFile(join(codexHome, '.omx-config.json'), JSON.stringify({
592
+ env: {
593
+ OMX_DEFAULT_STANDARD_MODEL: 'standard-local',
594
+ OMX_DEFAULT_SPARK_MODEL: 'spark-local',
595
+ OMX_EXPLORE_MODEL_INSTRUCTIONS_FILE: '/config/explore-instructions.md',
596
+ },
597
+ }));
598
+ try {
599
+ const wd = join(tmpdir(), 'omx-explore-arg-test');
600
+ const args = buildExploreHarnessArgs('find auth', wd, {
601
+ CODEX_HOME: codexHome,
602
+ }, '/pkg');
603
+ assert.deepEqual(args.slice(4), [
604
+ '--prompt-file',
605
+ '/pkg/prompts/explore-harness.md',
606
+ '--instructions-file',
607
+ '/config/explore-instructions.md',
608
+ '--model-spark',
609
+ 'spark-local',
610
+ '--model-fallback',
611
+ 'standard-local',
612
+ ]);
613
+ }
614
+ finally {
615
+ await rm(codexHome, { recursive: true, force: true });
616
+ }
617
+ });
587
618
  });
588
619
  describe('resolveExploreSparkShellRoute', () => {
589
620
  it('keeps natural-language exploration prompts on the direct harness path', () => {
@@ -764,6 +795,7 @@ describe('exploreCommand', () => {
764
795
  assert.match(captured, /ALLOWED_STATUS=0/);
765
796
  assert.match(captured, /BLOCKED_STATUS=(?!0)\d+/);
766
797
  assert.match(captured, /--ARGV--[\s\S]*\nexec\n/);
798
+ assert.match(captured, /model_instructions_file=.*explore-lightweight-AGENTS\.md/);
767
799
  assert.match(captured, /--ALLOWED_STDOUT--[\s\S]*ripgrep/i);
768
800
  assert.match(captured, /--BLOCKED_STDERR--[\s\S]*not on the omx explore allowlist/);
769
801
  });