oh-my-codex 0.19.0 → 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 (245) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +8 -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__/mission.test.d.ts +2 -0
  59. package/dist/cli/__tests__/mission.test.d.ts.map +1 -0
  60. package/dist/cli/__tests__/mission.test.js +239 -0
  61. package/dist/cli/__tests__/mission.test.js.map +1 -0
  62. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +3 -3
  63. package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -1
  64. package/dist/cli/__tests__/resume.test.js +95 -6
  65. package/dist/cli/__tests__/resume.test.js.map +1 -1
  66. package/dist/cli/__tests__/session-scoped-runtime.test.js +67 -0
  67. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  68. package/dist/cli/__tests__/setup-install-mode.test.js +46 -18
  69. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  70. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +3 -3
  71. package/dist/cli/__tests__/setup-refresh.test.js +29 -7
  72. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  73. package/dist/cli/__tests__/ultragoal.test.js +1 -1
  74. package/dist/cli/__tests__/uninstall.test.js +4 -4
  75. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  76. package/dist/cli/agents.js +1 -1
  77. package/dist/cli/agents.js.map +1 -1
  78. package/dist/cli/capabilities.d.ts +3 -0
  79. package/dist/cli/capabilities.d.ts.map +1 -0
  80. package/dist/cli/capabilities.js +118 -0
  81. package/dist/cli/capabilities.js.map +1 -0
  82. package/dist/cli/doctor.d.ts +1 -1
  83. package/dist/cli/doctor.d.ts.map +1 -1
  84. package/dist/cli/doctor.js +9 -4
  85. package/dist/cli/doctor.js.map +1 -1
  86. package/dist/cli/index.d.ts +7 -4
  87. package/dist/cli/index.d.ts.map +1 -1
  88. package/dist/cli/index.js +107 -23
  89. package/dist/cli/index.js.map +1 -1
  90. package/dist/cli/mission.d.ts +36 -0
  91. package/dist/cli/mission.d.ts.map +1 -0
  92. package/dist/cli/mission.js +443 -0
  93. package/dist/cli/mission.js.map +1 -0
  94. package/dist/cli/plugin-marketplace.d.ts +1 -0
  95. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  96. package/dist/cli/plugin-marketplace.js +31 -0
  97. package/dist/cli/plugin-marketplace.js.map +1 -1
  98. package/dist/cli/setup.d.ts.map +1 -1
  99. package/dist/cli/setup.js +7 -4
  100. package/dist/cli/setup.js.map +1 -1
  101. package/dist/config/__tests__/generator-idempotent.test.js +23 -23
  102. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  103. package/dist/config/__tests__/generator-notify.test.js +9 -9
  104. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  105. package/dist/config/__tests__/models.test.js +51 -26
  106. package/dist/config/__tests__/models.test.js.map +1 -1
  107. package/dist/config/generator.d.ts +1 -1
  108. package/dist/config/generator.d.ts.map +1 -1
  109. package/dist/config/models.d.ts +16 -6
  110. package/dist/config/models.d.ts.map +1 -1
  111. package/dist/config/models.js +19 -8
  112. package/dist/config/models.js.map +1 -1
  113. package/dist/hooks/__tests__/keyword-detector.test.js +12 -4
  114. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  116. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +2 -2
  117. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  118. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  119. package/dist/hud/__tests__/state.test.js +31 -6
  120. package/dist/hud/__tests__/state.test.js.map +1 -1
  121. package/dist/hud/state.d.ts.map +1 -1
  122. package/dist/hud/state.js +7 -3
  123. package/dist/hud/state.js.map +1 -1
  124. package/dist/leader/__tests__/contract.test.js +108 -1
  125. package/dist/leader/__tests__/contract.test.js.map +1 -1
  126. package/dist/leader/contract.d.ts +26 -0
  127. package/dist/leader/contract.d.ts.map +1 -1
  128. package/dist/leader/contract.js +170 -0
  129. package/dist/leader/contract.js.map +1 -1
  130. package/dist/mcp/__tests__/state-paths.test.js +32 -1
  131. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  132. package/dist/mcp/state-paths.d.ts +4 -0
  133. package/dist/mcp/state-paths.d.ts.map +1 -1
  134. package/dist/mcp/state-paths.js +6 -15
  135. package/dist/mcp/state-paths.js.map +1 -1
  136. package/dist/modes/base.d.ts +1 -1
  137. package/dist/modes/base.d.ts.map +1 -1
  138. package/dist/pipeline/__tests__/stages.test.js +27 -1
  139. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  140. package/dist/pipeline/stages/ralplan.d.ts +2 -1
  141. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  142. package/dist/pipeline/stages/ralplan.js +1 -0
  143. package/dist/pipeline/stages/ralplan.js.map +1 -1
  144. package/dist/pipeline/stages/ultragoal.d.ts +5 -1
  145. package/dist/pipeline/stages/ultragoal.d.ts.map +1 -1
  146. package/dist/pipeline/stages/ultragoal.js +14 -4
  147. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  148. package/dist/ralplan/__tests__/consensus-gate.test.js +193 -0
  149. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  150. package/dist/ralplan/__tests__/runtime.test.js +132 -0
  151. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  152. package/dist/ralplan/consensus-gate.js +24 -3
  153. package/dist/ralplan/consensus-gate.js.map +1 -1
  154. package/dist/ralplan/runtime.d.ts +17 -0
  155. package/dist/ralplan/runtime.d.ts.map +1 -1
  156. package/dist/ralplan/runtime.js +97 -1
  157. package/dist/ralplan/runtime.js.map +1 -1
  158. package/dist/scripts/__tests__/codex-native-hook.test.js +762 -20
  159. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  160. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  161. package/dist/scripts/codex-native-hook.js +306 -22
  162. package/dist/scripts/codex-native-hook.js.map +1 -1
  163. package/dist/scripts/generate-catalog-docs.js +1 -0
  164. package/dist/scripts/generate-catalog-docs.js.map +1 -1
  165. package/dist/state/__tests__/operations.test.js +737 -0
  166. package/dist/state/__tests__/operations.test.js.map +1 -1
  167. package/dist/state/__tests__/skill-active.test.js +59 -0
  168. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  169. package/dist/state/__tests__/workflow-transition.test.js +14 -0
  170. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  171. package/dist/state/operations.d.ts.map +1 -1
  172. package/dist/state/operations.js +80 -17
  173. package/dist/state/operations.js.map +1 -1
  174. package/dist/state/skill-active.js +3 -3
  175. package/dist/state/skill-active.js.map +1 -1
  176. package/dist/state/terminal-normalization.d.ts +8 -0
  177. package/dist/state/terminal-normalization.d.ts.map +1 -0
  178. package/dist/state/terminal-normalization.js +127 -0
  179. package/dist/state/terminal-normalization.js.map +1 -0
  180. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  181. package/dist/state/workflow-transition-reconcile.js +3 -1
  182. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  183. package/dist/team/__tests__/delegation-policy.test.js +2 -2
  184. package/dist/team/__tests__/delegation-policy.test.js.map +1 -1
  185. package/dist/team/__tests__/model-contract.test.js +25 -25
  186. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  187. package/dist/team/__tests__/runtime.test.js +28 -28
  188. package/dist/team/__tests__/runtime.test.js.map +1 -1
  189. package/dist/team/__tests__/scaling.test.js +7 -7
  190. package/dist/team/__tests__/scaling.test.js.map +1 -1
  191. package/dist/team/__tests__/tmux-claude-workers-demo.test.js +5 -5
  192. package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -1
  193. package/dist/team/__tests__/tmux-session.test.js +12 -12
  194. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  195. package/dist/team/__tests__/worker-bootstrap.test.js +57 -9
  196. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  197. package/dist/team/__tests__/worker-runtime-identity.test.js +3 -3
  198. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  199. package/dist/team/model-contract.d.ts +1 -1
  200. package/dist/team/model-contract.d.ts.map +1 -1
  201. package/dist/team/runtime.d.ts.map +1 -1
  202. package/dist/team/runtime.js +11 -0
  203. package/dist/team/runtime.js.map +1 -1
  204. package/dist/team/worker-bootstrap.d.ts +2 -0
  205. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  206. package/dist/team/worker-bootstrap.js +6 -1
  207. package/dist/team/worker-bootstrap.js.map +1 -1
  208. package/dist/ultragoal/__tests__/artifacts.test.js +82 -5
  209. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  210. package/dist/ultragoal/artifacts.d.ts +5 -1
  211. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  212. package/dist/ultragoal/artifacts.js +111 -14
  213. package/dist/ultragoal/artifacts.js.map +1 -1
  214. package/dist/utils/__tests__/agents-model-table.test.js +7 -7
  215. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  216. package/dist/utils/__tests__/worktree-tool-context.test.d.ts +2 -0
  217. package/dist/utils/__tests__/worktree-tool-context.test.d.ts.map +1 -0
  218. package/dist/utils/__tests__/worktree-tool-context.test.js +118 -0
  219. package/dist/utils/__tests__/worktree-tool-context.test.js.map +1 -0
  220. package/dist/utils/worktree-tool-context.d.ts +25 -0
  221. package/dist/utils/worktree-tool-context.d.ts.map +1 -0
  222. package/dist/utils/worktree-tool-context.js +116 -0
  223. package/dist/utils/worktree-tool-context.js.map +1 -0
  224. package/package.json +1 -1
  225. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  226. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +96 -10
  227. package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +1 -1
  228. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -1
  229. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +5 -5
  230. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +3 -3
  231. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  232. package/plugins/oh-my-codex/skills/team/SKILL.md +1 -1
  233. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +2 -2
  234. package/prompts/prometheus-strict-metis.md +1 -1
  235. package/skills/ai-slop-cleaner/SKILL.md +1 -1
  236. package/skills/code-review/SKILL.md +1 -1
  237. package/skills/ecomode/references/agent-tiers.md +1 -1
  238. package/skills/omx-setup/SKILL.md +5 -5
  239. package/skills/prometheus-strict/SKILL.md +3 -3
  240. package/skills/ralplan/SKILL.md +1 -1
  241. package/skills/team/SKILL.md +1 -1
  242. package/skills/ultrawork/references/agent-tiers.md +2 -2
  243. package/src/scripts/__tests__/codex-native-hook.test.ts +824 -7
  244. package/src/scripts/codex-native-hook.ts +347 -20
  245. package/src/scripts/generate-catalog-docs.ts +1 -0
