oh-my-codex 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/Cargo.lock +13 -5
  2. package/Cargo.toml +2 -1
  3. package/README.md +1 -0
  4. package/crates/omx-api/Cargo.toml +19 -0
  5. package/crates/omx-api/src/lib.rs +2940 -0
  6. package/crates/omx-api/src/main.rs +10 -0
  7. package/crates/omx-api/tests/cli.rs +558 -0
  8. package/crates/omx-explore/src/main.rs +4 -0
  9. package/crates/omx-sparkshell/src/codex_bridge.rs +437 -123
  10. package/crates/omx-sparkshell/src/exec.rs +4 -0
  11. package/crates/omx-sparkshell/src/main.rs +738 -29
  12. package/crates/omx-sparkshell/src/prompt.rs +25 -3
  13. package/crates/omx-sparkshell/src/redaction.rs +241 -0
  14. package/crates/omx-sparkshell/tests/execution.rs +479 -238
  15. package/dist/cli/__tests__/api.test.d.ts +2 -0
  16. package/dist/cli/__tests__/api.test.d.ts.map +1 -0
  17. package/dist/cli/__tests__/api.test.js +175 -0
  18. package/dist/cli/__tests__/api.test.js.map +1 -0
  19. package/dist/cli/__tests__/ask.test.js +72 -5
  20. package/dist/cli/__tests__/ask.test.js.map +1 -1
  21. package/dist/cli/__tests__/autoresearch-goal.test.js +14 -1
  22. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
  23. package/dist/cli/__tests__/doctor-warning-copy.test.js +51 -0
  24. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  25. package/dist/cli/__tests__/explore.test.js +23 -0
  26. package/dist/cli/__tests__/explore.test.js.map +1 -1
  27. package/dist/cli/__tests__/index.test.js +123 -5
  28. package/dist/cli/__tests__/index.test.js.map +1 -1
  29. package/dist/cli/__tests__/launch-fallback.test.js +76 -0
  30. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  31. package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
  32. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  33. package/dist/cli/__tests__/question.test.js +45 -22
  34. package/dist/cli/__tests__/question.test.js.map +1 -1
  35. package/dist/cli/__tests__/setup-agents-overwrite.test.js +2 -0
  36. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  37. package/dist/cli/__tests__/setup-install-mode.test.js +138 -0
  38. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  39. package/dist/cli/__tests__/setup-scope.test.js +8 -2
  40. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  41. package/dist/cli/__tests__/sparkshell-cli.test.js +5 -0
  42. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  43. package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
  44. package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
  45. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
  46. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  47. package/dist/cli/api.d.ts +26 -0
  48. package/dist/cli/api.d.ts.map +1 -0
  49. package/dist/cli/api.js +153 -0
  50. package/dist/cli/api.js.map +1 -0
  51. package/dist/cli/doctor.d.ts.map +1 -1
  52. package/dist/cli/doctor.js +39 -4
  53. package/dist/cli/doctor.js.map +1 -1
  54. package/dist/cli/explore.d.ts +2 -0
  55. package/dist/cli/explore.d.ts.map +1 -1
  56. package/dist/cli/explore.js +43 -1
  57. package/dist/cli/explore.js.map +1 -1
  58. package/dist/cli/index.d.ts +10 -4
  59. package/dist/cli/index.d.ts.map +1 -1
  60. package/dist/cli/index.js +128 -10
  61. package/dist/cli/index.js.map +1 -1
  62. package/dist/cli/native-assets.d.ts +2 -1
  63. package/dist/cli/native-assets.d.ts.map +1 -1
  64. package/dist/cli/native-assets.js +1 -0
  65. package/dist/cli/native-assets.js.map +1 -1
  66. package/dist/cli/setup.d.ts.map +1 -1
  67. package/dist/cli/setup.js +6 -1
  68. package/dist/cli/setup.js.map +1 -1
  69. package/dist/cli/sparkshell.d.ts.map +1 -1
  70. package/dist/cli/sparkshell.js +20 -3
  71. package/dist/cli/sparkshell.js.map +1 -1
  72. package/dist/config/generator.d.ts.map +1 -1
  73. package/dist/config/generator.js +90 -0
  74. package/dist/config/generator.js.map +1 -1
  75. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
  76. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
  77. package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
  78. package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
  79. package/dist/hooks/__tests__/keyword-detector.test.js +11 -0
  80. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  81. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
  82. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  83. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
  84. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  85. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  86. package/dist/hooks/keyword-registry.js +1 -0
  87. package/dist/hooks/keyword-registry.js.map +1 -1
  88. package/dist/hud/__tests__/reconcile.test.js +2 -2
  89. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  90. package/dist/hud/__tests__/tmux.test.js +23 -18
  91. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  92. package/dist/hud/tmux.d.ts.map +1 -1
  93. package/dist/hud/tmux.js +7 -6
  94. package/dist/hud/tmux.js.map +1 -1
  95. package/dist/mcp/__tests__/bootstrap.test.js +75 -1
  96. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  97. package/dist/mcp/bootstrap.d.ts +3 -1
  98. package/dist/mcp/bootstrap.d.ts.map +1 -1
  99. package/dist/mcp/bootstrap.js +71 -2
  100. package/dist/mcp/bootstrap.js.map +1 -1
  101. package/dist/scripts/__tests__/codex-native-hook.test.js +737 -26
  102. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  103. package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
  104. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  105. package/dist/scripts/__tests__/smoke-packed-install.test.js +4 -1
  106. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  107. package/dist/scripts/build-api.d.ts +2 -0
  108. package/dist/scripts/build-api.d.ts.map +1 -0
  109. package/dist/scripts/build-api.js +44 -0
  110. package/dist/scripts/build-api.js.map +1 -0
  111. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  112. package/dist/scripts/codex-native-hook.js +208 -8
  113. package/dist/scripts/codex-native-hook.js.map +1 -1
  114. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  115. package/dist/scripts/codex-native-pre-post.js +89 -24
  116. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  117. package/dist/scripts/notify-dispatcher.js +88 -0
  118. package/dist/scripts/notify-dispatcher.js.map +1 -1
  119. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  120. package/dist/scripts/notify-hook/team-dispatch.js +27 -9
  121. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  122. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  123. package/dist/scripts/notify-hook/team-leader-nudge.js +26 -11
  124. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  125. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -0
  126. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  127. package/dist/scripts/notify-hook/team-tmux-guard.js +38 -0
  128. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  129. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  130. package/dist/scripts/notify-hook/team-worker-stop.js +27 -14
  131. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  132. package/dist/scripts/run-provider-advisor.js +9 -3
  133. package/dist/scripts/run-provider-advisor.js.map +1 -1
  134. package/dist/scripts/smoke-packed-install.d.ts +1 -1
  135. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  136. package/dist/scripts/smoke-packed-install.js +2 -0
  137. package/dist/scripts/smoke-packed-install.js.map +1 -1
  138. package/dist/team/__tests__/runtime.test.js +2 -2
  139. package/dist/team/__tests__/runtime.test.js.map +1 -1
  140. package/dist/team/__tests__/tmux-session.test.js +153 -25
  141. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  142. package/dist/team/tmux-session.d.ts +1 -0
  143. package/dist/team/tmux-session.d.ts.map +1 -1
  144. package/dist/team/tmux-session.js +55 -10
  145. package/dist/team/tmux-session.js.map +1 -1
  146. package/dist/utils/__tests__/agents-md.test.js +45 -1
  147. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  148. package/dist/utils/agents-md.d.ts +2 -0
  149. package/dist/utils/agents-md.d.ts.map +1 -1
  150. package/dist/utils/agents-md.js +19 -0
  151. package/dist/utils/agents-md.js.map +1 -1
  152. package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
  153. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  154. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
  155. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  156. package/package.json +4 -3
  157. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  158. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
  159. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +1 -0
  160. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  161. package/prompts/researcher.md +15 -10
  162. package/skills/best-practice-research/SKILL.md +83 -0
  163. package/skills/deep-interview/SKILL.md +1 -0
  164. package/skills/ralplan/SKILL.md +1 -1
  165. package/src/scripts/__tests__/codex-native-hook.test.ts +810 -4
  166. package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
  167. package/src/scripts/__tests__/smoke-packed-install.test.ts +8 -2
  168. package/src/scripts/build-api.ts +48 -0
  169. package/src/scripts/codex-native-hook.ts +262 -10
  170. package/src/scripts/codex-native-pre-post.ts +103 -24
  171. package/src/scripts/notify-dispatcher.ts +97 -0
  172. package/src/scripts/notify-hook/team-dispatch.ts +27 -8
  173. package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
  174. package/src/scripts/notify-hook/team-tmux-guard.ts +42 -0
  175. package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
  176. package/src/scripts/run-provider-advisor.ts +11 -3
  177. package/src/scripts/smoke-packed-install.ts +2 -0
  178. package/templates/catalog-manifest.json +7 -0
