oh-my-codex 0.18.7 → 0.18.8

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 (259) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +5 -5
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/native-config.test.js +42 -1
  6. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  7. package/dist/agents/definitions.d.ts +8 -0
  8. package/dist/agents/definitions.d.ts.map +1 -1
  9. package/dist/agents/definitions.js +1 -0
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/agents/native-config.d.ts +5 -1
  12. package/dist/agents/native-config.d.ts.map +1 -1
  13. package/dist/agents/native-config.js +17 -2
  14. package/dist/agents/native-config.js.map +1 -1
  15. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  16. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +61 -5
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  22. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  23. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  24. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  25. package/dist/cli/__tests__/ralph.test.js +14 -0
  26. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  27. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  28. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  29. package/dist/cli/__tests__/setup-refresh.test.js +65 -0
  30. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  31. package/dist/cli/__tests__/state.test.js +21 -0
  32. package/dist/cli/__tests__/state.test.js.map +1 -1
  33. package/dist/cli/__tests__/team.test.js +2 -2
  34. package/dist/cli/__tests__/update.test.js +110 -2
  35. package/dist/cli/__tests__/update.test.js.map +1 -1
  36. package/dist/cli/doctor.d.ts.map +1 -1
  37. package/dist/cli/doctor.js +8 -1
  38. package/dist/cli/doctor.js.map +1 -1
  39. package/dist/cli/index.d.ts +11 -2
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +108 -15
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/plugin-marketplace.d.ts +14 -2
  44. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  45. package/dist/cli/plugin-marketplace.js +62 -15
  46. package/dist/cli/plugin-marketplace.js.map +1 -1
  47. package/dist/cli/ralph.d.ts.map +1 -1
  48. package/dist/cli/ralph.js +3 -1
  49. package/dist/cli/ralph.js.map +1 -1
  50. package/dist/cli/setup-preferences.d.ts +2 -0
  51. package/dist/cli/setup-preferences.d.ts.map +1 -1
  52. package/dist/cli/setup-preferences.js +4 -0
  53. package/dist/cli/setup-preferences.js.map +1 -1
  54. package/dist/cli/setup.d.ts +3 -0
  55. package/dist/cli/setup.d.ts.map +1 -1
  56. package/dist/cli/setup.js +166 -27
  57. package/dist/cli/setup.js.map +1 -1
  58. package/dist/cli/state.d.ts.map +1 -1
  59. package/dist/cli/state.js +8 -1
  60. package/dist/cli/state.js.map +1 -1
  61. package/dist/cli/tmux-hook.d.ts.map +1 -1
  62. package/dist/cli/tmux-hook.js +16 -0
  63. package/dist/cli/tmux-hook.js.map +1 -1
  64. package/dist/cli/update.d.ts +2 -0
  65. package/dist/cli/update.d.ts.map +1 -1
  66. package/dist/cli/update.js +47 -3
  67. package/dist/cli/update.js.map +1 -1
  68. package/dist/config/__tests__/generator-notify.test.js +1 -0
  69. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  70. package/dist/config/generator.d.ts +2 -2
  71. package/dist/config/generator.d.ts.map +1 -1
  72. package/dist/config/generator.js +2 -2
  73. package/dist/config/generator.js.map +1 -1
  74. package/dist/config/team-mode.d.ts +12 -0
  75. package/dist/config/team-mode.d.ts.map +1 -0
  76. package/dist/config/team-mode.js +91 -0
  77. package/dist/config/team-mode.js.map +1 -0
  78. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  79. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  80. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  81. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  82. package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
  83. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  84. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
  85. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  87. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  89. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  91. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  92. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  93. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  94. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  95. package/dist/hooks/agents-overlay.js +36 -50
  96. package/dist/hooks/agents-overlay.js.map +1 -1
  97. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  98. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  99. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  100. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  101. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  102. package/dist/hooks/keyword-detector.js +258 -12
  103. package/dist/hooks/keyword-detector.js.map +1 -1
  104. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  105. package/dist/hooks/prompt-guidance-contract.js +6 -0
  106. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  107. package/dist/hooks/session.d.ts +1 -0
  108. package/dist/hooks/session.d.ts.map +1 -1
  109. package/dist/hooks/session.js.map +1 -1
  110. package/dist/hud/__tests__/authority.test.js +435 -32
  111. package/dist/hud/__tests__/authority.test.js.map +1 -1
  112. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  113. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  114. package/dist/hud/__tests__/index.test.js +42 -0
  115. package/dist/hud/__tests__/index.test.js.map +1 -1
  116. package/dist/hud/__tests__/reconcile.test.js +521 -15
  117. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  118. package/dist/hud/__tests__/render.test.js +61 -0
  119. package/dist/hud/__tests__/render.test.js.map +1 -1
  120. package/dist/hud/__tests__/state.test.js +132 -4
  121. package/dist/hud/__tests__/state.test.js.map +1 -1
  122. package/dist/hud/__tests__/tmux.test.js +180 -21
  123. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  124. package/dist/hud/authority.d.ts +5 -0
  125. package/dist/hud/authority.d.ts.map +1 -1
  126. package/dist/hud/authority.js +324 -28
  127. package/dist/hud/authority.js.map +1 -1
  128. package/dist/hud/index.d.ts +3 -2
  129. package/dist/hud/index.d.ts.map +1 -1
  130. package/dist/hud/index.js +42 -19
  131. package/dist/hud/index.js.map +1 -1
  132. package/dist/hud/reconcile.d.ts +3 -3
  133. package/dist/hud/reconcile.d.ts.map +1 -1
  134. package/dist/hud/reconcile.js +128 -19
  135. package/dist/hud/reconcile.js.map +1 -1
  136. package/dist/hud/render.d.ts.map +1 -1
  137. package/dist/hud/render.js +35 -0
  138. package/dist/hud/render.js.map +1 -1
  139. package/dist/hud/state.d.ts.map +1 -1
  140. package/dist/hud/state.js +61 -62
  141. package/dist/hud/state.js.map +1 -1
  142. package/dist/hud/tmux.d.ts +24 -6
  143. package/dist/hud/tmux.d.ts.map +1 -1
  144. package/dist/hud/tmux.js +136 -38
  145. package/dist/hud/tmux.js.map +1 -1
  146. package/dist/hud/types.d.ts +11 -0
  147. package/dist/hud/types.d.ts.map +1 -1
  148. package/dist/hud/types.js.map +1 -1
  149. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  150. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  151. package/dist/mcp/state-paths.d.ts +32 -0
  152. package/dist/mcp/state-paths.d.ts.map +1 -1
  153. package/dist/mcp/state-paths.js +113 -17
  154. package/dist/mcp/state-paths.js.map +1 -1
  155. package/dist/mcp/state-server.d.ts +4 -4
  156. package/dist/scripts/__tests__/codex-native-hook.test.js +593 -11
  157. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  158. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  159. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  160. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  161. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  162. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  163. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  164. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  165. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  166. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  167. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  168. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  169. package/dist/scripts/codex-native-hook.js +88 -31
  170. package/dist/scripts/codex-native-hook.js.map +1 -1
  171. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  172. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  173. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  174. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  175. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  176. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  177. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  178. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  179. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  180. package/dist/scripts/notify-hook/state-io.js +62 -38
  181. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  182. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  183. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  184. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  185. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  186. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  187. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  188. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  189. package/dist/scripts/notify-hook.js +75 -11
  190. package/dist/scripts/notify-hook.js.map +1 -1
  191. package/dist/scripts/run-test-files.js +193 -22
  192. package/dist/scripts/run-test-files.js.map +1 -1
  193. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  194. package/dist/scripts/sync-plugin-mirror.js +61 -3
  195. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  196. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  197. package/dist/scripts/verify-native-agents.js +58 -1
  198. package/dist/scripts/verify-native-agents.js.map +1 -1
  199. package/dist/state/__tests__/operations.test.js +113 -0
  200. package/dist/state/__tests__/operations.test.js.map +1 -1
  201. package/dist/state/__tests__/skill-active.test.js +3 -16
  202. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  203. package/dist/state/__tests__/workflow-transition.test.js +25 -0
  204. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  205. package/dist/state/operations.d.ts.map +1 -1
  206. package/dist/state/operations.js +57 -2
  207. package/dist/state/operations.js.map +1 -1
  208. package/dist/state/skill-active.d.ts.map +1 -1
  209. package/dist/state/skill-active.js +7 -39
  210. package/dist/state/skill-active.js.map +1 -1
  211. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  212. package/dist/state/workflow-transition-reconcile.js +10 -14
  213. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  214. package/dist/team/__tests__/runtime.test.js +1 -1
  215. package/dist/team/__tests__/runtime.test.js.map +1 -1
  216. package/dist/team/__tests__/scaling.test.js +9 -4
  217. package/dist/team/__tests__/scaling.test.js.map +1 -1
  218. package/dist/team/__tests__/tmux-session.test.js +195 -2
  219. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  220. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  221. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  222. package/dist/team/scaling.d.ts.map +1 -1
  223. package/dist/team/scaling.js +3 -2
  224. package/dist/team/scaling.js.map +1 -1
  225. package/dist/team/tmux-session.d.ts +2 -0
  226. package/dist/team/tmux-session.d.ts.map +1 -1
  227. package/dist/team/tmux-session.js +142 -12
  228. package/dist/team/tmux-session.js.map +1 -1
  229. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  230. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  231. package/package.json +8 -8
  232. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  233. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  234. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  235. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
  236. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  237. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  238. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  239. package/skills/autopilot/SKILL.md +3 -1
  240. package/skills/code-review/SKILL.md +7 -7
  241. package/skills/ralph/SKILL.md +22 -22
  242. package/skills/ultraqa/SKILL.md +9 -0
  243. package/src/scripts/__tests__/codex-native-hook.test.ts +686 -13
  244. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  245. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  246. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  247. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  248. package/src/scripts/codex-native-hook.ts +105 -28
  249. package/src/scripts/demo-team-e2e.sh +10 -7
  250. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  251. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  252. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  253. package/src/scripts/notify-hook/state-io.ts +75 -37
  254. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  255. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  256. package/src/scripts/notify-hook.ts +91 -4
  257. package/src/scripts/run-test-files.ts +192 -22
  258. package/src/scripts/sync-plugin-mirror.ts +98 -9
  259. package/src/scripts/verify-native-agents.ts +65 -1