@@ -1479,6 +1479,166 @@ PY`,
1479
1479
  await rm(cwd, { recursive: true, force: true });
1480
1480
  }
1481
1481
  });
1482
+ it("adds resume-by-id instructions for persisted subagents on SessionStart resume", async () => {
1483
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-reopen-"));
1484
+ try {
1485
+ const stateDir = join(cwd, ".omx", "state");
1486
+ const sessionId = "omx-reopen-session";
1487
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reopen", pid: process.pid });
1488
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1489
+ schemaVersion: 1,
1490
+ sessions: {
1491
+ [sessionId]: {
1492
+ session_id: sessionId,
1493
+ leader_thread_id: "codex-leader-reopen",
1494
+ updated_at: "2026-07-09T00:00:00.000Z",
1495
+ threads: {
1496
+ "codex-leader-reopen": {
1497
+ thread_id: "codex-leader-reopen",
1498
+ kind: "leader",
1499
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1500
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1501
+ turn_count: 1,
1502
+ },
1503
+ "thread-architect-reopen": {
1504
+ thread_id: "thread-architect-reopen",
1505
+ kind: "subagent",
1506
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1507
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1508
+ turn_count: 2,
1509
+ role: "architect",
1510
+ lane_id: "plan-review",
1511
+ scope: "SessionStart reopen",
1512
+ status: "available",
1513
+ last_handoff_summary: "reviewed the restart plan",
1514
+ },
1515
+ },
1516
+ },
1517
+ },
1518
+ });
1519
+ const result = await dispatchCodexNativeHook({
1520
+ hook_event_name: "SessionStart",
1521
+ cwd,
1522
+ session_id: "codex-leader-reopen",
1523
+ source: "resume",
1524
+ }, { cwd, sessionOwnerPid: process.pid });
1525
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
1526
+ assert.match(additionalContext, /\[Persisted subagent reopen\]/);
1527
+ assert.match(additionalContext, /resume_agent\("thread-architect-reopen"\)/);
1528
+ assert.match(additionalContext, /role: architect; lane: plan-review; scope: SessionStart reopen; status: available/);
1529
+ assert.match(additionalContext, /saved subagent ids found: 1/);
1530
+ const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8"));
1531
+ assert.match(tracking.sessions?.[sessionId]?.threads?.["thread-architect-reopen"]?.resume_requested_at ?? "", /^\d{4}-\d{2}-\d{2}T/);
1532
+ }
1533
+ finally {
1534
+ await rm(cwd, { recursive: true, force: true });
1535
+ }
1536
+ });
1537
+ it("does not suggest duplicate same-role subagent spawns when reopen ids exist", async () => {
1538
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-no-duplicates-"));
1539
+ try {
1540
+ const stateDir = join(cwd, ".omx", "state");
1541
+ const sessionId = "omx-reuse-session";
1542
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reuse", pid: process.pid });
1543
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1544
+ schemaVersion: 1,
1545
+ sessions: {
1546
+ [sessionId]: {
1547
+ session_id: sessionId,
1548
+ leader_thread_id: "codex-leader-reuse",
1549
+ updated_at: "2026-07-09T00:00:00.000Z",
1550
+ threads: {
1551
+ "codex-leader-reuse": {
1552
+ thread_id: "codex-leader-reuse",
1553
+ kind: "leader",
1554
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1555
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1556
+ turn_count: 1,
1557
+ },
1558
+ "thread-critic-reuse": {
1559
+ thread_id: "thread-critic-reuse",
1560
+ kind: "subagent",
1561
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1562
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1563
+ turn_count: 1,
1564
+ role: "critic",
1565
+ lane_id: "risk-review",
1566
+ status: "closed",
1567
+ },
1568
+ },
1569
+ },
1570
+ },
1571
+ });
1572
+ const result = await dispatchCodexNativeHook({
1573
+ hook_event_name: "SessionStart",
1574
+ cwd,
1575
+ session_id: "codex-leader-reuse",
1576
+ source: "startup",
1577
+ }, { cwd, sessionOwnerPid: process.pid });
1578
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
1579
+ assert.match(additionalContext, /resume_agent\("thread-critic-reuse"\)/);
1580
+ assert.match(additionalContext, /avoid duplicate same-type subagent spawns/);
1581
+ assert.match(additionalContext, /do not spawn a new agent solely because reopen failed/);
1582
+ assert.doesNotMatch(additionalContext, /spawn.*critic.*replacement/i);
1583
+ }
1584
+ finally {
1585
+ await rm(cwd, { recursive: true, force: true });
1586
+ }
1587
+ });
1588
+ it("surfaces clear warnings for unavailable persisted subagents without spawning replacements", async () => {
1589
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-warning-"));
1590
+ try {
1591
+ const stateDir = join(cwd, ".omx", "state");
1592
+ const sessionId = "omx-warning-session";
1593
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-warning", pid: process.pid });
1594
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1595
+ schemaVersion: 1,
1596
+ sessions: {
1597
+ [sessionId]: {
1598
+ session_id: sessionId,
1599
+ leader_thread_id: "codex-leader-warning",
1600
+ updated_at: "2026-07-09T00:00:00.000Z",
1601
+ threads: {
1602
+ "codex-leader-warning": {
1603
+ thread_id: "codex-leader-warning",
1604
+ kind: "leader",
1605
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1606
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1607
+ turn_count: 1,
1608
+ },
1609
+ "thread-executor-unavailable": {
1610
+ thread_id: "thread-executor-unavailable",
1611
+ kind: "subagent",
1612
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1613
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1614
+ turn_count: 1,
1615
+ role: "executor",
1616
+ lane_id: "implementation",
1617
+ status: "unavailable",
1618
+ resume_failed_at: "2026-07-09T00:02:00.000Z",
1619
+ resume_failure_reason: "Codex reported missing thread id",
1620
+ },
1621
+ },
1622
+ },
1623
+ },
1624
+ });
1625
+ const result = await dispatchCodexNativeHook({
1626
+ hook_event_name: "SessionStart",
1627
+ cwd,
1628
+ session_id: "codex-leader-warning",
1629
+ source: "resume",
1630
+ }, { cwd, sessionOwnerPid: process.pid });
1631
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
1632
+ assert.match(additionalContext, /No compatible saved subagent id is currently marked reopenable/);
1633
+ assert.match(additionalContext, /Reopen warnings:/);
1634
+ assert.match(additionalContext, /thread-executor-unavailable/);
1635
+ assert.match(additionalContext, /last failure: Codex reported missing thread id/);
1636
+ assert.doesNotMatch(additionalContext, /resume_agent\("thread-executor-unavailable"\)/);
1637
+ }
1638
+ finally {
1639
+ await rm(cwd, { recursive: true, force: true });
1640
+ }
1641
+ });
1482
1642
  it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => {
1483
1643
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-"));
1484
1644
  try {
@@ -4150,6 +4310,66 @@ standardMaxRounds = 15
4150
4310
  await rm(cwd, { recursive: true, force: true });
4151
4311
  }
4152
4312
  });
4313
+ it("keeps conductor guidance on autopilot activation after capacity-only native subagent evidence", async () => {
4314
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-capacity-native-"));
4315
+ try {
4316
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
4317
+ await dispatchCodexNativeHook({
4318
+ hook_event_name: "PostToolUse",
4319
+ cwd,
4320
+ session_id: "sess-autopilot-capacity-native",
4321
+ thread_id: "thread-autopilot-capacity-native",
4322
+ turn_id: "turn-autopilot-capacity-native-spawn",
4323
+ tool_name: "multi_agent_v1.spawn_agent",
4324
+ tool_response: { error: "collab spawn failed: agent thread limit reached" },
4325
+ }, { cwd });
4326
+ const result = await dispatchCodexNativeHook({
4327
+ hook_event_name: "UserPromptSubmit",
4328
+ cwd,
4329
+ session_id: "sess-autopilot-capacity-native",
4330
+ thread_id: "thread-autopilot-capacity-native",
4331
+ turn_id: "turn-autopilot-capacity-native-prompt",
4332
+ prompt: "$autopilot implement issue #3078",
4333
+ }, { cwd });
4334
+ assert.equal(result.omxEventName, "keyword-detector");
4335
+ assert.equal(result.skillState?.skill, "autopilot");
4336
+ const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
4337
+ assert.match(message, /Autopilot protocol:/);
4338
+ assert.match(message, /Conductor mode contract:/);
4339
+ assert.match(message, /Golden Rule: When the Main agent is acting in Conductor mode/);
4340
+ assert.match(message, /Conductor reuse and ledger guidance:/);
4341
+ assert.doesNotMatch(message, /Native subagent support is unavailable in this environment/);
4342
+ assert.doesNotMatch(message, /Reason: agent_thread_limit_reached/);
4343
+ }
4344
+ finally {
4345
+ await rm(cwd, { recursive: true, force: true });
4346
+ }
4347
+ });
4348
+ it("omits conductor block and emits unsupported native guidance for unsupported autopilot first-run payload", async () => {
4349
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-unsupported-native-"));
4350
+ try {
4351
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
4352
+ const result = await dispatchCodexNativeHook({
4353
+ hook_event_name: "UserPromptSubmit",
4354
+ cwd,
4355
+ session_id: "sess-autopilot-unsupported-native",
4356
+ thread_id: "thread-autopilot-unsupported-native",
4357
+ turn_id: "turn-autopilot-unsupported-native",
4358
+ prompt: "$autopilot implement issue #3078",
4359
+ capabilities: { native_subagents: false, multi_agent_v1: false },
4360
+ }, { cwd });
4361
+ const message = String(result.outputJson?.hookSpecificOutput?.additionalContext || "");
4362
+ assert.match(message, /Autopilot protocol:/);
4363
+ assert.doesNotMatch(message, /Conductor mode contract:/);
4364
+ assert.doesNotMatch(message, /Conductor reuse and ledger guidance:/);
4365
+ assert.match(message, /Native subagent support is unavailable in this environment/);
4366
+ assert.match(message, /Reason: native_subagents_unsupported/);
4367
+ assert.match(message, /Do not call multi_agent_v1\.close_agent/);
4368
+ }
4369
+ finally {
4370
+ await rm(cwd, { recursive: true, force: true });
4371
+ }
4372
+ });
4153
4373
  it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
4154
4374
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
4155
4375
  try {
@@ -6035,6 +6255,71 @@ exit 0
6035
6255
  await rm(cwd, { recursive: true, force: true });
6036
6256
  }
6037
6257
  });
6258
+ it("does not self-lock PreToolUse after completed deep-interview handoff leaves Autopilot in deep-interview phase", async () => {
6259
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-completed-autopilot-"));
6260
+ try {
6261
+ const stateDir = join(cwd, ".omx", "state");
6262
+ const sessionId = "sess-di-completed-autopilot";
6263
+ const threadId = "thread-di-completed-autopilot";
6264
+ const sessionDir = join(stateDir, "sessions", sessionId);
6265
+ await mkdir(sessionDir, { recursive: true });
6266
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
6267
+ await writeJson(join(sessionDir, "skill-active-state.json"), {
6268
+ version: 1,
6269
+ active: true,
6270
+ skill: "autopilot",
6271
+ keyword: "$autopilot",
6272
+ phase: "deep-interview",
6273
+ initialized_mode: "autopilot",
6274
+ initialized_state_path: `.omx/state/sessions/${sessionId}/autopilot-state.json`,
6275
+ session_id: sessionId,
6276
+ thread_id: threadId,
6277
+ active_skills: [{ skill: "autopilot", phase: "deep-interview", active: true, session_id: sessionId, thread_id: threadId }],
6278
+ });
6279
+ await writeJson(join(sessionDir, "deep-interview-state.json"), {
6280
+ active: false,
6281
+ mode: "deep-interview",
6282
+ current_phase: "completed",
6283
+ session_id: sessionId,
6284
+ thread_id: threadId,
6285
+ deep_interview_gate: {
6286
+ status: "complete",
6287
+ handoff_summary: "Requirements were clarified before the Autopilot handoff.",
6288
+ },
6289
+ });
6290
+ await writeJson(join(sessionDir, "autopilot-state.json"), {
6291
+ active: true,
6292
+ mode: "autopilot",
6293
+ current_phase: "deep-interview",
6294
+ session_id: sessionId,
6295
+ thread_id: threadId,
6296
+ });
6297
+ const implementationEdit = await dispatchCodexNativeHook({
6298
+ hook_event_name: "PreToolUse",
6299
+ cwd,
6300
+ session_id: sessionId,
6301
+ thread_id: threadId,
6302
+ tool_name: "Edit",
6303
+ tool_use_id: "tool-di-completed-autopilot-edit",
6304
+ tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" },
6305
+ }, { cwd });
6306
+ assert.equal(implementationEdit.omxEventName, "pre-tool-use");
6307
+ assert.equal(implementationEdit.outputJson, null);
6308
+ const stateRepair = await dispatchCodexNativeHook({
6309
+ hook_event_name: "PreToolUse",
6310
+ cwd,
6311
+ session_id: sessionId,
6312
+ thread_id: threadId,
6313
+ tool_name: "mcp__omx_state__state_write",
6314
+ tool_use_id: "tool-di-completed-autopilot-state-repair",
6315
+ tool_input: { mode: "autopilot", current_phase: "ralplan", active: true },
6316
+ }, { cwd });
6317
+ assert.equal(stateRepair.outputJson, null);
6318
+ }
6319
+ finally {
6320
+ await rm(cwd, { recursive: true, force: true });
6321
+ }
6322
+ });
6038
6323
  it("allows deep-interview artifact and state writes while blocking implementation Bash writes", async () => {
6039
6324
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-artifact-"));
6040
6325
  try {
@@ -6457,6 +6742,69 @@ exit 0
6457
6742
  }, { cwd });
6458
6743
  assert.equal(protectedWrite.outputJson?.decision, "block", `${filePath} should not be model-writable during deep-interview`);
6459
6744
  }
6745
+ const runtimeRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-runtime-root-"));
6746
+ const previousOmxRoot = process.env.OMX_ROOT;
6747
+ const runtimeSessionId = "sess-di-runtime-artifact";
6748
+ const runtimeStateDir = join(runtimeRoot, ".omx", "state");
6749
+ const runtimeSessionDir = join(runtimeStateDir, "sessions", runtimeSessionId);
6750
+ try {
6751
+ process.env.OMX_ROOT = runtimeRoot;
6752
+ await mkdir(runtimeSessionDir, { recursive: true });
6753
+ await writeJson(join(runtimeStateDir, "session.json"), { session_id: runtimeSessionId, cwd });
6754
+ await writeJson(join(runtimeSessionDir, "skill-active-state.json"), {
6755
+ version: 1,
6756
+ active: true,
6757
+ skill: "deep-interview",
6758
+ phase: "planning",
6759
+ session_id: runtimeSessionId,
6760
+ thread_id: threadId,
6761
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: runtimeSessionId, thread_id: threadId }],
6762
+ });
6763
+ await writeJson(join(runtimeSessionDir, "deep-interview-state.json"), {
6764
+ active: true,
6765
+ mode: "deep-interview",
6766
+ current_phase: "intent-first",
6767
+ session_id: runtimeSessionId,
6768
+ thread_id: threadId,
6769
+ });
6770
+ const allowedRuntimeStateWrite = await dispatchCodexNativeHook({
6771
+ hook_event_name: "PreToolUse",
6772
+ cwd,
6773
+ session_id: runtimeSessionId,
6774
+ thread_id: threadId,
6775
+ tool_name: "Write",
6776
+ tool_use_id: "tool-di-runtime-state-write",
6777
+ tool_input: { file_path: join(runtimeSessionDir, "deep-interview-state.json"), content: "{}\n" },
6778
+ }, { cwd });
6779
+ assert.equal(allowedRuntimeStateWrite.outputJson, null);
6780
+ const blockedRuntimeStatePeerWrite = await dispatchCodexNativeHook({
6781
+ hook_event_name: "PreToolUse",
6782
+ cwd,
6783
+ session_id: runtimeSessionId,
6784
+ thread_id: threadId,
6785
+ tool_name: "Write",
6786
+ tool_use_id: "tool-di-runtime-peer-state-write",
6787
+ tool_input: { file_path: join(runtimeRoot, ".omx", "state", "sessions", "../outside", "deep-interview-state.json"), content: "{}\n" },
6788
+ }, { cwd });
6789
+ assert.equal(blockedRuntimeStatePeerWrite.outputJson?.decision, "block");
6790
+ const blockedRuntimeUnrelatedWrite = await dispatchCodexNativeHook({
6791
+ hook_event_name: "PreToolUse",
6792
+ cwd,
6793
+ session_id: runtimeSessionId,
6794
+ thread_id: threadId,
6795
+ tool_name: "Write",
6796
+ tool_use_id: "tool-di-runtime-unrelated-write",
6797
+ tool_input: { file_path: join(runtimeRoot, "unrelated", "deep-interview-state.json"), content: "{}\n" },
6798
+ }, { cwd });
6799
+ assert.equal(blockedRuntimeUnrelatedWrite.outputJson?.decision, "block");
6800
+ }
6801
+ finally {
6802
+ if (typeof previousOmxRoot === "string")
6803
+ process.env.OMX_ROOT = previousOmxRoot;
6804
+ else
6805
+ delete process.env.OMX_ROOT;
6806
+ await rm(runtimeRoot, { recursive: true, force: true });
6807
+ }
6460
6808
  // Cross-mode non-terminal `omx state write` payloads are activations,
6461
6809
  // because state_write normalizes them to active=true after the hook.
6462
6810
  const blockedStateCliMutation = await preToolUse({
@@ -9306,6 +9654,32 @@ PY`,
9306
9654
  assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
