oh-my-codex 0.18.2 → 0.18.3

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 (169) hide show
  1. package/Cargo.toml +1 -1
  2. package/dist/agents/__tests__/definitions.test.js +9 -0
  3. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  4. package/dist/agents/__tests__/native-config.test.js +1 -0
  5. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  6. package/dist/agents/definitions.d.ts.map +1 -1
  7. package/dist/agents/definitions.js +10 -0
  8. package/dist/agents/definitions.js.map +1 -1
  9. package/dist/auth/__tests__/config-sessions.test.d.ts +2 -0
  10. package/dist/auth/__tests__/config-sessions.test.d.ts.map +1 -0
  11. package/dist/auth/__tests__/config-sessions.test.js +48 -0
  12. package/dist/auth/__tests__/config-sessions.test.js.map +1 -0
  13. package/dist/auth/__tests__/quota-rotation.test.d.ts +2 -0
  14. package/dist/auth/__tests__/quota-rotation.test.d.ts.map +1 -0
  15. package/dist/auth/__tests__/quota-rotation.test.js +33 -0
  16. package/dist/auth/__tests__/quota-rotation.test.js.map +1 -0
  17. package/dist/auth/__tests__/redact.test.d.ts +2 -0
  18. package/dist/auth/__tests__/redact.test.d.ts.map +1 -0
  19. package/dist/auth/__tests__/redact.test.js +20 -0
  20. package/dist/auth/__tests__/redact.test.js.map +1 -0
  21. package/dist/auth/__tests__/storage.test.d.ts +2 -0
  22. package/dist/auth/__tests__/storage.test.d.ts.map +1 -0
  23. package/dist/auth/__tests__/storage.test.js +108 -0
  24. package/dist/auth/__tests__/storage.test.js.map +1 -0
  25. package/dist/auth/config.d.ts +9 -0
  26. package/dist/auth/config.d.ts.map +1 -0
  27. package/dist/auth/config.js +77 -0
  28. package/dist/auth/config.js.map +1 -0
  29. package/dist/auth/hotswap.d.ts +36 -0
  30. package/dist/auth/hotswap.d.ts.map +1 -0
  31. package/dist/auth/hotswap.js +159 -0
  32. package/dist/auth/hotswap.js.map +1 -0
  33. package/dist/auth/index.d.ts +8 -0
  34. package/dist/auth/index.d.ts.map +1 -0
  35. package/dist/auth/index.js +8 -0
  36. package/dist/auth/index.js.map +1 -0
  37. package/dist/auth/paths.d.ts +12 -0
  38. package/dist/auth/paths.d.ts.map +1 -0
  39. package/dist/auth/paths.js +78 -0
  40. package/dist/auth/paths.js.map +1 -0
  41. package/dist/auth/quota-detector.d.ts +10 -0
  42. package/dist/auth/quota-detector.d.ts.map +1 -0
  43. package/dist/auth/quota-detector.js +40 -0
  44. package/dist/auth/quota-detector.js.map +1 -0
  45. package/dist/auth/redact.d.ts +2 -0
  46. package/dist/auth/redact.d.ts.map +1 -0
  47. package/dist/auth/redact.js +26 -0
  48. package/dist/auth/redact.js.map +1 -0
  49. package/dist/auth/rotation.d.ts +9 -0
  50. package/dist/auth/rotation.d.ts.map +1 -0
  51. package/dist/auth/rotation.js +26 -0
  52. package/dist/auth/rotation.js.map +1 -0
  53. package/dist/auth/sessions.d.ts +15 -0
  54. package/dist/auth/sessions.d.ts.map +1 -0
  55. package/dist/auth/sessions.js +62 -0
  56. package/dist/auth/sessions.js.map +1 -0
  57. package/dist/auth/storage.d.ts +27 -0
  58. package/dist/auth/storage.d.ts.map +1 -0
  59. package/dist/auth/storage.js +111 -0
  60. package/dist/auth/storage.js.map +1 -0
  61. package/dist/cli/__tests__/auth.test.d.ts +2 -0
  62. package/dist/cli/__tests__/auth.test.d.ts.map +1 -0
  63. package/dist/cli/__tests__/auth.test.js +168 -0
  64. package/dist/cli/__tests__/auth.test.js.map +1 -0
  65. package/dist/cli/__tests__/doctor-warning-copy.test.js +51 -0
  66. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  67. package/dist/cli/__tests__/explore.test.js +20 -0
  68. package/dist/cli/__tests__/explore.test.js.map +1 -1
  69. package/dist/cli/__tests__/index.test.js +10 -0
  70. package/dist/cli/__tests__/index.test.js.map +1 -1
  71. package/dist/cli/__tests__/nested-help-routing.test.js +1 -0
  72. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  73. package/dist/cli/__tests__/setup-agents-overwrite.test.js +30 -1
  74. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  75. package/dist/cli/__tests__/setup-install-mode.test.js +47 -0
  76. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  77. package/dist/cli/auth.d.ts +4 -0
  78. package/dist/cli/auth.d.ts.map +1 -0
  79. package/dist/cli/auth.js +89 -0
  80. package/dist/cli/auth.js.map +1 -0
  81. package/dist/cli/doctor.d.ts.map +1 -1
  82. package/dist/cli/doctor.js +19 -7
  83. package/dist/cli/doctor.js.map +1 -1
  84. package/dist/cli/explore.d.ts.map +1 -1
  85. package/dist/cli/explore.js +12 -0
  86. package/dist/cli/explore.js.map +1 -1
  87. package/dist/cli/index.d.ts +20 -2
  88. package/dist/cli/index.d.ts.map +1 -1
  89. package/dist/cli/index.js +104 -6
  90. package/dist/cli/index.js.map +1 -1
  91. package/dist/cli/setup.d.ts.map +1 -1
  92. package/dist/cli/setup.js +11 -3
  93. package/dist/cli/setup.js.map +1 -1
  94. package/dist/config/__tests__/deep-interview.test.d.ts +2 -0
  95. package/dist/config/__tests__/deep-interview.test.d.ts.map +1 -0
  96. package/dist/config/__tests__/deep-interview.test.js +239 -0
  97. package/dist/config/__tests__/deep-interview.test.js.map +1 -0
  98. package/dist/config/__tests__/generator-idempotent.test.js +123 -0
  99. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  100. package/dist/config/deep-interview.d.ts +22 -0
  101. package/dist/config/deep-interview.d.ts.map +1 -0
  102. package/dist/config/deep-interview.js +151 -0
  103. package/dist/config/deep-interview.js.map +1 -0
  104. package/dist/config/generator.d.ts +5 -2
  105. package/dist/config/generator.d.ts.map +1 -1
  106. package/dist/config/generator.js +106 -36
  107. package/dist/config/generator.js.map +1 -1
  108. package/dist/hooks/__tests__/agents-overlay.test.js +2 -0
  109. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  110. package/dist/hooks/__tests__/explore-routing.test.js +1 -0
  111. package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
  112. package/dist/hooks/__tests__/keyword-detector.test.js +301 -0
  113. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  114. package/dist/hooks/deep-interview-config-instruction.d.ts +3 -0
  115. package/dist/hooks/deep-interview-config-instruction.d.ts.map +1 -0
  116. package/dist/hooks/deep-interview-config-instruction.js +47 -0
  117. package/dist/hooks/deep-interview-config-instruction.js.map +1 -0
  118. package/dist/hooks/explore-routing.d.ts.map +1 -1
  119. package/dist/hooks/explore-routing.js +1 -0
  120. package/dist/hooks/explore-routing.js.map +1 -1
  121. package/dist/hooks/keyword-detector.d.ts +5 -0
  122. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  123. package/dist/hooks/keyword-detector.js +52 -8
  124. package/dist/hooks/keyword-detector.js.map +1 -1
  125. package/dist/hud/__tests__/hud-tmux-injection.test.js +4 -4
  126. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  127. package/dist/hud/__tests__/reconcile.test.js +94 -9
  128. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  129. package/dist/hud/__tests__/tmux.test.js +103 -1
  130. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  131. package/dist/hud/reconcile.d.ts +1 -1
  132. package/dist/hud/reconcile.d.ts.map +1 -1
  133. package/dist/hud/reconcile.js +8 -0
  134. package/dist/hud/reconcile.js.map +1 -1
  135. package/dist/hud/tmux.d.ts +7 -0
  136. package/dist/hud/tmux.d.ts.map +1 -1
  137. package/dist/hud/tmux.js +46 -9
  138. package/dist/hud/tmux.js.map +1 -1
  139. package/dist/question/deep-interview.d.ts +2 -0
  140. package/dist/question/deep-interview.d.ts.map +1 -1
  141. package/dist/question/deep-interview.js.map +1 -1
  142. package/dist/scripts/__tests__/codex-native-hook.test.js +387 -0
  143. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  144. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  145. package/dist/scripts/codex-native-hook.js +24 -2
  146. package/dist/scripts/codex-native-hook.js.map +1 -1
  147. package/dist/state/__tests__/planning-gate.test.d.ts +2 -0
  148. package/dist/state/__tests__/planning-gate.test.d.ts.map +1 -0
  149. package/dist/state/__tests__/planning-gate.test.js +219 -0
  150. package/dist/state/__tests__/planning-gate.test.js.map +1 -0
  151. package/dist/state/workflow-transition.d.ts +23 -0
  152. package/dist/state/workflow-transition.d.ts.map +1 -1
  153. package/dist/state/workflow-transition.js +63 -0
  154. package/dist/state/workflow-transition.js.map +1 -1
  155. package/dist/team/__tests__/tmux-session.test.js +86 -0
  156. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  157. package/dist/team/tmux-session.d.ts.map +1 -1
  158. package/dist/team/tmux-session.js +7 -0
  159. package/dist/team/tmux-session.js.map +1 -1
  160. package/package.json +1 -1
  161. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  162. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +10 -0
  163. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +6 -2
  164. package/prompts/scholastic.md +11 -0
  165. package/skills/deep-interview/SKILL.md +10 -0
  166. package/skills/ralplan/SKILL.md +6 -2
  167. package/src/scripts/__tests__/codex-native-hook.test.ts +485 -0
  168. package/src/scripts/codex-native-hook.ts +23 -1
  169. package/templates/catalog-manifest.json +5 -0
