oh-my-codex 0.18.9 → 0.18.11

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 (195) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +6 -6
  4. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +2 -0
  5. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +1 -0
  6. package/dist/autopilot/__tests__/deep-interview-gate.test.js +215 -0
  7. package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +1 -0
  8. package/dist/autopilot/__tests__/ralplan-gate.test.js +148 -0
  9. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  10. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -1
  11. package/dist/autopilot/deep-interview-gate.js +140 -0
  12. package/dist/autopilot/deep-interview-gate.js.map +1 -1
  13. package/dist/catalog/__tests__/schema.test.js +6 -0
  14. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  15. package/dist/cli/__tests__/auth.test.js +36 -3
  16. package/dist/cli/__tests__/auth.test.js.map +1 -1
  17. package/dist/cli/__tests__/codex-feature-probe.test.d.ts +2 -0
  18. package/dist/cli/__tests__/codex-feature-probe.test.d.ts.map +1 -0
  19. package/dist/cli/__tests__/codex-feature-probe.test.js +46 -0
  20. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -0
  21. package/dist/cli/__tests__/doctor-spark-routing.test.d.ts +2 -0
  22. package/dist/cli/__tests__/doctor-spark-routing.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/doctor-spark-routing.test.js +79 -0
  24. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -0
  25. package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
  26. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  27. package/dist/cli/__tests__/explore.test.js +47 -1175
  28. package/dist/cli/__tests__/explore.test.js.map +1 -1
  29. package/dist/cli/__tests__/index.test.js +276 -6
  30. package/dist/cli/__tests__/index.test.js.map +1 -1
  31. package/dist/cli/__tests__/launch-fallback.test.js +19 -5
  32. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  33. package/dist/cli/__tests__/nested-help-routing.test.js +1 -1
  34. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  35. package/dist/cli/__tests__/package-bin-contract.test.js +19 -6
  36. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  37. package/dist/cli/__tests__/setup-refresh.test.js +6 -2
  38. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  39. package/dist/cli/__tests__/sparkshell-packaging.test.js +45 -2
  40. package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
  41. package/dist/cli/__tests__/team-decompose.test.js +10 -5
  42. package/dist/cli/__tests__/team-decompose.test.js.map +1 -1
  43. package/dist/cli/__tests__/team.test.js +45 -1
  44. package/dist/cli/__tests__/team.test.js.map +1 -1
  45. package/dist/cli/__tests__/ultragoal.test.js +75 -0
  46. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  47. package/dist/cli/auth.d.ts.map +1 -1
  48. package/dist/cli/auth.js +25 -1
  49. package/dist/cli/auth.js.map +1 -1
  50. package/dist/cli/codex-feature-probe.d.ts +5 -2
  51. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  52. package/dist/cli/codex-feature-probe.js +25 -9
  53. package/dist/cli/codex-feature-probe.js.map +1 -1
  54. package/dist/cli/doctor.d.ts +17 -0
  55. package/dist/cli/doctor.d.ts.map +1 -1
  56. package/dist/cli/doctor.js +121 -0
  57. package/dist/cli/doctor.js.map +1 -1
  58. package/dist/cli/explore.d.ts +2 -21
  59. package/dist/cli/explore.d.ts.map +1 -1
  60. package/dist/cli/explore.js +25 -624
  61. package/dist/cli/explore.js.map +1 -1
  62. package/dist/cli/index.d.ts +34 -2
  63. package/dist/cli/index.d.ts.map +1 -1
  64. package/dist/cli/index.js +166 -89
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/cli/setup.d.ts.map +1 -1
  67. package/dist/cli/setup.js +9 -1
  68. package/dist/cli/setup.js.map +1 -1
  69. package/dist/cli/team.d.ts +4 -0
  70. package/dist/cli/team.d.ts.map +1 -1
  71. package/dist/cli/team.js +43 -4
  72. package/dist/cli/team.js.map +1 -1
  73. package/dist/cli/ultragoal.d.ts.map +1 -1
  74. package/dist/cli/ultragoal.js +29 -0
  75. package/dist/cli/ultragoal.js.map +1 -1
  76. package/dist/hooks/__tests__/agents-overlay.test.js +8 -10
  77. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  78. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +15 -0
  79. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  80. package/dist/hooks/__tests__/deep-interview-contract.test.js +16 -0
  81. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  82. package/dist/hooks/__tests__/explore-routing.test.js +5 -8
  83. package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
  84. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +6 -6
  85. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
  86. package/dist/hooks/__tests__/skill-guidance-contract.test.js +14 -5
  87. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  88. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  89. package/dist/hooks/agents-overlay.js +2 -1
  90. package/dist/hooks/agents-overlay.js.map +1 -1
  91. package/dist/hooks/explore-routing.d.ts +1 -1
  92. package/dist/hooks/explore-routing.d.ts.map +1 -1
  93. package/dist/hooks/explore-routing.js +3 -8
  94. package/dist/hooks/explore-routing.js.map +1 -1
  95. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +112 -1
  96. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  97. package/dist/hooks/extensibility/plugin-runner-stdin.d.ts +2 -0
  98. package/dist/hooks/extensibility/plugin-runner-stdin.d.ts.map +1 -0
  99. package/dist/hooks/extensibility/plugin-runner-stdin.js +16 -0
  100. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -0
  101. package/dist/hooks/extensibility/plugin-runner.js +2 -4
  102. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  103. package/dist/hud/__tests__/index.test.js +23 -2
  104. package/dist/hud/__tests__/index.test.js.map +1 -1
  105. package/dist/hud/__tests__/reconcile.test.js +367 -1
  106. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  107. package/dist/hud/__tests__/tmux.test.js +118 -7
  108. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  109. package/dist/hud/constants.d.ts +10 -0
  110. package/dist/hud/constants.d.ts.map +1 -1
  111. package/dist/hud/constants.js +19 -0
  112. package/dist/hud/constants.js.map +1 -1
  113. package/dist/hud/index.d.ts +6 -1
  114. package/dist/hud/index.d.ts.map +1 -1
  115. package/dist/hud/index.js +12 -3
  116. package/dist/hud/index.js.map +1 -1
  117. package/dist/hud/reconcile.d.ts +10 -2
  118. package/dist/hud/reconcile.d.ts.map +1 -1
  119. package/dist/hud/reconcile.js +78 -30
  120. package/dist/hud/reconcile.js.map +1 -1
  121. package/dist/hud/tmux.d.ts +14 -1
  122. package/dist/hud/tmux.d.ts.map +1 -1
  123. package/dist/hud/tmux.js +129 -15
  124. package/dist/hud/tmux.js.map +1 -1
  125. package/dist/mcp/wiki-server.d.ts +3 -3
  126. package/dist/ralplan/consensus-gate.js +9 -1
  127. package/dist/ralplan/consensus-gate.js.map +1 -1
  128. package/dist/scripts/__tests__/codex-native-hook.test.js +170 -17
  129. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  130. package/dist/scripts/__tests__/run-test-files.test.js +115 -1
  131. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  132. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  133. package/dist/scripts/codex-native-hook.js +74 -11
  134. package/dist/scripts/codex-native-hook.js.map +1 -1
  135. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  136. package/dist/scripts/notify-hook/team-worker-stop.js +54 -21
  137. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  138. package/dist/scripts/run-test-files.js +218 -160
  139. package/dist/scripts/run-test-files.js.map +1 -1
  140. package/dist/state/__tests__/operations.test.js +463 -0
  141. package/dist/state/__tests__/operations.test.js.map +1 -1
  142. package/dist/team/__tests__/delivery-log.test.js +18 -0
  143. package/dist/team/__tests__/delivery-log.test.js.map +1 -1
  144. package/dist/team/__tests__/runtime.test.js +48 -0
  145. package/dist/team/__tests__/runtime.test.js.map +1 -1
  146. package/dist/team/__tests__/tmux-session.test.js +107 -0
  147. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  148. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  149. package/dist/team/__tests__/tmux-test-fixture.js +14 -2
  150. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  151. package/dist/team/__tests__/tmux-test-fixture.test.js +1 -0
  152. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  153. package/dist/team/__tests__/worker-bootstrap.test.js +54 -1
  154. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  155. package/dist/team/delivery-log.d.ts +1 -1
  156. package/dist/team/delivery-log.d.ts.map +1 -1
  157. package/dist/team/delivery-log.js.map +1 -1
  158. package/dist/team/repo-aware-decomposition.d.ts +4 -0
  159. package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
  160. package/dist/team/repo-aware-decomposition.js.map +1 -1
  161. package/dist/team/runtime.d.ts.map +1 -1
  162. package/dist/team/runtime.js +78 -9
  163. package/dist/team/runtime.js.map +1 -1
  164. package/dist/team/tmux-session.d.ts +1 -0
  165. package/dist/team/tmux-session.d.ts.map +1 -1
  166. package/dist/team/tmux-session.js +16 -5
  167. package/dist/team/tmux-session.js.map +1 -1
  168. package/dist/team/ultragoal-context.d.ts +12 -0
  169. package/dist/team/ultragoal-context.d.ts.map +1 -1
  170. package/dist/team/ultragoal-context.js +32 -8
  171. package/dist/team/ultragoal-context.js.map +1 -1
  172. package/dist/utils/__tests__/paths.test.js +23 -0
  173. package/dist/utils/__tests__/paths.test.js.map +1 -1
  174. package/dist/utils/paths.d.ts.map +1 -1
  175. package/dist/utils/paths.js +4 -2
  176. package/dist/utils/paths.js.map +1 -1
  177. package/dist/utils/toml.d.ts +4 -0
  178. package/dist/utils/toml.d.ts.map +1 -0
  179. package/dist/utils/toml.js +75 -0
  180. package/dist/utils/toml.js.map +1 -0
  181. package/package.json +1 -1
  182. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  183. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -0
  184. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +34 -0
  185. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +32 -17
  186. package/skills/autopilot/SKILL.md +3 -0
  187. package/skills/deep-interview/SKILL.md +34 -0
  188. package/skills/ultrawork/SKILL.md +32 -17
  189. package/src/scripts/__tests__/codex-native-hook.test.ts +218 -28
  190. package/src/scripts/__tests__/run-test-files.test.ts +138 -2
  191. package/src/scripts/codex-native-hook.ts +80 -10
  192. package/src/scripts/notify-hook/team-worker-stop.ts +58 -18
  193. package/src/scripts/run-test-files.ts +229 -150
  194. package/templates/AGENTS.md +39 -199
  195. package/templates/catalog-manifest.json +7 -0
