oh-my-codex 0.17.3 → 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 (158) 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__/explore.test.js +23 -0
  24. package/dist/cli/__tests__/explore.test.js.map +1 -1
  25. package/dist/cli/__tests__/index.test.js +123 -5
  26. package/dist/cli/__tests__/index.test.js.map +1 -1
  27. package/dist/cli/__tests__/launch-fallback.test.js +76 -0
  28. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  29. package/dist/cli/__tests__/package-bin-contract.test.js +4 -3
  30. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  31. package/dist/cli/__tests__/setup-install-mode.test.js +138 -0
  32. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  33. package/dist/cli/__tests__/sparkshell-cli.test.js +5 -0
  34. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  35. package/dist/cli/__tests__/version-sync-contract.test.js +4 -0
  36. package/dist/cli/__tests__/version-sync-contract.test.js.map +1 -1
  37. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
  38. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  39. package/dist/cli/api.d.ts +26 -0
  40. package/dist/cli/api.d.ts.map +1 -0
  41. package/dist/cli/api.js +153 -0
  42. package/dist/cli/api.js.map +1 -0
  43. package/dist/cli/explore.d.ts +2 -0
  44. package/dist/cli/explore.d.ts.map +1 -1
  45. package/dist/cli/explore.js +43 -1
  46. package/dist/cli/explore.js.map +1 -1
  47. package/dist/cli/index.d.ts +10 -4
  48. package/dist/cli/index.d.ts.map +1 -1
  49. package/dist/cli/index.js +128 -10
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/cli/native-assets.d.ts +2 -1
  52. package/dist/cli/native-assets.d.ts.map +1 -1
  53. package/dist/cli/native-assets.js +1 -0
  54. package/dist/cli/native-assets.js.map +1 -1
  55. package/dist/cli/sparkshell.d.ts.map +1 -1
  56. package/dist/cli/sparkshell.js +20 -3
  57. package/dist/cli/sparkshell.js.map +1 -1
  58. package/dist/config/generator.d.ts.map +1 -1
  59. package/dist/config/generator.js +90 -0
  60. package/dist/config/generator.js.map +1 -1
  61. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts +2 -0
  62. package/dist/hooks/__tests__/best-practice-research-skill.test.d.ts.map +1 -0
  63. package/dist/hooks/__tests__/best-practice-research-skill.test.js +27 -0
  64. package/dist/hooks/__tests__/best-practice-research-skill.test.js.map +1 -0
  65. package/dist/hooks/__tests__/keyword-detector.test.js +11 -0
  66. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  67. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +6 -0
  68. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  69. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +4 -0
  70. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  71. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  72. package/dist/hooks/keyword-registry.js +1 -0
  73. package/dist/hooks/keyword-registry.js.map +1 -1
  74. package/dist/hud/__tests__/reconcile.test.js +2 -2
  75. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  76. package/dist/hud/__tests__/tmux.test.js +23 -18
  77. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  78. package/dist/hud/tmux.d.ts.map +1 -1
  79. package/dist/hud/tmux.js +7 -6
  80. package/dist/hud/tmux.js.map +1 -1
  81. package/dist/mcp/__tests__/bootstrap.test.js +75 -1
  82. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  83. package/dist/mcp/bootstrap.d.ts +3 -1
  84. package/dist/mcp/bootstrap.d.ts.map +1 -1
  85. package/dist/mcp/bootstrap.js +71 -2
  86. package/dist/mcp/bootstrap.js.map +1 -1
  87. package/dist/scripts/__tests__/codex-native-hook.test.js +737 -26
  88. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  89. package/dist/scripts/__tests__/notify-dispatcher.test.js +183 -1
  90. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  91. package/dist/scripts/__tests__/smoke-packed-install.test.js +4 -1
  92. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  93. package/dist/scripts/build-api.d.ts +2 -0
  94. package/dist/scripts/build-api.d.ts.map +1 -0
  95. package/dist/scripts/build-api.js +44 -0
  96. package/dist/scripts/build-api.js.map +1 -0
  97. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  98. package/dist/scripts/codex-native-hook.js +208 -8
  99. package/dist/scripts/codex-native-hook.js.map +1 -1
  100. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  101. package/dist/scripts/codex-native-pre-post.js +89 -24
  102. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  103. package/dist/scripts/notify-dispatcher.js +88 -0
  104. package/dist/scripts/notify-dispatcher.js.map +1 -1
  105. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  106. package/dist/scripts/notify-hook/team-dispatch.js +27 -9
  107. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  108. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  109. package/dist/scripts/notify-hook/team-leader-nudge.js +26 -11
  110. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  111. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +1 -0
  112. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  113. package/dist/scripts/notify-hook/team-tmux-guard.js +38 -0
  114. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  115. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  116. package/dist/scripts/notify-hook/team-worker-stop.js +27 -14
  117. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  118. package/dist/scripts/run-provider-advisor.js +9 -3
  119. package/dist/scripts/run-provider-advisor.js.map +1 -1
  120. package/dist/scripts/smoke-packed-install.d.ts +1 -1
  121. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  122. package/dist/scripts/smoke-packed-install.js +2 -0
  123. package/dist/scripts/smoke-packed-install.js.map +1 -1
  124. package/dist/team/__tests__/runtime.test.js +2 -2
  125. package/dist/team/__tests__/runtime.test.js.map +1 -1
  126. package/dist/team/__tests__/tmux-session.test.js +96 -19
  127. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  128. package/dist/team/tmux-session.d.ts +1 -0
  129. package/dist/team/tmux-session.d.ts.map +1 -1
  130. package/dist/team/tmux-session.js +34 -10
  131. package/dist/team/tmux-session.js.map +1 -1
  132. package/dist/verification/__tests__/ci-rust-gates.test.js +85 -10
  133. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  134. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +1 -0
  135. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  136. package/package.json +4 -3
  137. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  138. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +83 -0
  139. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +1 -0
  140. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  141. package/prompts/researcher.md +15 -10
  142. package/skills/best-practice-research/SKILL.md +83 -0
  143. package/skills/deep-interview/SKILL.md +1 -0
  144. package/skills/ralplan/SKILL.md +1 -1
  145. package/src/scripts/__tests__/codex-native-hook.test.ts +810 -4
  146. package/src/scripts/__tests__/notify-dispatcher.test.ts +223 -1
  147. package/src/scripts/__tests__/smoke-packed-install.test.ts +8 -2
  148. package/src/scripts/build-api.ts +48 -0
  149. package/src/scripts/codex-native-hook.ts +262 -10
  150. package/src/scripts/codex-native-pre-post.ts +103 -24
  151. package/src/scripts/notify-dispatcher.ts +97 -0
  152. package/src/scripts/notify-hook/team-dispatch.ts +27 -8
  153. package/src/scripts/notify-hook/team-leader-nudge.ts +25 -11
  154. package/src/scripts/notify-hook/team-tmux-guard.ts +42 -0
  155. package/src/scripts/notify-hook/team-worker-stop.ts +24 -13
  156. package/src/scripts/run-provider-advisor.ts +11 -3
  157. package/src/scripts/smoke-packed-install.ts +2 -0
  158. 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';