@@ -6,7 +6,7 @@ import { PassThrough } from 'node:stream';
6
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, restoreStandaloneHudPane, translatePathForMsys, isWsl2, isWorkerAlive, killWorker, killWorkerByPaneId, teardownWorkerPanes, listTeamSessions, resolveTeamWorkerCli, resolveTeamWorkerLaunchMode, resolveWorkerCliForSend, resolveTeamWorkerCliPlan, buildWorkerSubmitPlan, sanitizeTeamName, shouldAttemptAdaptiveRetry, sendToWorker, sendToWorkerStdin, sleepFractionalSeconds, translateWorkerLaunchArgsForCli, waitForWorkerReady, waitForWorkerReadyAsync, paneIsBootstrapping, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, mitigateCopyModeUnderlineArtifacts, } from '../tmux-session.js';
9
+ import { buildClientAttachedReconcileHookName, assertTeamWorkerCliBinaryAvailable, buildWorkerProcessLaunchSpec, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, buildWorkerStartupCommand, shouldSourceTeamWorkerShellRc, 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, waitForWorkerReadyAsync, paneIsBootstrapping, classifyWorkerStartupInjectSafety, checkWorkerStartupInjectSafety, dismissTrustPromptIfPresent, evaluateStartupDirectTriggerSafetyCapture, mitigateCopyModeUnderlineArtifacts, } 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
  import { OMX_ENTRY_PATH_ENV, OMX_STARTUP_CWD_ENV } from '../../utils/paths.js';