@@ -25,6 +25,7 @@ import { writeSessionStart } from "../../hooks/session.js";
25
25
  import { resetTriageConfigCache } from "../../hooks/triage-config.js";
26
26
  import { executeStateOperation } from "../../state/operations.js";
27
27
  import { OMX_TMUX_HUD_OWNER_ENV } from "../../hud/reconcile.js";
28
+ import { OMX_TMUX_HUD_LEADER_PANE_ENV } from "../../hud/tmux.js";
28
29
  import { readAllState } from "../../hud/state.js";
29
30
  import { renderHud } from "../../hud/render.js";
30
31
  import { getLegacyWikiDir, serializePage, writePage } from "../../wiki/storage.js";
@@ -65,6 +66,19 @@ async function writeJson(path: string, value: unknown): Promise<void> {
65
66
  await writeFile(path, JSON.stringify(value, null, 2));
66
67
  }
67
68
 
69
+ async function withIsolatedHome<T>(prefix: string, run: (homeDir: string) => Promise<T>): Promise<T> {
70
+ const homeDir = await mkdtemp(join(tmpdir(), `omx-native-hook-home-${prefix}-`));
71
+ const previousHome = process.env.HOME;
72
+ try {
73
+ process.env.HOME = homeDir;
74
+ return await run(homeDir);
75
+ } finally {
76
+ if (typeof previousHome === "string") process.env.HOME = previousHome;
77
+ else delete process.env.HOME;
78
+ await rm(homeDir, { recursive: true, force: true });
79
+ }
80
+ }
81
+
68
82
  async function withLoreGuardConfig<T>(
69
83
  value: string,
70
84
  prefix: string,
@@ -1620,6 +1634,405 @@ describe("codex native hook dispatch", () => {
1620
1634
  }
1621
1635
  });
