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
@@ -1819,6 +1819,193 @@ PY`,
1819
1819
  }
1820
1820
  });
1821
1821
 
1822
+ it("adds resume-by-id instructions for persisted subagents on SessionStart resume", async () => {
1823
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-reopen-"));
1824
+ try {
1825
+ const stateDir = join(cwd, ".omx", "state");
1826
+ const sessionId = "omx-reopen-session";
1827
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reopen", pid: process.pid });
1828
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1829
+ schemaVersion: 1,
1830
+ sessions: {
1831
+ [sessionId]: {
1832
+ session_id: sessionId,
1833
+ leader_thread_id: "codex-leader-reopen",
1834
+ updated_at: "2026-07-09T00:00:00.000Z",
1835
+ threads: {
1836
+ "codex-leader-reopen": {
1837
+ thread_id: "codex-leader-reopen",
1838
+ kind: "leader",
1839
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1840
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1841
+ turn_count: 1,
1842
+ },
1843
+ "thread-architect-reopen": {
1844
+ thread_id: "thread-architect-reopen",
1845
+ kind: "subagent",
1846
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1847
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1848
+ turn_count: 2,
1849
+ role: "architect",
1850
+ lane_id: "plan-review",
1851
+ scope: "SessionStart reopen",
1852
+ status: "available",
1853
+ last_handoff_summary: "reviewed the restart plan",
1854
+ },
1855
+ },
1856
+ },
1857
+ },
1858
+ });
1859
+
1860
+ const result = await dispatchCodexNativeHook(
1861
+ {
1862
+ hook_event_name: "SessionStart",
1863
+ cwd,
1864
+ session_id: "codex-leader-reopen",
1865
+ source: "resume",
1866
+ },
1867
+ { cwd, sessionOwnerPid: process.pid },
1868
+ );
1869
+
1870
+ const additionalContext = String(
1871
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1872
+ );
1873
+ assert.match(additionalContext, /\[Persisted subagent reopen\]/);
1874
+ assert.match(additionalContext, /resume_agent\("thread-architect-reopen"\)/);
1875
+ assert.match(additionalContext, /role: architect; lane: plan-review; scope: SessionStart reopen; status: available/);
1876
+ assert.match(additionalContext, /saved subagent ids found: 1/);
1877
+
1878
+ const tracking = JSON.parse(await readFile(join(stateDir, "subagent-tracking.json"), "utf-8")) as {
1879
+ sessions?: Record<string, { threads?: Record<string, { resume_requested_at?: string }> }>;
1880
+ };
1881
+ assert.match(
1882
+ tracking.sessions?.[sessionId]?.threads?.["thread-architect-reopen"]?.resume_requested_at ?? "",
1883
+ /^\d{4}-\d{2}-\d{2}T/,
1884
+ );
1885
+ } finally {
1886
+ await rm(cwd, { recursive: true, force: true });
1887
+ }
1888
+ });
1889
+
1890
+ it("does not suggest duplicate same-role subagent spawns when reopen ids exist", async () => {
1891
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-no-duplicates-"));
1892
+ try {
1893
+ const stateDir = join(cwd, ".omx", "state");
1894
+ const sessionId = "omx-reuse-session";
1895
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-reuse", pid: process.pid });
1896
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1897
+ schemaVersion: 1,
1898
+ sessions: {
1899
+ [sessionId]: {
1900
+ session_id: sessionId,
1901
+ leader_thread_id: "codex-leader-reuse",
1902
+ updated_at: "2026-07-09T00:00:00.000Z",
1903
+ threads: {
1904
+ "codex-leader-reuse": {
1905
+ thread_id: "codex-leader-reuse",
1906
+ kind: "leader",
1907
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1908
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1909
+ turn_count: 1,
1910
+ },
1911
+ "thread-critic-reuse": {
1912
+ thread_id: "thread-critic-reuse",
1913
+ kind: "subagent",
1914
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1915
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1916
+ turn_count: 1,
1917
+ role: "critic",
1918
+ lane_id: "risk-review",
1919
+ status: "closed",
1920
+ },
1921
+ },
1922
+ },
1923
+ },
1924
+ });
1925
+
1926
+ const result = await dispatchCodexNativeHook(
1927
+ {
1928
+ hook_event_name: "SessionStart",
1929
+ cwd,
1930
+ session_id: "codex-leader-reuse",
1931
+ source: "startup",
1932
+ },
1933
+ { cwd, sessionOwnerPid: process.pid },
1934
+ );
1935
+
1936
+ const additionalContext = String(
1937
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1938
+ );
1939
+ assert.match(additionalContext, /resume_agent\("thread-critic-reuse"\)/);
1940
+ assert.match(additionalContext, /avoid duplicate same-type subagent spawns/);
1941
+ assert.match(additionalContext, /do not spawn a new agent solely because reopen failed/);
1942
+ assert.doesNotMatch(additionalContext, /spawn.*critic.*replacement/i);
1943
+ } finally {
1944
+ await rm(cwd, { recursive: true, force: true });
1945
+ }
1946
+ });
1947
+
1948
+ it("surfaces clear warnings for unavailable persisted subagents without spawning replacements", async () => {
1949
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-warning-"));
1950
+ try {
1951
+ const stateDir = join(cwd, ".omx", "state");
1952
+ const sessionId = "omx-warning-session";
1953
+ await writeSessionStart(cwd, sessionId, { nativeSessionId: "codex-leader-warning", pid: process.pid });
1954
+ await writeJson(join(stateDir, "subagent-tracking.json"), {
1955
+ schemaVersion: 1,
1956
+ sessions: {
1957
+ [sessionId]: {
1958
+ session_id: sessionId,
1959
+ leader_thread_id: "codex-leader-warning",
1960
+ updated_at: "2026-07-09T00:00:00.000Z",
1961
+ threads: {
1962
+ "codex-leader-warning": {
1963
+ thread_id: "codex-leader-warning",
1964
+ kind: "leader",
1965
+ first_seen_at: "2026-07-09T00:00:00.000Z",
1966
+ last_seen_at: "2026-07-09T00:00:00.000Z",
1967
+ turn_count: 1,
1968
+ },
1969
+ "thread-executor-unavailable": {
1970
+ thread_id: "thread-executor-unavailable",
1971
+ kind: "subagent",
1972
+ first_seen_at: "2026-07-09T00:01:00.000Z",
1973
+ last_seen_at: "2026-07-09T00:01:00.000Z",
1974
+ turn_count: 1,
1975
+ role: "executor",
1976
+ lane_id: "implementation",
1977
+ status: "unavailable",
1978
+ resume_failed_at: "2026-07-09T00:02:00.000Z",
1979
+ resume_failure_reason: "Codex reported missing thread id",
1980
+ },
1981
+ },
1982
+ },
1983
+ },
1984
+ });
1985
+
1986
+ const result = await dispatchCodexNativeHook(
1987
+ {
1988
+ hook_event_name: "SessionStart",
1989
+ cwd,
1990
+ session_id: "codex-leader-warning",
1991
+ source: "resume",
1992
+ },
1993
+ { cwd, sessionOwnerPid: process.pid },
1994
+ );
1995
+
1996
+ const additionalContext = String(
1997
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1998
+ );
1999
+ assert.match(additionalContext, /No compatible saved subagent id is currently marked reopenable/);
2000
+ assert.match(additionalContext, /Reopen warnings:/);
2001
+ assert.match(additionalContext, /thread-executor-unavailable/);
2002
+ assert.match(additionalContext, /last failure: Codex reported missing thread id/);
2003
+ assert.doesNotMatch(additionalContext, /resume_agent\("thread-executor-unavailable"\)/);
2004
+ } finally {
2005
+ await rm(cwd, { recursive: true, force: true });
2006
+ }
2007
+ });
2008
+
1822
2009
  it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => {
1823
2010
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-"));
1824
2011
  try {
@@ -5039,6 +5226,82 @@ standardMaxRounds = 15
5039
5226
  }
5040
5227
  });
5041
5228
 
5229
+ it("keeps conductor guidance on autopilot activation after capacity-only native subagent evidence", async () => {
5230
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-capacity-native-"));
5231
+ try {
5232
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
5233
+ await dispatchCodexNativeHook(
5234
+ {
5235
+ hook_event_name: "PostToolUse",
5236
+ cwd,
5237
+ session_id: "sess-autopilot-capacity-native",
5238
+ thread_id: "thread-autopilot-capacity-native",
5239
+ turn_id: "turn-autopilot-capacity-native-spawn",
5240
+ tool_name: "multi_agent_v1.spawn_agent",
5241
+ tool_response: { error: "collab spawn failed: agent thread limit reached" },
5242
+ },
5243
+ { cwd },
5244
+ );
5245
+
5246
+ const result = await dispatchCodexNativeHook(
5247
+ {
5248
+ hook_event_name: "UserPromptSubmit",
5249
+ cwd,
5250
+ session_id: "sess-autopilot-capacity-native",
5251
+ thread_id: "thread-autopilot-capacity-native",
5252
+ turn_id: "turn-autopilot-capacity-native-prompt",
5253
+ prompt: "$autopilot implement issue #3078",
5254
+ },
5255
+ { cwd },
5256
+ );
5257
+
5258
+ assert.equal(result.omxEventName, "keyword-detector");
5259
+ assert.equal(result.skillState?.skill, "autopilot");
5260
+ const message = String(
5261
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
5262
+ );
5263
+ assert.match(message, /Autopilot protocol:/);
5264
+ assert.match(message, /Conductor mode contract:/);
5265
+ assert.match(message, /Golden Rule: When the Main agent is acting in Conductor mode/);
5266
+ assert.match(message, /Conductor reuse and ledger guidance:/);
5267
+ assert.doesNotMatch(message, /Native subagent support is unavailable in this environment/);
5268
+ assert.doesNotMatch(message, /Reason: agent_thread_limit_reached/);
5269
+ } finally {
5270
+ await rm(cwd, { recursive: true, force: true });
5271
+ }
5272
+ });
5273
+
5274
+ it("omits conductor block and emits unsupported native guidance for unsupported autopilot first-run payload", async () => {
5275
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-unsupported-native-"));
5276
+ try {
5277
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
5278
+ const result = await dispatchCodexNativeHook(
5279
+ {
5280
+ hook_event_name: "UserPromptSubmit",
5281
+ cwd,
5282
+ session_id: "sess-autopilot-unsupported-native",
5283
+ thread_id: "thread-autopilot-unsupported-native",
5284
+ turn_id: "turn-autopilot-unsupported-native",
5285
+ prompt: "$autopilot implement issue #3078",
5286
+ capabilities: { native_subagents: false, multi_agent_v1: false },
5287
+ },
5288
+ { cwd },
5289
+ );
5290
+
5291
+ const message = String(
5292
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || "",
5293
+ );
5294
+ assert.match(message, /Autopilot protocol:/);
5295
+ assert.doesNotMatch(message, /Conductor mode contract:/);
5296
+ assert.doesNotMatch(message, /Conductor reuse and ledger guidance:/);
5297
+ assert.match(message, /Native subagent support is unavailable in this environment/);
5298
+ assert.match(message, /Reason: native_subagents_unsupported/);
5299
+ assert.match(message, /Do not call multi_agent_v1\.close_agent/);
5300
+ } finally {
5301
+ await rm(cwd, { recursive: true, force: true });
5302
+ }
5303
+ });
5304
+
5042
5305
  it("records ultragoal prompt skill activation with goal-tool handoff guidance", async () => {
5043
5306
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-"));
5044
5307
  try {
@@ -7841,6 +8104,70 @@ exit 0
7841
8104
  );
7842
8105
  }
7843
8106
 
8107
+ const runtimeRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-runtime-root-"));
8108
+ const previousOmxRoot = process.env.OMX_ROOT;
8109
+ const runtimeSessionId = "sess-di-runtime-artifact";
8110
+ const runtimeStateDir = join(runtimeRoot, ".omx", "state");
8111
+ const runtimeSessionDir = join(runtimeStateDir, "sessions", runtimeSessionId);
8112
+ try {
8113
+ process.env.OMX_ROOT = runtimeRoot;
8114
+ await mkdir(runtimeSessionDir, { recursive: true });
8115
+ await writeJson(join(runtimeStateDir, "session.json"), { session_id: runtimeSessionId, cwd });
8116
+ await writeJson(join(runtimeSessionDir, "skill-active-state.json"), {
8117
+ version: 1,
8118
+ active: true,
8119
+ skill: "deep-interview",
8120
+ phase: "planning",
8121
+ session_id: runtimeSessionId,
8122
+ thread_id: threadId,
8123
+ active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: runtimeSessionId, thread_id: threadId }],
8124
+ });
8125
+ await writeJson(join(runtimeSessionDir, "deep-interview-state.json"), {
8126
+ active: true,
8127
+ mode: "deep-interview",
8128
+ current_phase: "intent-first",
8129
+ session_id: runtimeSessionId,
8130
+ thread_id: threadId,
8131
+ });
8132
+
8133
+ const allowedRuntimeStateWrite = await dispatchCodexNativeHook({
8134
+ hook_event_name: "PreToolUse",
8135
+ cwd,
8136
+ session_id: runtimeSessionId,
8137
+ thread_id: threadId,
8138
+ tool_name: "Write",
8139
+ tool_use_id: "tool-di-runtime-state-write",
8140
+ tool_input: { file_path: join(runtimeSessionDir, "deep-interview-state.json"), content: "{}\n" },
8141
+ }, { cwd });
8142
+ assert.equal(allowedRuntimeStateWrite.outputJson, null);
8143
+
8144
+ const blockedRuntimeStatePeerWrite = await dispatchCodexNativeHook({
8145
+ hook_event_name: "PreToolUse",
8146
+ cwd,
8147
+ session_id: runtimeSessionId,
8148
+ thread_id: threadId,
8149
+ tool_name: "Write",
8150
+ tool_use_id: "tool-di-runtime-peer-state-write",
8151
+ tool_input: { file_path: join(runtimeRoot, ".omx", "state", "sessions", "../outside", "deep-interview-state.json"), content: "{}\n" },
8152
+ }, { cwd });
8153
+ assert.equal((blockedRuntimeStatePeerWrite.outputJson as { decision?: string } | null)?.decision, "block");
8154
+
8155
+ const blockedRuntimeUnrelatedWrite = await dispatchCodexNativeHook({
8156
+ hook_event_name: "PreToolUse",
8157
+ cwd,
8158
+ session_id: runtimeSessionId,
8159
+ thread_id: threadId,
8160
+ tool_name: "Write",
8161
+ tool_use_id: "tool-di-runtime-unrelated-write",
8162
+ tool_input: { file_path: join(runtimeRoot, "unrelated", "deep-interview-state.json"), content: "{}\n" },
8163
+ }, { cwd });
8164
+ assert.equal((blockedRuntimeUnrelatedWrite.outputJson as { decision?: string } | null)?.decision, "block");
8165
+ } finally {
8166
+ if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot;
8167
+ else delete process.env.OMX_ROOT;
8168
+ await rm(runtimeRoot, { recursive: true, force: true });
8169
+ }
8170
+
7844
8171
  // Cross-mode non-terminal `omx state write` payloads are activations,
7845
8172
  // because state_write normalizes them to active=true after the hook.
7846
8173
  const blockedStateCliMutation = await preToolUse(
@@ -11490,6 +11817,38 @@ PY`,
11490
11817
  assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