@@ -1763,7 +1840,7 @@ describe('buildWorkerStartupCommand', () => {
1763
1840
  try {
1764
1841
  const cmd = withMockedExistsSync((candidate) => candidate === '/opt/custom/fish' || candidate === '/bin/bash', () => buildWorkerStartupCommand('alpha', 1, [], process.cwd()));
1765
1842
  assert.match(cmd, /\/bin\/bash\b/, 'must fall back to bash when zsh is unavailable');
1766
- 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');
1767
1844
  assert.doesNotMatch(cmd, /fish/, 'must not launch unsupported fish shell');
1768
1845
  }
1769
1846
  finally {
@@ -3090,7 +3167,7 @@ esac
3090
3167
  assert.equal(session.resizeHookTarget, null);
3091
3168
  const tmuxLog = await readFile(logPath, 'utf-8');
3092
3169
  assert.match(tmuxLog, new RegExp(`resize-pane -t %3 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
3093
- assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-resized\[\d+\]/);
3170
+ assert.doesNotMatch(tmuxLog, /set-hook -w -t leader:0 window-resized\[\d+\]/);
3094
3171
  assert.doesNotMatch(tmuxLog, /set-hook -t leader:0 client-attached\[\d+\]/);
3095
3172
  assert.doesNotMatch(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %3 -y \d+ >/);
3096
3173
  assert.doesNotMatch(tmuxLog, /run-shell tmux resize-pane -t %3 -y \d+ >/);