1622
1636
 
1637
+ it("injects deep-interview config overrides into UserPromptSubmit developer context", async () => {
1638
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-"));
1639
+ try {
1640
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1641
+ await writeFile(
1642
+ join(cwd, ".omx", "config.toml"),
1643
+ `[omx.deepInterview]
1644
+ defaultProfile = "standard"
1645
+ standardThreshold = 0.05
1646
+ standardMaxRounds = 15
1647
+ enableChallengeModes = false
1648
+ `,
1649
+ );
1650
+
1651
+ const result = await dispatchCodexNativeHook(
1652
+ {
1653
+ hook_event_name: "UserPromptSubmit",
1654
+ cwd,
1655
+ session_id: "sess-deep-interview-config",
1656
+ thread_id: "thread-1",
1657
+ turn_id: "turn-1",
1658
+ prompt: "$deep-interview prove config reflection",
1659
+ },
1660
+ { cwd },
1661
+ );
1662
+
1663
+ assert.equal(result.omxEventName, "keyword-detector");
1664
+ assert.equal(result.skillState?.skill, "deep-interview");
1665
+ const serializedOutput = JSON.stringify(result.outputJson);
1666
+ assert.match(serializedOutput, /Deep-interview config override active/);
1667
+ assert.match(serializedOutput, /threshold=0\.05/);
1668
+ assert.match(serializedOutput, /max_rounds=15/);
1669
+ assert.match(serializedOutput, /enableChallengeModes=false/);
1670
+
1671
+ const modeState = JSON.parse(
1672
+ await readFile(join(cwd, ".omx", "state", "sessions", "sess-deep-interview-config", "deep-interview-state.json"), "utf-8"),
1673
+ ) as { threshold?: number; max_rounds?: number; profile?: string };
1674
+ assert.equal(modeState.profile, "standard");
1675
+ assert.equal(modeState.threshold, 0.05);
1676
+ assert.equal(modeState.max_rounds, 15);
1677
+ } finally {
1678
+ await rm(cwd, { recursive: true, force: true });
1679
+ }
1680
+ });
1681
+
1682
+ it("proves UserPromptSubmit context changes before and after adding deep-interview config", async () => {
1683
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-before-after-"));
1684
+ const sessionId = "sess-deep-interview-config-before-after";
1685
+ try {
1686
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1687
+
1688
+ const before = await withIsolatedHome("deep-interview-config-before-after", async () => (
1689
+ dispatchCodexNativeHook(
1690
+ {
1691
+ hook_event_name: "UserPromptSubmit",
1692
+ cwd,
1693
+ session_id: sessionId,
1694
+ thread_id: "thread-before-after",
1695
+ turn_id: "turn-before",
1696
+ prompt: "$deep-interview prove before config context",
1697
+ },
1698
+ { cwd },
1699
+ )
1700
+ ));
1701
+ const beforeOutput = JSON.stringify(before.outputJson);
1702
+ const beforeState = JSON.parse(
1703
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1704
+ ) as {
1705
+ deep_interview_config?: unknown;
1706
+ threshold?: number;
1707
+ max_rounds?: number;
1708
+ };
1709
+ assert.equal(before.skillState?.skill, "deep-interview");
1710
+ assert.doesNotMatch(beforeOutput, /Deep-interview config override active/);
1711
+ assert.equal(before.skillState?.deep_interview_config, undefined);
1712
+ assert.equal(beforeState.deep_interview_config, undefined);
1713
+ assert.equal(beforeState.threshold, undefined);
1714
+ assert.equal(beforeState.max_rounds, undefined);
1715
+
1716
+ await writeFile(
1717
+ join(cwd, ".omx", "config.toml"),
1718
+ `[omx.deepInterview]
1719
+ defaultProfile = "standard"
1720
+ standardThreshold = 0.05
1721
+ standardMaxRounds = 15
1722
+ `,
1723
+ );
1724
+
1725
+ const after = await dispatchCodexNativeHook(
1726
+ {
1727
+ hook_event_name: "UserPromptSubmit",
1728
+ cwd,
1729
+ session_id: sessionId,
1730
+ thread_id: "thread-before-after",
1731
+ turn_id: "turn-after",
1732
+ prompt: "$deep-interview prove after config context",
1733
+ },
1734
+ { cwd },
1735
+ );
1736
+ const afterOutput = JSON.stringify(after.outputJson);
1737
+ const afterState = JSON.parse(
1738
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1739
+ ) as {
1740
+ deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number };
1741
+ threshold?: number;
1742
+ max_rounds?: number;
1743
+ };
1744
+ assert.equal(after.skillState?.deep_interview_config?.profile, "standard");
1745
+ assert.match(afterOutput, /Deep-interview config override active/);
1746
+ assert.match(afterOutput, /threshold=0\.05/);
1747
+ assert.match(afterOutput, /max_rounds=15/);
1748
+ assert.equal(afterState.deep_interview_config?.profile, "standard");
1749
+ assert.equal(afterState.threshold, 0.05);
1750
+ assert.equal(afterState.max_rounds, 15);
1751
+ } finally {
1752
+ await rm(cwd, { recursive: true, force: true });
1753
+ }
1754
+ });
1755
+
1756
+ it("injects deep-interview config for mixed workflow prompts that defer execution modes", async () => {
1757
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-mixed-"));
1758
+ const sessionId = "sess-deep-interview-config-mixed";
1759
+ try {
1760
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1761
+ await writeFile(
1762
+ join(cwd, ".omx", "config.toml"),
1763
+ `[omx.deepInterview]
1764
+ defaultProfile = "deep"
1765
+ deepThreshold = 0.13
1766
+ deepMaxRounds = 21
1767
+ enableChallengeModes = false
1768
+ `,
1769
+ );
1770
+
1771
+ const result = await withIsolatedHome("deep-interview-config-mixed", async () => (
1772
+ dispatchCodexNativeHook(
1773
+ {
1774
+ hook_event_name: "UserPromptSubmit",
1775
+ cwd,
1776
+ session_id: sessionId,
1777
+ thread_id: "thread-mixed-config",
1778
+ turn_id: "turn-mixed-config",
1779
+ prompt: "$autopilot $deep-interview prove mixed config context",
1780
+ },
1781
+ { cwd },
1782
+ )
1783
+ ));
1784
+ const serializedOutput = JSON.stringify(result.outputJson);
1785
+ const modeState = JSON.parse(
1786
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1787
+ ) as {
1788
+ deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number; enableChallengeModes?: boolean };
1789
+ profile?: string;
1790
+ threshold?: number;
1791
+ max_rounds?: number;
1792
+ enable_challenge_modes?: boolean;
1793
+ };
1794
+
1795
+ assert.equal(result.skillState?.skill, "deep-interview");
1796
+ assert.deepEqual(result.skillState?.deferred_skills, ["autopilot"]);
1797
+ assert.equal(result.skillState?.deep_interview_config?.profile, "deep");
1798
+ assert.equal(result.skillState?.deep_interview_config?.threshold, 0.13);
1799
+ assert.equal(result.skillState?.deep_interview_config?.maxRounds, 21);
1800
+ assert.equal(result.skillState?.deep_interview_config?.enableChallengeModes, false);
1801
+ assert.match(serializedOutput, /Deep-interview config override active/);
1802
+ assert.match(serializedOutput, /profile=deep/);
1803
+ assert.match(serializedOutput, /threshold=0\.13/);
1804
+ assert.match(serializedOutput, /max_rounds=21/);
1805
+ assert.match(serializedOutput, /enableChallengeModes=false/);
1806
+ assert.equal(modeState.deep_interview_config?.profile, "deep");
1807
+ assert.equal(modeState.profile, "deep");
1808
+ assert.equal(modeState.threshold, 0.13);
1809
+ assert.equal(modeState.max_rounds, 21);
1810
+ assert.equal(modeState.enable_challenge_modes, false);
1811
+ } finally {
1812
+ await rm(cwd, { recursive: true, force: true });
1813
+ }
1814
+ });
1815
+
1816
+ it("keeps deep-interview config override context on continuation prompts", async () => {
1817
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-continuation-"));
1818
+ const sessionId = "sess-deep-interview-config-continuation";
1819
+ try {
1820
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1821
+ await writeFile(
1822
+ join(cwd, ".omx", "config.toml"),
1823
+ `[omx.deepInterview]
1824
+ defaultProfile = "standard"
1825
+ standardThreshold = 0.05
1826
+ standardMaxRounds = 15
1827
+ `,
1828
+ );
1829
+
1830
+ await dispatchCodexNativeHook(
1831
+ {
1832
+ hook_event_name: "UserPromptSubmit",
1833
+ cwd,
1834
+ session_id: sessionId,
1835
+ thread_id: "thread-continuation",
1836
+ turn_id: "turn-start",
1837
+ prompt: "$deep-interview prove config continuation",
1838
+ },
1839
+ { cwd },
1840
+ );
1841
+ const continued = await dispatchCodexNativeHook(
1842
+ {
1843
+ hook_event_name: "UserPromptSubmit",
1844
+ cwd,
1845
+ session_id: sessionId,
1846
+ thread_id: "thread-continuation",
1847
+ turn_id: "turn-continue",
1848
+ prompt: "continue",
1849
+ },
1850
+ { cwd },
1851
+ );
1852
+ const serializedOutput = JSON.stringify(continued.outputJson);
1853
+ const modeState = JSON.parse(
1854
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1855
+ ) as { threshold?: number; max_rounds?: number; profile?: string };
1856
+
1857
+ assert.equal(continued.skillState?.skill, "deep-interview");
1858
+ assert.match(serializedOutput, /Deep-interview config override active/);
1859
+ assert.match(serializedOutput, /threshold=0\.05/);
1860
+ assert.match(serializedOutput, /max_rounds=15/);
1861
+ assert.equal(modeState.profile, "standard");
1862
+ assert.equal(modeState.threshold, 0.05);
1863
+ assert.equal(modeState.max_rounds, 15);
1864
+ } finally {
1865
+ await rm(cwd, { recursive: true, force: true });
1866
+ }
1867
+ });
1868
+
1869
+ it("keeps explicit deep-interview profile flags reflected on continuation prompts", async () => {
1870
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-profile-continuation-"));
1871
+ const sessionId = "sess-deep-interview-config-profile-continuation";
1872
+ try {
1873
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1874
+ await writeFile(
1875
+ join(cwd, ".omx", "config.toml"),
1876
+ `[omx.deepInterview]
1877
+ defaultProfile = "standard"
1878
+ standardThreshold = 0.22
1879
+ standardMaxRounds = 13
1880
+ deepThreshold = 0.13
1881
+ deepMaxRounds = 21
1882
+ `,
1883
+ );
1884
+
1885
+ await dispatchCodexNativeHook(
1886
+ {
1887
+ hook_event_name: "UserPromptSubmit",
1888
+ cwd,
1889
+ session_id: sessionId,
1890
+ thread_id: "thread-profile-continuation",
1891
+ turn_id: "turn-start",
1892
+ prompt: "$deep-interview --deep prove explicit profile continuation",
1893
+ },
1894
+ { cwd },
1895
+ );
1896
+ const continued = await dispatchCodexNativeHook(
1897
+ {
1898
+ hook_event_name: "UserPromptSubmit",
1899
+ cwd,
1900
+ session_id: sessionId,
1901
+ thread_id: "thread-profile-continuation",
1902
+ turn_id: "turn-continue",
1903
+ prompt: "continue",
1904
+ },
1905
+ { cwd },
1906
+ );
1907
+ const serializedOutput = JSON.stringify(continued.outputJson);
1908
+ const modeState = JSON.parse(
1909
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1910
+ ) as { threshold?: number; max_rounds?: number; profile?: string; deep_interview_config?: { profile?: string } };
1911
+
1912
+ assert.equal(continued.skillState?.skill, "deep-interview");
1913
+ assert.equal(continued.skillState?.deep_interview_config?.profile, "deep");
1914
+ assert.match(serializedOutput, /Deep-interview config override active/);
1915
+ assert.match(serializedOutput, /profile=deep/);
1916
+ assert.match(serializedOutput, /threshold=0\.13/);
1917
+ assert.match(serializedOutput, /max_rounds=21/);
1918
+ assert.equal(modeState.deep_interview_config?.profile, "deep");
1919
+ assert.equal(modeState.profile, "deep");
1920
+ assert.equal(modeState.threshold, 0.13);
1921
+ assert.equal(modeState.max_rounds, 21);
1922
+ } finally {
1923
+ await rm(cwd, { recursive: true, force: true });
1924
+ }
1925
+ });
1926
+
1927
+ it("keeps the documented deep-interview Suggested Config reflected in UserPromptSubmit context", async () => {
1928
+ const skillDoc = await readFile(join(process.cwd(), "skills", "deep-interview", "SKILL.md"), "utf-8");
1929
+ const markerIndex = skillDoc.indexOf("## Suggested Config (optional)");
1930
+ assert.notEqual(markerIndex, -1);
1931
+ const configMatch = skillDoc.slice(markerIndex).match(/```toml\n([\s\S]*?)\n```/);
1932
+ assert.ok(configMatch);
1933
+ const documentedConfig = configMatch[1]?.trimEnd();
1934
+ assert.ok(documentedConfig);
1935
+ assert.match(documentedConfig, /standardThreshold = 0\.20/);
1936
+ assert.match(documentedConfig, /standardMaxRounds = 12/);
1937
+
1938
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-doc-config-"));
1939
+ const sessionId = "sess-deep-interview-doc-config";
1940
+ try {
1941
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1942
+ await writeFile(join(cwd, ".omx", "config.toml"), `${documentedConfig}\n`);
1943
+
1944
+ const result = await dispatchCodexNativeHook(
1945
+ {
1946
+ hook_event_name: "UserPromptSubmit",
1947
+ cwd,
1948
+ session_id: sessionId,
1949
+ thread_id: "thread-doc-config",
1950
+ turn_id: "turn-doc-config",
1951
+ prompt: "$deep-interview prove documented config context",
1952
+ },
1953
+ { cwd },
1954
+ );
1955
+ const serializedOutput = JSON.stringify(result.outputJson);
1956
+ const modeState = JSON.parse(
1957
+ await readFile(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json"), "utf-8"),
1958
+ ) as {
1959
+ deep_interview_config?: { profile?: string; threshold?: number; maxRounds?: number };
1960
+ profile?: string;
1961
+ threshold?: number;
1962
+ max_rounds?: number;
1963
+ };
1964
+
1965
+ assert.equal(result.skillState?.deep_interview_config?.profile, "standard");
1966
+ assert.equal(result.skillState?.deep_interview_config?.threshold, 0.2);
1967
+ assert.equal(result.skillState?.deep_interview_config?.maxRounds, 12);
1968
+ assert.match(serializedOutput, /Deep-interview config override active/);
1969
+ assert.match(serializedOutput, /profile=standard/);
1970
+ assert.match(serializedOutput, /threshold=0\.2/);
1971
+ assert.match(serializedOutput, /max_rounds=12/);
1972
+ assert.equal(modeState.deep_interview_config?.profile, "standard");
1973
+ assert.equal(modeState.profile, "standard");
1974
+ assert.equal(modeState.threshold, 0.2);
1975
+ assert.equal(modeState.max_rounds, 12);
1976
+ } finally {
1977
+ await rm(cwd, { recursive: true, force: true });
1978
+ }
1979
+ });
1980
+
1981
+ it("injects deep-interview config overrides when state is boxed under OMX_ROOT", async () => {
1982
+ const root = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-config-boxed-"));
1983
+ const cwd = join(root, "source");
1984
+ const omxRoot = join(root, "box");
1985
+ const sessionId = "sess-boxed-deep-interview-config";
1986
+ const previousOmxRoot = process.env.OMX_ROOT;
1987
+ const previousOmxStateRoot = process.env.OMX_STATE_ROOT;
1988
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
1989
+ try {
1990
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
1991
+ await writeFile(
1992
+ join(cwd, ".omx", "config.toml"),
1993
+ `[omx.deepInterview]
1994
+ defaultProfile = "standard"
1995
+ standardThreshold = 0.05
1996
+ standardMaxRounds = 15
1997
+ `,
1998
+ );
1999
+ process.env.OMX_ROOT = omxRoot;
2000
+ delete process.env.OMX_STATE_ROOT;
2001
+ delete process.env.OMX_TEAM_STATE_ROOT;
2002
+
2003
+ const result = await dispatchCodexNativeHook(
2004
+ {
2005
+ hook_event_name: "UserPromptSubmit",
2006
+ cwd,
2007
+ session_id: sessionId,
2008
+ thread_id: "thread-boxed",
2009
+ turn_id: "turn-boxed",
2010
+ prompt: "$deep-interview prove boxed config reflection",
2011
+ },
2012
+ { cwd },
2013
+ );
2014
+
2015
+ assert.equal(result.omxEventName, "keyword-detector");
2016
+ assert.equal(result.skillState?.initialized_state_path, `.omx/state/sessions/${sessionId}/deep-interview-state.json`);
2017
+ const boxedStatePath = join(omxRoot, ".omx", "state", "sessions", sessionId, "deep-interview-state.json");
2018
+ assert.equal(existsSync(boxedStatePath), true);
2019
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", sessionId, "deep-interview-state.json")), false);
2020
+
2021
+ const serializedOutput = JSON.stringify(result.outputJson);
2022
+ assert.match(serializedOutput, /Deep-interview config override active/);
2023
+ assert.match(serializedOutput, /threshold=0\.05/);
2024
+ assert.match(serializedOutput, /max_rounds=15/);
2025
+ } finally {
2026
+ if (typeof previousOmxRoot === "string") process.env.OMX_ROOT = previousOmxRoot;
2027
+ else delete process.env.OMX_ROOT;
2028
+ if (typeof previousOmxStateRoot === "string") process.env.OMX_STATE_ROOT = previousOmxStateRoot;
2029
+ else delete process.env.OMX_STATE_ROOT;
2030
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
2031
+ else delete process.env.OMX_TEAM_STATE_ROOT;
2032
+ await rm(root, { recursive: true, force: true });
2033
+ }
2034
+ });
2035
+
1623
2036
  it("records boxed keyword activation mode detail and skill state under OMX_ROOT", async () => {
1624
2037
  const root = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-"));
1625
2038
  const cwd = join(root, "source");
@@ -3521,6 +3934,78 @@ esac
3521
3934
  }
3522
3935
  });
