oh-my-codex 0.21.3 → 0.21.5

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 (291) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/LICENSE +21 -0
  4. package/README.md +6 -1
  5. package/crates/omx-runtime/src/lease_mutex.rs +379 -0
  6. package/crates/omx-runtime/src/main.rs +4 -0
  7. package/crates/omx-runtime/tests/execution.rs +152 -0
  8. package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
  9. package/crates/omx-sparkshell/tests/execution.rs +49 -36
  10. package/dist/agents/__tests__/definitions.test.js +5 -3
  11. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  12. package/dist/agents/__tests__/native-config.test.js +68 -11
  13. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  14. package/dist/agents/definitions.d.ts +1 -1
  15. package/dist/agents/definitions.d.ts.map +1 -1
  16. package/dist/agents/definitions.js +3 -3
  17. package/dist/agents/definitions.js.map +1 -1
  18. package/dist/agents/native-config.d.ts +1 -1
  19. package/dist/agents/native-config.d.ts.map +1 -1
  20. package/dist/agents/native-config.js +1 -1
  21. package/dist/agents/native-config.js.map +1 -1
  22. package/dist/autopilot/__tests__/planner-routing.test.js +12 -2
  23. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -1
  25. package/dist/autopilot/planner-routing.js +2 -2
  26. package/dist/autopilot/planner-routing.js.map +1 -1
  27. package/dist/cli/__tests__/agents.test.js +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/auth.test.js +1 -1
  30. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +1 -1
  31. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -1
  32. package/dist/cli/__tests__/doctor-spark-routing.test.js +1 -1
  33. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  34. package/dist/cli/__tests__/doctor-state-root-binding.test.js +36 -0
  35. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -1
  36. package/dist/cli/__tests__/doctor-warning-copy.test.js +100 -4
  37. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  38. package/dist/cli/__tests__/index.test.js +87 -7
  39. package/dist/cli/__tests__/index.test.js.map +1 -1
  40. package/dist/cli/__tests__/package-bin-contract.test.js +1 -1
  41. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  42. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js +17 -13
  43. package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js.map +1 -1
  44. package/dist/cli/__tests__/setup-agents-overwrite.test.js +11 -9
  45. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  46. package/dist/cli/__tests__/setup-install-mode.test.js +134 -0
  47. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  48. package/dist/cli/__tests__/setup-reasoning-preserve.test.d.ts +2 -0
  49. package/dist/cli/__tests__/setup-reasoning-preserve.test.d.ts.map +1 -0
  50. package/dist/cli/__tests__/setup-reasoning-preserve.test.js +198 -0
  51. package/dist/cli/__tests__/setup-reasoning-preserve.test.js.map +1 -0
  52. package/dist/cli/__tests__/setup-refresh.test.js +7 -6
  53. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  54. package/dist/cli/__tests__/team.test.js +67 -1
  55. package/dist/cli/__tests__/team.test.js.map +1 -1
  56. package/dist/cli/__tests__/update.test.js +47 -0
  57. package/dist/cli/__tests__/update.test.js.map +1 -1
  58. package/dist/cli/agents.d.ts.map +1 -1
  59. package/dist/cli/agents.js +2 -1
  60. package/dist/cli/agents.js.map +1 -1
  61. package/dist/cli/auth.d.ts.map +1 -1
  62. package/dist/cli/auth.js +2 -1
  63. package/dist/cli/auth.js.map +1 -1
  64. package/dist/cli/codex-feature-probe.d.ts +2 -1
  65. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  66. package/dist/cli/codex-feature-probe.js +4 -2
  67. package/dist/cli/codex-feature-probe.js.map +1 -1
  68. package/dist/cli/codex-home.d.ts +14 -0
  69. package/dist/cli/codex-home.d.ts.map +1 -1
  70. package/dist/cli/codex-home.js +21 -0
  71. package/dist/cli/codex-home.js.map +1 -1
  72. package/dist/cli/doctor.d.ts +2 -0
  73. package/dist/cli/doctor.d.ts.map +1 -1
  74. package/dist/cli/doctor.js +122 -9
  75. package/dist/cli/doctor.js.map +1 -1
  76. package/dist/cli/index.d.ts +1 -1
  77. package/dist/cli/index.d.ts.map +1 -1
  78. package/dist/cli/index.js +28 -7
  79. package/dist/cli/index.js.map +1 -1
  80. package/dist/cli/launch-credential-provenance.d.ts +49 -0
  81. package/dist/cli/launch-credential-provenance.d.ts.map +1 -0
  82. package/dist/cli/launch-credential-provenance.js +163 -0
  83. package/dist/cli/launch-credential-provenance.js.map +1 -0
  84. package/dist/cli/setup.d.ts.map +1 -1
  85. package/dist/cli/setup.js +15 -4
  86. package/dist/cli/setup.js.map +1 -1
  87. package/dist/cli/team.d.ts.map +1 -1
  88. package/dist/cli/team.js +23 -7
  89. package/dist/cli/team.js.map +1 -1
  90. package/dist/cli/uninstall.js +1 -1
  91. package/dist/cli/uninstall.js.map +1 -1
  92. package/dist/cli/update.d.ts.map +1 -1
  93. package/dist/cli/update.js +7 -0
  94. package/dist/cli/update.js.map +1 -1
  95. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  96. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  97. package/dist/config/__tests__/codex-feature-flags.test.js +10 -1
  98. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -1
  99. package/dist/config/__tests__/generator-notify.test.js +13 -3
  100. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  101. package/dist/config/__tests__/models.test.js +9 -3
  102. package/dist/config/__tests__/models.test.js.map +1 -1
  103. package/dist/config/codex-feature-flags.d.ts +4 -0
  104. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  105. package/dist/config/codex-feature-flags.js +17 -1
  106. package/dist/config/codex-feature-flags.js.map +1 -1
  107. package/dist/config/generator.d.ts +9 -2
  108. package/dist/config/generator.d.ts.map +1 -1
  109. package/dist/config/generator.js +23 -9
  110. package/dist/config/generator.js.map +1 -1
  111. package/dist/config/models.d.ts +6 -6
  112. package/dist/config/models.d.ts.map +1 -1
  113. package/dist/config/models.js +5 -5
  114. package/dist/config/models.js.map +1 -1
  115. package/dist/hooks/__tests__/anti-slop-workflow.test.js +2 -2
  116. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  117. package/dist/hooks/__tests__/code-review-skill-contract.test.js +3 -3
  118. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  119. package/dist/hooks/__tests__/deep-interview-contract.test.js +6 -7
  120. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  121. package/dist/hooks/__tests__/keyword-detector.test.js +2 -2
  122. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +16 -33
  123. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  124. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +9 -29
  125. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  126. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +9 -29
  127. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  128. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +10 -7
  129. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  130. package/dist/hooks/__tests__/session.test.js +41 -6
  131. package/dist/hooks/__tests__/session.test.js.map +1 -1
  132. package/dist/hooks/__tests__/sunset-routing-contract.test.js +23 -0
  133. package/dist/hooks/__tests__/sunset-routing-contract.test.js.map +1 -1
  134. package/dist/hooks/__tests__/visual-ralph-skill.test.js +3 -3
  135. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  136. package/dist/hooks/session.d.ts +1 -1
  137. package/dist/hooks/session.d.ts.map +1 -1
  138. package/dist/hooks/session.js +85 -29
  139. package/dist/hooks/session.js.map +1 -1
  140. package/dist/hud/__tests__/index.test.js +130 -2
  141. package/dist/hud/__tests__/index.test.js.map +1 -1
  142. package/dist/hud/__tests__/reconcile.test.js +76 -0
  143. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  144. package/dist/hud/__tests__/render.test.js +155 -4
  145. package/dist/hud/__tests__/render.test.js.map +1 -1
  146. package/dist/hud/__tests__/roster-height-realtmux.test.d.ts +2 -0
  147. package/dist/hud/__tests__/roster-height-realtmux.test.d.ts.map +1 -0
  148. package/dist/hud/__tests__/roster-height-realtmux.test.js +70 -0
  149. package/dist/hud/__tests__/roster-height-realtmux.test.js.map +1 -0
  150. package/dist/hud/__tests__/state.test.js +92 -1
  151. package/dist/hud/__tests__/state.test.js.map +1 -1
  152. package/dist/hud/__tests__/tmux.test.js +24 -1
  153. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  154. package/dist/hud/index.d.ts +3 -0
  155. package/dist/hud/index.d.ts.map +1 -1
  156. package/dist/hud/index.js +40 -18
  157. package/dist/hud/index.js.map +1 -1
  158. package/dist/hud/reconcile.d.ts +1 -0
  159. package/dist/hud/reconcile.d.ts.map +1 -1
  160. package/dist/hud/reconcile.js +9 -3
  161. package/dist/hud/reconcile.js.map +1 -1
  162. package/dist/hud/render.d.ts +1 -1
  163. package/dist/hud/render.d.ts.map +1 -1
  164. package/dist/hud/render.js +81 -7
  165. package/dist/hud/render.js.map +1 -1
  166. package/dist/hud/state.d.ts.map +1 -1
  167. package/dist/hud/state.js +38 -3
  168. package/dist/hud/state.js.map +1 -1
  169. package/dist/hud/tmux.d.ts +4 -0
  170. package/dist/hud/tmux.d.ts.map +1 -1
  171. package/dist/hud/tmux.js +66 -12
  172. package/dist/hud/tmux.js.map +1 -1
  173. package/dist/hud/types.d.ts +9 -0
  174. package/dist/hud/types.d.ts.map +1 -1
  175. package/dist/hud/types.js.map +1 -1
  176. package/dist/leader/__tests__/contract.test.js +9 -2
  177. package/dist/leader/__tests__/contract.test.js.map +1 -1
  178. package/dist/leader/contract.d.ts +2 -2
  179. package/dist/leader/contract.d.ts.map +1 -1
  180. package/dist/leader/contract.js +2 -2
  181. package/dist/leader/contract.js.map +1 -1
  182. package/dist/ralplan/__tests__/advisory-activation.test.js +3 -3
  183. package/dist/ralplan/__tests__/advisory-activation.test.js.map +1 -1
  184. package/dist/runtime/__tests__/bridge.test.js +91 -23
  185. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  186. package/dist/runtime/bridge.d.ts.map +1 -1
  187. package/dist/runtime/bridge.js +7 -1
  188. package/dist/runtime/bridge.js.map +1 -1
  189. package/dist/scripts/__tests__/codex-native-hook.test.js +10 -5
  190. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  191. package/dist/scripts/__tests__/prompt-inventory.test.js +83 -2
  192. package/dist/scripts/__tests__/prompt-inventory.test.js.map +1 -1
  193. package/dist/scripts/__tests__/smoke-packed-install.test.js +75 -23
  194. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  195. package/dist/scripts/prompt-inventory.d.ts +17 -0
  196. package/dist/scripts/prompt-inventory.d.ts.map +1 -1
  197. package/dist/scripts/prompt-inventory.js +64 -11
  198. package/dist/scripts/prompt-inventory.js.map +1 -1
  199. package/dist/scripts/run-test-files.js +1 -0
  200. package/dist/scripts/run-test-files.js.map +1 -1
  201. package/dist/scripts/smoke-packed-install.d.ts +1 -1
  202. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  203. package/dist/scripts/smoke-packed-install.js +46 -17
  204. package/dist/scripts/smoke-packed-install.js.map +1 -1
  205. package/dist/state/__tests__/mode-binding-lease.test.js +58 -2
  206. package/dist/state/__tests__/mode-binding-lease.test.js.map +1 -1
  207. package/dist/state/mode-binding-lease-helper.js +9 -41
  208. package/dist/state/mode-binding-lease-helper.js.map +1 -1
  209. package/dist/state/mode-binding-lease.d.ts.map +1 -1
  210. package/dist/state/mode-binding-lease.js +32 -10
  211. package/dist/state/mode-binding-lease.js.map +1 -1
  212. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  213. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  214. package/dist/team/__tests__/model-contract.test.js +10 -10
  215. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  216. package/dist/team/__tests__/runtime.test.js +28 -15
  217. package/dist/team/__tests__/runtime.test.js.map +1 -1
  218. package/dist/team/__tests__/scaling.test.js +5 -1
  219. package/dist/team/__tests__/scaling.test.js.map +1 -1
  220. package/dist/team/__tests__/tmux-session.test.js +60 -0
  221. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  222. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +2 -2
  223. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -1
  224. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  225. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  226. package/dist/team/model-contract.d.ts +1 -1
  227. package/dist/team/model-contract.d.ts.map +1 -1
  228. package/dist/team/runtime-cli.js +4 -2
  229. package/dist/team/runtime-cli.js.map +1 -1
  230. package/dist/team/runtime.d.ts +11 -0
  231. package/dist/team/runtime.d.ts.map +1 -1
  232. package/dist/team/runtime.js +24 -3
  233. package/dist/team/runtime.js.map +1 -1
  234. package/dist/team/scaling.d.ts.map +1 -1
  235. package/dist/team/scaling.js +11 -5
  236. package/dist/team/scaling.js.map +1 -1
  237. package/dist/team/state.d.ts +1 -1
  238. package/dist/team/state.d.ts.map +1 -1
  239. package/dist/team/state.js +5 -3
  240. package/dist/team/state.js.map +1 -1
  241. package/dist/team/team-identity.d.ts.map +1 -1
  242. package/dist/team/team-identity.js +8 -14
  243. package/dist/team/team-identity.js.map +1 -1
  244. package/dist/team/tmux-session.d.ts.map +1 -1
  245. package/dist/team/tmux-session.js +17 -8
  246. package/dist/team/tmux-session.js.map +1 -1
  247. package/dist/ultragoal/__tests__/artifacts.test.js +43 -0
  248. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  249. package/dist/ultragoal/artifacts.d.ts +1 -0
  250. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  251. package/dist/ultragoal/artifacts.js +9 -5
  252. package/dist/ultragoal/artifacts.js.map +1 -1
  253. package/dist/utils/__tests__/agents-model-table.test.js +13 -10
  254. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  255. package/dist/utils/__tests__/platform-command.test.js +10 -0
  256. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  257. package/dist/utils/platform-command.d.ts.map +1 -1
  258. package/dist/utils/platform-command.js +10 -4
  259. package/dist/utils/platform-command.js.map +1 -1
  260. package/dist/utils/toml.d.ts.map +1 -1
  261. package/dist/utils/toml.js +4 -3
  262. package/dist/utils/toml.js.map +1 -1
  263. package/dist/verification/__tests__/ci-rust-gates.test.js +11 -0
  264. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  265. package/package.json +2 -2
  266. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  267. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +2 -3
  268. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
  269. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  270. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +13 -22
  271. package/plugins/oh-my-codex/skills/design/SKILL.md +1 -1
  272. package/plugins/oh-my-codex/skills/hud/SKILL.md +1 -1
  273. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  274. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +9 -74
  275. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +7 -7
  276. package/skills/ai-slop-cleaner/SKILL.md +2 -3
  277. package/skills/best-practice-research/SKILL.md +1 -1
  278. package/skills/code-review/SKILL.md +1 -1
  279. package/skills/deep-interview/SKILL.md +13 -22
  280. package/skills/design/SKILL.md +1 -1
  281. package/skills/hud/SKILL.md +1 -1
  282. package/skills/plan/SKILL.md +1 -1
  283. package/skills/ralplan/SKILL.md +9 -74
  284. package/skills/visual-ralph/SKILL.md +7 -7
  285. package/src/scripts/__tests__/codex-native-hook.test.ts +9 -5
  286. package/src/scripts/__tests__/prompt-inventory.test.ts +84 -1
  287. package/src/scripts/__tests__/smoke-packed-install.test.ts +75 -23
  288. package/src/scripts/prompt-inventory.ts +71 -12
  289. package/src/scripts/run-test-files.ts +1 -0
  290. package/src/scripts/smoke-packed-install.ts +56 -17
  291. package/templates/AGENTS.md +4 -7
