oh-my-codex 0.11.12 → 0.11.13

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 (248) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +23 -0
  4. package/README.vi.md +144 -185
  5. package/crates/omx-runtime-core/src/engine.rs +122 -4
  6. package/crates/omx-runtime-core/src/lib.rs +17 -0
  7. package/dist/cli/__tests__/autoresearch.test.js +11 -0
  8. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  9. package/dist/cli/__tests__/cleanup.test.js +117 -4
  10. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  11. package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
  12. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  13. package/dist/cli/__tests__/exec.test.js +6 -0
  14. package/dist/cli/__tests__/exec.test.js.map +1 -1
  15. package/dist/cli/__tests__/index.test.js +94 -1
  16. package/dist/cli/__tests__/index.test.js.map +1 -1
  17. package/dist/cli/__tests__/launch-fallback.test.js +3 -0
  18. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  19. package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
  20. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  21. package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
  22. package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
  23. package/dist/cli/__tests__/resume.test.js +6 -0
  24. package/dist/cli/__tests__/resume.test.js.map +1 -1
  25. package/dist/cli/__tests__/setup-refresh.test.js +29 -12
  26. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  27. package/dist/cli/__tests__/star-prompt.test.js +16 -0
  28. package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
  29. package/dist/cli/__tests__/uninstall.test.js +112 -1
  30. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  31. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
  32. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
  33. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
  34. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
  35. package/dist/cli/cleanup.d.ts +2 -0
  36. package/dist/cli/cleanup.d.ts.map +1 -1
  37. package/dist/cli/cleanup.js +26 -1
  38. package/dist/cli/cleanup.js.map +1 -1
  39. package/dist/cli/index.d.ts +7 -0
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +161 -50
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/setup.d.ts.map +1 -1
  44. package/dist/cli/setup.js +15 -14
  45. package/dist/cli/setup.js.map +1 -1
  46. package/dist/cli/star-prompt.d.ts.map +1 -1
  47. package/dist/cli/star-prompt.js +1 -0
  48. package/dist/cli/star-prompt.js.map +1 -1
  49. package/dist/cli/team.d.ts.map +1 -1
  50. package/dist/cli/team.js +5 -1
  51. package/dist/cli/team.js.map +1 -1
  52. package/dist/cli/uninstall.d.ts.map +1 -1
  53. package/dist/cli/uninstall.js +26 -0
  54. package/dist/cli/uninstall.js.map +1 -1
  55. package/dist/cli/update.d.ts.map +1 -1
  56. package/dist/cli/update.js +1 -0
  57. package/dist/cli/update.js.map +1 -1
  58. package/dist/config/__tests__/generator-idempotent.test.js +4 -4
  59. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  60. package/dist/config/__tests__/mcp-registry.test.js +13 -16
  61. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  62. package/dist/config/mcp-registry.d.ts +1 -0
  63. package/dist/config/mcp-registry.d.ts.map +1 -1
  64. package/dist/config/mcp-registry.js +4 -4
  65. package/dist/config/mcp-registry.js.map +1 -1
  66. package/dist/config/models.d.ts +1 -0
  67. package/dist/config/models.d.ts.map +1 -1
  68. package/dist/config/models.js +39 -1
  69. package/dist/config/models.js.map +1 -1
  70. package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
  71. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  72. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +499 -17
  73. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  74. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +140 -14
  75. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  76. package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
  77. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
  79. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
  80. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
  81. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
  82. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +15 -1
  83. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  84. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
  85. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
  87. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
  89. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
  91. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  92. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  93. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  94. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
  95. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  96. package/dist/hooks/keyword-detector.d.ts +1 -0
  97. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  98. package/dist/hooks/keyword-detector.js +48 -0
  99. package/dist/hooks/keyword-detector.js.map +1 -1
  100. package/dist/hooks/session.d.ts.map +1 -1
  101. package/dist/hooks/session.js +1 -0
  102. package/dist/hooks/session.js.map +1 -1
  103. package/dist/hud/__tests__/state.test.js +70 -1
  104. package/dist/hud/__tests__/state.test.js.map +1 -1
  105. package/dist/hud/state.d.ts.map +1 -1
  106. package/dist/hud/state.js +10 -37
  107. package/dist/hud/state.js.map +1 -1
  108. package/dist/mcp/state-server.d.ts.map +1 -1
  109. package/dist/mcp/state-server.js +5 -0
  110. package/dist/mcp/state-server.js.map +1 -1
  111. package/dist/modes/__tests__/base-session-scope.test.js +46 -0
  112. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  113. package/dist/modes/base.d.ts.map +1 -1
  114. package/dist/modes/base.js +4 -0
  115. package/dist/modes/base.js.map +1 -1
  116. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
  117. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
  118. package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
  119. package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
  120. package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
  121. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  122. package/dist/notifications/idle-cooldown.d.ts +8 -6
  123. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  124. package/dist/notifications/idle-cooldown.js +53 -22
  125. package/dist/notifications/idle-cooldown.js.map +1 -1
  126. package/dist/notifications/notifier.js +1 -1
  127. package/dist/notifications/notifier.js.map +1 -1
  128. package/dist/notifications/reply-listener.d.ts.map +1 -1
  129. package/dist/notifications/reply-listener.js +1 -0
  130. package/dist/notifications/reply-listener.js.map +1 -1
  131. package/dist/openclaw/config.js +2 -2
  132. package/dist/openclaw/config.js.map +1 -1
  133. package/dist/runtime/bridge.d.ts +1 -0
  134. package/dist/runtime/bridge.d.ts.map +1 -1
  135. package/dist/runtime/bridge.js +2 -6
  136. package/dist/runtime/bridge.js.map +1 -1
  137. package/dist/scripts/notify-fallback-watcher.js +97 -59
  138. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  139. package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
  140. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  141. package/dist/scripts/notify-hook/auto-nudge.js +72 -238
  142. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  143. package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
  144. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
  145. package/dist/scripts/notify-hook/managed-tmux.js +320 -0
  146. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
  147. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
  148. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
  149. package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
  150. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
  151. package/dist/scripts/notify-hook/state-io.d.ts +1 -1
  152. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  153. package/dist/scripts/notify-hook/state-io.js +2 -10
  154. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  155. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  156. package/dist/scripts/notify-hook/team-dispatch.js +60 -59
  157. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  158. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
  159. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  160. package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
  161. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  162. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  163. package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
  164. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  165. package/dist/scripts/notify-hook/team-worker.js +4 -4
  166. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  167. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
  168. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  169. package/dist/scripts/notify-hook/tmux-injection.js +102 -35
  170. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  171. package/dist/scripts/notify-hook.js +144 -20
  172. package/dist/scripts/notify-hook.js.map +1 -1
  173. package/dist/scripts/tmux-hook-engine.d.ts +1 -0
  174. package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
  175. package/dist/scripts/tmux-hook-engine.js +3 -0
  176. package/dist/scripts/tmux-hook-engine.js.map +1 -1
  177. package/dist/team/__tests__/api-interop.test.js +96 -4
  178. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  179. package/dist/team/__tests__/leader-activity.test.js +107 -2
  180. package/dist/team/__tests__/leader-activity.test.js.map +1 -1
  181. package/dist/team/__tests__/runtime-cli.test.js +32 -0
  182. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  183. package/dist/team/__tests__/runtime.test.js +148 -0
  184. package/dist/team/__tests__/runtime.test.js.map +1 -1
  185. package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
  186. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  187. package/dist/team/__tests__/state-root.test.js +11 -1
  188. package/dist/team/__tests__/state-root.test.js.map +1 -1
  189. package/dist/team/__tests__/state.test.js +16 -5
  190. package/dist/team/__tests__/state.test.js.map +1 -1
  191. package/dist/team/__tests__/tmux-session.test.js +460 -2
  192. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  193. package/dist/team/api-interop.d.ts.map +1 -1
  194. package/dist/team/api-interop.js +34 -7
  195. package/dist/team/api-interop.js.map +1 -1
  196. package/dist/team/commit-hygiene.d.ts +60 -0
  197. package/dist/team/commit-hygiene.d.ts.map +1 -0
  198. package/dist/team/commit-hygiene.js +232 -0
  199. package/dist/team/commit-hygiene.js.map +1 -0
  200. package/dist/team/leader-activity.d.ts.map +1 -1
  201. package/dist/team/leader-activity.js +17 -35
  202. package/dist/team/leader-activity.js.map +1 -1
  203. package/dist/team/runtime-cli.d.ts +9 -1
  204. package/dist/team/runtime-cli.d.ts.map +1 -1
  205. package/dist/team/runtime-cli.js +15 -6
  206. package/dist/team/runtime-cli.js.map +1 -1
  207. package/dist/team/runtime.d.ts +7 -2
  208. package/dist/team/runtime.d.ts.map +1 -1
  209. package/dist/team/runtime.js +391 -63
  210. package/dist/team/runtime.js.map +1 -1
  211. package/dist/team/state/dispatch.js +1 -1
  212. package/dist/team/state/dispatch.js.map +1 -1
  213. package/dist/team/state/mailbox.d.ts +1 -0
  214. package/dist/team/state/mailbox.d.ts.map +1 -1
  215. package/dist/team/state/mailbox.js +54 -8
  216. package/dist/team/state/mailbox.js.map +1 -1
  217. package/dist/team/state-root.d.ts +1 -1
  218. package/dist/team/state-root.d.ts.map +1 -1
  219. package/dist/team/state-root.js +8 -3
  220. package/dist/team/state-root.js.map +1 -1
  221. package/dist/team/state.d.ts.map +1 -1
  222. package/dist/team/state.js +66 -3
  223. package/dist/team/state.js.map +1 -1
  224. package/dist/team/tmux-session.d.ts.map +1 -1
  225. package/dist/team/tmux-session.js +69 -27
  226. package/dist/team/tmux-session.js.map +1 -1
  227. package/dist/utils/__tests__/platform-command.test.js +101 -2
  228. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  229. package/dist/utils/git-layout.d.ts +8 -0
  230. package/dist/utils/git-layout.d.ts.map +1 -0
  231. package/dist/utils/git-layout.js +58 -0
  232. package/dist/utils/git-layout.js.map +1 -0
  233. package/dist/utils/platform-command.d.ts.map +1 -1
  234. package/dist/utils/platform-command.js +32 -1
  235. package/dist/utils/platform-command.js.map +1 -1
  236. package/package.json +6 -6
  237. package/src/scripts/notify-fallback-watcher.ts +96 -58
  238. package/src/scripts/notify-hook/auto-nudge.ts +75 -230
  239. package/src/scripts/notify-hook/managed-tmux.ts +324 -0
  240. package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
  241. package/src/scripts/notify-hook/state-io.ts +2 -10
  242. package/src/scripts/notify-hook/team-dispatch.ts +70 -54
  243. package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
  244. package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
  245. package/src/scripts/notify-hook/team-worker.ts +4 -4
  246. package/src/scripts/notify-hook/tmux-injection.ts +103 -33
  247. package/src/scripts/notify-hook.ts +150 -21
  248. package/src/scripts/tmux-hook-engine.ts +4 -0
