oh-my-codex 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -1
  4. package/crates/omx-api/src/lib.rs +3 -3
  5. package/crates/omx-api/tests/cli.rs +4 -4
  6. package/crates/omx-explore/src/main.rs +4 -4
  7. package/crates/omx-sparkshell/src/codex_bridge.rs +2 -2
  8. package/dist/agents/__tests__/definitions.test.js +3 -3
  9. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  10. package/dist/agents/__tests__/native-config.test.js +71 -42
  11. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  12. package/dist/agents/definitions.d.ts +1 -1
  13. package/dist/agents/definitions.d.ts.map +1 -1
  14. package/dist/agents/definitions.js +3 -3
  15. package/dist/agents/definitions.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -2
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js +7 -5
  19. package/dist/agents/native-config.js.map +1 -1
  20. package/dist/autopilot/__tests__/planner-routing.test.js +19 -12
  21. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -1
  22. package/dist/autopilot/__tests__/ralplan-gate.test.js +151 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -1
  25. package/dist/autopilot/planner-routing.js +4 -2
  26. package/dist/autopilot/planner-routing.js.map +1 -1
  27. package/dist/autopilot/ralplan-gate.d.ts +1 -0
  28. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  29. package/dist/autopilot/ralplan-gate.js +38 -2
  30. package/dist/autopilot/ralplan-gate.js.map +1 -1
  31. package/dist/autoresearch/runtime.d.ts +2 -0
  32. package/dist/autoresearch/runtime.d.ts.map +1 -1
  33. package/dist/autoresearch/runtime.js +8 -0
  34. package/dist/autoresearch/runtime.js.map +1 -1
  35. package/dist/capabilities/__tests__/lockfile.test.d.ts +2 -0
  36. package/dist/capabilities/__tests__/lockfile.test.d.ts.map +1 -0
  37. package/dist/capabilities/__tests__/lockfile.test.js +115 -0
  38. package/dist/capabilities/__tests__/lockfile.test.js.map +1 -0
  39. package/dist/capabilities/lockfile.d.ts +102 -0
  40. package/dist/capabilities/lockfile.d.ts.map +1 -0
  41. package/dist/capabilities/lockfile.js +334 -0
  42. package/dist/capabilities/lockfile.js.map +1 -0
  43. package/dist/cli/__tests__/agents.test.js +5 -5
  44. package/dist/cli/__tests__/agents.test.js.map +1 -1
  45. package/dist/cli/__tests__/capabilities.test.d.ts +2 -0
  46. package/dist/cli/__tests__/capabilities.test.d.ts.map +1 -0
  47. package/dist/cli/__tests__/capabilities.test.js +135 -0
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -0
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +237 -6
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js +9 -9
  52. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
  54. package/dist/cli/__tests__/doctor-spark-routing.test.js +22 -6
  55. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  56. package/dist/cli/__tests__/index.test.js +53 -18
  57. package/dist/cli/__tests__/index.test.js.map +1 -1
  58. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
  59. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  60. package/dist/cli/__tests__/resume.test.js +95 -6
  61. package/dist/cli/__tests__/resume.test.js.map +1 -1
  62. package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
  63. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  64. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
  65. package/dist/cli/__tests__/setup-refresh.test.js +29 -7
  66. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  67. package/dist/cli/__tests__/uninstall.test.js +4 -4
  68. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  69. package/dist/cli/agents.js +1 -1
  70. package/dist/cli/agents.js.map +1 -1
  71. package/dist/cli/capabilities.d.ts +3 -0
  72. package/dist/cli/capabilities.d.ts.map +1 -0
  73. package/dist/cli/capabilities.js +118 -0
  74. package/dist/cli/capabilities.js.map +1 -0
  75. package/dist/cli/doctor.d.ts +1 -1
  76. package/dist/cli/doctor.d.ts.map +1 -1
  77. package/dist/cli/doctor.js +9 -4
  78. package/dist/cli/doctor.js.map +1 -1
  79. package/dist/cli/index.d.ts +7 -4
  80. package/dist/cli/index.d.ts.map +1 -1
  81. package/dist/cli/index.js +77 -19
  82. package/dist/cli/index.js.map +1 -1
  83. package/dist/cli/plugin-marketplace.d.ts +1 -0
  84. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  85. package/dist/cli/plugin-marketplace.js +31 -0
  86. package/dist/cli/plugin-marketplace.js.map +1 -1
  87. package/dist/cli/setup.d.ts.map +1 -1
  88. package/dist/cli/setup.js +7 -4
  89. package/dist/cli/setup.js.map +1 -1
  90. package/dist/config/__tests__/generator-idempotent.test.js +23 -23
  91. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  92. package/dist/config/__tests__/generator-notify.test.js +9 -9
  93. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  94. package/dist/config/__tests__/models.test.js +51 -26
  95. package/dist/config/__tests__/models.test.js.map +1 -1
  96. package/dist/config/generator.d.ts +1 -1
  97. package/dist/config/generator.d.ts.map +1 -1
  98. package/dist/config/models.d.ts +16 -6
  99. package/dist/config/models.d.ts.map +1 -1
  100. package/dist/config/models.js +19 -8
  101. package/dist/config/models.js.map +1 -1
  102. package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
  103. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  104. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  105. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
  106. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  107. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  108. package/dist/leader/__tests__/contract.test.js +108 -1
  109. package/dist/leader/__tests__/contract.test.js.map +1 -1
  110. package/dist/leader/contract.d.ts +26 -0
  111. package/dist/leader/contract.d.ts.map +1 -1
  112. package/dist/leader/contract.js +170 -0
  113. package/dist/leader/contract.js.map +1 -1
  114. package/dist/modes/base.d.ts +1 -1
  115. package/dist/modes/base.d.ts.map +1 -1
  116. package/dist/pipeline/__tests__/stages.test.js +27 -1
  117. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  118. package/dist/pipeline/stages/ralplan.d.ts +2 -1
  119. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  120. package/dist/pipeline/stages/ralplan.js +1 -0
  121. package/dist/pipeline/stages/ralplan.js.map +1 -1
  122. package/dist/pipeline/stages/ultragoal.d.ts +5 -1
  123. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  124. package/dist/pipeline/stages/ultragoal.js +14 -4
  125. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  126. package/dist/ralplan/__tests__/runtime.test.js +132 -0
  127. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  128. package/dist/ralplan/runtime.d.ts +17 -0
  129. package/dist/ralplan/runtime.d.ts.map +1 -1
  130. package/dist/ralplan/runtime.js +97 -1
  131. package/dist/ralplan/runtime.js.map +1 -1
  132. package/dist/scripts/__tests__/codex-native-hook.test.js +412 -0
  133. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  134. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  135. package/dist/scripts/codex-native-hook.js +238 -12
  136. package/dist/scripts/codex-native-hook.js.map +1 -1
  137. package/dist/state/__tests__/operations.test.js +450 -0
  138. package/dist/state/__tests__/operations.test.js.map +1 -1
  139. package/dist/state/__tests__/workflow-transition.test.js +14 -0
  140. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  141. package/dist/state/operations.d.ts.map +1 -1
  142. package/dist/state/operations.js +72 -11
  143. package/dist/state/operations.js.map +1 -1
  144. package/dist/state/terminal-normalization.d.ts +8 -0
  145. package/dist/state/terminal-normalization.d.ts.map +1 -0
  146. package/dist/state/terminal-normalization.js +127 -0
  147. package/dist/state/terminal-normalization.js.map +1 -0
  148. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  149. package/dist/state/workflow-transition-reconcile.js +3 -1
  150. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  151. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  152. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  153. package/dist/team/__tests__/model-contract.test.js +25 -25
  154. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  155. package/dist/team/__tests__/runtime.test.js +28 -28
  156. package/dist/team/__tests__/runtime.test.js.map +1 -1
  157. package/dist/team/__tests__/scaling.test.js +7 -7
  158. package/dist/team/__tests__/scaling.test.js.map +1 -1
  159. package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
  160. package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
  161. package/dist/team/__tests__/tmux-session.test.js +12 -12
  162. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  163. package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
  164. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  165. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  166. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  167. package/dist/team/model-contract.d.ts +1 -1
  168. package/dist/team/model-contract.d.ts.map +1 -1
  169. package/dist/team/runtime.d.ts.map +1 -1
  170. package/dist/team/runtime.js +11 -0
  171. package/dist/team/runtime.js.map +1 -1
  172. package/dist/team/worker-bootstrap.d.ts +2 -0
  173. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  174. package/dist/team/worker-bootstrap.js +6 -1
  175. package/dist/team/worker-bootstrap.js.map +1 -1
  176. package/dist/ultragoal/__tests__/artifacts.test.js +68 -1
  177. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  178. package/dist/ultragoal/artifacts.d.ts +5 -1
  179. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  180. package/dist/ultragoal/artifacts.js +41 -9
  181. package/dist/ultragoal/artifacts.js.map +1 -1
  182. package/dist/utils/__tests__/agents-model-table.test.js +7 -7
  183. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  184. package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
  185. package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
  186. package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
  187. package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
  188. package/dist/utils/worktree-tool-context.d.ts +25 -0
  189. package/dist/utils/worktree-tool-context.d.ts.map +1 -0
  190. package/dist/utils/worktree-tool-context.js +116 -0
  191. package/dist/utils/worktree-tool-context.js.map +1 -0
  192. package/package.json +1 -1
  193. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  194. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
  195. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
  196. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  197. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
  198. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
  199. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  200. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  201. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
  202. package/prompts/prometheus-strict-metis.md +1 -1
  203. package/skills/ai-slop-cleaner/SKILL.md +1 -1
  204. package/skills/code-review/SKILL.md +1 -1
  205. package/skills/ecomode/references/agent-tiers.md +1 -1
  206. package/skills/omx-setup/SKILL.md +5 -5
  207. package/skills/prometheus-strict/SKILL.md +3 -3
  208. package/skills/ralplan/SKILL.md +1 -1
  209. package/skills/team/SKILL.md +1 -1
  210. package/skills/ultrawork/references/agent-tiers.md +2 -2
  211. package/src/scripts/__tests__/codex-native-hook.test.ts +477 -0
  212. package/src/scripts/codex-native-hook.ts +272 -11