9307
9655
  assert.match(String(blocker.error_summary), /agent thread limit reached/);
9308
9656
  assert.ok(Date.parse(String(blocker.expires_at)) > Date.parse(String(blocker.observed_at)));
9657
+ assert.equal(existsSync(join(cwd, ".omx", "state", "native-subagent-support.json")), false);
9658
+ }
9659
+ finally {
9660
+ await rm(cwd, { recursive: true, force: true });
9661
+ }
9662
+ });
9663
+ it("records unsupported native subagent support blocker from spawn_agent PostToolUse output", async () => {
9664
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-support-record-"));
9665
+ try {
9666
+ const result = await dispatchCodexNativeHook({
9667
+ hook_event_name: "PostToolUse",
9668
+ cwd,
9669
+ session_id: "sess-subagent-support-record",
9670
+ thread_id: "thread-subagent-support-record",
9671
+ turn_id: "turn-subagent-support-record",
9672
+ tool_name: "multi_agent_v1.spawn_agent",
9673
+ tool_response: { error: "unknown tool: multi_agent_v1.spawn_agent is unavailable" },
9674
+ }, { cwd });
9675
+ assert.equal(result.omxEventName, "post-tool-use");
9676
+ const blocker = JSON.parse(await readFile(join(cwd, ".omx", "state", "native-subagent-support.json"), "utf-8"));
9677
+ assert.equal(blocker.status, "unsupported");
9678
+ assert.equal(blocker.reason, "multi_agent_v1_unavailable");
9679
+ assert.equal(blocker.session_id, "sess-subagent-support-record");
9680
+ assert.equal(blocker.thread_id, "thread-subagent-support-record");
9681
+ assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
9682
+ assert.match(String(blocker.evidence), /unknown tool/);
9309
9683
  }
9310
9684
  finally {
9311
9685
  await rm(cwd, { recursive: true, force: true });
@@ -13406,13 +13780,14 @@ PY`,
13406
13780
  await rm(cwd, { recursive: true, force: true });
13407
13781
  }
13408
13782
  });
13409
- it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => {
13410
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-"));
13783
+ it("clears stale ralplan Stop cache when authoritative state is terminal inactive", async () => {
13784
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-terminal-cache-"));
13411
13785
  try {
13412
13786
  const stateDir = join(cwd, ".omx", "state");
13413
- const sessionId = "sess-stop-ralplan-canonical-root-no-cwd";
13787
+ const sessionId = "sess-stop-ralplan-terminal-cache";
13788
+ const threadId = "thread-stop-ralplan-terminal-cache";
13414
13789
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13415
- await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13790
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
13416
13791
  await writeJson(join(stateDir, "skill-active-state.json"), {
13417
13792
  active: false,
13418
13793
  skill: "ralplan",
@@ -13424,52 +13799,119 @@ PY`,
13424
13799
  active: false,
13425
13800
  mode: "ralplan",
13426
13801
  current_phase: "complete",
13802
+ status: "complete",
13803
+ run_outcome: "complete",
13427
13804
  session_id: sessionId,
13805
+ cwd,
13428
13806
  });
13429
13807
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13430
- active: true,
13808
+ active: false,
13431
13809
  skill: "ralplan",
13432
- phase: "planning",
13810
+ phase: "complete",
13433
13811
  session_id: sessionId,
13434
- active_skills: [{
13435
- skill: "ralplan",
13436
- phase: "planning",
13437
- active: true,
13438
- session_id: sessionId,
13439
- }],
13812
+ active_skills: [],
13440
13813
  });