11491
11818
  assert.match(String(blocker.error_summary), /agent thread limit reached/);
11492
11819
  assert.ok(Date.parse(String(blocker.expires_at)) > Date.parse(String(blocker.observed_at)));
11820
+ assert.equal(existsSync(join(cwd, ".omx", "state", "native-subagent-support.json")), false);
11821
+ } finally {
11822
+ await rm(cwd, { recursive: true, force: true });
11823
+ }
11824
+ });
11825
+
11826
+ it("records unsupported native subagent support blocker from spawn_agent PostToolUse output", async () => {
11827
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-support-record-"));
11828
+ try {
11829
+ const result = await dispatchCodexNativeHook(
11830
+ {
11831
+ hook_event_name: "PostToolUse",
11832
+ cwd,
11833
+ session_id: "sess-subagent-support-record",
11834
+ thread_id: "thread-subagent-support-record",
11835
+ turn_id: "turn-subagent-support-record",
11836
+ tool_name: "multi_agent_v1.spawn_agent",
11837
+ tool_response: { error: "unknown tool: multi_agent_v1.spawn_agent is unavailable" },
11838
+ },
11839
+ { cwd },
11840
+ );
11841
+
11842
+ assert.equal(result.omxEventName, "post-tool-use");
11843
+ const blocker = JSON.parse(
11844
+ await readFile(join(cwd, ".omx", "state", "native-subagent-support.json"), "utf-8"),
11845
+ ) as Record<string, unknown>;
11846
+ assert.equal(blocker.status, "unsupported");
11847
+ assert.equal(blocker.reason, "multi_agent_v1_unavailable");
11848
+ assert.equal(blocker.session_id, "sess-subagent-support-record");
11849
+ assert.equal(blocker.thread_id, "thread-subagent-support-record");
11850
+ assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
11851
+ assert.match(String(blocker.evidence), /unknown tool/);
11493
11852
  } finally {
11494
11853
  await rm(cwd, { recursive: true, force: true });
11495
11854
  }
@@ -21459,6 +21818,55 @@ PY`,
21459
21818
  }
21460
21819
  });
21461
21820
 
21821
+ it("blocks unsupported active conductor source edits with native delegation recovery guidance", async () => {
21822
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unsupported-conductor-source-"));
21823
+ try {
21824
+ const stateDir = join(cwd, ".omx", "state");
21825
+ const sessionId = "sess-unsupported-conductor-source";
21826
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
21827
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
21828
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
21829
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
21830
+ active: true,
21831
+ mode: "ultragoal",
21832
+ current_phase: "planning",
21833
+ session_id: sessionId,
21834
+ });
21835
+ await writeJson(join(stateDir, "native-subagent-support.json"), {
21836
+ schema_version: 1,
21837
+ status: "unsupported",
21838
+ reason: "multi_agent_v1_unavailable",
21839
+ session_id: sessionId,
21840
+ evidence: "unknown tool: multi_agent_v1.spawn_agent",
21841
+ observed_at: new Date().toISOString(),
21842
+ cwd,
21843
+ });
21844
+
21845
+ const result = await dispatchCodexNativeHook(
21846
+ {
21847
+ hook_event_name: "PreToolUse",
21848
+ cwd,
21849
+ session_id: sessionId,
21850
+ thread_id: "thread-unsupported-conductor-source",
21851
+ tool_name: "Write",
21852
+ tool_input: { file_path: "src/runtime.ts", content: "export const value = 1;\n" },
21853
+ },
21854
+ { cwd },
21855
+ );
21856
+
21857
+ const output = result.outputJson as { decision?: string; hookSpecificOutput?: { additionalContext?: string } } | null;
21858
+ const context = String(output?.hookSpecificOutput?.additionalContext ?? "");
21859
+ assert.equal(output?.decision, "block");
21860
+ assert.match(String(result.outputJson?.reason ?? ""), /Main-root Conductor mode is active \(ultragoal phase: planning\)/);
21861
+ assert.match(context, /Native subagent support is unavailable in this environment/);
21862
+ assert.match(context, /Reason: multi_agent_v1_unavailable/);
21863
+ assert.match(context, /blocked\/cancelled/);
21864
+ assert.match(context, /do not call multi_agent_v1\.close_agent/i);
21865
+ } finally {
21866
+ await rm(cwd, { recursive: true, force: true });
21867
+ }
21868
+ });
21869
+
21462
21870
  it("blocks Main-root ralplan writes even when payload has only a typed agent_role", async () => {
21463
21871
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-agent-role-main-"));
21464
21872
  try {
@@ -21884,6 +22292,75 @@ PY`,
21884
22292
  }
21885
22293
  });