@@ -6,6 +6,7 @@ import { join } from 'node:path';
6
6
 
7
7
  import {
8
8
  getScopedStatePath,
9
+ readCurrentSessionId,
9
10
  readScopedJsonIfExists,
10
11
  resolveScopedStateDir,
11
12
  writeScopedJson,
@@ -70,4 +71,98 @@ describe('notify-hook state I/O session authority', () => {
70
71
  await rm(wd, { recursive: true, force: true });
71
72
  }
72
73
  });
74
+
75
+ it('resolves current session from authoritative team state root without cwd inference', async () => {
76
+ const wd = await mkdtemp(join(tmpdir(), 'omx-notify-state-io-team-root-'));
77
+ try {
78
+ const teamStateRoot = join(wd, 'team-state-root');
79
+ await mkdir(join(teamStateRoot, 'sessions', 'sess-team-root'), { recursive: true });
80
+ await writeFile(
81
+ join(teamStateRoot, 'session.json'),
82
+ JSON.stringify({ session_id: 'sess-team-root', cwd: join(wd, 'source-repo') }, null, 2),
83
+ 'utf-8',
84
+ );
85
+ await writeFile(
86
+ join(teamStateRoot, 'hud-state.json'),
87
+ JSON.stringify({ turn_count: 99 }, null, 2),
88
+ 'utf-8',
89
+ );
90
+ await writeFile(
91
+ join(teamStateRoot, 'sessions', 'sess-team-root', 'hud-state.json'),
92
+ JSON.stringify({ turn_count: 4 }, null, 2),
93
+ 'utf-8',
94
+ );
95
+
96
+ assert.equal(await resolveScopedStateDir(teamStateRoot), join(teamStateRoot, 'sessions', 'sess-team-root'));
97
+ const value = await readScopedJsonIfExists(teamStateRoot, 'hud-state.json', undefined, null);
98
+ assert.equal(value?.turn_count, 4);
99
+ } finally {
100
+ await rm(wd, { recursive: true, force: true });
101
+ }
102
+ });
103
+
104
+ it('prefers OMX_SESSION_ID over stale session.json for notify state writes', async () => {
105
+ const wd = await mkdtemp(join(tmpdir(), 'omx-notify-state-io-env-'));
106
+ const previousSessionId = process.env.OMX_SESSION_ID;
107
+ try {
108
+ const stateDir = join(wd, '.omx', 'state');
109
+ await mkdir(join(stateDir, 'sessions', 'sess-env'), { recursive: true });
110
+ await mkdir(join(stateDir, 'sessions', 'sess-stale'), { recursive: true });
111
+ await writeFile(
112
+ join(stateDir, 'session.json'),
113
+ JSON.stringify({ session_id: 'sess-stale', cwd: join(wd, '..', 'other-worktree') }, null, 2),
114
+ 'utf-8',
115
+ );
116
+ process.env.OMX_SESSION_ID = 'sess-env';
117
+
118
+ assert.equal(await readCurrentSessionId(stateDir), 'sess-env');
119
+ assert.equal(await resolveScopedStateDir(stateDir), join(stateDir, 'sessions', 'sess-env'));
120
+
121
+ await writeScopedJson(stateDir, 'hud-state.json', undefined, { turn_count: 7 });
122
+ const value = JSON.parse(
123
+ await readFile(join(stateDir, 'sessions', 'sess-env', 'hud-state.json'), 'utf-8'),
124
+ ) as { turn_count?: unknown };
125
+ assert.equal(value.turn_count, 7);
126
+ } finally {
127
+ if (typeof previousSessionId === 'string') process.env.OMX_SESSION_ID = previousSessionId;
128
+ else delete process.env.OMX_SESSION_ID;
129
+ await rm(wd, { recursive: true, force: true });
130
+ }
131
+ });
132
+
133
+ it('maps native Codex session aliases to the canonical OMX session', async () => {
134
+ const wd = await mkdtemp(join(tmpdir(), 'omx-notify-state-io-native-alias-'));
135
+ const previousOmxSessionId = process.env.OMX_SESSION_ID;
136
+ const previousCodexSessionId = process.env.CODEX_SESSION_ID;
137
+ try {
138
+ const stateDir = join(wd, '.omx', 'state');
139
+ await mkdir(join(stateDir, 'sessions', 'omx-canonical'), { recursive: true });
140
+ await writeFile(
141
+ join(stateDir, 'session.json'),
142
+ JSON.stringify({
143
+ session_id: 'omx-canonical',
144
+ native_session_id: 'codex-native',
145
+ cwd: wd,
146
+ }, null, 2),
147
+ 'utf-8',
148
+ );
149
+ delete process.env.OMX_SESSION_ID;
150
+ process.env.CODEX_SESSION_ID = 'codex-native';
151
+
152
+ assert.equal(await readCurrentSessionId(stateDir), 'omx-canonical');
153
+ assert.equal(await resolveScopedStateDir(stateDir), join(stateDir, 'sessions', 'omx-canonical'));
154
+
155
+ await writeScopedJson(stateDir, 'hud-state.json', undefined, { turn_count: 11 });
156
+ const value = JSON.parse(
157
+ await readFile(join(stateDir, 'sessions', 'omx-canonical', 'hud-state.json'), 'utf-8'),
158
+ ) as { turn_count?: unknown };
159
+ assert.equal(value.turn_count, 11);
160
+ } finally {
161
+ if (typeof previousOmxSessionId === 'string') process.env.OMX_SESSION_ID = previousOmxSessionId;
162
+ else delete process.env.OMX_SESSION_ID;
163
+ if (typeof previousCodexSessionId === 'string') process.env.CODEX_SESSION_ID = previousCodexSessionId;
164
+ else delete process.env.CODEX_SESSION_ID;
165
+ await rm(wd, { recursive: true, force: true });
166
+ }
167
+ });
73
168
  });