13441
13814
  await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
13442
- active: true,
13815
+ active: false,
13443
13816
  mode: "ralplan",
13444
- current_phase: "planning",
13817
+ current_phase: "complete",
13818
+ status: "complete",
13819
+ run_outcome: "complete",
13445
13820
  session_id: sessionId,
13446
13821
  cwd,
13447
13822
  });
13823
+ await writeJson(join(stateDir, "native-stop-state.json"), {
13824
+ sessions: {
13825
+ [sessionId]: {
13826
+ last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
13827
+ updated_at: "2026-07-04T00:00:00.000Z",
13828
+ },
13829
+ [threadId]: {
13830
+ last_signature: `skill-stop|${sessionId}|${threadId}|no-message|skill_ralplan_planning_continue_artifact`,
13831
+ updated_at: "2026-07-04T00:00:00.000Z",
13832
+ },
13833
+ },
13834
+ });
13448
13835
  const result = await dispatchCodexNativeHook({
13449
13836
  hook_event_name: "Stop",
13450
13837
  cwd,
13451
13838
  session_id: sessionId,
13839
+ thread_id: threadId,
13840
+ stop_hook_active: true,
13452
13841
  }, { cwd });
13453
13842
  assert.equal(result.omxEventName, "stop");
13454
- assert.equal(result.outputJson?.decision, "block");
13455
- assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
13843
+ assert.equal(result.outputJson, null);
13844
+ const stopState = JSON.parse(await readFile(join(stateDir, "native-stop-state.json"), "utf-8"));
13845
+ assert.deepEqual(stopState.sessions, {});
13456
13846
  }
