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
@@ -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 {
@@ -6522,6 +6742,69 @@ exit 0
6522
6742
  }, { cwd });
6523
6743
  assert.equal(protectedWrite.outputJson?.decision, "block", `${filePath} should not be model-writable during deep-interview`);
6524
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
+ }
6525
6808
  // Cross-mode non-terminal `omx state write` payloads are activations,
6526
6809
  // because state_write normalizes them to active=true after the hook.
6527
6810
  const blockedStateCliMutation = await preToolUse({
@@ -9371,6 +9654,32 @@ PY`,
9371
9654
  assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
9372
9655
  assert.match(String(blocker.error_summary), /agent thread limit reached/);
9373
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/);
9374
9683
  }
9375
9684
  finally {
9376
9685
  await rm(cwd, { recursive: true, force: true });
@@ -17768,6 +18077,50 @@ PY`,
17768
18077
  await rm(cwd, { recursive: true, force: true });
17769
18078
  }
17770
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
+ });
17771
18124
  it("blocks Main-root ralplan writes even when payload has only a typed agent_role", async () => {
17772
18125
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-agent-role-main-"));
17773
18126
  try {
@@ -18167,6 +18520,65 @@ PY`,
18167
18520
  await rm(cwd, { recursive: true, force: true });
18168
18521
  }
18169
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
+ });
18170
18582
  it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => {
18171
18583
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-"));
18172
18584
  try {