@@ -0,0 +1,82 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+
7
+ import { readVisibleAllowedModes } from '../notify-hook/tmux-injection.js';
8
+
9
+ describe('notify-hook tmux injection canonical skill gating', () => {
10
+ it('reads canonical skill-active state from authoritative team state root', async () => {
11
+ const wd = await mkdtemp(join(tmpdir(), 'omx-notify-tmux-team-root-'));
12
+ try {
13
+ const teamStateRoot = join(wd, 'team-state-root');
14
+ const sessionId = 'sess-team-root';
15
+ await mkdir(join(teamStateRoot, 'sessions', sessionId), { recursive: true });
16
+ await writeFile(
17
+ join(teamStateRoot, 'session.json'),
18
+ JSON.stringify({ session_id: sessionId, cwd: join(wd, 'source-repo') }, null, 2),
19
+ 'utf-8',
20
+ );
21
+ await writeFile(
22
+ join(teamStateRoot, 'sessions', sessionId, 'skill-active-state.json'),
23
+ JSON.stringify({
24
+ version: 1,
25
+ active: true,
26
+ skill: 'ralplan',
27
+ phase: 'draft',
28
+ session_id: sessionId,
29
+ active_skills: [{ skill: 'ralplan', active: true, phase: 'draft', session_id: sessionId }],
30
+ }, null, 2),
31
+ 'utf-8',
32
+ );
33
+
34
+ const visible = await readVisibleAllowedModes(
35
+ join(wd, 'source-repo'),
36
+ teamStateRoot,
37
+ {},
38
+ ['ralplan', 'deep-interview'],
39
+ );
40
+
41
+ assert.equal(visible.canonicalPresent, true);
42
+ assert.equal(visible.sessionScoped, true);
43
+ assert.equal(visible.preferredMode, 'ralplan');
44
+ assert.deepEqual([...visible.allowedSet ?? []], ['ralplan']);
45
+ } finally {
46
+ await rm(wd, { recursive: true, force: true });
47
+ }
48
+ });
49
+
50
+ it('treats missing session canonical state as session-scoped inactive instead of root fallback', async () => {
51
+ const wd = await mkdtemp(join(tmpdir(), 'omx-notify-tmux-missing-canonical-'));
52
+ try {
53
+ const stateDir = join(wd, '.omx', 'state');
54
+ const sessionId = 'sess-current';
55
+ await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
56
+ await writeFile(
57
+ join(stateDir, 'session.json'),
58
+ JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2),
59
+ 'utf-8',
60
+ );
61
+ await writeFile(
62
+ join(stateDir, 'skill-active-state.json'),
63
+ JSON.stringify({
64
+ version: 1,
65
+ active: true,
66
+ skill: 'ralplan',
67
+ active_skills: [{ skill: 'ralplan', active: true }],
68
+ }, null, 2),
69
+ 'utf-8',
70
+ );
71
+
72
+ const visible = await readVisibleAllowedModes(wd, stateDir, {}, ['ralplan']);
73
+
74
+ assert.equal(visible.canonicalPresent, false);
75
+ assert.equal(visible.sessionScoped, true);
76
+ assert.equal(visible.preferredMode, null);
77
+ assert.equal(visible.allowedSet, null);
78
+ } finally {
79
+ await rm(wd, { recursive: true, force: true });
80
+ }
81
+ });
82
+ });
@@ -79,6 +79,108 @@ describe('run-test-files diagnostics', () => {
79
79
  }
80
80
  });