13457
13847
  finally {
13458
13848
  await rm(cwd, { recursive: true, force: true });
13459
13849
  }
13460
13850
  });
13461
- it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => {
13462
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-"));
13851
+ it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => {
13852
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-"));
13463
13853
  try {
13464
13854
  const stateDir = join(cwd, ".omx", "state");
13465
- const sessionId = "sess-stop-current-active-ralplan";
13855
+ const sessionId = "sess-stop-ralplan-canonical-root-no-cwd";
13466
13856
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13467
13857
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13468
13858
  await writeJson(join(stateDir, "skill-active-state.json"), {
13469
13859
  active: false,
13470
13860
  skill: "ralplan",
13471
13861
  phase: "complete",
13472
- session_id: "sess-stop-old-ralplan",
13862
+ session_id: sessionId,
13863
+ active_skills: [],
13864
+ });
13865
+ await writeJson(join(stateDir, "ralplan-state.json"), {
13866
+ active: false,
13867
+ mode: "ralplan",
13868
+ current_phase: "complete",
13869
+ session_id: sessionId,
13870
+ });
13871
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13872
+ active: true,
13873
+ skill: "ralplan",
13874
+ phase: "planning",
13875
+ session_id: sessionId,
13876
+ active_skills: [{
13877
+ skill: "ralplan",
13878
+ phase: "planning",
13879
+ active: true,
13880
+ session_id: sessionId,
13881
+ }],
13882
+ });
13883
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
13884
+ active: true,
13885
+ mode: "ralplan",
13886
+ current_phase: "planning",
13887
+ session_id: sessionId,
13888
+ cwd,
13889
+ });
13890
+ const result = await dispatchCodexNativeHook({
13891
+ hook_event_name: "Stop",
13892
+ cwd,
13893
+ session_id: sessionId,
13894
+ }, { cwd });
13895
+ assert.equal(result.omxEventName, "stop");
13896
+ assert.equal(result.outputJson?.decision, "block");
13897
+ assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
13898
+ }
13899
+ finally {
13900
+ await rm(cwd, { recursive: true, force: true });
13901
+ }
13902
+ });
13903
+ it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => {
13904
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-"));
13905
+ try {
13906
+ const stateDir = join(cwd, ".omx", "state");
13907
+ const sessionId = "sess-stop-current-active-ralplan";
13908
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13909
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13910
+ await writeJson(join(stateDir, "skill-active-state.json"), {
13911
+ active: false,
13912
+ skill: "ralplan",
13913
+ phase: "complete",
13914
+ session_id: "sess-stop-old-ralplan",
13473
13915
  active_skills: [],
13474
13916
  });
13475
13917
  await writeJson(join(stateDir, "ralplan-state.json"), {
@@ -17635,6 +18077,50 @@ PY`,
17635
18077
  await rm(cwd, { recursive: true, force: true });
17636
18078
  }
17637
18079
  });
18080
+ it("blocks unsupported active conductor source edits with native delegation recovery guidance", async () => {
18081
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unsupported-conductor-source-"));
18082
+ try {
18083
+ const stateDir = join(cwd, ".omx", "state");
18084
+ const sessionId = "sess-unsupported-conductor-source";
18085
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18086
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
18087
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
18088
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18089
+ active: true,
18090
+ mode: "ultragoal",
18091
+ current_phase: "planning",
18092
+ session_id: sessionId,
18093
+ });
18094
+ await writeJson(join(stateDir, "native-subagent-support.json"), {
18095
+ schema_version: 1,
18096
+ status: "unsupported",
18097
+ reason: "multi_agent_v1_unavailable",
18098
+ session_id: sessionId,
18099
+ evidence: "unknown tool: multi_agent_v1.spawn_agent",
18100
+ observed_at: new Date().toISOString(),
18101
+ cwd,
18102
+ });
18103
+ const result = await dispatchCodexNativeHook({
18104
+ hook_event_name: "PreToolUse",
18105
+ cwd,
18106
+ session_id: sessionId,
18107
+ thread_id: "thread-unsupported-conductor-source",
18108
+ tool_name: "Write",
18109
+ tool_input: { file_path: "src/runtime.ts", content: "export const value = 1;\n" },
18110
+ }, { cwd });
18111
+ const output = result.outputJson;
18112
+ const context = String(output?.hookSpecificOutput?.additionalContext ?? "");
18113
+ assert.equal(output?.decision, "block");
18114
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/);
18115
+ assert.match(context, /Native subagent support is unavailable in this environment/);
18116
+ assert.match(context, /Reason: multi_agent_v1_unavailable/);
18117
+ assert.match(context, /blocked\/cancelled/);
18118
+ assert.match(context, /do not call multi_agent_v1\.close_agent/i);
18119
+ }
18120
+ finally {
18121
+ await rm(cwd, { recursive: true, force: true });
18122
+ }
18123
+ });
17638
18124
  it("blocks Main-root ralplan writes even when payload has only a typed agent_role", async () => {
17639
18125
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-agent-role-main-"));
17640
18126
  try {
@@ -17766,6 +18252,203 @@ PY`,
17766
18252
  await rm(cwd, { recursive: true, force: true });
17767
18253
  }
17768
18254
  });
18255
+ it("allows conductor writes from tracked typed native subagents when PreToolUse omits thread_spawn source", async () => {
18256
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-tracked-subagent-no-source-"));
18257
+ const originalOmxRoot = process.env.OMX_ROOT;
18258
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
18259
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
18260
+ try {
18261
+ process.env.OMX_ROOT = cwd;
18262
+ delete process.env.OMX_STATE_ROOT;
18263
+ delete process.env.OMX_TEAM_STATE_ROOT;
18264
+ const stateDir = join(cwd, ".omx", "state");
18265
+ const sessionId = "sess-conductor-tracked-subagent-no-source";
18266
+ const leaderThreadId = "thread-conductor-leader-no-source";
18267
+ const childThreadId = "thread-conductor-child-no-source";
18268
+ const nowIso = new Date().toISOString();
18269
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18270
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
18271
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
18272
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18273
+ active: true,
18274
+ mode: "ultragoal",
18275
+ current_phase: "executing",
18276
+ session_id: sessionId,
18277
+ });
18278
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
18279
+ schemaVersion: 1,
18280
+ sessions: {
18281
+ [sessionId]: {
18282
+ session_id: sessionId,
18283
+ leader_thread_id: leaderThreadId,
18284
+ updated_at: nowIso,
18285
+ threads: {
18286
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
18287
+ [childThreadId]: { thread_id: childThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
18288
+ },
18289
+ },
18290
+ },
18291
+ });
18292
+ const result = await dispatchCodexNativeHook({
18293
+ hook_event_name: "PreToolUse",
18294
+ cwd,
18295
+ session_id: sessionId,
18296
+ thread_id: childThreadId,
18297
+ agent_role: "executor",
18298
+ tool_name: "apply_patch",
18299
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
18300
+ }, { cwd });
18301
+ assert.equal(result.outputJson, null);
18302
+ }
18303
+ finally {
18304
+ if (originalOmxRoot === undefined)
18305
+ delete process.env.OMX_ROOT;
18306
+ else
18307
+ process.env.OMX_ROOT = originalOmxRoot;
18308
+ if (originalOmxStateRoot === undefined)
18309
+ delete process.env.OMX_STATE_ROOT;
18310
+ else
18311
+ process.env.OMX_STATE_ROOT = originalOmxStateRoot;
18312
+ if (originalOmxTeamStateRoot === undefined)
18313
+ delete process.env.OMX_TEAM_STATE_ROOT;
18314
+ else
18315
+ process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
18316
+ await rm(cwd, { recursive: true, force: true });
18317
+ }
18318
+ });
18319
+ it("blocks conductor writes when corrupt tracker state labels the leader as a subagent", async () => {
18320
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-corrupt-leader-subagent-"));
18321
+ const originalOmxRoot = process.env.OMX_ROOT;
18322
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
18323
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
18324
+ try {
18325
+ process.env.OMX_ROOT = cwd;
18326
+ delete process.env.OMX_STATE_ROOT;
18327
+ delete process.env.OMX_TEAM_STATE_ROOT;
18328
+ const stateDir = join(cwd, ".omx", "state");
18329
+ const sessionId = "sess-conductor-corrupt-leader-subagent";
18330
+ const leaderThreadId = "thread-conductor-corrupt-leader";
18331
+ const nowIso = new Date().toISOString();
18332
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18333
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
18334
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
18335
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18336
+ active: true,
18337
+ mode: "ultragoal",
18338
+ current_phase: "executing",
18339
+ session_id: sessionId,
18340
+ });
18341
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
18342
+ schemaVersion: 1,
18343
+ sessions: {
18344
+ [sessionId]: {
18345
+ session_id: sessionId,
18346
+ leader_thread_id: leaderThreadId,
18347
+ updated_at: nowIso,
18348
+ threads: {
18349
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "subagent", mode: "executor", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
18350
+ },
18351
+ },
18352
+ },
18353
+ });
18354
+ const result = await dispatchCodexNativeHook({
18355
+ hook_event_name: "PreToolUse",
18356
+ cwd,
18357
+ session_id: sessionId,
18358
+ thread_id: leaderThreadId,
18359
+ agent_role: "executor",
18360
+ tool_name: "apply_patch",
18361
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
18362
+ }, { cwd });
18363
+ assert.equal(result.outputJson?.decision, "block");
18364
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
18365
+ }
18366
+ finally {
18367
+ if (originalOmxRoot === undefined)
18368
+ delete process.env.OMX_ROOT;
18369
+ else
18370
+ process.env.OMX_ROOT = originalOmxRoot;
18371
+ if (originalOmxStateRoot === undefined)
18372
+ delete process.env.OMX_STATE_ROOT;
18373
+ else
18374
+ process.env.OMX_STATE_ROOT = originalOmxStateRoot;
18375
+ if (originalOmxTeamStateRoot === undefined)
18376
+ delete process.env.OMX_TEAM_STATE_ROOT;
18377
+ else
18378
+ process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
18379
+ await rm(cwd, { recursive: true, force: true });
18380
+ }
18381
+ });
18382
+ it("blocks conductor writes when thread_spawn provenance is attached to the leader thread", async () => {
18383
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-thread-spawn-leader-"));
18384
+ const originalOmxRoot = process.env.OMX_ROOT;
18385
+ const originalOmxStateRoot = process.env.OMX_STATE_ROOT;
18386
+ const originalOmxTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
18387
+ try {
18388
+ process.env.OMX_ROOT = cwd;
18389
+ delete process.env.OMX_STATE_ROOT;
18390
+ delete process.env.OMX_TEAM_STATE_ROOT;
18391
+ const stateDir = join(cwd, ".omx", "state");
18392
+ const sessionId = "sess-conductor-thread-spawn-leader";
18393
+ const leaderThreadId = "thread-conductor-thread-spawn-leader";
18394
+ const nowIso = new Date().toISOString();
18395
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18396
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId, native_session_id: leaderThreadId });
18397
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "executing");
18398
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18399
+ active: true,
18400
+ mode: "ultragoal",
18401
+ current_phase: "executing",
18402
+ session_id: sessionId,
18403
+ });
18404
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
18405
+ schemaVersion: 1,
18406
+ sessions: {
18407
+ [sessionId]: {
18408
+ session_id: sessionId,
18409
+ leader_thread_id: leaderThreadId,
18410
+ updated_at: nowIso,
18411
+ threads: {
18412
+ [leaderThreadId]: { thread_id: leaderThreadId, kind: "leader", first_seen_at: nowIso, last_seen_at: nowIso, turn_count: 1 },
18413
+ },
18414
+ },
18415
+ },
18416
+ });
18417
+ const result = await dispatchCodexNativeHook({
18418
+ hook_event_name: "PreToolUse",
18419
+ cwd,
18420
+ session_id: sessionId,
18421
+ thread_id: leaderThreadId,
18422
+ agent_role: "executor",
18423
+ source: {
18424
+ subagent: {
18425
+ thread_spawn: {
18426
+ parent_thread_id: leaderThreadId,
18427
+ },
18428
+ },
18429
+ },
18430
+ tool_name: "apply_patch",
18431
+ tool_input: { file_path: "configs/ws/kalshi_ws_auth_evidence.json" },
18432
+ }, { cwd });
18433
+ assert.equal(result.outputJson?.decision, "block");
18434
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: executing\)/);
18435
+ }
18436
+ finally {
18437
+ if (originalOmxRoot === undefined)
18438
+ delete process.env.OMX_ROOT;
18439
+ else
18440
+ process.env.OMX_ROOT = originalOmxRoot;
18441
+ if (originalOmxStateRoot === undefined)
18442
+ delete process.env.OMX_STATE_ROOT;
18443
+ else
18444
+ process.env.OMX_STATE_ROOT = originalOmxStateRoot;
18445
+ if (originalOmxTeamStateRoot === undefined)
18446
+ delete process.env.OMX_TEAM_STATE_ROOT;
18447
+ else
18448
+ process.env.OMX_TEAM_STATE_ROOT = originalOmxTeamStateRoot;
18449
+ await rm(cwd, { recursive: true, force: true });
18450
+ }
18451
+ });
17769
18452
  it("blocks Main-root ralph conductor source and planning artifact writes while allowing .omx workflow state writes", async () => {
17770
18453
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralph-conductor-write-"));
17771
18454
  try {
@@ -17837,6 +18520,65 @@ PY`,
17837
18520
  await rm(cwd, { recursive: true, force: true });
17838
18521
  }
17839
18522
  });
18523
+ it("allows structured ultragoal steering cleanup through dynamic nested shell loops", async () => {
18524
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-cleanup-"));
18525
+ try {
18526
+ const stateDir = join(cwd, ".omx", "state");
18527
+ const sessionId = "sess-ultragoal-steer-cleanup";
18528
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18529
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
18530
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
18531
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18532
+ active: true,
18533
+ mode: "ultragoal",
18534
+ current_phase: "planning",
18535
+ session_id: sessionId,
18536
+ });
18537
+ const result = await dispatchCodexNativeHook({
18538
+ hook_event_name: "PreToolUse",
18539
+ cwd,
18540
+ session_id: sessionId,
18541
+ thread_id: "thread-ultragoal-steer-cleanup",
18542
+ tool_name: "Bash",
18543
+ tool_input: {
18544
+ command: `bash -lc 'for goal_id in G001-atomized G002-atomized; do omx ultragoal steer --kind mark_blocked_superseded --target-goal-id "$goal_id" --evidence ".omx/ultragoal cleanup supersedes atomized pseudo-goals." --rationale "Structured steering cleanup keeps durable Ultragoal metadata auditable." --json; done'`,
18545
+ },
18546
+ }, { cwd });
18547
+ assert.equal(result.outputJson, null);
18548
+ }
18549
+ finally {
18550
+ await rm(cwd, { recursive: true, force: true });
18551
+ }
18552
+ });
18553
+ it("blocks unsafe dynamic nested shell writes in conductor mode", async () => {
18554
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-dynamic-shell-write-block-"));
18555
+ try {
18556
+ const stateDir = join(cwd, ".omx", "state");
18557
+ const sessionId = "sess-dynamic-shell-write-block";
18558
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
18559
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
18560
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
18561
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
18562
+ active: true,
18563
+ mode: "ultragoal",
18564
+ current_phase: "planning",
18565
+ session_id: sessionId,
18566
+ });
18567
+ const result = await dispatchCodexNativeHook({
18568
+ hook_event_name: "PreToolUse",
18569
+ cwd,
18570
+ session_id: sessionId,
18571
+ thread_id: "thread-dynamic-shell-write-block",
18572
+ tool_name: "Bash",
18573
+ tool_input: { command: `bash -lc 'cp "$SOURCE_FILE" src/runtime.ts'` },
18574
+ }, { cwd });
18575
+ assert.equal(result.outputJson?.decision, "block");
18576
+ assert.match(String(result.outputJson?.reason ?? ""), /Bash nested shell execution is dynamic and cannot be validated/);
18577
+ }
18578
+ finally {
18579
+ await rm(cwd, { recursive: true, force: true });
18580
+ }
18581
+ });
17840
18582
  it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => {
17841
18583
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-"));
17842
18584
  try {