@@ -1,8 +1,10 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { OMX_TMUX_HUD_OWNER_ENV, reconcileHudForPromptSubmit } from '../reconcile.js';
4
- import { HUD_TMUX_HEIGHT_LINES, HUD_TMUX_ULTRAGOAL_HEIGHT_LINES } from '../constants.js';
4
+ import { HUD_TMUX_HEIGHT_LINES, HUD_TMUX_ULTRAGOAL_HEIGHT_LINES, HUD_TMUX_MIN_LAUNCH_WINDOW_HEIGHT_LINES } from '../constants.js';
5
5
  import { OMX_TMUX_HUD_LEADER_PANE_ENV } from '../tmux.js';
6
+ const noOpRegisterHudResizeHook = () => true;
7
+ const noOpUnregisterHudResizeHook = () => true;
6
8
  describe('reconcileHudForPromptSubmit', () => {
7
9
  it('skips reconciliation outside tmux', async () => {
8
10
  const result = await reconcileHudForPromptSubmit('/tmp', {
@@ -49,6 +51,8 @@ describe('reconcileHudForPromptSubmit', () => {
49
51
  resized.push({ paneId, heightLines });
50
52
  return true;
51
53
  },
54
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
55
+ registerHudResizeHook: noOpRegisterHudResizeHook,
52
56
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
53
57
  });
54
58
  assert.equal(result.status, 'skipped_no_session_id');
@@ -230,6 +234,8 @@ describe('reconcileHudForPromptSubmit', () => {
230
234
  return '%9';
231
235
  },
232
236
  resizeTmuxPane: () => true,
237
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
238
+ registerHudResizeHook: noOpRegisterHudResizeHook,
233
239
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
234
240
  });
235
241
  assert.equal(result.status, 'recreated');
@@ -255,6 +261,8 @@ describe('reconcileHudForPromptSubmit', () => {
255
261
  return '%9';
256
262
  },
257
263
  resizeTmuxPane: () => true,
264
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
265
+ registerHudResizeHook: noOpRegisterHudResizeHook,
258
266
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
259
267
  });
260
268
  assert.equal(result.status, 'recreated');
@@ -358,6 +366,8 @@ describe('reconcileHudForPromptSubmit', () => {
358
366
  return '%hud';
359
367
  },
360
368
  resizeTmuxPane: () => true,
369
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
370
+ registerHudResizeHook: noOpRegisterHudResizeHook,
361
371
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
362
372
  });
363
373
  assert.equal(result.status, 'recreated');
@@ -646,6 +656,8 @@ describe('reconcileHudForPromptSubmit', () => {
646
656
  resized.push({ paneId, heightLines });
647
657
  return true;
648
658
  },
659
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
660
+ registerHudResizeHook: noOpRegisterHudResizeHook,
649
661
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
650
662
  });
651
663
  assert.equal(result.status, 'replaced_duplicates');
@@ -839,6 +851,8 @@ describe('reconcileHudForPromptSubmit', () => {
839
851
  created.push({ cmd, options });
840
852
  return '%4';
841
853
  },
854
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
855
+ registerHudResizeHook: noOpRegisterHudResizeHook,
842
856
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
843
857
  });