3523
3936
 
3937
+ it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
3938
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
3939
+ const originalTmux = process.env.TMUX;
3940
+ const originalTmuxPane = process.env.TMUX_PANE;
3941
+ const originalPath = process.env.PATH;
3942
+ const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
3943
+ try {
3944
+ process.env.TMUX = "1";
3945
+ process.env.TMUX_PANE = "%1";
3946
+ process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
3947
+ const canonicalSessionId = "omx-canonical-hud-reuse";
3948
+ const nativeSessionId = "codex-native-hud-reuse";
3949
+ await mkdir(join(cwd, ".omx", "state", "sessions", canonicalSessionId), { recursive: true });
3950
+ await writeSessionStart(cwd, canonicalSessionId);
3951
+
3952
+ const binDir = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-bin-"));
3953
+ const tmuxLog = join(cwd, "tmux.log");
3954
+ await writeFile(
3955
+ join(binDir, "tmux"),
3956
+ `#!/usr/bin/env bash
3957
+ set -euo pipefail
3958
+ printf '%s\n' "$*" >> ${JSON.stringify(tmuxLog)}
3959
+ case "$1" in
3960
+ list-panes)
3961
+ printf '%%1\tcodex\tcodex\n'
3962
+ printf '%%2\tnode\texec env OMX_TMUX_HUD_OWNER='"'"'1'"'"' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='"'"'%%1'"'"' /node /omx.js hud --watch\n'
3963
+ ;;
3964
+ display-message)
3965
+ printf '80\t24\n'
3966
+ ;;
3967
+ resize-pane)
3968
+ ;;
3969
+ split-window)
3970
+ printf '%%9\n'
3971
+ ;;
3972
+ esac
3973
+ `,
3974
+ );
3975
+ await chmod(join(binDir, "tmux"), 0o755);
3976
+ process.env.PATH = `${binDir}:${originalPath}`;
3977
+
3978
+ const result = await dispatchCodexNativeHook(
3979
+ {
3980
+ hook_event_name: "UserPromptSubmit",
3981
+ cwd,
3982
+ session_id: nativeSessionId,
3983
+ thread_id: "thread-hud-reuse",
3984
+ turn_id: "turn-hud-reuse",
3985
+ prompt: "$ralplan prepare plan",
3986
+ },
3987
+ { cwd },
3988
+ );
3989
+
3990
+ assert.equal(result.omxEventName, "keyword-detector");
3991
+ const tmuxCalls = await readFile(tmuxLog, "utf-8");
3992
+ assert.match(tmuxCalls, /list-panes -t %1 -F/);
3993
+ assert.match(tmuxCalls, /resize-pane -t %2 -y 3/);
3994
+ assert.doesNotMatch(tmuxCalls, /split-window/);
3995
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
3996
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
3997
+ } finally {
3998
+ if (originalTmux === undefined) delete process.env.TMUX;
3999
+ else process.env.TMUX = originalTmux;
4000
+ if (originalTmuxPane === undefined) delete process.env.TMUX_PANE;
4001
+ else process.env.TMUX_PANE = originalTmuxPane;
4002
+ if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV];
4003
+ else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
4004
+ process.env.PATH = originalPath;
4005
+ await rm(cwd, { recursive: true, force: true });
4006
+ }
4007
+ });
4008
+
3524
4009
  it("skips prompt-submit HUD reconciliation inside unowned tmux panes", async () => {
3525
4010
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-unowned-"));
3526
4011
  const originalTmux = process.env.TMUX;
@@ -44,6 +44,7 @@ import {
44
44
  recordSkillActivation,
45
45
  type SkillActiveState,
46
46
  } from "../hooks/keyword-detector.js";
47
+ import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
47
48
  import {
48
49
  detectNativeStopStallPattern,
49
50
  loadAutoNudgeConfig,
@@ -1720,7 +1721,24 @@ function buildAdditionalContextMessage(
1720
1721
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
1721
1722
  const matches = detectKeywords(prompt);
1722
1723
  const match = detectPrimaryKeyword(prompt);
1723
- if (!match) return promptPriorityMessage;
1724
+ if (!match) {
1725
+ const continuedSkill = safeString(skillState?.skill).trim();
1726
+ if (!continuedSkill) return promptPriorityMessage;
1727
+ const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
1728
+ ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1729
+ : null;
1730
+ const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1731
+ return [
1732
+ `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
1733
+ promptPriorityMessage,
1734
+ skillState?.initialized_mode && skillState.initialized_state_path
1735
+ ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
1736
+ : null,
1737
+ deepInterviewPromptActivationNote,
1738
+ deepInterviewConfigPromptActivationNote,
1739
+ "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1740
+ ].filter(Boolean).join(" ");
1741
+ }
1724
1742
  const detectedKeywordMessage = matches.length > 1
1725
1743
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
1726
1744
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
@@ -1737,6 +1755,7 @@ function buildAdditionalContextMessage(
1737
1755
  const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
1738
1756
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1739
1757
  : null;
1758
+ const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1740
1759
  const ultraworkPromptActivationNote = skillState?.initialized_mode === "ultrawork"
1741
1760
  ? "Ultrawork protocol: ground the task before editing, define pass/fail acceptance criteria, keep shared-file work local, and use direct-tool plus background evidence lanes only for truly independent work. Direct ultrawork provides lightweight verification only; Ralph owns persistence and the full verified-completion promise."
1742
1761
  : null;
@@ -1772,6 +1791,7 @@ function buildAdditionalContextMessage(
1772
1791
  promptPriorityMessage,
1773
1792
  ultragoalPromptActivationNote,
1774
1793
  autopilotPromptActivationNote,
1794
+ deepInterviewConfigPromptActivationNote,
1775
1795
  skillState.initialized_mode && skillState.initialized_state_path
1776
1796
  ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
1777
1797
  : null,
@@ -1796,6 +1816,7 @@ function buildAdditionalContextMessage(
1796
1816
  promptPriorityMessage,
1797
1817
  initializedStateMessage,
1798
1818
  deepInterviewPromptActivationNote,
1819
+ deepInterviewConfigPromptActivationNote,
1799
1820
  ultraworkPromptActivationNote,
1800
1821
  ultragoalPromptActivationNote,
1801
1822
  autopilotPromptActivationNote,
@@ -1815,6 +1836,7 @@ function buildAdditionalContextMessage(
1815
1836
  promptPriorityMessage,
1816
1837
  buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path),
1817
1838
  deepInterviewPromptActivationNote,
1839
+ deepInterviewConfigPromptActivationNote,
1818
1840
  ultraworkPromptActivationNote,
1819
1841
  ultragoalPromptActivationNote,
1820
1842
  autopilotPromptActivationNote,
@@ -526,6 +526,11 @@
526
526
  "category": "coordination",
527
527
  "status": "active"
528
528
  },
529
+ {
530
+ "name": "scholastic",
531
+ "category": "coordination",
532
+ "status": "active"
533
+ },
529
534
  {
530
535
  "name": "vision",
531
536
  "category": "coordination",