@@ -610,6 +610,58 @@ describe('state operations directory initialization', () => {
610
610
  await rm(wd, { recursive: true, force: true });
611
611
  }
612
612
  });
613
+ it('normalizes terminal deep-interview snapshots by releasing stale locks', async () => {
614
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-di-terminal-normalize-'));
615
+ try {
616
+ const completedAt = '2026-07-09T00:00:00.000Z';
617
+ const writeResponse = await executeStateOperation('state_write', {
618
+ workingDirectory: wd,
619
+ mode: 'deep-interview',
620
+ active: false,
621
+ current_phase: 'cancelled',
622
+ completed_at: completedAt,
623
+ input_lock: {
624
+ active: true,
625
+ owner: 'question-round',
626
+ },
627
+ approval_lock: {
628
+ status: 'pending',
629
+ reviewer: 'user',
630
+ },
631
+ question_enforcement: {
632
+ obligation_id: 'obligation-stale',
633
+ source: 'omx-question',
634
+ status: 'pending',
635
+ lifecycle_outcome: 'askuserQuestion',
636
+ requested_at: '2026-07-08T23:59:00.000Z',
637
+ },
638
+ });
639
+ assert.equal(writeResponse.isError, undefined);
640
+ const readResponse = await executeStateOperation('state_read', {
641
+ workingDirectory: wd,
642
+ mode: 'deep-interview',
643
+ });
644
+ const readBody = readResponse.payload;
645
+ const inputLock = readBody.input_lock;
646
+ const approvalLock = readBody.approval_lock;
647
+ const questionEnforcement = readBody.question_enforcement;
648
+ assert.equal(readBody.active, false);
649
+ assert.equal(readBody.current_phase, 'cancelled');
650
+ assert.equal(readBody.completed_at, completedAt);
651
+ assert.equal(readBody.run_outcome, 'cancelled');
652
+ assert.equal(inputLock.active, false);
653
+ assert.equal(inputLock.status, 'released');
654
+ assert.equal(inputLock.released_at, completedAt);
655
+ assert.equal(approvalLock.active, false);
656
+ assert.equal(approvalLock.status, 'released');
657
+ assert.equal(inputLock.release_reason, 'terminal_state_normalization');
658
+ assert.equal(questionEnforcement.status, 'cleared');
659
+ assert.equal(questionEnforcement.clear_reason, 'abort');
660
+ }
661
+ finally {
662
+ await rm(wd, { recursive: true, force: true });
663
+ }
664
+ });
613
665
  it('writes and reads autoresearch state', async () => {
614
666
  const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autoresearch-'));
615
667
  try {
@@ -1096,6 +1148,285 @@ describe('state operations directory initialization', () => {
1096
1148
  await rm(wd, { recursive: true, force: true });
1097
1149
  }
1098
1150
  });
1151
+ it('allows ralplan unsupported native non-clean recovery without tracker-backed consensus', async () => {
1152
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-unsupported-recovery-'));
1153
+ try {
1154
+ const sessionId = 'sess-ralplan-unsupported-recovery';
1155
+ const stateDir = join(wd, '.omx', 'state');
1156
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1157
+ await mkdir(sessionDir, { recursive: true });
1158
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1159
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1160
+ mode: 'ralplan',
1161
+ active: true,
1162
+ current_phase: 'planning',
1163
+ session_id: sessionId,
1164
+ }, null, 2));
1165
+ const response = await executeStateOperation('state_write', {
1166
+ workingDirectory: wd,
1167
+ session_id: sessionId,
1168
+ mode: 'ralplan',
1169
+ active: false,
1170
+ current_phase: 'blocked',
1171
+ native_subagent_support: {
1172
+ status: 'unsupported',
1173
+ reason: 'multi_agent_v1_unavailable',
1174
+ source: 'post_tool_failure',
1175
+ },
1176
+ });
1177
+ assert.equal(response.isError, undefined);
1178
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1179
+ assert.equal(state.active, false);
1180
+ assert.equal(state.current_phase, 'blocked');
1181
+ assert.equal(state.ralplan_consensus_gate, undefined);
1182
+ }
1183
+ finally {
1184
+ await rm(wd, { recursive: true, force: true });
1185
+ }
1186
+ });
1187
+ it('rejects ralplan clean complete without tracker-backed native consensus after unsupported recovery support', async () => {
1188
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-clean-still-strict-'));
1189
+ try {
1190
+ const sessionId = 'sess-ralplan-clean-still-strict';
1191
+ const stateDir = join(wd, '.omx', 'state');
1192
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1193
+ await mkdir(sessionDir, { recursive: true });
1194
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1195
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1196
+ mode: 'ralplan',
1197
+ active: true,
1198
+ current_phase: 'planning',
1199
+ session_id: sessionId,
1200
+ native_subagent_support: {
1201
+ status: 'unsupported',
1202
+ reason: 'multi_agent_v1_unavailable',
1203
+ source: 'post_tool_failure',
1204
+ },
1205
+ }, null, 2));
1206
+ const response = await executeStateOperation('state_write', {
1207
+ workingDirectory: wd,
1208
+ session_id: sessionId,
1209
+ mode: 'ralplan',
1210
+ active: false,
1211
+ current_phase: 'complete',
1212
+ status: 'complete',
1213
+ });
1214
+ assert.equal(response.isError, true);
1215
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1216
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1217
+ assert.equal(state.active, true);
1218
+ assert.equal(state.current_phase, 'planning');
1219
+ }
1220
+ finally {
1221
+ await rm(wd, { recursive: true, force: true });
1222
+ }
1223
+ });
1224
+ it('rejects ralplan clean complete with unsupported evidence even when native consensus is valid', async () => {
1225
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-clean-unsupported-valid-consensus-deny-'));
1226
+ try {
1227
+ const sessionId = 'sess-ralplan-clean-unsupported-valid-consensus-deny';
1228
+ await writeNativeSubagentTracking(wd, sessionId);
1229
+ const stateDir = join(wd, '.omx', 'state');
1230
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1231
+ await mkdir(sessionDir, { recursive: true });
1232
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1233
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1234
+ mode: 'ralplan',
1235
+ active: true,
1236
+ current_phase: 'planning',
1237
+ session_id: sessionId,
1238
+ }, null, 2));
1239
+ const response = await executeStateOperation('state_write', {
1240
+ workingDirectory: wd,
1241
+ session_id: sessionId,
1242
+ mode: 'ralplan',
1243
+ active: false,
1244
+ current_phase: 'complete',
1245
+ status: 'complete',
1246
+ native_subagent_support: {
1247
+ status: 'unsupported',
1248
+ reason: 'multi_agent_v1_unavailable',
1249
+ source: 'post_tool_failure',
1250
+ },
1251
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1252
+ });
1253
+ assert.equal(response.isError, true);
1254
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1255
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1256
+ assert.equal(state.active, true);
1257
+ assert.equal(state.current_phase, 'planning');
1258
+ }
1259
+ finally {
1260
+ await rm(wd, { recursive: true, force: true });
1261
+ }
1262
+ });
1263
+ it('rejects ralplan clean complete with handoff-artifact unsupported evidence', async () => {
1264
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-clean-handoff-unsupported-deny-'));
1265
+ try {
1266
+ const sessionId = 'sess-ralplan-clean-handoff-unsupported-deny';
1267
+ await writeNativeSubagentTracking(wd, sessionId);
1268
+ const stateDir = join(wd, '.omx', 'state');
1269
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1270
+ await mkdir(sessionDir, { recursive: true });
1271
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1272
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1273
+ mode: 'ralplan',
1274
+ active: true,
1275
+ current_phase: 'planning',
1276
+ session_id: sessionId,
1277
+ }, null, 2));
1278
+ const response = await executeStateOperation('state_write', {
1279
+ workingDirectory: wd,
1280
+ session_id: sessionId,
1281
+ mode: 'ralplan',
1282
+ active: false,
1283
+ current_phase: 'complete',
1284
+ status: 'complete',
1285
+ handoff_artifacts: {
1286
+ ralplan: {
1287
+ native_subagent_support: {
1288
+ status: 'unsupported',
1289
+ reason: 'multi_agent_v1_unavailable',
1290
+ source: 'post_tool_failure',
1291
+ },
1292
+ },
1293
+ },
1294
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1295
+ });
1296
+ assert.equal(response.isError, true);
1297
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1298
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1299
+ assert.equal(state.active, true);
1300
+ assert.equal(state.current_phase, 'planning');
1301
+ }
1302
+ finally {
1303
+ await rm(wd, { recursive: true, force: true });
1304
+ }
1305
+ });
1306
+ it('rejects ralplan clean complete with nested handoff-artifact unsupported evidence', async () => {
1307
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-clean-nested-handoff-unsupported-deny-'));
1308
+ try {
1309
+ const sessionId = 'sess-ralplan-clean-nested-handoff-unsupported-deny';
1310
+ await writeNativeSubagentTracking(wd, sessionId);
1311
+ const stateDir = join(wd, '.omx', 'state');
1312
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1313
+ await mkdir(sessionDir, { recursive: true });
1314
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1315
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1316
+ mode: 'ralplan',
1317
+ active: true,
1318
+ current_phase: 'planning',
1319
+ session_id: sessionId,
1320
+ }, null, 2));
1321
+ const response = await executeStateOperation('state_write', {
1322
+ workingDirectory: wd,
1323
+ session_id: sessionId,
1324
+ mode: 'ralplan',
1325
+ active: false,
1326
+ current_phase: 'complete',
1327
+ status: 'complete',
1328
+ state: {
1329
+ handoff_artifacts: {
1330
+ ralplan: {
1331
+ native_subagent_support: {
1332
+ status: 'unsupported',
1333
+ reason: 'multi_agent_v1_unavailable',
1334
+ source: 'post_tool_failure',
1335
+ },
1336
+ },
1337
+ },
1338
+ },
1339
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1340
+ });
1341
+ assert.equal(response.isError, true);
1342
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1343
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1344
+ assert.equal(state.active, true);
1345
+ assert.equal(state.current_phase, 'planning');
1346
+ }
1347
+ finally {
1348
+ await rm(wd, { recursive: true, force: true });
1349
+ }
1350
+ });
1351
+ it('rejects ralplan clean complete with handoff-root unsupported evidence', async () => {
1352
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-clean-handoff-root-unsupported-deny-'));
1353
+ try {
1354
+ const sessionId = 'sess-ralplan-clean-handoff-root-unsupported-deny';
1355
+ await writeNativeSubagentTracking(wd, sessionId);
1356
+ const stateDir = join(wd, '.omx', 'state');
1357
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1358
+ await mkdir(sessionDir, { recursive: true });
1359
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1360
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1361
+ mode: 'ralplan',
1362
+ active: true,
1363
+ current_phase: 'planning',
1364
+ session_id: sessionId,
1365
+ }, null, 2));
1366
+ const response = await executeStateOperation('state_write', {
1367
+ workingDirectory: wd,
1368
+ session_id: sessionId,
1369
+ mode: 'ralplan',
1370
+ active: false,
1371
+ current_phase: 'complete',
1372
+ status: 'complete',
1373
+ handoff_artifacts: {
1374
+ native_subagent_support: {
1375
+ status: 'unsupported',
1376
+ reason: 'multi_agent_v1_unavailable',
1377
+ source: 'post_tool_failure',
1378
+ },
1379
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1380
+ },
1381
+ });
1382
+ assert.equal(response.isError, true);
1383
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1384
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1385
+ assert.equal(state.active, true);
1386
+ assert.equal(state.current_phase, 'planning');
1387
+ }
1388
+ finally {
1389
+ await rm(wd, { recursive: true, force: true });
1390
+ }
1391
+ });
1392
+ it('rejects ralplan clean status alias with unsupported evidence even when native consensus is valid', async () => {
1393
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-status-alias-unsupported-deny-'));
1394
+ try {
1395
+ const sessionId = 'sess-ralplan-status-alias-unsupported-deny';
1396
+ await writeNativeSubagentTracking(wd, sessionId);
1397
+ const stateDir = join(wd, '.omx', 'state');
1398
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1399
+ await mkdir(sessionDir, { recursive: true });
1400
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd }, null, 2));
1401
+ await writeFile(join(sessionDir, 'ralplan-state.json'), JSON.stringify({
1402
+ mode: 'ralplan',
1403
+ active: true,
1404
+ current_phase: 'planning',
1405
+ session_id: sessionId,
1406
+ }, null, 2));
1407
+ const response = await executeStateOperation('state_write', {
1408
+ workingDirectory: wd,
1409
+ session_id: sessionId,
1410
+ mode: 'ralplan',
1411
+ active: false,
1412
+ status: 'complete',
1413
+ native_subagent_support: {
1414
+ status: 'unsupported',
1415
+ reason: 'multi_agent_v1_unavailable',
1416
+ source: 'post_tool_failure',
1417
+ },
1418
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
1419
+ });
1420
+ assert.equal(response.isError, true);
1421
+ assert.match(String(response.payload.error ?? ''), /Cannot complete ralplan cleanly while native subagent support is unavailable/);
1422
+ const state = JSON.parse(await readFile(join(sessionDir, 'ralplan-state.json'), 'utf-8'));
1423
+ assert.equal(state.active, true);
1424
+ assert.equal(state.current_phase, 'planning');
1425
+ }
1426
+ finally {
1427
+ await rm(wd, { recursive: true, force: true });
1428
+ }
1429
+ });
1099
1430
  it('normalizes currentPhase before gating ralplan terminal writes', async () => {
1100
1431
  const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-ralplan-current-phase-alias-'));
1101
1432
  try {
@@ -3329,6 +3660,40 @@ describe('state operations directory initialization', () => {
3329
3660
  await rm(wd, { recursive: true, force: true });
3330
3661
  }
3331
3662
  });
3663
+ it('allows Autopilot ralplan unsupported native non-clean recovery', async () => {
3664
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-unsupported-recovery-'));
3665
+ try {
3666
+ await withOmxRootEnv(wd, async () => {
3667
+ const sessionId = 'sess-autopilot-ralplan-unsupported-recovery';
3668
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
3669
+ await mkdir(sessionDir, { recursive: true });
3670
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
3671
+ active: true,
3672
+ mode: 'autopilot',
3673
+ current_phase: 'ralplan',
3674
+ }, null, 2));
3675
+ const response = await executeStateOperation('state_write', {
3676
+ workingDirectory: wd,
3677
+ session_id: sessionId,
3678
+ mode: 'autopilot',
3679
+ active: false,
3680
+ current_phase: 'blocked',
3681
+ native_subagent_support: {
3682
+ status: 'unsupported',
3683
+ reason: 'multi_agent_v1_unavailable',
3684
+ source: 'post_tool_failure',
3685
+ },
3686
+ });
3687
+ assert.equal(response.isError, undefined);
3688
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
3689
+ assert.equal(state.active, false);
3690
+ assert.equal(state.current_phase, 'blocked');
3691
+ });
3692
+ }
3693
+ finally {
3694
+ await rm(wd, { recursive: true, force: true });
3695
+ }
3696
+ });
3332
3697
  it('denies Autopilot ralplan to ultragoal self-write with codex_exec consensus evidence', async () => {
3333
3698
  const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-native-deny-'));
3334
3699
  try {
@@ -3367,6 +3732,91 @@ describe('state operations directory initialization', () => {
3367
3732
  await rm(wd, { recursive: true, force: true });
3368
3733
  }
3369
3734
  });
3735
+ it('denies Autopilot ralplan to ultragoal when unsupported native evidence is present', async () => {
3736
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-unsupported-ultragoal-deny-'));
3737
+ try {
3738
+ await withOmxRootEnv(wd, async () => {
3739
+ const sessionId = 'sess-autopilot-ralplan-unsupported-ultragoal-deny';
3740
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
3741
+ await mkdir(sessionDir, { recursive: true });
3742
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
3743
+ active: true,
3744
+ mode: 'autopilot',
3745
+ current_phase: 'ralplan',
3746
+ native_subagent_support: {
3747
+ status: 'unsupported',
3748
+ reason: 'native_subagents_unsupported',
3749
+ source: 'post_tool_failure',
3750
+ },
3751
+ }, null, 2));
3752
+ const response = await executeStateOperation('state_write', {
3753
+ workingDirectory: wd,
3754
+ session_id: sessionId,
3755
+ mode: 'autopilot',
3756
+ active: true,
3757
+ current_phase: 'ultragoal',
3758
+ native_subagent_support: {
3759
+ status: 'unsupported',
3760
+ reason: 'native_subagents_unsupported',
3761
+ source: 'post_tool_failure',
3762
+ },
3763
+ });
3764
+ assert.equal(response.isError, true);
3765
+ assert.match(String(response.payload.error || ''), /Cannot transition ralplan -> ultragoal/i);
3766
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
3767
+ assert.equal(state.current_phase, 'ralplan');
3768
+ });
3769
+ }
3770
+ finally {
3771
+ await rm(wd, { recursive: true, force: true });
3772
+ }
3773
+ });
3774
+ it('denies Autopilot ralplan to ultragoal with unsupported evidence even when native consensus is valid', async () => {
3775
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-autopilot-ralplan-unsupported-valid-consensus-deny-'));
3776
+ try {
3777
+ await withOmxRootEnv(wd, async () => {
3778
+ const sessionId = 'sess-autopilot-ralplan-unsupported-valid-consensus-deny';
3779
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', sessionId);
3780
+ await mkdir(sessionDir, { recursive: true });
3781
+ await writeNativeSubagentTracking(wd, sessionId);
3782
+ await writeFile(join(sessionDir, 'autopilot-state.json'), JSON.stringify({
3783
+ active: true,
3784
+ mode: 'autopilot',
3785
+ current_phase: 'ralplan',
3786
+ native_subagent_support: {
3787
+ status: 'unsupported',
3788
+ reason: 'multi_agent_v1_unavailable',
3789
+ source: 'post_tool_failure',
3790
+ },
3791
+ state: {
3792
+ handoff_artifacts: {
3793
+ ralplan: {
3794
+ plan_path: '.omx/plans/prd.md',
3795
+ test_spec_path: '.omx/plans/test-spec.md',
3796
+ },
3797
+ ralplan_consensus_gate: ralplanConsensusGate(sessionId, 'native_subagent'),
3798
+ },
3799
+ },
3800
+ }, null, 2));
3801
+ const response = await executeStateOperation('state_write', {
3802
+ workingDirectory: wd,
3803
+ session_id: sessionId,
3804
+ mode: 'autopilot',
3805
+ active: true,
3806
+ current_phase: 'ultragoal',
3807
+ });
3808
+ assert.equal(response.isError, true);
3809
+ const error = String(response.payload.error || '');
3810
+ assert.match(error, /terminalize non-clean/);
3811
+ assert.match(error, /blocked\/cancelled\/failed/);
3812
+ const state = JSON.parse(await readFile(join(sessionDir, 'autopilot-state.json'), 'utf-8'));
3813
+ assert.equal(state.current_phase, 'ralplan');
3814
+ });
3815
+ }
3816
+ finally {
3817
+ await rm(wd, { recursive: true, force: true });
3818
+ }
3819
+ });
3370
3820
  for (const { lane, architectVerdict, criticVerdict } of [
3371
3821
  { lane: 'architect', architectVerdict: 'iterate', criticVerdict: 'approve' },
3372
3822
  { lane: 'critic', architectVerdict: 'approve', criticVerdict: 'iterate' },