844
858
  assert.equal(result.status, 'recreated');
@@ -882,6 +896,8 @@ describe('reconcileHudForPromptSubmit', () => {
882
896
  },
883
897
  createHudWatchPane: () => '%9',
884
898
  resizeTmuxPane: () => true,
899
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
900
+ registerHudResizeHook: noOpRegisterHudResizeHook,
885
901
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
886
902
  });
887
903
  assert.equal(result.status, 'replaced_duplicates');
@@ -955,6 +971,7 @@ describe('reconcileHudForPromptSubmit', () => {
955
971
  resized.push({ paneId, heightLines });
956
972
  return true;
957
973
  },
974
+ registerHudResizeHook: noOpRegisterHudResizeHook,
958
975
  resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
959
976
  });
960
977
  assert.equal(result.status, 'resized');
@@ -1090,6 +1107,254 @@ describe('reconcileHudForPromptSubmit', () => {
1090
1107
  assert.deepEqual(created, []);
1091
1108
  assert.deepEqual(resized, [{ paneId: '%2', heightLines: HUD_TMUX_HEIGHT_LINES }]);
1092
1109
  });
1110
+ it('leaves an existing full-width bottom HUD pane unchanged when geometry and height are healthy', async () => {
1111
+ const killed = [];
1112
+ const created = [];
1113
+ const resized = [];
1114
+ const result = await reconcileHudForPromptSubmit('/repo', {
1115
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1116
+ listCurrentWindowPanes: () => [
1117
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 0, paneWidth: 160, paneHeight: 50 - HUD_TMUX_HEIGHT_LINES, paneBottom: 49 - HUD_TMUX_HEIGHT_LINES, windowWidth: 160, windowHeight: 50 },
1118
+ {
1119
+ paneId: '%2',
1120
+ currentCommand: 'node',
1121
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1122
+ paneLeft: 0,
1123
+ paneTop: 50 - HUD_TMUX_HEIGHT_LINES,
1124
+ paneWidth: 160,
1125
+ paneHeight: HUD_TMUX_HEIGHT_LINES,
1126
+ paneBottom: 49,
1127
+ windowWidth: 160,
1128
+ windowHeight: 50,
1129
+ },
1130
+ ],
1131
+ killTmuxPane: (paneId) => {
1132
+ killed.push(paneId);
1133
+ return true;
1134
+ },
1135
+ createHudWatchPane: (_cwd, _cmd, options) => {
1136
+ created.push({ options });
1137
+ return '%9';
1138
+ },
1139
+ resizeTmuxPane: (paneId, heightLines) => {
1140
+ resized.push({ paneId, heightLines });
1141
+ return true;
1142
+ },
1143
+ registerHudResizeHook: noOpRegisterHudResizeHook,
1144
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1145
+ });
1146
+ assert.equal(result.status, 'unchanged');
1147
+ assert.deepEqual(killed, []);
1148
+ assert.deepEqual(created, []);
1149
+ assert.deepEqual(resized, []);
1150
+ });
1151
+ it('recreates a single owned HUD pane when tmux layout narrows it below the window width', async () => {
1152
+ const killed = [];
1153
+ const resized = [];
1154
+ const created = [];
1155
+ const registered = [];
1156
+ const unregistered = [];
1157
+ const result = await reconcileHudForPromptSubmit('/repo', {
1158
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1159
+ listCurrentWindowPanes: () => [
1160
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 0, paneWidth: 80, paneHeight: 50, paneBottom: 49, windowWidth: 160, windowHeight: 50 },
1161
+ {
1162
+ paneId: '%2',
1163
+ currentCommand: 'node',
1164
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1165
+ paneLeft: 80,
1166
+ paneTop: 0,
1167
+ paneWidth: 80,
1168
+ paneHeight: 50,
1169
+ paneBottom: 49,
1170
+ windowWidth: 160,
1171
+ windowHeight: 50,
1172
+ },
1173
+ { paneId: '%3', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 25, paneWidth: 80, paneHeight: 25, paneBottom: 49, windowWidth: 160, windowHeight: 50 },
1174
+ ],
1175
+ unregisterHudResizeHook: (currentPaneId) => {
1176
+ unregistered.push(currentPaneId);
1177
+ return true;
1178
+ },
1179
+ killTmuxPane: (paneId) => {
1180
+ killed.push(paneId);
1181
+ return true;
1182
+ },
1183
+ createHudWatchPane: (_cwd, _cmd, options) => {
1184
+ created.push({ options });
1185
+ return '%9';
1186
+ },
1187
+ resizeTmuxPane: (paneId, heightLines) => {
1188
+ resized.push({ paneId, heightLines });
1189
+ return true;
1190
+ },
1191
+ registerHudResizeHook: (hudPaneId, currentPaneId, heightLines) => {
1192
+ registered.push({ hudPaneId, currentPaneId, heightLines });
1193
+ return true;
1194
+ },
1195
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1196
+ });
1197
+ assert.equal(result.status, 'recreated');
1198
+ assert.equal(result.paneId, '%9');
1199
+ assert.equal(result.duplicateCount, 0);
1200
+ assert.deepEqual(unregistered, ['%1']);
1201
+ assert.deepEqual(killed, ['%2']);
1202
+ assert.equal(created.length, 1);
1203
+ assert.equal(created[0]?.options?.fullWidth, true);
1204
+ assert.equal(created[0]?.options?.targetPaneId, '%1');
1205
+ assert.equal(created[0]?.options?.heightLines, HUD_TMUX_HEIGHT_LINES);
1206
+ assert.deepEqual(resized, [{ paneId: '%9', heightLines: HUD_TMUX_HEIGHT_LINES }]);
1207
+ assert.deepEqual(registered, [{ hudPaneId: '%9', currentPaneId: '%1', heightLines: HUD_TMUX_HEIGHT_LINES }]);
1208
+ });
1209
+ it('recreates a single owned HUD pane when tmux layout moves it away from the bottom', async () => {
1210
+ const killed = [];
1211
+ const created = [];
1212
+ const result = await reconcileHudForPromptSubmit('/repo', {
1213
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1214
+ listCurrentWindowPanes: () => [
1215
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 3, paneWidth: 160, paneHeight: 47, paneBottom: 49, windowWidth: 160, windowHeight: 50 },
1216
+ {
1217
+ paneId: '%2',
1218
+ currentCommand: 'node',
1219
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1220
+ paneLeft: 0,
1221
+ paneTop: 0,
1222
+ paneWidth: 160,
1223
+ paneHeight: 3,
1224
+ paneBottom: 2,
1225
+ windowWidth: 160,
1226
+ windowHeight: 50,
1227
+ },
1228
+ { paneId: '%3', currentCommand: 'codex', startCommand: 'codex', paneLeft: 80, paneTop: 3, paneWidth: 80, paneHeight: 47, paneBottom: 49, windowWidth: 160, windowHeight: 50 },
1229
+ ],
1230
+ unregisterHudResizeHook: () => true,
1231
+ killTmuxPane: (paneId) => {
1232
+ killed.push(paneId);
1233
+ return true;
1234
+ },
1235
+ createHudWatchPane: (_cwd, _cmd, options) => {
1236
+ created.push({ options });
1237
+ return '%9';
1238
+ },
1239
+ resizeTmuxPane: () => true,
1240
+ registerHudResizeHook: () => true,
1241
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1242
+ });
1243
+ assert.equal(result.status, 'recreated');
1244
+ assert.deepEqual(killed, ['%2']);
1245
+ assert.equal(created[0]?.options?.fullWidth, true);
1246
+ assert.equal(created[0]?.options?.targetPaneId, '%1');
1247
+ });
1248
+ it('keeps a geometrically healthy duplicate HUD pane instead of preserving a malformed first duplicate', async () => {
1249
+ const killed = [];
1250
+ const resized = [];
1251
+ const created = [];
1252
+ const result = await reconcileHudForPromptSubmit('/repo', {
1253
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1254
+ listCurrentWindowPanes: () => [
1255
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 0, paneWidth: 160, paneHeight: 47, paneBottom: 46, windowWidth: 160, windowHeight: 50 },
1256
+ {
1257
+ paneId: '%bad',
1258
+ currentCommand: 'node',
1259
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1260
+ paneLeft: 80,
1261
+ paneTop: 0,
1262
+ paneWidth: 80,
1263
+ paneHeight: 50,
1264
+ paneBottom: 49,
1265
+ windowWidth: 160,
1266
+ windowHeight: 50,
1267
+ },
1268
+ {
1269
+ paneId: '%good',
1270
+ currentCommand: 'node',
1271
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1272
+ paneLeft: 0,
1273
+ paneTop: 47,
1274
+ paneWidth: 160,
1275
+ paneHeight: 3,
1276
+ paneBottom: 49,
1277
+ windowWidth: 160,
1278
+ windowHeight: 50,
1279
+ },
1280
+ ],
1281
+ killTmuxPane: (paneId) => {
1282
+ killed.push(paneId);
1283
+ return true;
1284
+ },
1285
+ createHudWatchPane: (_cwd, _cmd, options) => {
1286
+ created.push({ options });
1287
+ return '%new';
1288
+ },
1289
+ resizeTmuxPane: (paneId, heightLines) => {
1290
+ resized.push({ paneId, heightLines });
1291
+ return true;
1292
+ },
1293
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1294
+ });
1295
+ assert.equal(result.status, 'replaced_duplicates');
1296
+ assert.equal(result.paneId, '%good');
1297
+ assert.deepEqual(killed, ['%bad']);
1298
+ assert.deepEqual(resized, [{ paneId: '%good', heightLines: HUD_TMUX_HEIGHT_LINES }]);
1299
+ assert.deepEqual(created, []);
1300
+ });
1301
+ it('recreates duplicate HUD panes when every duplicate has malformed topology', async () => {
1302
+ const unregistered = [];
1303
+ const killed = [];
1304
+ const created = [];
1305
+ const result = await reconcileHudForPromptSubmit('/repo', {
1306
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1307
+ listCurrentWindowPanes: () => [
1308
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex', paneLeft: 0, paneTop: 0, paneWidth: 80, paneHeight: 50, paneBottom: 49, windowWidth: 160, windowHeight: 50 },
1309
+ {
1310
+ paneId: '%bad-a',
1311
+ currentCommand: 'node',
1312
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1313
+ paneLeft: 80,
1314
+ paneTop: 0,
1315
+ paneWidth: 80,
1316
+ paneHeight: 50,
1317
+ paneBottom: 49,
1318
+ windowWidth: 160,
1319
+ windowHeight: 50,
1320
+ },
1321
+ {
1322
+ paneId: '%bad-b',
1323
+ currentCommand: 'node',
1324
+ startCommand: `env OMX_SESSION_ID='sess-a' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch`,
1325
+ paneLeft: 0,
1326
+ paneTop: 0,
1327
+ paneWidth: 160,
1328
+ paneHeight: 3,
1329
+ paneBottom: 2,
1330
+ windowWidth: 160,
1331
+ windowHeight: 50,
1332
+ },
1333
+ ],
1334
+ unregisterHudResizeHook: (leaderPaneId) => {
1335
+ unregistered.push(leaderPaneId);
1336
+ return true;
1337
+ },
1338
+ killTmuxPane: (paneId) => {
1339
+ killed.push(paneId);
1340
+ return true;
1341
+ },
1342
+ createHudWatchPane: (_cwd, _cmd, options) => {
1343
+ created.push({ options });
1344
+ return '%new';
1345
+ },
1346
+ resizeTmuxPane: () => true,
1347
+ registerHudResizeHook: () => true,
1348
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1349
+ });
1350
+ assert.equal(result.status, 'replaced_duplicates');
1351
+ assert.equal(result.paneId, '%new');
1352
+ assert.deepEqual(unregistered, ['%1']);
1353
+ assert.deepEqual(killed, ['%bad-a', '%bad-b']);
1354
+ assert.equal(created.length, 1);
1355
+ assert.equal(created[0]?.options?.fullWidth, true);
1356
+ assert.equal(created[0]?.options?.targetPaneId, '%1');
1357
+ });
1093
1358
  it('registers client-resized hook scoped from the emitting pane after resizing an existing HUD pane', async () => {
1094
1359
  const registered = [];
1095
1360
  await reconcileHudForPromptSubmit('/repo', {
@@ -1123,6 +1388,7 @@ describe('reconcileHudForPromptSubmit', () => {
1123
1388
  ],
1124
1389
  createHudWatchPane: () => '%9',
1125
1390
  resizeTmuxPane: () => true,
1391
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
1126
1392
  registerHudResizeHook: (hudPaneId, leaderPaneId, heightLines) => {
1127
1393
  registered.push({ hudPaneId, leaderPaneId, heightLines });
1128
1394
  return true;
@@ -1157,4 +1423,104 @@ describe('reconcileHudForPromptSubmit', () => {
1157
1423
  assert.equal(registered[0]?.leaderPaneId, '%1');
1158
1424
  });
1159
1425
  });
1426
+ describe('reconcileHudForPromptSubmit cramped-window guard (#2754)', () => {
1427
+ const crampedHeight = HUD_TMUX_MIN_LAUNCH_WINDOW_HEIGHT_LINES - 1;
1428
+ const roomyHeight = HUD_TMUX_MIN_LAUNCH_WINDOW_HEIGHT_LINES;
1429
+ it('does not create a HUD split on prompt submit when the existing window is too cramped', async () => {
1430
+ const created = [];
1431
+ const result = await reconcileHudForPromptSubmit('/repo', {
1432
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1433
+ listCurrentWindowPanes: () => [
1434
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex' },
1435
+ ],
1436
+ createHudWatchPane: (_cwd, cmd) => {
1437
+ created.push(cmd);
1438
+ return '%9';
1439
+ },
1440
+ resizeTmuxPane: () => true,
1441
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
1442
+ registerHudResizeHook: noOpRegisterHudResizeHook,
1443
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1444
+ readCurrentWindowSize: () => ({ width: 160, height: crampedHeight }),
1445
+ });
1446
+ assert.equal(result.status, 'skipped_window_too_cramped');
1447
+ assert.equal(result.paneId, null);
1448
+ assert.deepEqual(created, []);
1449
+ });
1450
+ it('creates the HUD on prompt submit when the existing window has room', async () => {
1451
+ const created = [];
1452
+ const result = await reconcileHudForPromptSubmit('/repo', {
1453
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1454
+ listCurrentWindowPanes: () => [
1455
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex' },
1456
+ ],
1457
+ createHudWatchPane: (_cwd, cmd) => {
1458
+ created.push(cmd);
1459
+ return '%9';
1460
+ },
1461
+ resizeTmuxPane: () => true,
1462
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
1463
+ registerHudResizeHook: noOpRegisterHudResizeHook,
1464
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1465
+ readCurrentWindowSize: () => ({ width: 160, height: roomyHeight }),
1466
+ });
1467
+ assert.equal(result.status, 'recreated');
1468
+ assert.equal(result.paneId, '%9');
1469
+ assert.equal(created.length, 1);
1470
+ });
1471
+ it('creates the HUD on prompt submit when the window height is unknown', async () => {
1472
+ const created = [];
1473
+ const result = await reconcileHudForPromptSubmit('/repo', {
1474
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1475
+ listCurrentWindowPanes: () => [
1476
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex' },
1477
+ ],
1478
+ createHudWatchPane: (_cwd, cmd) => {
1479
+ created.push(cmd);
1480
+ return '%9';
1481
+ },
1482
+ resizeTmuxPane: () => true,
1483
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
1484
+ registerHudResizeHook: noOpRegisterHudResizeHook,
1485
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1486
+ readCurrentWindowSize: () => ({ width: null, height: null }),
1487
+ });
1488
+ assert.equal(result.status, 'recreated');
1489
+ assert.equal(created.length, 1);
1490
+ });
1491
+ it('keeps an existing HUD pane even when the window is cramped', async () => {
1492
+ const created = [];
1493
+ const resized = [];
1494
+ const result = await reconcileHudForPromptSubmit('/repo', {
1495
+ env: { TMUX: '1', TMUX_PANE: '%1', OMX_SESSION_ID: 'sess-a', [OMX_TMUX_HUD_OWNER_ENV]: '1' },
1496
+ listCurrentWindowPanes: () => [
1497
+ { paneId: '%1', currentCommand: 'codex', startCommand: 'codex' },
1498
+ {
1499
+ paneId: '%2',
1500
+ currentCommand: 'node',
1501
+ startCommand: `exec env OMX_SESSION_ID='sess-a' OMX_TMUX_HUD_OWNER='1' ${OMX_TMUX_HUD_LEADER_PANE_ENV}='%1' node omx hud --watch --preset=focused`,
1502
+ },
1503
+ ],
1504
+ createHudWatchPane: (_cwd, cmd) => {
1505
+ created.push(cmd);
1506
+ return '%9';
1507
+ },
1508
+ resizeTmuxPane: (paneId, lines) => {
1509
+ resized.push({ paneId, lines });
1510
+ return true;
1511
+ },
1512
+ unregisterHudResizeHook: noOpUnregisterHudResizeHook,
1513
+ registerHudResizeHook: noOpRegisterHudResizeHook,
1514
+ resolveOmxCliEntryPath: () => '/repo/dist/cli/omx.js',
1515
+ readCurrentWindowSize: () => ({ width: 160, height: crampedHeight }),
1516
+ });
1517
+ // The cramped guard only blocks fresh creation; an already-present HUD pane
1518
+ // is kept (resized) rather than removed.
1519
+ assert.equal(result.status, 'resized');
1520
+ assert.equal(result.paneId, '%2');
1521
+ assert.deepEqual(created, []);
1522
+ assert.equal(resized[0]?.paneId, '%2');
1523
+ assert.equal(resized[0]?.lines, HUD_TMUX_HEIGHT_LINES);
1524
+ });
1525
+ });
1160
1526
  //# sourceMappingURL=reconcile.test.js.map