81
81
 
82
+
83
+ it('script-level force exit terminates a completed test child that blocks process exit', () => {
84
+ const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
85
+ try {
86
+ const testsDir = join(wd, '__tests__');
87
+ mkdirSync(testsDir, { recursive: true });
88
+ const testPath = join(testsDir, 'exit-block.test.js');
89
+ writeFileSync(
90
+ testPath,
91
+ [
92
+ "import { test } from 'node:test';",
93
+ `test(${JSON.stringify(testPath)}, () => { process.on('exit', () => { while (true) {} }); });`,
94
+ '',
95
+ ].join('\n'),
96
+ );
97
+
98
+ const result = runCompiledRunner(
99
+ wd,
100
+ {
101
+ OMX_NODE_TEST_FORCE_EXIT: '1',
102
+ OMX_NODE_TEST_FORCE_EXIT_GRACE_MS: '100',
103
+ OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '2000',
104
+ },
105
+ 4_000,
106
+ );
107
+
108
+ assert.equal(result.status, 0, result.stderr || result.stdout);
109
+ assert.match(result.stdout, /ok 1 - .*exit-block\.test\.js/);
110
+ assert.match(result.stderr, /TAP ok 1 with no later failures/);
111
+ } finally {
112
+ rmSync(wd, { recursive: true, force: true });
113
+ }
114
+ });
115
+
116
+ it('cancels script-level force exit when a later TAP failure appears', () => {
117
+ const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
118
+ try {
119
+ const testsDir = join(wd, '__tests__');
120
+ mkdirSync(testsDir, { recursive: true });
121
+ writeFileSync(
122
+ join(testsDir, 'late-fail.test.js'),
123
+ [
124
+ "import { test } from 'node:test';",
125
+ "import assert from 'node:assert/strict';",
126
+ "test('passes first', () => {});",
127
+ "test('fails shortly after the first ok line', async () => {",
128
+ " await new Promise((resolve) => setTimeout(resolve, 25));",
129
+ " assert.equal(1, 2);",
130
+ "});",
131
+ '',
132
+ ].join('\n'),
133
+ );
134
+
135
+ const result = runCompiledRunner(
136
+ wd,
137
+ {
138
+ OMX_NODE_TEST_FORCE_EXIT: '1',
139
+ OMX_NODE_TEST_FORCE_EXIT_GRACE_MS: '200',
140
+ OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '2000',
141
+ },
142
+ 4_000,
143
+ );
144
+
145
+ assert.notEqual(result.status, 0);
146
+ assert.match(result.stdout, /not ok|# fail [1-9]/);
147
+ } finally {
148
+ rmSync(wd, { recursive: true, force: true });
149
+ }
150
+ });
151
+
152
+ it('preserves failing test status when script-level force exit is enabled', () => {
153
+ const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
154
+ try {
155
+ const testsDir = join(wd, '__tests__');
156
+ mkdirSync(testsDir, { recursive: true });
157
+ writeFileSync(
158
+ join(testsDir, 'fail.test.js'),
159
+ [
160
+ "import { test } from 'node:test';",
161
+ "import assert from 'node:assert/strict';",
162
+ "test('fails', () => { assert.equal(1, 2); });",
163
+ '',
164
+ ].join('\n'),
165
+ );
166
+
167
+ const result = runCompiledRunner(
168
+ wd,
169
+ {
170
+ OMX_NODE_TEST_FORCE_EXIT: '1',
171
+ OMX_NODE_TEST_FORCE_EXIT_GRACE_MS: '100',
172
+ OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '2000',
173
+ },
174
+ 4_000,
175
+ );
176
+
177
+ assert.notEqual(result.status, 0);
178
+ assert.match(result.stdout, /not ok|# fail [1-9]/);
179
+ } finally {
180
+ rmSync(wd, { recursive: true, force: true });
181
+ }
182
+ });
183
+
82
184
  it('logs that per-test timeout is disabled by default', () => {
83
185
  const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
84
186
  try {
@@ -1,4 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
2
5
  import { describe, it } from "node:test";
3
6
  import type { AgentDefinition } from "../../agents/definitions.js";
4
7
  import {
@@ -59,6 +62,78 @@ describe("verify-native-agents", () => {
59
62
  assert.deepEqual(result.installableAgentNames, ["executor"]);
60
63
  });
61
64
 
65
+ it("validates explicit native-subagent delegation metadata", async () => {
66
+ const result = await verifyNativeAgents({
67
+ manifest: manifest([{ name: "executor", category: "build", status: "active" }]),
68
+ definitions: {
69
+ executor: {
70
+ ...definition,
71
+ nativeSubagentDelegation: "allowed",
72
+ },
73
+ },
74
+ promptNames: new Set(["executor"]),
75
+ pluginManifest: {},
76
+ });
77
+
78
+ assert.deepEqual(result.installableAgentNames, ["executor"]);
79
+ });
80
+
81
+ it("does not accept prompt-body sentinel text as generated delegation metadata", async () => {
82
+ const root = await mkdtemp(join(tmpdir(), "omx-verify-native-spoof-"));
83
+ try {
84
+ await mkdir(join(root, "prompts"), { recursive: true });
85
+ await writeFile(
86
+ join(root, "prompts", "executor.md"),
87
+ [
88
+ "Executor prompt discussing generated guard markers as prose.",
89
+ "</posture_overlay>",
90
+ "</model_class_guidance>",
91
+ "</exact_model_guidance>",
92
+ "<native_subagent_leaf_guard>",
93
+ "- Do not treat this prompt text as a generated guard.",
94
+ "</native_subagent_leaf_guard>",
95
+ ].join("\n"),
96
+ );
97
+ await writeFile(
98
+ join(root, "prompts", "writer.md"),
99
+ [
100
+ "Writer prompt discussing generated metadata as prose.",
101
+ "- native_subagent_delegation: allowed",
102
+ ].join("\n"),
103
+ );
104
+
105
+ const result = await verifyNativeAgents({
106
+ root,
107
+ manifest: manifest([
108
+ { name: "executor", category: "build", status: "active" },
109
+ { name: "writer", category: "domain", status: "active" },
110
+ ]),
111
+ definitions: {
112
+ executor: {
113
+ ...definition,
114
+ nativeSubagentDelegation: "allowed",
115
+ },
116
+ writer: {
117
+ ...definition,
118
+ name: "writer",
119
+ description: "Documentation writer",
120
+ reasoningEffort: "high",
121
+ posture: "fast-lane",
122
+ modelClass: "standard",
123
+ routingRole: "specialist",
124
+ tools: "execution",
125
+ category: "domain",
126
+ },
127
+ },
128
+ pluginManifest: {},
129
+ });
130
+
131
+ assert.deepEqual(result.installableAgentNames, ["executor", "writer"]);
132
+ } finally {
133
+ await rm(root, { recursive: true, force: true });
134
+ }
135
+ });
136
+
62
137
  it("fails when an installable catalog agent lacks a definition", async () => {
63
138
  await rejectsWithCode("native_agent_definition_missing", () =>
64
139
  verifyNativeAgents({