@@ -3,10 +3,10 @@ import assert from 'node:assert/strict';
3
3
  import fs from 'node:fs';
4
4
  import { syncBuiltinESMExports } from 'node:module';
5
5
  import { PassThrough } from 'node:stream';
6
- import { mkdtemp, readFile, rm, writeFile, chmod } from 'fs/promises';
6
+ import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises';
7
7
  import { join } from 'path';
8
8
  import { tmpdir } from 'os';
9
- import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, killWorkerByPaneId, teardownWorkerPanes, listTeamSessions, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, paneIsBootstrapping, dismissTrustPromptIfPresent, } from '../tmux-session.js';
9
+ import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, buildHudPaneTarget, chooseTeamLeaderPaneId, createTeamSession, enableMouseScrolling, isMsysOrGitBash, isNativeWindows, isTmuxAvailable, restoreStandaloneHudPane, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, killWorkerByPaneId, teardownWorkerPanes, listTeamSessions, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, paneIsBootstrapping, dismissTrustPromptIfPresent, } from '../tmux-session.js';
10
10
  import { HUD_RESIZE_RECONCILE_DELAY_SECONDS, HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
11
11
  import * as tmuxSessionModule from '../tmux-session.js';
12
12
  function withEmptyPath(fn) {
@@ -190,6 +190,71 @@ describe('HUD resize hook command builders', () => {
190
190
  assert.equal(args.join(' ').includes('split-window'), false);
191
191
  assert.deepEqual(args, ['run-shell', `tmux resize-pane -t %7 -y ${HUD_TMUX_TEAM_HEIGHT_LINES} >/dev/null 2>&1 || true`]);
192
192
  });
193
+ it('resolves the tmux executable for win32 hook shell snippets', async () => {
194
+ const fakeBin = await mkdtemp(join(tmpdir(), 'omx-win32-hook-tmux-'));
195
+ const prevPath = process.env.PATH;
196
+ const prevPathext = process.env.PATHEXT;
197
+ const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
198
+ try {
199
+ const tmuxPath = join(fakeBin, 'tmux.exe');
200
+ await writeFile(tmuxPath, '');
201
+ process.env.PATH = fakeBin;
202
+ process.env.PATHEXT = '.EXE';
203
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
204
+ const resizeArgs = buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1');
205
+ const delayedArgs = buildScheduleDelayedHudResizeArgs('%1');
206
+ const reconcileArgs = buildReconcileHudResizeArgs('%1');
207
+ assert.match(resizeArgs[4] ?? '', new RegExp(escapeRegExp(tmuxPath)));
208
+ assert.doesNotMatch(resizeArgs[4] ?? '', /^run-shell -b 'tmux resize-pane/);
209
+ assert.match(delayedArgs[2] ?? '', new RegExp(escapeRegExp(tmuxPath)));
210
+ assert.doesNotMatch(delayedArgs[2] ?? '', /sleep \d+; tmux resize-pane/);
211
+ assert.match(reconcileArgs[1] ?? '', new RegExp(escapeRegExp(tmuxPath)));
212
+ assert.doesNotMatch(reconcileArgs[1] ?? '', /^tmux resize-pane/);
213
+ }
214
+ finally {
215
+ if (origPlatform)
216
+ Object.defineProperty(process, 'platform', origPlatform);
217
+ if (typeof prevPath === 'string')
218
+ process.env.PATH = prevPath;
219
+ else
220
+ delete process.env.PATH;
221
+ if (typeof prevPathext === 'string')
222
+ process.env.PATHEXT = prevPathext;
223
+ else
224
+ delete process.env.PATHEXT;
225
+ await rm(fakeBin, { recursive: true, force: true });
226
+ }
227
+ });
228
+ it('resolves the tmux executable twice for win32 client-attached one-shot hooks', async () => {
229
+ const fakeBin = await mkdtemp(join(tmpdir(), 'omx-win32-attached-hook-'));
230
+ const prevPath = process.env.PATH;
231
+ const prevPathext = process.env.PATHEXT;
232
+ const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
233
+ try {
234
+ const tmuxPath = join(fakeBin, 'tmux.exe');
235
+ await writeFile(tmuxPath, '');
236
+ process.env.PATH = fakeBin;
237
+ process.env.PATHEXT = '.EXE';
238
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
239
+ const args = buildRegisterClientAttachedReconcileArgs('my-session:0', 'omx_attached_team_session_0_1', '%1');
240
+ const matches = (args[4] ?? '').match(new RegExp(escapeRegExp(tmuxPath), 'g')) || [];
241
+ assert.equal(matches.length, 2, 'client-attached hook should resolve tmux for both resize and unregister commands');
242
+ assert.doesNotMatch(args[4] ?? '', /; tmux set-hook -u -t my-session:0 client-attached/);
243
+ }
244
+ finally {
245
+ if (origPlatform)
246
+ Object.defineProperty(process, 'platform', origPlatform);
247
+ if (typeof prevPath === 'string')
248
+ process.env.PATH = prevPath;
249
+ else
250
+ delete process.env.PATH;
251
+ if (typeof prevPathext === 'string')
252
+ process.env.PATHEXT = prevPathext;
253
+ else
254
+ delete process.env.PATHEXT;
255
+ await rm(fakeBin, { recursive: true, force: true });
256
+ }
257
+ });
193
258
  });
194
259
  describe('sendToWorker validation', () => {
195
260
  it('rejects text over 200 chars', async () => {
@@ -1116,6 +1181,212 @@ describe('team worker launch mode helpers', () => {
1116
1181
  delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1117
1182
  }
1118
1183
  });
1184
+ it('buildWorkerProcessLaunchSpec injects the active provider env_key from CODEX_HOME config.toml', async () => {
1185
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1186
+ const prevCodexHome = process.env.CODEX_HOME;
1187
+ const prevProviderEnv = process.env.CUSTOM_PROVIDER_API_KEY;
1188
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-team-provider-env-'));
1189
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1190
+ process.env.CODEX_HOME = codexHome;
1191
+ process.env.CUSTOM_PROVIDER_API_KEY = 'test-secret';
1192
+ try {
1193
+ await writeFile(join(codexHome, 'config.toml'), [
1194
+ 'model_provider = "custom_provider"',
1195
+ '',
1196
+ '[model_providers.custom_provider]',
1197
+ 'name = "custom_provider"',
1198
+ 'base_url = "http://localhost:3000/v1"',
1199
+ 'wire_api = "responses"',
1200
+ 'requires_openai_auth = true',
1201
+ 'env_key = "CUSTOM_PROVIDER_API_KEY"',
1202
+ '',
1203
+ ].join('\n'));
1204
+ const spec = buildWorkerProcessLaunchSpec('gamma-team', 1, [], '/tmp/workspace', {}, 'codex');
1205
+ assert.equal(spec.env.CUSTOM_PROVIDER_API_KEY, 'test-secret');
1206
+ }
1207
+ finally {
1208
+ if (typeof prevBypass === 'string')
1209
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1210
+ else
1211
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1212
+ if (typeof prevCodexHome === 'string')
1213
+ process.env.CODEX_HOME = prevCodexHome;
1214
+ else
1215
+ delete process.env.CODEX_HOME;
1216
+ if (typeof prevProviderEnv === 'string')
1217
+ process.env.CUSTOM_PROVIDER_API_KEY = prevProviderEnv;
1218
+ else
1219
+ delete process.env.CUSTOM_PROVIDER_API_KEY;
1220
+ await rm(codexHome, { recursive: true, force: true });
1221
+ }
1222
+ });
1223
+ it('buildWorkerProcessLaunchSpec does not inject the active provider env_key for non-codex workers', async () => {
1224
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1225
+ const prevCodexHome = process.env.CODEX_HOME;
1226
+ const prevProviderEnv = process.env.CUSTOM_PROVIDER_API_KEY;
1227
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-team-provider-env-'));
1228
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1229
+ process.env.CODEX_HOME = codexHome;
1230
+ process.env.CUSTOM_PROVIDER_API_KEY = 'test-secret';
1231
+ try {
1232
+ await writeFile(join(codexHome, 'config.toml'), [
1233
+ 'model_provider = "custom_provider"',
1234
+ '',
1235
+ '[model_providers.custom_provider]',
1236
+ 'name = "custom_provider"',
1237
+ 'base_url = "http://localhost:3000/v1"',
1238
+ 'wire_api = "responses"',
1239
+ 'requires_openai_auth = true',
1240
+ 'env_key = "CUSTOM_PROVIDER_API_KEY"',
1241
+ '',
1242
+ ].join('\n'));
1243
+ const spec = buildWorkerProcessLaunchSpec('delta-team', 1, [], '/tmp/workspace', {}, 'claude');
1244
+ assert.equal(spec.workerCli, 'claude');
1245
+ assert.equal(spec.env.CUSTOM_PROVIDER_API_KEY, undefined);
1246
+ }
1247
+ finally {
1248
+ if (typeof prevBypass === 'string')
1249
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1250
+ else
1251
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1252
+ if (typeof prevCodexHome === 'string')
1253
+ process.env.CODEX_HOME = prevCodexHome;
1254
+ else
1255
+ delete process.env.CODEX_HOME;
1256
+ if (typeof prevProviderEnv === 'string')
1257
+ process.env.CUSTOM_PROVIDER_API_KEY = prevProviderEnv;
1258
+ else
1259
+ delete process.env.CUSTOM_PROVIDER_API_KEY;
1260
+ await rm(codexHome, { recursive: true, force: true });
1261
+ }
1262
+ });
1263
+ it('buildWorkerProcessLaunchSpec reads provider env from worker CODEX_HOME override', async () => {
1264
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1265
+ const prevCodexHome = process.env.CODEX_HOME;
1266
+ const prevPrimaryProviderEnv = process.env.PRIMARY_PROVIDER_API_KEY;
1267
+ const prevWorkerProviderEnv = process.env.WORKER_PROVIDER_API_KEY;
1268
+ const leaderCodexHome = await mkdtemp(join(tmpdir(), 'omx-team-provider-env-leader-'));
1269
+ const workerCodexHome = await mkdtemp(join(tmpdir(), 'omx-team-provider-env-worker-'));
1270
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1271
+ process.env.CODEX_HOME = leaderCodexHome;
1272
+ process.env.PRIMARY_PROVIDER_API_KEY = 'leader-secret';
1273
+ process.env.WORKER_PROVIDER_API_KEY = 'worker-secret';
1274
+ try {
1275
+ await writeFile(join(leaderCodexHome, 'config.toml'), [
1276
+ 'model_provider = "primary_provider"',
1277
+ '',
1278
+ '[model_providers.primary_provider]',
1279
+ 'name = "primary_provider"',
1280
+ 'base_url = "http://localhost:3000/v1"',
1281
+ 'wire_api = "responses"',
1282
+ 'requires_openai_auth = true',
1283
+ 'env_key = "PRIMARY_PROVIDER_API_KEY"',
1284
+ '',
1285
+ ].join('\n'));
1286
+ await writeFile(join(workerCodexHome, 'config.toml'), [
1287
+ 'model_provider = "worker_provider"',
1288
+ '',
1289
+ '[model_providers.worker_provider]',
1290
+ 'name = "worker_provider"',
1291
+ 'base_url = "http://localhost:4000/v1"',
1292
+ 'wire_api = "responses"',
1293
+ 'requires_openai_auth = true',
1294
+ 'env_key = "WORKER_PROVIDER_API_KEY"',
1295
+ '',
1296
+ ].join('\n'));
1297
+ const spec = buildWorkerProcessLaunchSpec('epsilon-team', 1, [], '/tmp/workspace', { CODEX_HOME: workerCodexHome }, 'codex');
1298
+ assert.equal(spec.env.CODEX_HOME, workerCodexHome);
1299
+ assert.equal(spec.env.WORKER_PROVIDER_API_KEY, 'worker-secret');
1300
+ assert.equal(spec.env.PRIMARY_PROVIDER_API_KEY, undefined);
1301
+ }
1302
+ finally {
1303
+ if (typeof prevBypass === 'string')
1304
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1305
+ else
1306
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1307
+ if (typeof prevCodexHome === 'string')
1308
+ process.env.CODEX_HOME = prevCodexHome;
1309
+ else
1310
+ delete process.env.CODEX_HOME;
1311
+ if (typeof prevPrimaryProviderEnv === 'string')
1312
+ process.env.PRIMARY_PROVIDER_API_KEY = prevPrimaryProviderEnv;
1313
+ else
1314
+ delete process.env.PRIMARY_PROVIDER_API_KEY;
1315
+ if (typeof prevWorkerProviderEnv === 'string')
1316
+ process.env.WORKER_PROVIDER_API_KEY = prevWorkerProviderEnv;
1317
+ else
1318
+ delete process.env.WORKER_PROVIDER_API_KEY;
1319
+ await rm(leaderCodexHome, { recursive: true, force: true });
1320
+ await rm(workerCodexHome, { recursive: true, force: true });
1321
+ }
1322
+ });
1323
+ it('buildWorkerProcessLaunchSpec resolves relative worker CODEX_HOME against the worker cwd', async () => {
1324
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1325
+ const prevCodexHome = process.env.CODEX_HOME;
1326
+ const prevLeaderProviderEnv = process.env.LEADER_PROVIDER_API_KEY;
1327
+ const prevWorkerProviderEnv = process.env.WORKER_PROVIDER_API_KEY;
1328
+ const originalCwd = process.cwd();
1329
+ const leaderCwd = await mkdtemp(join(tmpdir(), 'omx-team-provider-relative-leader-'));
1330
+ const workerCwd = await mkdtemp(join(tmpdir(), 'omx-team-provider-relative-worker-'));
1331
+ const leaderCodexHome = join(leaderCwd, '.codex');
1332
+ const workerCodexHome = join(workerCwd, '.codex');
1333
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1334
+ process.env.CODEX_HOME = leaderCodexHome;
1335
+ process.env.LEADER_PROVIDER_API_KEY = 'leader-secret';
1336
+ process.env.WORKER_PROVIDER_API_KEY = 'worker-secret';
1337
+ try {
1338
+ await mkdir(leaderCodexHome, { recursive: true });
1339
+ await mkdir(workerCodexHome, { recursive: true });
1340
+ await writeFile(join(leaderCodexHome, 'config.toml'), [
1341
+ 'model_provider = "leader_provider"',
1342
+ '',
1343
+ '[model_providers.leader_provider]',
1344
+ 'name = "leader_provider"',
1345
+ 'base_url = "http://localhost:3000/v1"',
1346
+ 'wire_api = "responses"',
1347
+ 'requires_openai_auth = true',
1348
+ 'env_key = "LEADER_PROVIDER_API_KEY"',
1349
+ '',
1350
+ ].join('\n'));
1351
+ await writeFile(join(workerCodexHome, 'config.toml'), [
1352
+ 'model_provider = "worker_provider"',
1353
+ '',
1354
+ '[model_providers.worker_provider]',
1355
+ 'name = "worker_provider"',
1356
+ 'base_url = "http://localhost:4000/v1"',
1357
+ 'wire_api = "responses"',
1358
+ 'requires_openai_auth = true',
1359
+ 'env_key = "WORKER_PROVIDER_API_KEY"',
1360
+ '',
1361
+ ].join('\n'));
1362
+ process.chdir(leaderCwd);
1363
+ const spec = buildWorkerProcessLaunchSpec('zeta-team', 1, [], workerCwd, { CODEX_HOME: '.codex' }, 'codex');
1364
+ assert.equal(spec.env.CODEX_HOME, '.codex');
1365
+ assert.equal(spec.env.WORKER_PROVIDER_API_KEY, 'worker-secret');
1366
+ assert.equal(spec.env.LEADER_PROVIDER_API_KEY, undefined);
1367
+ }
1368
+ finally {
1369
+ process.chdir(originalCwd);
1370
+ if (typeof prevBypass === 'string')
1371
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1372
+ else
1373
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1374
+ if (typeof prevCodexHome === 'string')
1375
+ process.env.CODEX_HOME = prevCodexHome;
1376
+ else
1377
+ delete process.env.CODEX_HOME;
1378
+ if (typeof prevLeaderProviderEnv === 'string')
1379
+ process.env.LEADER_PROVIDER_API_KEY = prevLeaderProviderEnv;
1380
+ else
1381
+ delete process.env.LEADER_PROVIDER_API_KEY;
1382
+ if (typeof prevWorkerProviderEnv === 'string')
1383
+ process.env.WORKER_PROVIDER_API_KEY = prevWorkerProviderEnv;
1384
+ else
1385
+ delete process.env.WORKER_PROVIDER_API_KEY;
1386
+ await rm(leaderCwd, { recursive: true, force: true });
1387
+ await rm(workerCwd, { recursive: true, force: true });
1388
+ }
1389
+ });
1119
1390
  });
1120
1391
  describe('sendToWorkerStdin', () => {
1121
1392
  it('writes a newline-terminated trigger message to worker stdin', () => {
@@ -1320,6 +1591,193 @@ esac
1320
1591
  });
1321
1592
  });
1322
1593
  });
