oh-my-codex 0.8.13 → 0.8.15

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.
@@ -5,6 +5,7 @@ import { mkdtemp, rm, writeFile, readFile, mkdir, chmod } from 'fs/promises';
5
5
  import { join } from 'path';
6
6
  import { tmpdir } from 'os';
7
7
  import { existsSync } from 'fs';
8
+ import { HUD_TMUX_TEAM_HEIGHT_LINES } from '../../hud/constants.js';
8
9
  import { initTeamState, createTask, readTeamConfig, saveTeamConfig, listMailboxMessages, listDispatchRequests, updateWorkerHeartbeat, writeAtomic, readTask, readMonitorSnapshot, claimTask, transitionTaskStatus, } from '../state.js';
9
10
  import { monitorTeam, shutdownTeam, resumeTeam, startTeam, assignTask, sendWorkerMessage, resolveWorkerLaunchArgsFromEnv, waitForWorkerStartupEvidence, waitForClaudeStartupEvidence, } from '../runtime.js';
10
11
  import { resolveTeamLowComplexityDefaultModel } from '../model-contract.js';
@@ -732,6 +733,130 @@ process.on('SIGTERM', () => process.exit(0));
732
733
  await rm(cwd, { recursive: true, force: true });
733
734
  }
734
735
  });
736
+ it('startTeam relaunch re-creates HUD pane and re-registers reconcile hooks after shutdown', async () => {
737
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-relaunch-hud-'));
738
+ const fakeBinDir = await mkdtemp(join(tmpdir(), 'omx-runtime-relaunch-hud-bin-'));
739
+ const tmuxLogPath = join(fakeBinDir, 'tmux.log');
740
+ const tmuxStubPath = join(fakeBinDir, 'tmux');
741
+ const geminiStubPath = join(fakeBinDir, 'gemini');
742
+ const previousPath = process.env.PATH;
743
+ const previousTmux = process.env.TMUX;
744
+ const previousTmuxPane = process.env.TMUX_PANE;
745
+ const previousLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
746
+ const previousWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
747
+ let runtime = null;
748
+ try {
749
+ await writeFile(tmuxStubPath, `#!/bin/sh
750
+ set -eu
751
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
752
+ case "\${1:-}" in
753
+ -V)
754
+ echo "tmux 3.4"
755
+ exit 0
756
+ ;;
757
+ display-message)
758
+ case "$*" in
759
+ *"#{window_width}"*)
760
+ echo "120"
761
+ ;;
762
+ *)
763
+ echo "leader:0 %1"
764
+ ;;
765
+ esac
766
+ exit 0
767
+ ;;
768
+ list-panes)
769
+ case "$*" in
770
+ *"pane_current_command"* )
771
+ printf "%%1\\tnode\\t'codex'\\n"
772
+ ;;
773
+ *"#{pane_dead} #{pane_pid}"*)
774
+ echo "1 999999"
775
+ ;;
776
+ *"#{pane_pid}"*)
777
+ echo "999999"
778
+ ;;
779
+ *)
780
+ exit 0
781
+ ;;
782
+ esac
783
+ exit 0
784
+ ;;
785
+ split-window)
786
+ case "$*" in
787
+ *" -h "*)
788
+ echo "%2"
789
+ ;;
790
+ *)
791
+ echo "%3"
792
+ ;;
793
+ esac
794
+ exit 0
795
+ ;;
796
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session)
797
+ exit 0
798
+ ;;
799
+ *)
800
+ exit 0
801
+ ;;
802
+ esac
803
+ `);
804
+ await chmod(tmuxStubPath, 0o755);
805
+ await writeFile(geminiStubPath, `#!/bin/sh
806
+ exit 0
807
+ `);
808
+ await chmod(geminiStubPath, 0o755);
809
+ process.env.PATH = `${fakeBinDir}:${previousPath ?? ''}`;
810
+ process.env.TMUX = 'leader-session,stub,0';
811
+ process.env.TMUX_PANE = '%1';
812
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
813
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
814
+ runtime = await withoutTeamWorkerEnv(() => startTeam('team-rerun-hud', 'rerun hud restore', 'explore', 1, [{ subject: 'restore hud', description: 'restore hud', owner: 'worker-1' }], cwd));
815
+ assert.equal(runtime.config.hud_pane_id, '%3');
816
+ assert.ok(runtime.config.resize_hook_name);
817
+ await shutdownTeam(runtime.teamName, cwd, { force: true });
818
+ runtime = null;
819
+ runtime = await withoutTeamWorkerEnv(() => startTeam('team-rerun-hud', 'rerun hud restore', 'explore', 1, [{ subject: 'restore hud again', description: 'restore hud again', owner: 'worker-1' }], cwd));
820
+ assert.equal(runtime.config.hud_pane_id, '%3');
821
+ assert.ok(runtime.config.resize_hook_name);
822
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
823
+ const hudSplitRe = new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %1 -d -P -F #\\{pane_id\\}`, 'g');
824
+ assert.equal(tmuxLog.match(hudSplitRe)?.length ?? 0, 3);
825
+ assert.equal(tmuxLog.match(/set-hook -t leader:0 client-resized\[\d+\]/g)?.length ?? 0, 2);
826
+ assert.equal(tmuxLog.match(/set-hook -t leader:0 client-attached\[\d+\]/g)?.length ?? 0, 2);
827
+ assert.equal(tmuxLog.match(/run-shell -b sleep \d+; tmux resize-pane -t %3 -y \d+ >/g)?.length ?? 0, 3);
828
+ assert.equal(tmuxLog.match(/run-shell tmux resize-pane -t %3 -y \d+ >/g)?.length ?? 0, 3);
829
+ assert.ok((tmuxLog.match(/select-layout -t leader:0 main-vertical/g)?.length ?? 0) >= 2);
830
+ assert.match(tmuxLog, /kill-pane -t %3/);
831
+ }
832
+ finally {
833
+ if (runtime) {
834
+ await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
835
+ }
836
+ if (typeof previousPath === 'string')
837
+ process.env.PATH = previousPath;
838
+ else
839
+ delete process.env.PATH;
840
+ if (typeof previousTmux === 'string')
841
+ process.env.TMUX = previousTmux;
842
+ else
843
+ delete process.env.TMUX;
844
+ if (typeof previousTmuxPane === 'string')
845
+ process.env.TMUX_PANE = previousTmuxPane;
846
+ else
847
+ delete process.env.TMUX_PANE;
848
+ if (typeof previousLaunchMode === 'string')
849
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = previousLaunchMode;
850
+ else
851
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
852
+ if (typeof previousWorkerCli === 'string')
853
+ process.env.OMX_TEAM_WORKER_CLI = previousWorkerCli;
854
+ else
855
+ delete process.env.OMX_TEAM_WORKER_CLI;
856
+ await rm(cwd, { recursive: true, force: true });
857
+ await rm(fakeBinDir, { recursive: true, force: true });
858
+ }
859
+ });
735
860
  it('startTeam routes detached worktree worker inbox and mailbox triggers through leader-root state references', async () => {
736
861
  const repo = await initRepo();
737
862
  const binDir = join(repo, 'bin');
@@ -1511,6 +1636,69 @@ esac
1511
1636
  await rm(fakeBinDir, { recursive: true, force: true });
1512
1637
  }
1513
1638
  });