package/Cargo.lock CHANGED
@@ -32,7 +32,7 @@ checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
32
32
 
33
33
  [[package]]
34
34
  name = "omx-api"
35
- version = "0.20.5"
35
+ version = "0.21.5"
36
36
  dependencies = [
37
37
  "serde",
38
38
  "serde_json",
@@ -40,14 +40,14 @@ dependencies = [
40
40
 
41
41
  [[package]]
42
42
  name = "omx-explore-harness"
43
- version = "0.20.5"
43
+ version = "0.21.5"
44
44
  dependencies = [
45
45
  "libc",
46
46
  ]
47
47
 
48
48
  [[package]]
49
49
  name = "omx-mux"
50
- version = "0.20.5"
50
+ version = "0.21.5"
51
51
  dependencies = [
52
52
  "serde",
53
53
  "serde_json",
@@ -55,7 +55,7 @@ dependencies = [
55
55
 
56
56
  [[package]]
57
57
  name = "omx-runtime"
58
- version = "0.20.5"
58
+ version = "0.21.5"
59
59
  dependencies = [
60
60
  "fs2",
61
61
  "libc",
@@ -67,7 +67,7 @@ dependencies = [
67
67
 
68
68
  [[package]]
69
69
  name = "omx-runtime-core"
70
- version = "0.20.5"
70
+ version = "0.21.5"
71
71
  dependencies = [
72
72
  "fs2",
73
73
  "serde",
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "omx-sparkshell"
79
- version = "0.20.5"
79
+ version = "0.21.5"
80
80
  dependencies = [
81
81
  "omx-mux",
82
82
  ]
package/Cargo.toml CHANGED
@@ -11,7 +11,7 @@ resolver = "2"
11
11
 
12
12
  [workspace.package]
13
13
 
14
- version = "0.21.3"
14
+ version = "0.21.5"
15
15
 
16
16
  edition = "2021"
17
17
  rust-version = "1.73"
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yeachan-Heo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -375,12 +375,16 @@ These are operator/support surfaces:
375
375
  - `omx uninstall` removes OMX-managed wrappers from `.codex/hooks.json` but keeps the file when user hooks remain
376
376
  - `omx update` checks npm immediately, installs the newest global OMX build, then reruns the same interactive setup refresh path
377
377
  - launch-time update checks are throttled and prompt by default; use `OMX_AUTO_UPDATE=0` to disable them or `OMX_AUTO_UPDATE=defer` to schedule deferred updates without a prompt
378
- - fresh OMX-managed `gpt-5.6-sol` config seeding now recommends `model_context_window = 250000` and `model_auto_compact_token_limit = 200000`, but only when those keys are missing
378
+ - fresh OMX setup defaults to `gpt-6-astra` across frontier, standard, and spark/fast agents, with the same role reasoning defaults; existing user model configuration and explicit overrides are preserved
379
379
  - `.omx-config.json` model/env routing is documented in [the model/env routing reference](./docs/reference/omx-config-schema-routing.md); only edit keys supported by your installed OMX version
380
380
  - `omx doctor` verifies the install when something seems wrong; it does not prove that the active Codex profile can make an authenticated model call
381
381
  - `omx hud --watch` is a monitoring/status surface, not the primary user workflow
382
+ - Active session teams show their name and worker count beside the OMX version (for example, `[OMX#0.21.4] team:checkout (3 workers) | repo/branch`) in every HUD preset, before long repository labels. When ultragoal is active, the combined ultragoal/team summary also takes priority over repository labels and other modes. Team discovery remains session-scoped; this does not show teams from other sessions or change when team startup publishes active state.
383
+ - Below the summary, each team agent gets a live-refreshing row with its name, last reported state (`working`, `idle`, `blocked`, `done`, `failed`, `draining`, or `unknown`), current task ID, role, tmux pane ID, and update age when available. Columns stay aligned across different name lengths and missing fields; `working` and `done` are green, while `blocked` and `failed` are yellow. The HUD grows to fit the roster and shrinks after the team stops. Missing or malformed status files display `unknown`; a reported `working` state is not a process-liveness check. Membership and status are read without modifying team files.
382
384
  - GitGuardex finish progress is opt-in. Add `"guardex": { "enabled": true }` to the project-local `.omx/hud-config.json` to show `gx:<step>/<total> <phase>`; running review/autofix phases animate in the HUD. OMX does not read Guardex state when this flag is absent or false.
383
385
 
386
+ Tmux roster height is bounded by live window and leader/HUD geometry, reserving at least half of their shared rows for the leader. Workers that do not fit are represented by a `+N workers` overflow row. The watch loop remeasures after resizing; unknown geometry keeps the HUD compact rather than expanding it unchecked.
387
+
384
388
  For non-team sessions, native Codex hooks are now the canonical lifecycle surface:
385
389
  - `plugins/oh-my-codex/hooks/hooks.json` = official plugin-scoped hook registrations for plugin installs
386
390
  - `.codex/hooks.json` = legacy/fallback native Codex hook registrations preserved for legacy installs and older Codex versions
@@ -413,6 +417,7 @@ If `Shift+Enter` still submits instead of inserting a newline inside an OMX-mana
413
417
 
414
418
  - `omx sparkshell <command>` is for shell-native inspection and bounded verification
415
419
  - for read-only repository lookups, use normal Codex repository inspection tools/subagents (the deprecated `omx explore` command has been removed)
420
+ - primary and fallback Sparkshell summary models both default to `gpt-6-astra`; set `OMX_SPARKSHELL_FALLBACK_MODEL` to a different model to enable a distinct fallback
416
421
  - sparkshell env overrides are intentionally narrow: `OMX_SPARKSHELL_BIN` selects a native sidecar path, `OMX_SPARKSHELL_MODEL` selects the primary summary model, `OMX_SPARKSHELL_FALLBACK_MODEL` selects the retry model, `OMX_SPARKSHELL_MODEL_INSTRUCTIONS_FILE` selects summary instructions, and `OMX_SPARKSHELL_SUMMARY_TIMEOUT_MS` controls the local API summary timeout
417
422
 
418
423
  Examples:
@@ -0,0 +1,379 @@
1
+ use fs2::FileExt;
2
+ use serde_json::Value;
3
+ use std::io::{self, BufRead, Write};
4
+ #[cfg(unix)]
5
+ use std::os::unix::ffi::OsStrExt;
6
+ use std::path::{Path, PathBuf};
7
+ use std::time::{Duration, Instant};
8
+
9
+ const LOCK_TIMEOUT: Duration = Duration::from_secs(34);
10
+ const SAFE_LEAF_SUFFIX: &str = ".mutex";
11
+
12
+ pub fn run(args: &[String]) -> Result<(), String> {
13
+ if args.len() != 4 {
14
+ return Err("lease-mutex requires exactly <absolute-canonical-dir> <expected-dev> <expected-ino> <safe-leaf>".to_string());
15
+ }
16
+ let leaf = validate_safe_leaf(&args[3])?;
17
+ #[cfg(unix)]
18
+ {
19
+ let dir = PathBuf::from(&args[0]);
20
+ if !dir.is_absolute() {
21
+ return emit_startup_error("lease-mutex directory must be absolute".to_string());
22
+ }
23
+ let expected_dev = match parse_identity(&args[1], "expected-dev") {
24
+ Ok(value) => value,
25
+ Err(error) => return emit_startup_error(error),
26
+ };
27
+ let expected_ino = match parse_identity(&args[2], "expected-ino") {
28
+ Ok(value) => value,
29
+ Err(error) => return emit_startup_error(error),
30
+ };
31
+ let lease = match UnixLease::open(&dir, expected_dev, expected_ino, leaf) {
32
+ Ok(lease) => lease,
33
+ Err(error) => return emit_startup_error(error),
34
+ };
35
+ run_protocol(lease)
36
+ }
37
+ #[cfg(not(unix))]
38
+ {
39
+ let _ = args;
40
+ Err("lease-mutex unsupported on this platform; native no-follow identity semantics are unavailable".to_string())
41
+ }
42
+ }
43
+
44
+ fn validate_safe_leaf(raw: &str) -> Result<String, String> {
45
+ let valid_hex = raw.strip_suffix(SAFE_LEAF_SUFFIX).is_some_and(|key| {
46
+ key.len() == 64
47
+ && key
48
+ .bytes()
49
+ .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
50
+ });
51
+ if !valid_hex {
52
+ return Err("lease-mutex safe-leaf must match ^[a-f0-9]{64}\\.mutex$".to_string());
53
+ }
54
+ Ok(raw.to_string())
55
+ }
56
+
57
+ fn parse_identity(raw: &str, name: &str) -> Result<u64, String> {
58
+ raw.parse::<u64>()
59
+ .map_err(|_| format!("{name} must be a non-negative decimal integer"))
60
+ }
61
+
62
+ fn run_protocol<L: Lease>(mut lease: L) -> Result<(), String> {
63
+ let mut stdout = io::BufWriter::new(io::stdout().lock());
64
+ write_json(
65
+ &mut stdout,
66
+ serde_json::json!({"id":0,"ok":true,"ready":true}),
67
+ )?;
68
+ let stdin = io::stdin();
69
+ for line in stdin.lock().lines() {
70
+ let line = line.map_err(|e| format!("stdin read failed: {e}"))?;
71
+ if line.trim().is_empty() {
72
+ continue;
73
+ }
74
+ let request: Value = match serde_json::from_str(&line) {
75
+ Ok(value) => value,
76
+ Err(error) => {
77
+ write_json(
78
+ &mut stdout,
79
+ serde_json::json!({"id":null,"ok":false,"error":format!("invalid JSON request: {error}")}),
80
+ )?;
81
+ continue;
82
+ }
83
+ };
84
+ let id = request.get("id").cloned().unwrap_or(Value::Null);
85
+ let op = request.get("op").and_then(Value::as_str);
86
+ match op {
87
+ Some("assert") => match lease.verify() {
88
+ Ok(()) => write_json(&mut stdout, serde_json::json!({"id":id,"ok":true}))?,
89
+ Err(error) => write_json(
90
+ &mut stdout,
91
+ serde_json::json!({"id":id,"ok":false,"error":error}),
92
+ )?,
93
+ },
94
+ Some("close") => match lease.verify() {
95
+ Ok(()) => {
96
+ lease.release()?;
97
+ write_json(&mut stdout, serde_json::json!({"id":id,"ok":true}))?;
98
+ return Ok(());
99
+ }
100
+ Err(error) => {
101
+ let release_error = lease.release().err();
102
+ let message = match release_error {
103
+ Some(release_error) => format!("{error}; {release_error}"),
104
+ None => error,
105
+ };
106
+ write_json(
107
+ &mut stdout,
108
+ serde_json::json!({"id":id,"ok":false,"error":message}),
109
+ )?;
110
+ return Ok(());
111
+ }
112
+ },
113
+ Some(other) => write_json(
114
+ &mut stdout,
115
+ serde_json::json!({"id":id,"ok":false,"error":format!("unsupported lease-mutex operation: {other}")}),
116
+ )?,
117
+ None => write_json(
118
+ &mut stdout,
119
+ serde_json::json!({"id":id,"ok":false,"error":"request op must be assert or close"}),
120
+ )?,
121
+ }
122
+ }
123
+ Ok(())
124
+ }
125
+
126
+ fn write_json(out: &mut impl Write, value: Value) -> Result<(), String> {
127
+ serde_json::to_writer(&mut *out, &value)
128
+ .map_err(|e| format!("stdout JSON write failed: {e}"))?;
129
+ out.write_all(b"\n")
130
+ .map_err(|e| format!("stdout write failed: {e}"))?;
131
+ out.flush().map_err(|e| format!("stdout flush failed: {e}"))
132
+ }
133
+
134
+ fn emit_startup_error(error: String) -> Result<(), String> {
135
+ let mut stdout = io::stdout().lock();
136
+ let _ = write_json(
137
+ &mut stdout,
138
+ serde_json::json!({"id":0,"ok":false,"error":error}),
139
+ );
140
+ Err(error)
141
+ }
142
+
143
+ trait Lease {
144
+ fn verify(&self) -> Result<(), String>;
145
+ fn release(&mut self) -> Result<(), String>;
146
+ }
147
+
148
+ #[cfg(unix)]
149
+ struct UnixLease {
150
+ dir: std::fs::File,
151
+ mutex: std::fs::File,
152
+ dir_path: PathBuf,
153
+ expected_dev: u64,
154
+ expected_ino: u64,
155
+ mutex_dev: u64,
156
+ mutex_ino: u64,
157
+ leaf: String,
158
+ held: bool,
159
+ }
160
+
161
+ #[cfg(unix)]
162
+ impl UnixLease {
163
+ fn open(
164
+ path: &Path,
165
+ expected_dev: u64,
166
+ expected_ino: u64,
167
+ leaf: String,
168
+ ) -> Result<Self, String> {
169
+ std::fs::canonicalize(path)
170
+ .map_err(|e| format!("lease-mutex cannot canonicalize directory: {e}"))?;
171
+ let dir = open_directory(path)?;
172
+ let dir_identity = stat_fd(dir.as_raw_fd(), "pinned directory")?;
173
+ if !dir_identity.is_dir {
174
+ return Err("lease-mutex path is not a directory".to_string());
175
+ }
176
+ if dir_identity.dev != expected_dev || dir_identity.ino != expected_ino {
177
+ return Err(format!("lease-mutex directory identity mismatch: expected dev={expected_dev} ino={expected_ino}, got dev={} ino={}", dir_identity.dev, dir_identity.ino));
178
+ }
179
+ let mutex = open_mutex(dir.as_raw_fd(), &leaf)?;
180
+ let mutex_identity = stat_fd(mutex.as_raw_fd(), "mutex file")?;
181
+ if !mutex_identity.is_regular || mutex_identity.nlink != 1 {
182
+ return Err(
183
+ "lease-mutex mutex leaf must be a regular file with link count 1".to_string(),
184
+ );
185
+ }
186
+ try_lock(&mutex)?;
187
+ let lease = Self {
188
+ dir,
189
+ mutex,
190
+ dir_path: path.to_path_buf(),
191
+ expected_dev,
192
+ expected_ino,
193
+ mutex_dev: mutex_identity.dev,
194
+ mutex_ino: mutex_identity.ino,
195
+ leaf,
196
+ held: true,
197
+ };
198
+ lease.verify()?;
199
+ Ok(lease)
200
+ }
201
+ }
202
+
203
+ #[cfg(unix)]
204
+ impl Lease for UnixLease {
205
+ fn verify(&self) -> Result<(), String> {
206
+ if !self.held {
207
+ return Err("lease-mutex lock is no longer held".to_string());
208
+ }
209
+ let pinned = stat_fd(self.dir.as_raw_fd(), "pinned directory")?;
210
+ if !pinned.is_dir || pinned.dev != self.expected_dev || pinned.ino != self.expected_ino {
211
+ return Err("lease-mutex pinned directory identity changed".to_string());
212
+ }
213
+ let visible = stat_at(self.dir.as_raw_fd(), &self.leaf)?;
214
+ if !visible.is_regular || visible.nlink != 1 {
215
+ return Err("lease-mutex visible mutex leaf is not a regular nlink-1 file".to_string());
216
+ }
217
+ if visible.dev != self.mutex_dev || visible.ino != self.mutex_ino {
218
+ return Err("lease-mutex visible mutex identity changed".to_string());
219
+ }
220
+ let opened = stat_fd(self.mutex.as_raw_fd(), "opened mutex")?;
221
+ if !opened.is_regular
222
+ || opened.nlink != 1
223
+ || opened.dev != self.mutex_dev
224
+ || opened.ino != self.mutex_ino
225
+ {
226
+ return Err("lease-mutex opened mutex identity changed".to_string());
227
+ }
228
+ let path_identity = stat_path(&self.dir_path)?;
229
+ if path_identity.dev != self.expected_dev || path_identity.ino != self.expected_ino {
230
+ return Err("lease-mutex directory path identity changed".to_string());
231
+ }
232
+ Ok(())
233
+ }
234
+
235
+ fn release(&mut self) -> Result<(), String> {
236
+ if self.held {
237
+ FileExt::unlock(&self.mutex).map_err(|e| format!("lease-mutex unlock failed: {e}"))?;
238
+ self.held = false;
239
+ }
240
+ Ok(())
241
+ }
242
+ }
243
+
244
+ #[cfg(unix)]
245
+ use std::os::fd::{AsRawFd, FromRawFd};
246
+
247
+ #[cfg(unix)]
248
+ #[derive(Clone, Copy)]
249
+ struct Identity {
250
+ dev: u64,
251
+ ino: u64,
252
+ nlink: u64,
253
+ is_dir: bool,
254
+ is_regular: bool,
255
+ }
256
+
257
+ #[cfg(unix)]
258
+ fn open_directory(path: &Path) -> Result<std::fs::File, String> {
259
+ let c = cstring(path, "directory")?;
260
+ let fd = unsafe {
261
+ libc::open(
262
+ c.as_ptr(),
263
+ libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC | libc::O_NOFOLLOW,
264
+ )
265
+ };
266
+ if fd < 0 {
267
+ return Err(format!(
268
+ "lease-mutex cannot open directory without following symlinks: {}",
269
+ io::Error::last_os_error()
270
+ ));
271
+ }
272
+ Ok(unsafe { std::fs::File::from_raw_fd(fd) })
273
+ }
274
+
275
+ #[cfg(unix)]
276
+ fn open_mutex(dir_fd: i32, leaf_name: &str) -> Result<std::fs::File, String> {
277
+ let leaf = std::ffi::CString::new(leaf_name)
278
+ .map_err(|_| "lease-mutex safe-leaf contains NUL".to_string())?;
279
+ let flags = libc::O_RDWR | libc::O_CLOEXEC | libc::O_NOFOLLOW;
280
+ let mut fd = unsafe { libc::openat(dir_fd, leaf.as_ptr(), flags) };
281
+ if fd < 0 && io::Error::last_os_error().raw_os_error() == Some(libc::ENOENT) {
282
+ fd = unsafe {
283
+ libc::openat(
284
+ dir_fd,
285
+ leaf.as_ptr(),
286
+ flags | libc::O_CREAT | libc::O_EXCL,
287
+ 0o600,
288
+ )
289
+ };
290
+ if fd < 0 && io::Error::last_os_error().raw_os_error() == Some(libc::EEXIST) {
291
+ fd = unsafe { libc::openat(dir_fd, leaf.as_ptr(), flags) };
292
+ }
293
+ }
294
+ if fd < 0 {
295
+ return Err(format!(
296
+ "lease-mutex cannot open permanent mutex leaf without following symlinks: {}",
297
+ io::Error::last_os_error()
298
+ ));
299
+ }
300
+ Ok(unsafe { std::fs::File::from_raw_fd(fd) })
301
+ }
302
+
303
+ #[cfg(unix)]
304
+ fn try_lock(file: &std::fs::File) -> Result<(), String> {
305
+ let start = Instant::now();
306
+ loop {
307
+ match file.try_lock_exclusive() {
308
+ Ok(()) => return Ok(()),
309
+ Err(error)
310
+ if error.kind() == io::ErrorKind::WouldBlock && start.elapsed() < LOCK_TIMEOUT =>
311
+ {
312
+ std::thread::sleep(Duration::from_millis(20))
313
+ }
314
+ Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
315
+ return Err(format!(
316
+ "lease-mutex lock acquisition timed out after {} ms",
317
+ LOCK_TIMEOUT.as_millis()
318
+ ))
319
+ }
320
+ Err(error) => return Err(format!("lease-mutex lock acquisition failed: {error}")),
321
+ }
322
+ }
323
+ }
324
+
325
+ #[cfg(unix)]
326
+ fn cstring(path: &Path, label: &str) -> Result<std::ffi::CString, String> {
327
+ std::ffi::CString::new(path.as_os_str().as_bytes())
328
+ .map_err(|_| format!("lease-mutex {label} path contains an embedded NUL byte"))
329
+ }
330
+
331
+ #[cfg(unix)]
332
+ fn stat_path(path: &Path) -> Result<Identity, String> {
333
+ let c = cstring(path, "directory")?;
334
+ let mut st = unsafe { std::mem::zeroed() };
335
+ if unsafe { libc::lstat(c.as_ptr(), &mut st) } != 0 {
336
+ return Err(format!(
337
+ "lease-mutex cannot verify directory path: {}",
338
+ io::Error::last_os_error()
339
+ ));
340
+ }
341
+ Ok(identity_from_stat(&st))
342
+ }
343
+
344
+ #[cfg(unix)]
345
+ fn stat_at(dir_fd: i32, leaf: &str) -> Result<Identity, String> {
346
+ let c = std::ffi::CString::new(leaf).unwrap();
347
+ let mut st = unsafe { std::mem::zeroed() };
348
+ if unsafe { libc::fstatat(dir_fd, c.as_ptr(), &mut st, libc::AT_SYMLINK_NOFOLLOW) } != 0 {
349
+ return Err(format!(
350
+ "lease-mutex cannot verify visible mutex leaf: {}",
351
+ io::Error::last_os_error()
352
+ ));
353
+ }
354
+ Ok(identity_from_stat(&st))
355
+ }
356
+
357
+ #[cfg(unix)]
358
+ fn stat_fd(fd: i32, label: &str) -> Result<Identity, String> {
359
+ let mut st = unsafe { std::mem::zeroed() };
360
+ if unsafe { libc::fstat(fd, &mut st) } != 0 {
361
+ return Err(format!(
362
+ "lease-mutex cannot stat {label}: {}",
363
+ io::Error::last_os_error()
364
+ ));
365
+ }
366
+ Ok(identity_from_stat(&st))
367
+ }
368
+
369
+ #[cfg(unix)]
370
+ #[allow(clippy::unnecessary_cast)]
371
+ fn identity_from_stat(st: &libc::stat) -> Identity {
372
+ Identity {
373
+ dev: st.st_dev as u64,
374
+ ino: st.st_ino as u64,
375
+ nlink: st.st_nlink as u64,
376
+ is_dir: (st.st_mode & libc::S_IFMT) == libc::S_IFDIR,
377
+ is_regular: (st.st_mode & libc::S_IFMT) == libc::S_IFREG,
378
+ }
379
+ }
@@ -1,3 +1,5 @@
1
+ mod lease_mutex;
2
+
1
3
  use fs2::FileExt;
2
4
  use omx_mux::{canonical_contract_summary, MuxAdapter, MuxOperation, MuxTarget, TmuxAdapter};
3
5
  use omx_runtime_core::{runtime_contract_summary, RuntimeCommand, RuntimeEngine};
@@ -123,6 +125,7 @@ fn run() -> Result<(), String> {
123
125
  );
124
126
  Ok(())
125
127
  }
128
+ Some("lease-mutex") => lease_mutex::run(&args[1..]),
126
129
  Some("fs-rename-no-replace") => run_fs_rename_no_replace(&args[1..]),
127
130
  Some("process-identity") => run_process_identity(&args[1..]),
128
131
  Some("init") => {
@@ -533,6 +536,7 @@ fn print_usage() {
533
536
  " snapshot [--json] [--state-dir=DIR] print a runtime snapshot\n",
534
537
  " mux-contract print the mux boundary summary\n",
535
538
  " exec <json> [--state-dir=DIR] process a runtime command from JSON\n",
539
+ " lease-mutex <absolute-canonical-dir> <expected-dev> <expected-ino> <safe-leaf> hold native lease mutex over stdin JSON protocol\n",
536
540
  " init <state-dir> initialize a fresh state directory\n",
537
541
  ));
538
542
  }
@@ -1,3 +1,154 @@
1
+ #[cfg(unix)]
2
+ mod lease_mutex_tests {
3
+ use super::*;
4
+ use std::io::{BufRead, BufReader, Write};
5
+ use std::os::unix::fs::MetadataExt;
6
+ use std::process::{Child, ChildStdout, Stdio};
7
+ use std::sync::mpsc;
8
+ use std::thread;
9
+ use std::time::{Duration, SystemTime, UNIX_EPOCH};
10
+ const MUTEX_LEAF: &str =
11
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.mutex";
12
+
13
+ fn temp_dir() -> std::path::PathBuf {
14
+ let dir = std::env::temp_dir().join(format!(
15
+ "omx-lease-mutex-{}-{}",
16
+ std::process::id(),
17
+ SystemTime::now()
18
+ .duration_since(UNIX_EPOCH)
19
+ .unwrap()
20
+ .as_nanos()
21
+ ));
22
+ std::fs::create_dir_all(&dir).unwrap();
23
+ dir
24
+ }
25
+
26
+ fn spawn_lease(dir: &std::path::Path) -> (Child, BufReader<ChildStdout>) {
27
+ let metadata = std::fs::metadata(dir).unwrap();
28
+ let mut child = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
29
+ .args([
30
+ "lease-mutex",
31
+ dir.to_str().unwrap(),
32
+ &metadata.dev().to_string(),
33
+ &metadata.ino().to_string(),
34
+ MUTEX_LEAF,
35
+ ])
36
+ .stdin(Stdio::piped())
37
+ .stdout(Stdio::piped())
38
+ .stderr(Stdio::piped())
39
+ .spawn()
40
+ .unwrap();
41
+ let stdout = BufReader::new(child.stdout.take().unwrap());
42
+ (child, stdout)
43
+ }
44
+
45
+ fn read_line(reader: &mut BufReader<ChildStdout>) -> serde_json::Value {
46
+ let mut line = String::new();
47
+ reader.read_line(&mut line).unwrap();
48
+ serde_json::from_str(&line).unwrap()
49
+ }
50
+
51
+ #[test]
52
+ fn lease_mutex_serializes_contenders() {
53
+ let dir = temp_dir();
54
+ let (mut first, mut first_out) = spawn_lease(&dir);
55
+ assert_eq!(read_line(&mut first_out)["ready"], true);
56
+ let (mut second, second_out) = spawn_lease(&dir);
57
+ let (tx, rx) = mpsc::channel();
58
+ thread::spawn(move || {
59
+ let mut reader = second_out;
60
+ let mut line = String::new();
61
+ let result = reader.read_line(&mut line).map(|_| line);
62
+ let _ = tx.send(result);
63
+ });
64
+ assert!(rx.recv_timeout(Duration::from_millis(150)).is_err());
65
+ first
66
+ .stdin
67
+ .as_mut()
68
+ .unwrap()
69
+ .write_all(b"{\"id\":1,\"op\":\"close\"}\n")
70
+ .unwrap();
71
+ assert!(first.wait().unwrap().success());
72
+ let line = rx.recv_timeout(Duration::from_secs(3)).unwrap().unwrap();
73
+ assert_eq!(
74
+ serde_json::from_str::<serde_json::Value>(&line).unwrap()["ready"],
75
+ true
76
+ );
77
+ let _ = second.kill();
78
+ let _ = second.wait();
79
+ let _ = std::fs::remove_dir_all(dir);
80
+ }
81
+
82
+ #[test]
83
+ fn lease_mutex_releases_on_process_death() {
84
+ let dir = temp_dir();
85
+ let (mut first, mut first_out) = spawn_lease(&dir);
86
+ assert_eq!(read_line(&mut first_out)["ready"], true);
87
+ first.kill().unwrap();
88
+ first.wait().unwrap();
89
+ let (mut second, mut second_out) = spawn_lease(&dir);
90
+ assert_eq!(read_line(&mut second_out)["ready"], true);
91
+ second.kill().unwrap();
92
+ second.wait().unwrap();
93
+ let _ = std::fs::remove_dir_all(dir);
94
+ }
95
+
96
+ #[test]
97
+ fn lease_mutex_rejects_symlink_and_directory_identity_mismatch() {
98
+ use std::os::unix::fs::symlink;
99
+ let dir = temp_dir();
100
+ let target = dir.join("target");
101
+ std::fs::create_dir(&target).unwrap();
102
+ symlink(&target, dir.join("link")).unwrap();
103
+ let output = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
104
+ .args([
105
+ "lease-mutex",
106
+ dir.join("link").to_str().unwrap(),
107
+ "0",
108
+ "0",
109
+ MUTEX_LEAF,
110
+ ])
111
+ .output()
112
+ .unwrap();
113
+ assert!(!output.status.success());
114
+ let metadata = std::fs::metadata(&dir).unwrap();
115
+ let output = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
116
+ .args([
117
+ "lease-mutex",
118
+ target.to_str().unwrap(),
119
+ &metadata.dev().to_string(),
120
+ &metadata.ino().to_string(),
121
+ MUTEX_LEAF,
122
+ ])
123
+ .output()
124
+ .unwrap();
125
+ assert!(!output.status.success());
126
+ let _ = std::fs::remove_dir_all(dir);
127
+ }
128
+
129
+ #[test]
130
+ fn lease_mutex_assert_fails_after_directory_path_replacement() {
131
+ let dir = temp_dir();
132
+ let replacement = dir.with_extension("replacement");
133
+ let (mut child, mut output) = spawn_lease(&dir);
134
+ assert_eq!(read_line(&mut output)["ready"], true);
135
+ std::fs::rename(&dir, &replacement).unwrap();
136
+ std::fs::create_dir(&dir).unwrap();
137
+ child
138
+ .stdin
139
+ .as_mut()
140
+ .unwrap()
141
+ .write_all(b"{\"id\":7,\"op\":\"assert\"}\n")
142
+ .unwrap();
143
+ let response = read_line(&mut output);
144
+ assert_eq!(response["ok"], false);
145
+ assert!(response["error"].as_str().unwrap().contains("identity"));
146
+ let _ = child.kill();
147
+ let _ = child.wait();
148
+ let _ = std::fs::remove_dir_all(dir);
149
+ let _ = std::fs::remove_dir_all(replacement);
150
+ }
151
+ }
1
152
  use std::process::Command;
2
153
 
3
154
  #[test]
@@ -439,6 +590,7 @@ fn concurrent_exec_queue_accepts_exactly_one_request_id() {
439
590
  // ---------------------------------------------------------------------------
440
591
 
441
592
  /// Helper: run `fs-rename-no-replace <from> <to>` and return (exit_ok, stdout, stderr).
593
+ #[cfg(target_os = "linux")]
442
594
  fn run_rename_no_replace(from: &str, to: &str) -> (bool, String, String) {
443
595
  let output = Command::new(env!("CARGO_BIN_EXE_omx-runtime"))
444
596
  .args(["fs-rename-no-replace", from, to])
@@ -9,8 +9,8 @@ use std::time::Duration;
9
9
 
10
10
  pub const DEFAULT_SUMMARY_TIMEOUT_MS: u64 = 60_000;
11
11
  pub const DEFAULT_API_BASE_URL: &str = "http://127.0.0.1:14510";
12
- pub const DEFAULT_SPARK_MODEL: &str = "gpt-5.6-luna";
13
- pub const DEFAULT_STANDARD_MODEL: &str = "gpt-5.6-terra";
12
+ pub const DEFAULT_SPARK_MODEL: &str = "gpt-6-astra";
13
+ pub const DEFAULT_STANDARD_MODEL: &str = "gpt-6-astra";
14
14
 
15
15
  pub fn resolve_model() -> String {
16
16
  env::var("OMX_SPARKSHELL_MODEL")