@@ -133,18 +133,19 @@ describe('HUD resize hook command builders', () => {
133
133
  assert.equal(buildHudPaneTarget('%41'), '%41');
134
134
  assert.equal(buildHudPaneTarget('41'), '%41');
135
135
  });
136
- it('buildRegisterResizeHookArgs uses window target and numeric client-resized hook slot', () => {
136
+ it('buildRegisterResizeHookArgs uses window target and numeric window-resized hook slot', () => {
137
137
  const args = buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1');
138
138
  assert.equal(args[0], 'set-hook');
139
- assert.equal(args[1], '-t');
140
- assert.equal(args[2], 'my-session:0');
141
- assert.match(args[3] ?? '', /^client-resized\[\d+\]$/);
142
- assert.equal(args[4], `run-shell -b 'tmux resize-pane -t %1 -y ${HUD_TMUX_TEAM_HEIGHT_LINES} >/dev/null 2>&1 || true'`);
139
+ assert.equal(args[1], '-w');
140
+ assert.equal(args[2], '-t');
141
+ assert.equal(args[3], 'my-session:0');
142
+ assert.match(args[4] ?? '', /^window-resized\[\d+\]$/);
143
+ assert.equal(args[5], `run-shell -b 'tmux resize-pane -t %1 -y ${HUD_TMUX_TEAM_HEIGHT_LINES} >/dev/null 2>&1 || true; sleep ${HUD_RESIZE_RECONCILE_DELAY_SECONDS}; tmux resize-pane -t %1 -y ${HUD_TMUX_TEAM_HEIGHT_LINES} >/dev/null 2>&1 || true'`);
143
144
  });
144
145
  it('buildUnregisterResizeHookArgs removes the exact numeric hook slot', () => {
145
146
  const registered = buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1');
146
147
  const unregistered = buildUnregisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1');
147
- assert.deepEqual(unregistered, ['set-hook', '-u', '-t', 'my-session:0', registered[3]]);
148
+ assert.deepEqual(unregistered, ['set-hook', '-u', '-w', '-t', 'my-session:0', registered[4]]);
148
149
  });
149
150
  it('buildClientAttachedReconcileHookName normalizes all segments into collision-safe tokens', () => {
150
151
  const name = buildClientAttachedReconcileHookName('Team A', 'Session:Main', '0', '%12');
@@ -169,7 +170,7 @@ describe('HUD resize hook command builders', () => {
169
170
  const longName = 'omx_resize_' + 'a'.repeat(200);
170
171
  const resizeArgs = buildRegisterResizeHookArgs('sess:0', longName, '%1');
171
172
  const attachedArgs = buildRegisterClientAttachedReconcileArgs('sess:0', longName, '%1');
172
- const resizeSlot = resizeArgs[3] ?? '';
173
+ const resizeSlot = resizeArgs[4] ?? '';
173
174
  const attachedSlot = attachedArgs[3] ?? '';
174
175
  const resizeIndex = Number((resizeSlot.match(/\[(\d+)\]/) ?? [])[1]);
175
176
  const attachedIndex = Number((attachedSlot.match(/\[(\d+)\]/) ?? [])[1]);
@@ -182,7 +183,7 @@ describe('HUD resize hook command builders', () => {
182
183
  const name = 'omx_resize_team_session_0_1';
183
184
  const a = buildRegisterResizeHookArgs('s:0', name, '%1');
184
185
  const b = buildRegisterResizeHookArgs('s:0', name, '%1');
185
- assert.equal(a[3], b[3]);
186
+ assert.equal(a[4], b[4]);
186
187
  const c = buildRegisterClientAttachedReconcileArgs('s:0', name, '%1');
187
188
  const d = buildRegisterClientAttachedReconcileArgs('s:0', name, '%1');
188
189
  assert.equal(c[3], d[3]);
@@ -209,8 +210,8 @@ describe('HUD resize hook command builders', () => {
209
210
  const resizeArgs = buildRegisterResizeHookArgs('my-session:0', 'omx_resize_team_session_0_1', '%1');
210
211
  const delayedArgs = buildScheduleDelayedHudResizeArgs('%1');
211
212
  const reconcileArgs = buildReconcileHudResizeArgs('%1');
212
- assert.match(resizeArgs[4] ?? '', new RegExp(escapeRegExp(tmuxPath)));
213
- assert.doesNotMatch(resizeArgs[4] ?? '', /^run-shell -b 'tmux resize-pane/);
213
+ assert.match(resizeArgs[5] ?? '', new RegExp(escapeRegExp(tmuxPath)));
214
+ assert.doesNotMatch(resizeArgs[5] ?? '', /^run-shell -b 'tmux resize-pane/);
214
215
  assert.match(delayedArgs[2] ?? '', new RegExp(escapeRegExp(tmuxPath)));
215
216
  assert.doesNotMatch(delayedArgs[2] ?? '', /sleep \d+; tmux resize-pane/);
216
217
  assert.match(reconcileArgs[1] ?? '', new RegExp(escapeRegExp(tmuxPath)));
@@ -480,6 +481,51 @@ esac
480
481
  assert.ok(enterCount >= 4, `expected repeated submit nudges before failing closed on stuck queued banner:\n${log}`);
481
482
  });
482
483
  });
484
+ it('does not confirm delivery while a wrapped hyphenated trigger remains as an unsent draft', async () => {
485
+ const trigger = 'Read .omx/state/team/team-x/workers/worker-1/inbox.md';
486
+ await withMockTmuxFixture('omx-tmux-codex-wrapped-trigger-draft-', (logPath) => `#!/bin/sh
487
+ set -eu
488
+ state_dir="$(dirname "${logPath}")"
489
+ text_sent_file="$state_dir/text-sent"
490
+ printf '%s\n' "$*" >> "${logPath}"
491
+ case "$1" in
492
+ capture-pane)
493
+ if [ -f "$text_sent_file" ]; then
494
+ cat <<'EOF'
495
+ ${READY_HELPER_CAPTURE}
496
+
497
+ › Read .omx/state/team/team-x/workers/worker-
498
+ 1/inbox.md
499
+ EOF
500
+ else
501
+ cat <<'EOF'
502
+ ${READY_HELPER_CAPTURE}
503
+ EOF
504
+ fi
505
+ exit 0
506
+ ;;
507
+ send-keys)
508
+ if [ "\${4:-}" = "-l" ] && [ "\${6:-}" = "${trigger}" ]; then
509
+ : > "$text_sent_file"
510
+ fi
511
+ exit 0
512
+ ;;
513
+ *)
514
+ exit 0
515
+ ;;
516
+ esac
517
+ `, async ({ logPath }) => {
518
+ await assert.rejects(() => sendToWorker('omx-team-x', 1, trigger), /submit_failed/);
519
+ const log = await readFile(logPath, 'utf-8');
520
+ const enterCount = (log.match(/send-keys -t omx-team-x:1 C-m/g) || []).length;
521
+ assert.ok(enterCount >= 4, `expected repeated submit nudges before failing on the still-visible wrapped draft:\n${log}`);
522
+ });
523
+ });
524
+ });
525
+ describe('sendToWorker adaptive retry matching', () => {
526
+ it('recognizes hyphen-wrapped trigger drafts as still visible', () => {
527
+ assert.equal(shouldAttemptAdaptiveRetry('auto', true, true, `${READY_HELPER_CAPTURE}\n\n› Read .omx/state/team/team-x/workers/worker-\n 1/inbox.md`, 'Read .omx/state/team/team-x/workers/worker-1/inbox.md'), true);
528
+ });
483
529
  });
484
530
  describe('startup direct trigger safety', () => {
485
531
  it('classifies ready panes as safe and blocks trust, bypass, bootstrapping, and active-task captures', () => {
@@ -781,7 +827,7 @@ describe('buildWorkerStartupCommand', () => {
781
827
  delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
782
828
  }
783
829
  });
784
- it('uses zsh with ~/.zshrc and non-login shell exec semantics', () => {
830
+ it('uses zsh without sourcing ~/.zshrc by default and keeps non-login exec semantics', () => {
785
831
  const prevShell = process.env.SHELL;
786
832
  process.env.SHELL = '/bin/zsh';
787
833
  const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
@@ -791,7 +837,7 @@ describe('buildWorkerStartupCommand', () => {
791
837
  assert.match(cmd, /OMX_TEAM_WORKER=alpha\/worker-2/);
792
838
  assert.match(cmd, /'\/bin\/zsh' -c/);
793
839
  assert.doesNotMatch(cmd, /'\/bin\/zsh' -lc\b/);
794
- assert.match(cmd, /source ~\/\.zshrc/);
840
+ assert.doesNotMatch(cmd, /source ~\/\.zshrc/);
795
841
  assert.match(cmd, /exec .*codex/);
796
842
  }
797
843
  finally {
@@ -814,7 +860,7 @@ describe('buildWorkerStartupCommand', () => {
814
860
  const cmd = withMockedExistsSync((candidate) => candidate === '/opt/homebrew/bin/zsh', () => buildWorkerStartupCommand('alpha', 2));
815
861
  assert.match(cmd, /'\/opt\/homebrew\/bin\/zsh' -c/);
816
862
  assert.doesNotMatch(cmd, /'\/bin\/sh' -c/);
817
- assert.match(cmd, /source ~\/\.zshrc/);
863
+ assert.doesNotMatch(cmd, /source ~\/\.zshrc/);
818
864
  }
819
865
  finally {
820
866
  if (typeof prevShell === 'string')
@@ -836,7 +882,7 @@ describe('buildWorkerStartupCommand', () => {
836
882
  const cmd = withMockedExistsSync((candidate) => candidate === '/opt/local/bin/zsh', () => buildWorkerStartupCommand('alpha', 2));
837
883
  assert.match(cmd, /'\/opt\/local\/bin\/zsh' -c/);
838
884
  assert.doesNotMatch(cmd, /'\/bin\/sh' -c/);
839
- assert.match(cmd, /source ~\/\.zshrc/);
885
+ assert.doesNotMatch(cmd, /source ~\/\.zshrc/);
840
886
  }
841
887
  finally {
842
888
  if (typeof prevShell === 'string')
@@ -849,14 +895,14 @@ describe('buildWorkerStartupCommand', () => {
849
895
  delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
850
896
  }
851
897
  });
852
- it('uses bash with ~/.bashrc and preserves launch args', () => {
898
+ it('prevents issue #2358 bash rc fan-out by default and preserves launch args', () => {
853
899
  const prevShell = process.env.SHELL;
854
900
  process.env.SHELL = '/bin/bash';
855
901
  const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
856
902
  process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
857
903
  try {
858
904
  const cmd = buildWorkerStartupCommand('alpha', 1, ['--model', 'gpt-5']);
859
- assert.match(cmd, /source ~\/\.bashrc/);
905
+ assert.doesNotMatch(cmd, /source ~\/\.bashrc/);
860
906
  assert.match(cmd, /exec .*codex/);
861
907
  assert.match(cmd, /--model/);
862
908
  assert.match(cmd, /gpt-5/);
@@ -872,6 +918,37 @@ describe('buildWorkerStartupCommand', () => {
872
918
  delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
873
919
  }
874
920
  });
921
+ it('sources worker shell rc files only when explicitly opted in', () => {
922
+ const prevShell = process.env.SHELL;
923
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
924
+ const prevSourceRc = process.env.OMX_TMUX_SOURCE_SHELL_RC;
925
+ process.env.SHELL = '/bin/bash';
926
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
927
+ try {
928
+ delete process.env.OMX_TMUX_SOURCE_SHELL_RC;
929
+ assert.equal(shouldSourceTeamWorkerShellRc(process.env), false);
930
+ assert.doesNotMatch(buildWorkerStartupCommand('alpha', 1, ['--model', 'gpt-5']), /source ~\/\.bashrc/);
931
+ process.env.OMX_TMUX_SOURCE_SHELL_RC = '1';
932
+ assert.equal(shouldSourceTeamWorkerShellRc(process.env), true);
933
+ assert.match(buildWorkerStartupCommand('alpha', 1, ['--model', 'gpt-5']), /source ~\/\.bashrc/);
934
+ delete process.env.OMX_TMUX_SOURCE_SHELL_RC;
935
+ assert.match(buildWorkerStartupCommand('alpha', 1, ['--model', 'gpt-5'], process.cwd(), { OMX_TMUX_SOURCE_SHELL_RC: '1' }), /source ~\/\.bashrc/, 'per-worker explicit opt-in should be honored');
936
+ }
937
+ finally {
938
+ if (typeof prevShell === 'string')
939
+ process.env.SHELL = prevShell;
940
+ else
941
+ delete process.env.SHELL;
942
+ if (typeof prevBypass === 'string')
943
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
944
+ else
945
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
946
+ if (typeof prevSourceRc === 'string')
947
+ process.env.OMX_TMUX_SOURCE_SHELL_RC = prevSourceRc;
948
+ else
949
+ delete process.env.OMX_TMUX_SOURCE_SHELL_RC;
950
+ }
951
+ });
875
952
  it('injects canonical team state env vars when provided', () => {
876
953
  const prevShell = process.env.SHELL;
877
954
  process.env.SHELL = '/bin/bash';
@@ -1191,18 +1268,56 @@ describe('buildWorkerStartupCommand', () => {
1191
1268
  delete process.env.OMX_MODEL_INSTRUCTIONS_FILE;
1192
1269
  }
1193
1270
  });
1194
- it('disables first-party OMX MCP compatibility servers for Codex team workers by default', () => {
1271
+ it('does not synthesize absent first-party OMX MCP server tables for Codex team workers', async () => {
1195
1272
  const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1196
1273
  const prevCompat = process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1197
- process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1198
- delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1274
+ const prevCodexHome = process.env.CODEX_HOME;
1275
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-team-no-mcp-config-'));
1199
1276
  try {
1277
+ await writeFile(join(codexHome, 'config.toml'), '[mcp_servers.gitnexus]\ncommand = "gitnexus"\n');
1278
+ process.env.CODEX_HOME = codexHome;
1279
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1280
+ delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1281
+ const cmd = buildWorkerStartupCommand('alpha', 1, [], '/tmp/project', {}, 'codex');
1282
+ for (const server of ['omx_state', 'omx_memory', 'omx_code_intel', 'omx_trace', 'omx_wiki', 'omx_hermes']) {
1283
+ assert.doesNotMatch(cmd, new RegExp(`mcp_servers\\.${server}\\.enabled=false`));
1284
+ }
1285
+ }
1286
+ finally {
1287
+ await rm(codexHome, { recursive: true, force: true });
1288
+ if (typeof prevBypass === 'string')
1289
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1290
+ else
1291
+ delete process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1292
+ if (typeof prevCompat === 'string')
1293
+ process.env.OMX_TEAM_WORKER_MCP_COMPAT = prevCompat;
1294
+ else
1295
+ delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1296
+ if (typeof prevCodexHome === 'string')
1297
+ process.env.CODEX_HOME = prevCodexHome;
1298
+ else
1299
+ delete process.env.CODEX_HOME;
1300
+ }
1301
+ });
1302
+ it('disables configured first-party OMX MCP compatibility servers for Codex team workers by default', async () => {
1303
+ const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1304
+ const prevCompat = process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1305
+ const prevCodexHome = process.env.CODEX_HOME;
1306
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-team-mcp-config-'));
1307
+ try {
1308
+ await writeFile(join(codexHome, 'config.toml'), ['omx_state', 'omx_memory', 'omx_code_intel', 'omx_trace', 'omx_wiki', 'omx_hermes']
1309
+ .map((server) => `[mcp_servers.${server}]\ncommand = "omx"\nargs = ["mcp-serve", "${server}"]\n`)
1310
+ .join('\n'));
1311
+ process.env.CODEX_HOME = codexHome;
1312
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1313
+ delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1200
1314
  const cmd = buildWorkerStartupCommand('alpha', 1, [], '/tmp/project', {}, 'codex');
1201
1315
  for (const server of ['omx_state', 'omx_memory', 'omx_code_intel', 'omx_trace', 'omx_wiki', 'omx_hermes']) {
1202
1316
  assert.match(cmd, new RegExp(`mcp_servers\\.${server}\\.enabled=false`));
1203
1317
  }
1204
1318
  }
1205
1319
  finally {
1320
+ await rm(codexHome, { recursive: true, force: true });
1206
1321
  if (typeof prevBypass === 'string')
1207
1322
  process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1208
1323
  else
@@ -1211,18 +1326,27 @@ describe('buildWorkerStartupCommand', () => {
1211
1326
  process.env.OMX_TEAM_WORKER_MCP_COMPAT = prevCompat;
1212
1327
  else
1213
1328
  delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1329
+ if (typeof prevCodexHome === 'string')
1330
+ process.env.CODEX_HOME = prevCodexHome;
1331
+ else
1332
+ delete process.env.CODEX_HOME;
1214
1333
  }
1215
1334
  });
1216
- it('preserves explicit team-worker MCP compatibility opt-in', () => {
1335
+ it('preserves explicit team-worker MCP compatibility opt-in', async () => {
1217
1336
  const prevBypass = process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT;
1218
1337
  const prevCompat = process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1219
- process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1220
- process.env.OMX_TEAM_WORKER_MCP_COMPAT = '1';
1338
+ const prevCodexHome = process.env.CODEX_HOME;
1339
+ const codexHome = await mkdtemp(join(tmpdir(), 'omx-team-mcp-compat-'));
1221
1340
  try {
1341
+ await writeFile(join(codexHome, 'config.toml'), '[mcp_servers.omx_state]\ncommand = "omx"\n');
1342
+ process.env.CODEX_HOME = codexHome;
1343
+ process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = '0';
1344
+ process.env.OMX_TEAM_WORKER_MCP_COMPAT = '1';
1222
1345
  const cmd = buildWorkerStartupCommand('alpha', 1, [], '/tmp/project', {}, 'codex');
1223
1346
  assert.doesNotMatch(cmd, /mcp_servers\.omx_state\.enabled=false/);
1224
1347
  }
1225
1348
  finally {
1349
+ await rm(codexHome, { recursive: true, force: true });
1226
1350
  if (typeof prevBypass === 'string')
1227
1351
  process.env.OMX_BYPASS_DEFAULT_SYSTEM_PROMPT = prevBypass;
1228
1352
  else
@@ -1231,6 +1355,10 @@ describe('buildWorkerStartupCommand', () => {
1231
1355
  process.env.OMX_TEAM_WORKER_MCP_COMPAT = prevCompat;
1232
1356
  else
1233
1357
  delete process.env.OMX_TEAM_WORKER_MCP_COMPAT;
1358
+ if (typeof prevCodexHome === 'string')
1359
+ process.env.CODEX_HOME = prevCodexHome;
1360
+ else
1361
+ delete process.env.CODEX_HOME;
1234
1362
  }
1235
1363
  });
1236
1364
  it('does not inject model_instructions_file override when disabled', () => {
@@ -1712,7 +1840,7 @@ describe('buildWorkerStartupCommand', () => {
1712
1840
  try {
1713
1841
  const cmd = withMockedExistsSync((candidate) => candidate === '/opt/custom/fish' || candidate === '/bin/bash', () => buildWorkerStartupCommand('alpha', 1, [], process.cwd()));
1714
1842
  assert.match(cmd, /\/bin\/bash\b/, 'must fall back to bash when zsh is unavailable');
1715
- assert.match(cmd, /\.bashrc/, 'must source bash rc file for bash fallback');
1843
+ assert.doesNotMatch(cmd, /\.bashrc/, 'must not source bash rc file for bash fallback by default');
1716
1844
  assert.doesNotMatch(cmd, /fish/, 'must not launch unsupported fish shell');
1717
1845
  }
1718
1846
  finally {
@@ -3039,7 +3167,7 @@ esac
3039
3167
  assert.equal(session.resizeHookTarget, null);
3040
3168
  const tmuxLog = await readFile(logPath, 'utf-8');
3041
3169
  assert.match(tmuxLog, new RegExp(`resize-pane -t %3 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
3042
- assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-resized\[\d+\]/);
3170
+ assert.doesNotMatch(tmuxLog, /set-hook -w -t leader:0 window-resized\[\d+\]/);
3043
3171
  assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-attached\[\d+\]/);
3044
3172
  assert.doesNotMatch(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %3 -y \d+ >/);
3045
3173
  assert.doesNotMatch(tmuxLog, /run-shell tmux resize-pane -t %3 -y \d+ >/);