1639
+ it('shutdownTeam restores a standalone HUD pane after tearing down the team HUD', async () => {
1640
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-restore-hud-'));
1641
+ const fakeBinDir = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-restore-hud-bin-'));
1642
+ const tmuxLogPath = join(fakeBinDir, 'tmux.log');
1643
+ const tmuxStubPath = join(fakeBinDir, 'tmux');
1644
+ const previousPath = process.env.PATH;
1645
+ try {
1646
+ await writeFile(tmuxStubPath, `#!/bin/sh
1647
+ set -eu
1648
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
1649
+ case "$1" in
1650
+ -V)
1651
+ echo "tmux 3.4"
1652
+ exit 0
1653
+ ;;
1654
+ list-panes)
1655
+ exit 1
1656
+ ;;
1657
+ split-window)
1658
+ printf '%%44\n'
1659
+ exit 0
1660
+ ;;
1661
+ kill-pane|kill-session|select-pane)
1662
+ exit 0
1663
+ ;;
1664
+ *)
1665
+ exit 0
1666
+ ;;
1667
+ esac
1668
+ `);
1669
+ await chmod(tmuxStubPath, 0o755);
1670
+ process.env.PATH = `${fakeBinDir}:${previousPath ?? ''}`;
1671
+ await initTeamState('team-shutdown-restore-hud', 'shutdown restore hud test', 'executor', 2, cwd);
1672
+ const config = await readTeamConfig('team-shutdown-restore-hud', cwd);
1673
+ assert.ok(config);
1674
+ if (!config)
1675
+ return;
1676
+ config.tmux_session = 'leader:0';
1677
+ config.leader_pane_id = '%11';
1678
+ config.hud_pane_id = '%12';
1679
+ config.workers[0].pane_id = '%12';
1680
+ config.workers[1].pane_id = '%13';
1681
+ await saveTeamConfig(config, cwd);
1682
+ await shutdownTeam('team-shutdown-restore-hud', cwd, { force: true });
1683
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1684
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
1685
+ assert.match(tmuxLog, /kill-pane -t %12/);
1686
+ assert.match(tmuxLog, /kill-pane -t %13/);
1687
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\{pane_id\}`));
1688
+ assert.match(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %44 -y \d+ >/);
1689
+ assert.match(tmuxLog, /run-shell tmux resize-pane -t %44 -y \d+ >/);
1690
+ assert.match(tmuxLog, /hud --watch/);
1691
+ assert.match(tmuxLog, /select-pane -t %11/);
1692
+ }
1693
+ finally {
1694
+ if (typeof previousPath === 'string')
1695
+ process.env.PATH = previousPath;
1696
+ else
1697
+ delete process.env.PATH;
1698
+ await rm(cwd, { recursive: true, force: true });
1699
+ await rm(fakeBinDir, { recursive: true, force: true });
1700
+ }
1701
+ });
1514
1702
  it('shutdownTeam preserves leader exclusion while tearing down the hud pane', async () => {
1515
1703
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-exclusions-'));
1516
1704
  const fakeBinDir = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-exclusions-bin-'));