1594
+ describe('native Windows HUD reconciliation', () => {
1595
+ it('avoids nested tmux run-shell hooks during team HUD startup on native Windows', async () => {
1596
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-win32-hud-'));
1597
+ const prevTmux = process.env.TMUX;
1598
+ const prevTmuxPane = process.env.TMUX_PANE;
1599
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1600
+ const prevMsystem = process.env.MSYSTEM;
1601
+ const prevOstype = process.env.OSTYPE;
1602
+ const prevWsl = process.env.WSL_DISTRO_NAME;
1603
+ const prevWslInterop = process.env.WSL_INTEROP;
1604
+ const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
1605
+ try {
1606
+ await withMockTmuxFixture('omx-tmux-win32-hud-reconcile-', (logPath) => `#!/bin/sh
1607
+ set -eu
1608
+ printf '%s\\n' "$*" >> "${logPath}"
1609
+ case "\${1:-}" in
1610
+ -V)
1611
+ echo "tmux 3.4"
1612
+ exit 0
1613
+ ;;
1614
+ display-message)
1615
+ case "$*" in
1616
+ *"#{window_width}"*)
1617
+ echo "120"
1618
+ ;;
1619
+ *)
1620
+ echo "leader:0 %1"
1621
+ ;;
1622
+ esac
1623
+ exit 0
1624
+ ;;
1625
+ list-panes)
1626
+ printf "%%1\\tnode\\t'codex'\\n"
1627
+ exit 0
1628
+ ;;
1629
+ split-window)
1630
+ case "$*" in
1631
+ *" -h "*)
1632
+ echo "%2"
1633
+ ;;
1634
+ *)
1635
+ echo "%3"
1636
+ ;;
1637
+ esac
1638
+ exit 0
1639
+ ;;
1640
+ resize-pane|select-layout|set-window-option|select-pane|kill-pane)
1641
+ exit 0
1642
+ ;;
1643
+ set-hook|run-shell)
1644
+ exit 0
1645
+ ;;
1646
+ *)
1647
+ exit 0
1648
+ ;;
1649
+ esac
1650
+ `, async ({ logPath }) => {
1651
+ const fakeBinDir = join(logPath, '..');
1652
+ const geminiPath = join(fakeBinDir, 'gemini');
1653
+ await writeFile(geminiPath, '#!/bin/sh\nexit 0\n');
1654
+ await chmod(geminiPath, 0o755);
1655
+ process.env.TMUX = 'leader-session,stub,0';
1656
+ process.env.TMUX_PANE = '%1';
1657
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
1658
+ delete process.env.MSYSTEM;
1659
+ delete process.env.OSTYPE;
1660
+ delete process.env.WSL_DISTRO_NAME;
1661
+ delete process.env.WSL_INTEROP;
1662
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
1663
+ const session = createTeamSession('Windows Team', 1, cwd);
1664
+ assert.equal(session.hudPaneId, '%3');
1665
+ assert.equal(session.resizeHookName, null);
1666
+ assert.equal(session.resizeHookTarget, null);
1667
+ const tmuxLog = await readFile(logPath, 'utf-8');
1668
+ assert.match(tmuxLog, new RegExp(`resize-pane -t %3 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
1669
+ assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-resized\[\d+\]/);
1670
+ assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-attached\[\d+\]/);
1671
+ assert.doesNotMatch(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %3 -y \d+ >/);
1672
+ assert.doesNotMatch(tmuxLog, /run-shell tmux resize-pane -t %3 -y \d+ >/);
1673
+ });
1674
+ }
1675
+ finally {
1676
+ if (origPlatform)
1677
+ Object.defineProperty(process, 'platform', origPlatform);
1678
+ if (typeof prevTmux === 'string')
1679
+ process.env.TMUX = prevTmux;
1680
+ else
1681
+ delete process.env.TMUX;
1682
+ if (typeof prevTmuxPane === 'string')
1683
+ process.env.TMUX_PANE = prevTmuxPane;
1684
+ else
1685
+ delete process.env.TMUX_PANE;
1686
+ if (typeof prevWorkerCli === 'string')
1687
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
1688
+ else
1689
+ delete process.env.OMX_TEAM_WORKER_CLI;
1690
+ if (typeof prevMsystem === 'string')
1691
+ process.env.MSYSTEM = prevMsystem;
1692
+ else
1693
+ delete process.env.MSYSTEM;
1694
+ if (typeof prevOstype === 'string')
1695
+ process.env.OSTYPE = prevOstype;
1696
+ else
1697
+ delete process.env.OSTYPE;
1698
+ if (typeof prevWsl === 'string')
1699
+ process.env.WSL_DISTRO_NAME = prevWsl;
1700
+ else
1701
+ delete process.env.WSL_DISTRO_NAME;
1702
+ if (typeof prevWslInterop === 'string')
1703
+ process.env.WSL_INTEROP = prevWslInterop;
1704
+ else
1705
+ delete process.env.WSL_INTEROP;
1706
+ await rm(cwd, { recursive: true, force: true });
1707
+ }
1708
+ });
1709
+ it('restores standalone HUD panes with direct resize on native Windows', async () => {
1710
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-standalone-win32-hud-'));
1711
+ const prevLeaderNodePath = process.env.OMX_LEADER_NODE_PATH;
1712
+ const prevMsystem = process.env.MSYSTEM;
1713
+ const prevOstype = process.env.OSTYPE;
1714
+ const prevWsl = process.env.WSL_DISTRO_NAME;
1715
+ const prevWslInterop = process.env.WSL_INTEROP;
1716
+ const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
1717
+ try {
1718
+ await withMockTmuxFixture('omx-tmux-win32-standalone-hud-', (logPath) => `#!/bin/sh
1719
+ set -eu
1720
+ printf '%s\\n' "$*" >> "${logPath}"
1721
+ case "\${1:-}" in
1722
+ split-window)
1723
+ echo "%44"
1724
+ exit 0
1725
+ ;;
1726
+ resize-pane|select-pane)
1727
+ exit 0
1728
+ ;;
1729
+ set-hook|run-shell)
1730
+ exit 0
1731
+ ;;
1732
+ *)
1733
+ exit 0
1734
+ ;;
1735
+ esac
1736
+ `, async ({ logPath }) => {
1737
+ delete process.env.MSYSTEM;
1738
+ delete process.env.OSTYPE;
1739
+ delete process.env.WSL_DISTRO_NAME;
1740
+ delete process.env.WSL_INTEROP;
1741
+ process.env.OMX_LEADER_NODE_PATH = 'C:\\Program Files\\nodejs\\node.exe';
1742
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
1743
+ const paneId = restoreStandaloneHudPane('%11', cwd);
1744
+ assert.equal(paneId, '%44');
1745
+ const tmuxLog = await readFile(logPath, 'utf-8');
1746
+ assert.match(tmuxLog, /'C:\\Program Files\\nodejs\\node\.exe'/);
1747
+ assert.match(tmuxLog, new RegExp(`resize-pane -t %44 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
1748
+ assert.match(tmuxLog, /select-pane -t %11/);
1749
+ assert.doesNotMatch(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %44 -y \d+ >/);
1750
+ assert.doesNotMatch(tmuxLog, /run-shell tmux resize-pane -t %44 -y \d+ >/);
1751
+ assert.doesNotMatch(tmuxLog, /set-hook -t /);
1752
+ });
1753
+ }
1754
+ finally {
1755
+ if (origPlatform)
1756
+ Object.defineProperty(process, 'platform', origPlatform);
1757
+ if (typeof prevLeaderNodePath === 'string')
1758
+ process.env.OMX_LEADER_NODE_PATH = prevLeaderNodePath;
1759
+ else
1760
+ delete process.env.OMX_LEADER_NODE_PATH;
1761
+ if (typeof prevMsystem === 'string')
1762
+ process.env.MSYSTEM = prevMsystem;
1763
+ else
1764
+ delete process.env.MSYSTEM;
1765
+ if (typeof prevOstype === 'string')
1766
+ process.env.OSTYPE = prevOstype;
1767
+ else
1768
+ delete process.env.OSTYPE;
1769
+ if (typeof prevWsl === 'string')
1770
+ process.env.WSL_DISTRO_NAME = prevWsl;
1771
+ else
1772
+ delete process.env.WSL_DISTRO_NAME;
1773
+ if (typeof prevWslInterop === 'string')
1774
+ process.env.WSL_INTEROP = prevWslInterop;
1775
+ else
1776
+ delete process.env.WSL_INTEROP;
1777
+ await rm(cwd, { recursive: true, force: true });
1778
+ }
1779
+ });
1780
+ });
1323
1781
  describe('dismissTrustPromptIfPresent capture shape', () => {
1324
1782
  it('uses visible capture-pane argv without tail flags', async () => {
1325
1783
  const previousAutoTrust = process.env.OMX_TEAM_AUTO_TRUST;