21886
22294
 
22295
+ it("allows structured ultragoal steering cleanup through dynamic nested shell loops", async () => {
22296
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-steer-cleanup-"));
22297
+ try {
22298
+ const stateDir = join(cwd, ".omx", "state");
22299
+ const sessionId = "sess-ultragoal-steer-cleanup";
22300
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
22301
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
22302
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
22303
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
22304
+ active: true,
22305
+ mode: "ultragoal",
22306
+ current_phase: "planning",
22307
+ session_id: sessionId,
22308
+ });
22309
+
22310
+ const result = await dispatchCodexNativeHook(
22311
+ {
22312
+ hook_event_name: "PreToolUse",
22313
+ cwd,
22314
+ session_id: sessionId,
22315
+ thread_id: "thread-ultragoal-steer-cleanup",
22316
+ tool_name: "Bash",
22317
+ tool_input: {
22318
+ 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'`,
22319
+ },
22320
+ },
22321
+ { cwd },
22322
+ );
22323
+
22324
+ assert.equal(result.outputJson, null);
22325
+ } finally {
22326
+ await rm(cwd, { recursive: true, force: true });
22327
+ }
22328
+ });
22329
+
22330
+ it("blocks unsafe dynamic nested shell writes in conductor mode", async () => {
22331
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-dynamic-shell-write-block-"));
22332
+ try {
22333
+ const stateDir = join(cwd, ".omx", "state");
22334
+ const sessionId = "sess-dynamic-shell-write-block";
22335
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
22336
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
22337
+ await writeSessionSkillActiveState(stateDir, sessionId, "ultragoal", "planning");
22338
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
22339
+ active: true,
22340
+ mode: "ultragoal",
22341
+ current_phase: "planning",
22342
+ session_id: sessionId,
22343
+ });
22344
+
22345
+ const result = await dispatchCodexNativeHook(
22346
+ {
22347
+ hook_event_name: "PreToolUse",
22348
+ cwd,
22349
+ session_id: sessionId,
22350
+ thread_id: "thread-dynamic-shell-write-block",
22351
+ tool_name: "Bash",
22352
+ tool_input: { command: `bash -lc 'cp "$SOURCE_FILE" src/runtime.ts'` },
22353
+ },
22354
+ { cwd },
22355
+ );
22356
+
22357
+ assert.equal(result.outputJson?.decision, "block");
22358
+ assert.match(String(result.outputJson?.reason ?? ""), /Bash nested shell execution is dynamic and cannot be validated/);
22359
+ } finally {
22360
+ await rm(cwd, { recursive: true, force: true });
22361
+ }
22362
+ });
22363
+
21887
22364
  it("blocks common Bash file mutations in Main-root conductor states unless they target workflow metadata", async () => {
21888
22365
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-conductor-bash-mutations-"));
21889
22366
  try {