oh-my-codex 0.18.13 → 0.18.14

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 (277) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +31 -11
  4. package/dist/agents/__tests__/native-config.test.js +25 -0
  5. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  6. package/dist/agents/native-config.d.ts.map +1 -1
  7. package/dist/agents/native-config.js +5 -1
  8. package/dist/agents/native-config.js.map +1 -1
  9. package/dist/autopilot/__tests__/fsm.test.js +7 -0
  10. package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
  11. package/dist/autopilot/__tests__/planner-routing.test.d.ts +2 -0
  12. package/dist/autopilot/__tests__/planner-routing.test.d.ts.map +1 -0
  13. package/dist/autopilot/__tests__/planner-routing.test.js +74 -0
  14. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -0
  15. package/dist/autopilot/__tests__/ralplan-gate.test.js +315 -0
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  17. package/dist/autopilot/completion-gate.js +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +2 -2
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autopilot/fsm.js +3 -0
  22. package/dist/autopilot/fsm.js.map +1 -1
  23. package/dist/autopilot/planner-routing.d.ts +21 -0
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -0
  25. package/dist/autopilot/planner-routing.js +59 -0
  26. package/dist/autopilot/planner-routing.js.map +1 -0
  27. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  28. package/dist/autopilot/ralplan-gate.js +30 -0
  29. package/dist/autopilot/ralplan-gate.js.map +1 -1
  30. package/dist/autoresearch/goal.d.ts.map +1 -1
  31. package/dist/autoresearch/goal.js +10 -1
  32. package/dist/autoresearch/goal.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch-goal.test.js +33 -0
  34. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -0
  36. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts +2 -0
  38. package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts.map +1 -0
  39. package/dist/cli/__tests__/doctor-artifact-ownership.test.js +158 -0
  40. package/dist/cli/__tests__/doctor-artifact-ownership.test.js.map +1 -0
  41. package/dist/cli/__tests__/doctor-spark-routing.test.js +13 -0
  42. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  43. package/dist/cli/__tests__/doctor-warning-copy.test.js +1 -0
  44. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  45. package/dist/cli/__tests__/index.test.js +5 -0
  46. package/dist/cli/__tests__/index.test.js.map +1 -1
  47. package/dist/cli/__tests__/nested-help-routing.test.js +4 -0
  48. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  49. package/dist/cli/__tests__/package-bin-contract.test.js +7 -1
  50. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  51. package/dist/cli/__tests__/performance-goal.test.js +22 -0
  52. package/dist/cli/__tests__/performance-goal.test.js.map +1 -1
  53. package/dist/cli/__tests__/resume.test.js +96 -0
  54. package/dist/cli/__tests__/resume.test.js.map +1 -1
  55. package/dist/cli/__tests__/session-scoped-runtime.test.js +162 -0
  56. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  57. package/dist/cli/__tests__/session-search.test.js +36 -0
  58. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  59. package/dist/cli/__tests__/setup-agents-overwrite.test.js +35 -1
  60. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  61. package/dist/cli/__tests__/setup-install-mode.test.js +33 -0
  62. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  63. package/dist/cli/__tests__/team.test.js +17 -3
  64. package/dist/cli/__tests__/team.test.js.map +1 -1
  65. package/dist/cli/__tests__/ultragoal.test.js +37 -0
  66. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  67. package/dist/cli/__tests__/update.test.js +1 -0
  68. package/dist/cli/__tests__/update.test.js.map +1 -1
  69. package/dist/cli/autoresearch-goal.d.ts.map +1 -1
  70. package/dist/cli/autoresearch-goal.js +2 -1
  71. package/dist/cli/autoresearch-goal.js.map +1 -1
  72. package/dist/cli/doctor.d.ts +22 -0
  73. package/dist/cli/doctor.d.ts.map +1 -1
  74. package/dist/cli/doctor.js +263 -4
  75. package/dist/cli/doctor.js.map +1 -1
  76. package/dist/cli/index.d.ts +1 -1
  77. package/dist/cli/index.d.ts.map +1 -1
  78. package/dist/cli/index.js +65 -4
  79. package/dist/cli/index.js.map +1 -1
  80. package/dist/cli/performance-goal.d.ts.map +1 -1
  81. package/dist/cli/performance-goal.js +4 -2
  82. package/dist/cli/performance-goal.js.map +1 -1
  83. package/dist/cli/plugin-marketplace.d.ts +1 -0
  84. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  85. package/dist/cli/plugin-marketplace.js +67 -5
  86. package/dist/cli/plugin-marketplace.js.map +1 -1
  87. package/dist/cli/project-runtime-codex-homes.d.ts +2 -0
  88. package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -1
  89. package/dist/cli/project-runtime-codex-homes.js +112 -5
  90. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  91. package/dist/cli/setup.d.ts.map +1 -1
  92. package/dist/cli/setup.js +15 -6
  93. package/dist/cli/setup.js.map +1 -1
  94. package/dist/cli/ultragoal.d.ts.map +1 -1
  95. package/dist/cli/ultragoal.js +5 -1
  96. package/dist/cli/ultragoal.js.map +1 -1
  97. package/dist/cli/update.d.ts.map +1 -1
  98. package/dist/cli/update.js +3 -0
  99. package/dist/cli/update.js.map +1 -1
  100. package/dist/config/__tests__/models.test.js +21 -1
  101. package/dist/config/__tests__/models.test.js.map +1 -1
  102. package/dist/config/models.d.ts +5 -0
  103. package/dist/config/models.d.ts.map +1 -1
  104. package/dist/config/models.js +23 -0
  105. package/dist/config/models.js.map +1 -1
  106. package/dist/goal-workflows/codex-goal-snapshot.d.ts +2 -0
  107. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  108. package/dist/goal-workflows/codex-goal-snapshot.js +10 -0
  109. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  110. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +7 -0
  111. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  112. package/dist/hooks/__tests__/keyword-detector.test.js +46 -0
  113. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  114. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +100 -0
  115. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  116. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +75 -0
  117. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  118. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +225 -0
  119. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  120. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +75 -0
  121. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  122. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +200 -0
  123. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  124. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +187 -12
  125. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  126. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +225 -0
  127. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  128. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  129. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  130. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +75 -0
  131. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  132. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  133. package/dist/hooks/keyword-detector.js +4 -0
  134. package/dist/hooks/keyword-detector.js.map +1 -1
  135. package/dist/hud/__tests__/reconcile.test.js +137 -2
  136. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  137. package/dist/hud/__tests__/state.test.js +107 -0
  138. package/dist/hud/__tests__/state.test.js.map +1 -1
  139. package/dist/hud/__tests__/tmux.test.js +17 -0
  140. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  141. package/dist/hud/reconcile.d.ts.map +1 -1
  142. package/dist/hud/reconcile.js +21 -7
  143. package/dist/hud/reconcile.js.map +1 -1
  144. package/dist/hud/render.d.ts.map +1 -1
  145. package/dist/hud/render.js +9 -0
  146. package/dist/hud/render.js.map +1 -1
  147. package/dist/hud/state.d.ts.map +1 -1
  148. package/dist/hud/state.js +43 -5
  149. package/dist/hud/state.js.map +1 -1
  150. package/dist/hud/types.d.ts +6 -0
  151. package/dist/hud/types.d.ts.map +1 -1
  152. package/dist/hud/types.js.map +1 -1
  153. package/dist/modes/__tests__/base-autopilot-gates.test.js +48 -0
  154. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  155. package/dist/modes/base.d.ts.map +1 -1
  156. package/dist/modes/base.js +1 -0
  157. package/dist/modes/base.js.map +1 -1
  158. package/dist/performance-goal/artifacts.d.ts.map +1 -1
  159. package/dist/performance-goal/artifacts.js +10 -1
  160. package/dist/performance-goal/artifacts.js.map +1 -1
  161. package/dist/ralplan/__tests__/consensus-gate.test.js +112 -0
  162. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  163. package/dist/ralplan/consensus-gate.d.ts +23 -0
  164. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  165. package/dist/ralplan/consensus-gate.js +133 -21
  166. package/dist/ralplan/consensus-gate.js.map +1 -1
  167. package/dist/scripts/__tests__/codex-native-hook.test.js +831 -2
  168. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  169. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  170. package/dist/scripts/codex-native-hook.js +486 -21
  171. package/dist/scripts/codex-native-hook.js.map +1 -1
  172. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  173. package/dist/scripts/notify-hook/team-tmux-guard.js +87 -8
  174. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  175. package/dist/scripts/notify-hook/tmux-injection.js +1 -1
  176. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  177. package/dist/scripts/run-compiled-ci.d.ts +2 -0
  178. package/dist/scripts/run-compiled-ci.d.ts.map +1 -0
  179. package/dist/scripts/run-compiled-ci.js +77 -0
  180. package/dist/scripts/run-compiled-ci.js.map +1 -0
  181. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  182. package/dist/scripts/sync-plugin-mirror.js +1 -0
  183. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  184. package/dist/session-history/search.d.ts.map +1 -1
  185. package/dist/session-history/search.js +24 -9
  186. package/dist/session-history/search.js.map +1 -1
  187. package/dist/state/__tests__/operations.test.js +107 -3
  188. package/dist/state/__tests__/operations.test.js.map +1 -1
  189. package/dist/state/operations.d.ts.map +1 -1
  190. package/dist/state/operations.js +57 -2
  191. package/dist/state/operations.js.map +1 -1
  192. package/dist/team/__tests__/delivery-e2e-smoke.test.js +22 -1
  193. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  194. package/dist/team/__tests__/model-contract.test.js +57 -1
  195. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  196. package/dist/team/__tests__/runtime.test.js +1468 -110
  197. package/dist/team/__tests__/runtime.test.js.map +1 -1
  198. package/dist/team/__tests__/scaling.test.js +63 -0
  199. package/dist/team/__tests__/scaling.test.js.map +1 -1
  200. package/dist/team/__tests__/state.test.js +24 -1
  201. package/dist/team/__tests__/state.test.js.map +1 -1
  202. package/dist/team/__tests__/tmux-session.test.js +512 -0
  203. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  204. package/dist/team/__tests__/worker-bootstrap.test.js +42 -9
  205. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  206. package/dist/team/model-contract.d.ts +16 -0
  207. package/dist/team/model-contract.d.ts.map +1 -1
  208. package/dist/team/model-contract.js +41 -1
  209. package/dist/team/model-contract.js.map +1 -1
  210. package/dist/team/runtime.d.ts.map +1 -1
  211. package/dist/team/runtime.js +116 -30
  212. package/dist/team/runtime.js.map +1 -1
  213. package/dist/team/scaling.d.ts.map +1 -1
  214. package/dist/team/scaling.js +9 -1
  215. package/dist/team/scaling.js.map +1 -1
  216. package/dist/team/state/types.d.ts +2 -0
  217. package/dist/team/state/types.d.ts.map +1 -1
  218. package/dist/team/state/types.js.map +1 -1
  219. package/dist/team/state.d.ts +3 -0
  220. package/dist/team/state.d.ts.map +1 -1
  221. package/dist/team/state.js +20 -0
  222. package/dist/team/state.js.map +1 -1
  223. package/dist/team/tmux-session.d.ts +38 -3
  224. package/dist/team/tmux-session.d.ts.map +1 -1
  225. package/dist/team/tmux-session.js +275 -36
  226. package/dist/team/tmux-session.js.map +1 -1
  227. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  228. package/dist/team/worker-bootstrap.js +26 -1
  229. package/dist/team/worker-bootstrap.js.map +1 -1
  230. package/dist/ultragoal/__tests__/artifacts.test.js +238 -0
  231. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  232. package/dist/ultragoal/__tests__/docs-contract.test.js +3 -0
  233. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  234. package/dist/ultragoal/artifacts.d.ts +15 -0
  235. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  236. package/dist/ultragoal/artifacts.js +167 -4
  237. package/dist/ultragoal/artifacts.js.map +1 -1
  238. package/dist/utils/__tests__/agents-md.test.js +39 -1
  239. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  240. package/dist/utils/__tests__/agents-model-table.test.js +28 -0
  241. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  242. package/dist/utils/agents-md.d.ts +4 -0
  243. package/dist/utils/agents-md.d.ts.map +1 -1
  244. package/dist/utils/agents-md.js +25 -0
  245. package/dist/utils/agents-md.js.map +1 -1
  246. package/dist/utils/agents-model-table.d.ts +7 -3
  247. package/dist/utils/agents-model-table.d.ts.map +1 -1
  248. package/dist/utils/agents-model-table.js +16 -8
  249. package/dist/utils/agents-model-table.js.map +1 -1
  250. package/dist/verification/__tests__/ci-rust-gates.test.js +4 -0
  251. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  252. package/package.json +2 -2
  253. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  254. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +59 -8
  255. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +9 -5
  256. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +3 -0
  257. package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +3 -0
  258. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  259. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +22 -4
  260. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +2 -2
  261. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +61 -0
  262. package/skills/autopilot/SKILL.md +9 -5
  263. package/skills/autoresearch-goal/SKILL.md +3 -0
  264. package/skills/ecomode/SKILL.md +1 -1
  265. package/skills/ecomode/references/agent-tiers.md +61 -0
  266. package/skills/performance-goal/SKILL.md +3 -0
  267. package/skills/ralplan/SKILL.md +1 -1
  268. package/skills/ultragoal/SKILL.md +22 -4
  269. package/skills/ultrawork/SKILL.md +2 -2
  270. package/skills/ultrawork/references/agent-tiers.md +61 -0
  271. package/src/scripts/__tests__/codex-native-hook.test.ts +975 -40
  272. package/src/scripts/codex-native-hook.ts +529 -22
  273. package/src/scripts/notify-hook/team-tmux-guard.ts +92 -8
  274. package/src/scripts/notify-hook/tmux-injection.ts +1 -1
  275. package/src/scripts/run-compiled-ci.ts +85 -0
  276. package/src/scripts/sync-plugin-mirror.ts +1 -0
  277. package/templates/AGENTS.md +6 -0
@@ -18,6 +18,9 @@ const coverageRun = process.env.NODE_V8_COVERAGE ? true : false;
18
18
  const skipSlowLifecycleUnderCoverage = coverageRun
19
19
  ? 'covered by the team-state-runtime lane; skipped under c8 to keep the coverage gate bounded around slow process-lifecycle waits'
20
20
  : false;
21
+ function escapeRegExp(value) {
22
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
23
+ }
21
24
  async function initRepo() {
22
25
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-worktree-repo-'));
23
26
  execFileSync('git', ['init'], { cwd, stdio: 'ignore' });
@@ -446,6 +449,7 @@ describe('runtime', () => {
446
449
  assert.deepEqual(args, ['--no-alt-screen', '--model', expectedLowComplexityModel()]);
447
450
  });
448
451
  it('resolveWorkerLaunchArgsFromEnv reads low-complexity model from config when present', async () => {
452
+ // Intentional legacy model fixture: verifies explicit low-complexity config survives worker launch resolution.
449
453
  await withIsolatedDefaultModelEnvAsync(async () => {
450
454
  const previousCodexHome = process.env.CODEX_HOME;
451
455
  const tempCodexHome = await mkdtemp(join(tmpdir(), 'omx-codex-home-'));
@@ -1242,6 +1246,271 @@ process.on('SIGTERM', () => process.exit(0));`);
1242
1246
  assert.equal(shouldPrekillInteractiveShutdownProcessTrees('leader:0'), false);
1243
1247
  assert.equal(shouldPrekillInteractiveShutdownProcessTrees('omx-team-alpha'), true);
1244
1248
  });
1249
+ it('startTeam tags interactive panes with the derived tmux-pane leader identity when session id is unavailable', async () => {
1250
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-pane-derived-owner-'));
1251
+ const prevTmux = process.env.TMUX;
1252
+ const prevTmuxPane = process.env.TMUX_PANE;
1253
+ const prevSessionId = process.env.OMX_SESSION_ID;
1254
+ const prevCodexSessionId = process.env.CODEX_SESSION_ID;
1255
+ const prevGenericSessionId = process.env.SESSION_ID;
1256
+ const prevLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1257
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1258
+ const prevSkipReadyWait = process.env.OMX_TEAM_SKIP_READY_WAIT;
1259
+ let runtime = null;
1260
+ try {
1261
+ await withMockTmuxFixture({
1262
+ dirPrefix: 'omx-runtime-pane-derived-owner-bin-',
1263
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
1264
+ set -eu
1265
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
1266
+ case "\${1:-}" in
1267
+ -V)
1268
+ echo "tmux 3.4"
1269
+ exit 0
1270
+ ;;
1271
+ display-message)
1272
+ case "$*" in
1273
+ *"#{window_width}"*)
1274
+ echo "120"
1275
+ ;;
1276
+ *)
1277
+ echo "leader:0 %1"
1278
+ ;;
1279
+ esac
1280
+ exit 0
1281
+ ;;
1282
+ list-panes)
1283
+ case "$*" in
1284
+ *"-t %2 -F #{pane_pid}"*)
1285
+ echo "2000002222"
1286
+ ;;
1287
+ *"pane_current_command"*)
1288
+ printf "%%1\\tnode\\t'codex'\\n"
1289
+ ;;
1290
+ *)
1291
+ printf "%%1\\n"
1292
+ ;;
1293
+ esac
1294
+ exit 0
1295
+ ;;
1296
+ split-window)
1297
+ case "$*" in
1298
+ *" -h "*)
1299
+ echo "%2"
1300
+ ;;
1301
+ *)
1302
+ echo "%3"
1303
+ ;;
1304
+ esac
1305
+ exit 0
1306
+ ;;
1307
+ set-option|resize-pane|select-layout|set-window-option|select-pane|set-hook|run-shell|send-keys|kill-pane|kill-session)
1308
+ exit 0
1309
+ ;;
1310
+ *)
1311
+ exit 0
1312
+ ;;
1313
+ esac
1314
+ `,
1315
+ binaries: [{
1316
+ name: 'gemini',
1317
+ content: '#!/bin/sh\nexit 0\n',
1318
+ }],
1319
+ env: {
1320
+ OMX_SESSION_ID: undefined,
1321
+ CODEX_SESSION_ID: undefined,
1322
+ SESSION_ID: undefined,
1323
+ },
1324
+ }, async ({ tmuxLogPath }) => {
1325
+ process.env.TMUX = 'leader-session,stub,0';
1326
+ process.env.TMUX_PANE = '%1';
1327
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1328
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
1329
+ process.env.OMX_TEAM_SKIP_READY_WAIT = '1';
1330
+ runtime = await withoutTeamWorkerEnv(() => startTeam('pane-derived-owner', 'derived pane owner', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
1331
+ const manifest = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'manifest.v2.json'), 'utf-8'));
1332
+ assert.equal(manifest.leader?.session_id, '%1');
1333
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1334
+ assert.match(tmuxLog, /set-option -p -t %1 @omx_pane_instance_id %1/);
1335
+ assert.match(tmuxLog, /set-option -p -t %2 @omx_pane_instance_id %1/);
1336
+ assert.match(tmuxLog, /set-option -p -t %3 @omx_pane_instance_id %1/);
1337
+ assert.match(tmuxLog, /exec env OMX_SESSION_ID='%1' OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%1' .*hud --watch/);
1338
+ await shutdownTeam(runtime.teamName, cwd, { force: true });
1339
+ runtime = null;
1340
+ });
1341
+ }
1342
+ finally {
1343
+ const runtimeToShutdown = runtime;
1344
+ if (runtimeToShutdown) {
1345
+ await shutdownTeam(runtimeToShutdown.teamName, cwd, { force: true }).catch(() => { });
1346
+ }
1347
+ if (typeof prevTmux === 'string')
1348
+ process.env.TMUX = prevTmux;
1349
+ else
1350
+ delete process.env.TMUX;
1351
+ if (typeof prevTmuxPane === 'string')
1352
+ process.env.TMUX_PANE = prevTmuxPane;
1353
+ else
1354
+ delete process.env.TMUX_PANE;
1355
+ if (typeof prevSessionId === 'string')
1356
+ process.env.OMX_SESSION_ID = prevSessionId;
1357
+ else
1358
+ delete process.env.OMX_SESSION_ID;
1359
+ if (typeof prevCodexSessionId === 'string')
1360
+ process.env.CODEX_SESSION_ID = prevCodexSessionId;
1361
+ else
1362
+ delete process.env.CODEX_SESSION_ID;
1363
+ if (typeof prevGenericSessionId === 'string')
1364
+ process.env.SESSION_ID = prevGenericSessionId;
1365
+ else
1366
+ delete process.env.SESSION_ID;
1367
+ if (typeof prevLaunchMode === 'string')
1368
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = prevLaunchMode;
1369
+ else
1370
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1371
+ if (typeof prevWorkerCli === 'string')
1372
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
1373
+ else
1374
+ delete process.env.OMX_TEAM_WORKER_CLI;
1375
+ if (typeof prevSkipReadyWait === 'string')
1376
+ process.env.OMX_TEAM_SKIP_READY_WAIT = prevSkipReadyWait;
1377
+ else
1378
+ delete process.env.OMX_TEAM_SKIP_READY_WAIT;
1379
+ await rm(cwd, { recursive: true, force: true });
1380
+ }
1381
+ });
1382
+ it('startTeam keeps logical session id out of team shutdown ownership tags', async () => {
1383
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-pane-owner-env-isolated-'));
1384
+ const prevTmux = process.env.TMUX;
1385
+ const prevTmuxPane = process.env.TMUX_PANE;
1386
+ const prevSessionId = process.env.OMX_SESSION_ID;
1387
+ const prevLaunchMode = process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1388
+ const prevWorkerCli = process.env.OMX_TEAM_WORKER_CLI;
1389
+ const prevSkipReadyWait = process.env.OMX_TEAM_SKIP_READY_WAIT;
1390
+ let runtime = null;
1391
+ try {
1392
+ await withMockTmuxFixture({
1393
+ dirPrefix: 'omx-runtime-pane-owner-env-isolated-bin-',
1394
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
1395
+ set -eu
1396
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
1397
+ case "\${1:-}" in
1398
+ -V)
1399
+ echo "tmux 3.4"
1400
+ exit 0
1401
+ ;;
1402
+ display-message)
1403
+ case "$*" in
1404
+ *"#{window_width}"*)
1405
+ echo "120"
1406
+ ;;
1407
+ *)
1408
+ echo "leader:0 %1"
1409
+ ;;
1410
+ esac
1411
+ exit 0
1412
+ ;;
1413
+ list-panes)
1414
+ case "$*" in
1415
+ *"-t %2 -F #{pane_pid}"*)
1416
+ echo "2000002222"
1417
+ ;;
1418
+ *"pane_current_command"*)
1419
+ printf "%%1\\tnode\\t'codex'\\n"
1420
+ ;;
1421
+ *)
1422
+ printf "%%1\\n"
1423
+ ;;
1424
+ esac
1425
+ exit 0
1426
+ ;;
1427
+ split-window)
1428
+ case "$*" in
1429
+ *" -h "*)
1430
+ echo "%2"
1431
+ ;;
1432
+ *)
1433
+ echo "%3"
1434
+ ;;
1435
+ esac
1436
+ exit 0
1437
+ ;;
1438
+ show-option)
1439
+ case "$*" in
1440
+ *)
1441
+ exit 1
1442
+ ;;
1443
+ esac
1444
+ exit 0
1445
+ ;;
1446
+ set-option|resize-pane|select-layout|set-window-option|select-pane|set-hook|run-shell|send-keys|kill-pane|kill-session)
1447
+ exit 0
1448
+ ;;
1449
+ *)
1450
+ exit 0
1451
+ ;;
1452
+ esac
1453
+ `,
1454
+ binaries: [{
1455
+ name: 'gemini',
1456
+ content: '#!/bin/sh\nexit 0\n',
1457
+ }],
1458
+ }, async ({ tmuxLogPath }) => {
1459
+ process.env.TMUX = 'leader-session,stub,0';
1460
+ process.env.TMUX_PANE = '%1';
1461
+ process.env.OMX_SESSION_ID = 'logical-session-from-env';
1462
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
1463
+ process.env.OMX_TEAM_WORKER_CLI = 'gemini';
1464
+ process.env.OMX_TEAM_SKIP_READY_WAIT = '1';
1465
+ runtime = await withoutTeamWorkerEnv(() => startTeam('pane-owner-env-isolated', 'env session must not be pane owner', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd));
1466
+ const manifest = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'manifest.v2.json'), 'utf-8'));
1467
+ assert.equal(manifest.leader?.session_id, 'logical-session-from-env');
1468
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1469
+ assert.match(tmuxLog, /set-option -t leader @omx_instance_id logical-session-from-env/);
1470
+ assert.match(tmuxLog, /set-option -p -t %1 @omx_pane_instance_id logical-session-from-env/);
1471
+ assert.match(tmuxLog, /set-option -p -t %2 @omx_pane_instance_id logical-session-from-env/);
1472
+ assert.match(tmuxLog, /set-option -p -t %3 @omx_pane_instance_id logical-session-from-env/);
1473
+ assert.match(tmuxLog, /set-option -p -t %1 @omx_team_pane_owner_id team:pane-owner-env-isolat-[a-f0-9]{8}/);
1474
+ assert.match(tmuxLog, /set-option -p -t %2 @omx_team_pane_owner_id team:pane-owner-env-isolat-[a-f0-9]{8}/);
1475
+ assert.match(tmuxLog, /set-option -p -t %3 @omx_team_pane_owner_id team:pane-owner-env-isolat-[a-f0-9]{8}/);
1476
+ assert.doesNotMatch(tmuxLog, /set-option -p -t %1 @omx_team_pane_owner_id logical-session-from-env/);
1477
+ assert.match(tmuxLog, /exec env OMX_SESSION_ID='logical-session-from-env' OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%1' .*hud --watch/);
1478
+ await shutdownTeam(runtime.teamName, cwd, { force: true });
1479
+ runtime = null;
1480
+ });
1481
+ }
1482
+ finally {
1483
+ const runtimeToShutdown = runtime;
1484
+ if (runtimeToShutdown) {
1485
+ await shutdownTeam(runtimeToShutdown.teamName, cwd, { force: true }).catch(() => { });
1486
+ }
1487
+ if (typeof prevTmux === 'string')
1488
+ process.env.TMUX = prevTmux;
1489
+ else
1490
+ delete process.env.TMUX;
1491
+ if (typeof prevTmuxPane === 'string')
1492
+ process.env.TMUX_PANE = prevTmuxPane;
1493
+ else
1494
+ delete process.env.TMUX_PANE;
1495
+ if (typeof prevSessionId === 'string')
1496
+ process.env.OMX_SESSION_ID = prevSessionId;
1497
+ else
1498
+ delete process.env.OMX_SESSION_ID;
1499
+ if (typeof prevLaunchMode === 'string')
1500
+ process.env.OMX_TEAM_WORKER_LAUNCH_MODE = prevLaunchMode;
1501
+ else
1502
+ delete process.env.OMX_TEAM_WORKER_LAUNCH_MODE;
1503
+ if (typeof prevWorkerCli === 'string')
1504
+ process.env.OMX_TEAM_WORKER_CLI = prevWorkerCli;
1505
+ else
1506
+ delete process.env.OMX_TEAM_WORKER_CLI;
1507
+ if (typeof prevSkipReadyWait === 'string')
1508
+ process.env.OMX_TEAM_SKIP_READY_WAIT = prevSkipReadyWait;
1509
+ else
1510
+ delete process.env.OMX_TEAM_SKIP_READY_WAIT;
1511
+ await rm(cwd, { recursive: true, force: true });
1512
+ }
1513
+ });
1245
1514
  it('startTeam accepts native Windows tmux clients even when TMUX env vars are absent', async () => {
1246
1515
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-win32-no-env-'));
1247
1516
  const prevTmux = process.env.TMUX;
@@ -1395,10 +1664,12 @@ esac
1395
1664
  hudPaneId: '%4',
1396
1665
  resizeHookName: 'resize-hook',
1397
1666
  resizeHookTarget: 'leader:0',
1667
+ teamPaneOwnerId: 'team:team-pane-persist-race',
1398
1668
  }, workerPaneIds);
1399
1669
  assert.equal(config.tmux_session, 'leader:0');
1400
1670
  assert.equal(config.leader_pane_id, '%1');
1401
1671
  assert.equal(config.hud_pane_id, '%4');
1672
+ assert.equal(config.tmux_pane_owner_id, 'team:team-pane-persist-race');
1402
1673
  assert.deepEqual(workerPaneIds, ['%2', '%3']);
1403
1674
  assert.equal(config.workers[0]?.pane_id, '%2');
1404
1675
  assert.equal(config.workers[1]?.pane_id, '%3');
@@ -3315,6 +3586,10 @@ process.on('SIGTERM', () => process.exit(0));
3315
3586
  tmuxScript: (tmuxLogPath) => `#!/bin/sh
3316
3587
  set -eu
3317
3588
  printf '%s\\n' "$*" >> "${tmuxLogPath}"
3589
+ hud_state="${tmuxLogPath}.hud-state"
3590
+ if [ ! -f "$hud_state" ]; then
3591
+ printf 'absent' > "$hud_state"
3592
+ fi
3318
3593
  case "\${1:-}" in
3319
3594
  -V)
3320
3595
  echo "tmux 3.4"
@@ -3334,7 +3609,10 @@ case "\${1:-}" in
3334
3609
  list-panes)
3335
3610
  case "$*" in
3336
3611
  *"pane_current_command"* )
3337
- printf "%%1\\tnode\\t'codex'\\n"
3612
+ printf "%%1\\tnode\\t'codex'\\n%%2\\tgemini\\tgemini\\n"
3613
+ if [ "$(cat "$hud_state")" != "absent" ]; then
3614
+ printf "%%3\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%1' node /tmp/bin/omx.js hud --watch\\n"
3615
+ fi
3338
3616
  ;;
3339
3617
  *"#{pane_dead} #{pane_pid}"*)
3340
3618
  echo "1 2000999999"
@@ -3353,13 +3631,38 @@ case "\${1:-}" in
3353
3631
  *" -h "*)
3354
3632
  echo "%2"
3355
3633
  ;;
3634
+ *" -f "*)
3635
+ printf 'team' > "$hud_state"
3636
+ echo "%3"
3637
+ ;;
3356
3638
  *)
3639
+ printf 'standalone' > "$hud_state"
3357
3640
  echo "%3"
3358
3641
  ;;
3359
3642
  esac
3360
3643
  exit 0
3361
3644
  ;;
3362
- set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-pane|kill-session)
3645
+ show-option)
3646
+ case "$*" in
3647
+ *"-p -t %1 @omx_team_pane_owner_id"*)
3648
+ echo "team:team-rerun-hud-6aa4d480"
3649
+ ;;
3650
+ *"-p -t %3 @omx_team_pane_owner_id"*)
3651
+ echo "team:team-rerun-hud-6aa4d480"
3652
+ ;;
3653
+ *)
3654
+ exit 1
3655
+ ;;
3656
+ esac
3657
+ exit 0
3658
+ ;;
3659
+ kill-pane)
3660
+ case "$*" in
3661
+ *"%3"*) printf 'absent' > "$hud_state" ;;
3662
+ esac
3663
+ exit 0
3664
+ ;;
3665
+ set-hook|run-shell|select-layout|set-window-option|select-pane|send-keys|kill-session)
3363
3666
  exit 0
3364
3667
  ;;
3365
3668
  *)
@@ -3373,6 +3676,7 @@ esac
3373
3676
  exit 0
3374
3677
  `,
3375
3678
  }],
3679
+ env: { OMX_SESSION_ID: 'team-rerun-hud-session' },
3376
3680
  }, async ({ tmuxLogPath }) => {
3377
3681
  process.env.TMUX = 'leader-session,stub,0';
3378
3682
  process.env.TMUX_PANE = '%1';
@@ -3381,6 +3685,8 @@ exit 0
3381
3685
  runtime = await withoutTeamWorkerEnv(() => startTeam('team-rerun-hud', 'rerun hud restore', 'explore', 1, [{ subject: 'restore hud', description: 'restore hud', owner: 'worker-1' }], cwd));
3382
3686
  assert.equal(runtime.config.hud_pane_id, '%3');
3383
3687
  assert.ok(runtime.config.resize_hook_name);
3688
+ const configBeforeShutdown = await readTeamConfig(runtime.teamName, cwd);
3689
+ assert.equal(configBeforeShutdown?.hud_pane_id, '%3');
3384
3690
  await shutdownTeam(runtime.teamName, cwd, { force: true });
3385
3691
  runtime = null;
3386
3692
  runtime = await withoutTeamWorkerEnv(() => startTeam('team-rerun-hud', 'rerun hud restore', 'explore', 1, [{ subject: 'restore hud again', description: 'restore hud again', owner: 'worker-1' }], cwd));
@@ -3396,7 +3702,7 @@ exit 0
3396
3702
  assert.equal(tmuxLog.match(/run-shell -b sleep \d+; tmux resize-pane -t %3 -y \d+ >/g)?.length ?? 0, 3);
3397
3703
  assert.equal(tmuxLog.match(/run-shell tmux resize-pane -t %3 -y \d+ >/g)?.length ?? 0, 3);
3398
3704
  assert.ok((tmuxLog.match(/select-layout -t leader:0 main-vertical/g)?.length ?? 0) >= 2);
3399
- assert.match(tmuxLog, /kill-pane -t %3/);
3705
+ assert.equal(tmuxLog.match(/kill-pane -t %3/g)?.length ?? 0, 2);
3400
3706
  });
3401
3707
  }
3402
3708
  finally {
@@ -4896,6 +5202,7 @@ esac
4896
5202
  });
4897
5203
  it('shutdownTeam reconciles persisted worker panes with live tmux panes before teardown', async () => {
4898
5204
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-pane-reconcile-'));
5205
+ const powershellWorkerCommand = Buffer.from("$env:OMX_TEAM_INTERNAL_WORKER = 'team-shutdown-pane-reconcile/worker-6'; & '/opt/node.exe' '/tmp/node_modules/@openai/codex/bin/codex.js'", 'utf16le').toString('base64');
4899
5206
  try {
4900
5207
  await withMockTmuxFixture({
4901
5208
  dirPrefix: 'omx-runtime-shutdown-pane-reconcile-bin-',
@@ -4922,7 +5229,7 @@ case "$1" in
4922
5229
  exit 0
4923
5230
  ;;
4924
5231
  *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
4925
- printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tcodex\\n%%14\\tcodex\\tcodex\\n"
5232
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\texec /bin/sh '/tmp/.omx/state/team/team-shutdown-pane-reconcile/runtime/worker-1-startup.sh'\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-pane-reconcile/worker-2 codex\\n%%15\\tcodex\\tcodex unrelated-not-worker\\n%%16\\tcodex\\tenv OMX_TEAM_WORKER=team-shutdown-pane-reconcile/worker-3 codex\\n%%17\\tcodex\\tworker-wrapper OMX_TEAM_INTERNAL_WORKER='team-shutdown-pane-reconcile/worker-4' codex\\n%%18\\tcodex\\tenv 'OMX_TEAM_INTERNAL_WORKER=team-shutdown-pane-reconcile/worker-5' codex\\n%%19\\tpowershell.exe\\tpowershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${powershellWorkerCommand}\\n%%20\\tzsh\\techo 'OMX_TEAM_INTERNAL_WORKER=team-shutdown-pane-reconcile/worker-7'\\n%%21\\tzsh\\tprintf 'OMX_TEAM_INTERNAL_WORKER=team-shutdown-pane-reconcile/worker-8 codex'\\n%%22\\tzsh\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-pane-reconcile/worker-9 bash -lc 'echo codex'\\n"
4926
5233
  if [ -f "$restored_marker" ]; then
4927
5234
  printf "%%44\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n"
4928
5235
  fi
@@ -4938,6 +5245,23 @@ case "$1" in
4938
5245
  printf '%%44\\n'
4939
5246
  exit 0
4940
5247
  ;;
5248
+ show-option)
5249
+ case "$*" in
5250
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5251
+ echo "team:team-shutdown-pane-reconcile"
5252
+ ;;
5253
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5254
+ echo "team:team-shutdown-pane-reconcile"
5255
+ ;;
5256
+ *"-p -t %13 @omx_team_pane_owner_id"|*"-p -t %14 @omx_team_pane_owner_id"|*"-p -t %16 @omx_team_pane_owner_id"|*"-p -t %17 @omx_team_pane_owner_id"|*"-p -t %18 @omx_team_pane_owner_id"|*"-p -t %19 @omx_team_pane_owner_id"*)
5257
+ echo "team:team-shutdown-pane-reconcile"
5258
+ ;;
5259
+ *)
5260
+ exit 1
5261
+ ;;
5262
+ esac
5263
+ exit 0
5264
+ ;;
4941
5265
  kill-pane)
4942
5266
  if [ "\${3:-}" = "%999" ]; then
4943
5267
  echo "missing pane" >&2
@@ -4953,6 +5277,7 @@ case "$1" in
4953
5277
  ;;
4954
5278
  esac
4955
5279
  `,
5280
+ env: { OMX_SESSION_ID: 'team-shutdown-pane-reconcile-session' },
4956
5281
  }, async ({ tmuxLogPath }) => {
4957
5282
  await initTeamState('team-shutdown-pane-reconcile', 'shutdown pane reconcile test', 'executor', 2, cwd);
4958
5283
  const config = await readTeamConfig('team-shutdown-pane-reconcile', cwd);
@@ -4971,7 +5296,15 @@ esac
4971
5296
  assert.match(tmuxLog, /kill-pane -t %12/);
4972
5297
  assert.match(tmuxLog, /kill-pane -t %13/);
4973
5298
  assert.match(tmuxLog, /kill-pane -t %14/);
4974
- assert.match(tmuxLog, /kill-pane -t %999/);
5299
+ assert.match(tmuxLog, /kill-pane -t %16/);
5300
+ assert.match(tmuxLog, /kill-pane -t %17/);
5301
+ assert.match(tmuxLog, /kill-pane -t %18/);
5302
+ assert.match(tmuxLog, /kill-pane -t %19/);
5303
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %999/);
5304
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %15/);
5305
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %20/);
5306
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %21/);
5307
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %22/);
4975
5308
  assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\{pane_id\}`));
4976
5309
  assert.doesNotMatch(tmuxLog, /kill-pane -t %44/);
4977
5310
  });
@@ -4980,15 +5313,16 @@ esac
4980
5313
  await rm(cwd, { recursive: true, force: true });
4981
5314
  }
4982
5315
  });
4983
- it('shutdownTeam skips prekill and keeps the leader pane alive on native Windows split-pane shutdown', async () => {
4984
- const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-win32-split-'));
5316
+ it('shutdownTeam preserves unrelated non-worker panes during shared-session shutdown', async () => {
5317
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-unrelated-pane-'));
5318
+ const teamName = 'team-shutdown-unrelated-pane';
4985
5319
  try {
4986
- await withNativeWindowsPlatform(async () => {
4987
- await withMockTmuxFixture({
4988
- dirPrefix: 'omx-runtime-shutdown-win32-split-bin-',
4989
- tmuxScript: (tmuxLogPath) => `#!/bin/sh
5320
+ await withMockTmuxFixture({
5321
+ dirPrefix: 'omx-runtime-shutdown-unrelated-pane-bin-',
5322
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
4990
5323
  set -eu
4991
5324
  printf '%s\\n' "$*" >> "${tmuxLogPath}"
5325
+ restored_marker="${tmuxLogPath}.restored"
4992
5326
  case "$1" in
4993
5327
  -V)
4994
5328
  echo "tmux 3.4"
@@ -5000,7 +5334,10 @@ case "$1" in
5000
5334
  exit 1
5001
5335
  ;;
5002
5336
  *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5003
- printf "%%11\\tpwsh\\tpwsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tcodex\\n%%14\\tcodex\\tcodex\\n"
5337
+ printf "%%10\\tzsh\\tzsh\\n%%11\\tnode\\tnode existing-user-work\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-unrelated-pane/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-unrelated-pane/worker-2 codex\\n%%15\\tnode\\tnode /tmp/bin/omx.js sidecar --watch\\n"
5338
+ if [ -f "$restored_marker" ]; then
5339
+ printf "%%44\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n"
5340
+ fi
5004
5341
  exit 0
5005
5342
  ;;
5006
5343
  *)
@@ -5009,10 +5346,25 @@ case "$1" in
5009
5346
  esac
5010
5347
  ;;
5011
5348
  split-window)
5349
+ : > "$restored_marker"
5012
5350
  printf '%%44\\n'
5013
5351
  exit 0
5014
5352
  ;;
5015
- kill-pane|resize-pane|select-pane)
5353
+ show-option)
5354
+ case "$*" in
5355
+ *"-p -t %10 @omx_team_pane_owner_id"*)
5356
+ echo "team:team-shutdown-unrelated-pane"
5357
+ ;;
5358
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5359
+ echo "team:team-shutdown-unrelated-pane"
5360
+ ;;
5361
+ *)
5362
+ exit 1
5363
+ ;;
5364
+ esac
5365
+ exit 0
5366
+ ;;
5367
+ kill-pane|resize-pane|select-pane|run-shell)
5016
5368
  exit 0
5017
5369
  ;;
5018
5370
  *)
@@ -5020,48 +5372,47 @@ case "$1" in
5020
5372
  ;;
5021
5373
  esac
5022
5374
  `,
5023
- }, async ({ tmuxLogPath }) => {
5024
- await initTeamState('team-shutdown-win32-split', 'shutdown win32 split test', 'executor', 2, cwd);
5025
- const config = await readTeamConfig('team-shutdown-win32-split', cwd);
5026
- assert.ok(config);
5027
- if (!config)
5028
- return;
5029
- config.tmux_session = 'leader:0';
5030
- config.leader_pane_id = '%11';
5031
- config.hud_pane_id = '%12';
5032
- config.workers[0].pane_id = '%13';
5033
- config.workers[1].pane_id = '%14';
5034
- await saveTeamConfig(config, cwd);
5035
- await shutdownTeam('team-shutdown-win32-split', cwd, { force: true });
5036
- const teamRoot = join(cwd, '.omx', 'state', 'team', 'team-shutdown-win32-split');
5037
- assert.equal(existsSync(teamRoot), false);
5038
- assert.equal(await readMonitorSnapshot('team-shutdown-win32-split', cwd), null);
5039
- const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5040
- assert.doesNotMatch(tmuxLog, /list-panes -t %13 -F #\{pane_pid\}/);
5041
- assert.doesNotMatch(tmuxLog, /list-panes -t %14 -F #\{pane_pid\}/);
5042
- assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5043
- assert.doesNotMatch(tmuxLog, /kill-session -t leader:0/);
5044
- assert.match(tmuxLog, /kill-pane -t %12/);
5045
- assert.match(tmuxLog, /kill-pane -t %13/);
5046
- assert.match(tmuxLog, /kill-pane -t %14/);
5047
- assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
5048
- assert.match(tmuxLog, new RegExp(`resize-pane -t %44 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
5049
- assert.match(tmuxLog, /select-pane -t %11/);
5050
- });
5375
+ env: { OMX_SESSION_ID: 'team-shutdown-unrelated-pane-session' },
5376
+ }, async ({ tmuxLogPath }) => {
5377
+ await initTeamState(teamName, 'shutdown unrelated pane test', 'executor', 2, cwd);
5378
+ const config = await readTeamConfig(teamName, cwd);
5379
+ assert.ok(config);
5380
+ if (!config)
5381
+ return;
5382
+ config.tmux_session = 'leader:0';
5383
+ config.leader_pane_id = '%10';
5384
+ config.hud_pane_id = '%12';
5385
+ config.workers[0].pane_id = '%13';
5386
+ config.workers[1].pane_id = '%14';
5387
+ await saveTeamConfig(config, cwd);
5388
+ await shutdownTeam(teamName, cwd, { force: true });
5389
+ const teamRoot = join(cwd, '.omx', 'state', 'team', teamName);
5390
+ assert.equal(existsSync(teamRoot), false);
5391
+ assert.equal(await readMonitorSnapshot(teamName, cwd), null);
5392
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5393
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %10/);
5394
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5395
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %15/);
5396
+ assert.match(tmuxLog, /kill-pane -t %12/);
5397
+ assert.match(tmuxLog, /kill-pane -t %13/);
5398
+ assert.match(tmuxLog, /kill-pane -t %14/);
5399
+ assert.doesNotMatch(tmuxLog, /kill-session -t leader:0/);
5400
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %10 -d -P -F #\\{pane_id\\}`));
5401
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %44/);
5402
+ assert.match(tmuxLog, /select-pane -t %10/);
5051
5403
  });
5052
5404
  }
5053
5405
  finally {
5054
5406
  await rm(cwd, { recursive: true, force: true });
5055
5407
  }
5056
5408
  });
5057
- it('shutdownTeam reconciles stale leader and hud pane ids before native Windows split-pane teardown', async () => {
5058
- const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-win32-stale-topology-'));
5059
- const teamName = 'team-win32-stale-topo';
5409
+ it('shutdownTeam ignores stale persisted worker pane ids when a shared-session pane lacks worker evidence', async () => {
5410
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-stale-worker-pane-id-'));
5411
+ const teamName = 'team-stale-worker-pane-id';
5060
5412
  try {
5061
- await withNativeWindowsPlatform(async () => {
5062
- await withMockTmuxFixture({
5063
- dirPrefix: 'omx-runtime-shutdown-win32-stale-topology-bin-',
5064
- tmuxScript: (tmuxLogPath) => `#!/bin/sh
5413
+ await withMockTmuxFixture({
5414
+ dirPrefix: 'omx-runtime-shutdown-stale-worker-pane-id-bin-',
5415
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5065
5416
  set -eu
5066
5417
  printf '%s\\n' "$*" >> "${tmuxLogPath}"
5067
5418
  case "$1" in
@@ -5075,7 +5426,7 @@ case "$1" in
5075
5426
  exit 1
5076
5427
  ;;
5077
5428
  *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5078
- printf "%%21\\tpwsh\\tpwsh\\n%%22\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%23\\tcodex\\tcodex\\n%%24\\tcodex\\tcodex\\n"
5429
+ printf "%%10\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-stale-worker-pane-id/worker-1 codex\\n%%15\\tcodex\\tcodex unrelated-user-pane\\n"
5079
5430
  exit 0
5080
5431
  ;;
5081
5432
  *)
@@ -5087,7 +5438,21 @@ case "$1" in
5087
5438
  printf '%%44\\n'
5088
5439
  exit 0
5089
5440
  ;;
5090
- kill-pane|resize-pane|select-pane)
5441
+ show-option)
5442
+ case "$*" in
5443
+ *"-p -t %10 @omx_team_pane_owner_id"*)
5444
+ echo "team:team-stale-worker-pane-id"
5445
+ ;;
5446
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5447
+ echo "team:team-stale-worker-pane-id"
5448
+ ;;
5449
+ *)
5450
+ exit 1
5451
+ ;;
5452
+ esac
5453
+ exit 0
5454
+ ;;
5455
+ kill-pane|resize-pane|select-pane|run-shell)
5091
5456
  exit 0
5092
5457
  ;;
5093
5458
  *)
@@ -5095,42 +5460,38 @@ case "$1" in
5095
5460
  ;;
5096
5461
  esac
5097
5462
  `,
5098
- }, async ({ tmuxLogPath }) => {
5099
- await initTeamState(teamName, 'shutdown win32 stale topology test', 'executor', 2, cwd);
5100
- const config = await readTeamConfig(teamName, cwd);
5101
- assert.ok(config);
5102
- if (!config)
5103
- return;
5104
- config.tmux_session = 'leader:0';
5105
- config.leader_pane_id = '%11';
5106
- config.hud_pane_id = '%12';
5107
- config.workers[0].pane_id = '%23';
5108
- config.workers[1].pane_id = '%24';
5109
- await saveTeamConfig(config, cwd);
5110
- await shutdownTeam(teamName, cwd, { force: true });
5111
- const teamRoot = join(cwd, '.omx', 'state', 'team', teamName);
5112
- assert.equal(existsSync(teamRoot), false);
5113
- assert.equal(await readMonitorSnapshot(teamName, cwd), null);
5114
- const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5115
- assert.doesNotMatch(tmuxLog, /list-panes -t %21 -F #\{pane_pid\}/);
5116
- assert.doesNotMatch(tmuxLog, /kill-pane -t %21/);
5117
- assert.match(tmuxLog, /kill-pane -t %22/);
5118
- assert.match(tmuxLog, /kill-pane -t %23/);
5119
- assert.match(tmuxLog, /kill-pane -t %24/);
5120
- assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %21 -d -P -F #\\{pane_id\\}`));
5121
- assert.match(tmuxLog, /select-pane -t %21/);
5122
- });
5463
+ env: { OMX_SESSION_ID: 'team-stale-worker-pane-id-session' },
5464
+ }, async ({ tmuxLogPath }) => {
5465
+ await initTeamState(teamName, 'shutdown stale persisted worker pane id test', 'executor', 2, cwd);
5466
+ const config = await readTeamConfig(teamName, cwd);
5467
+ assert.ok(config);
5468
+ if (!config)
5469
+ return;
5470
+ config.tmux_session = 'leader:0';
5471
+ config.leader_pane_id = '%10';
5472
+ config.hud_pane_id = '%12';
5473
+ config.workers[0].pane_id = '%13';
5474
+ config.workers[1].pane_id = '%15';
5475
+ await saveTeamConfig(config, cwd);
5476
+ await shutdownTeam(teamName, cwd, { force: true });
5477
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5478
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %10/);
5479
+ assert.match(tmuxLog, /kill-pane -t %12/);
5480
+ assert.match(tmuxLog, /kill-pane -t %13/);
5481
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %15/);
5482
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %10 -d -P -F #\\{pane_id\\}`));
5123
5483
  });
5124
5484
  }
5125
5485
  finally {
5126
5486
  await rm(cwd, { recursive: true, force: true });
5127
5487
  }
5128
5488
  });
5129
- it('shutdownTeam skips prekill and keeps the leader pane alive on shared-session shutdown', async () => {
5130
- const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-shared-session-'));
5489
+ it('shutdownTeam preserves worker-looking panes with a mismatched team owner tag', async () => {
5490
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-worker-owner-mismatch-'));
5491
+ const teamName = 'team-worker-owner-mismatch';
5131
5492
  try {
5132
5493
  await withMockTmuxFixture({
5133
- dirPrefix: 'omx-runtime-shutdown-shared-session-bin-',
5494
+ dirPrefix: 'omx-runtime-shutdown-worker-owner-mismatch-bin-',
5134
5495
  tmuxScript: (tmuxLogPath) => `#!/bin/sh
5135
5496
  set -eu
5136
5497
  printf '%s\\n' "$*" >> "${tmuxLogPath}"
@@ -5145,7 +5506,7 @@ case "$1" in
5145
5506
  exit 1
5146
5507
  ;;
5147
5508
  *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5148
- printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tcodex\\n%%14\\tcodex\\tcodex\\n"
5509
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-worker-owner-mismatch/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-worker-owner-mismatch/worker-2 codex\\n"
5149
5510
  exit 0
5150
5511
  ;;
5151
5512
  *)
@@ -5157,7 +5518,24 @@ case "$1" in
5157
5518
  printf '%%44\\n'
5158
5519
  exit 0
5159
5520
  ;;
5160
- kill-pane|resize-pane|select-pane)
5521
+ show-option)
5522
+ case "$*" in
5523
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5524
+ echo "team:team-worker-owner-mismatch"
5525
+ ;;
5526
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5527
+ echo "team:team-worker-owner-mismatch"
5528
+ ;;
5529
+ *"-p -t %14 @omx_team_pane_owner_id"*)
5530
+ echo "team:other-team"
5531
+ ;;
5532
+ *)
5533
+ exit 1
5534
+ ;;
5535
+ esac
5536
+ exit 0
5537
+ ;;
5538
+ kill-pane|resize-pane|select-pane|run-shell)
5161
5539
  exit 0
5162
5540
  ;;
5163
5541
  *)
@@ -5166,8 +5544,8 @@ case "$1" in
5166
5544
  esac
5167
5545
  `,
5168
5546
  }, async ({ tmuxLogPath }) => {
5169
- await initTeamState('team-shutdown-shared-session', 'shutdown shared session test', 'executor', 2, cwd);
5170
- const config = await readTeamConfig('team-shutdown-shared-session', cwd);
5547
+ await initTeamState(teamName, 'shutdown worker owner mismatch test', 'executor', 2, cwd);
5548
+ const config = await readTeamConfig(teamName, cwd);
5171
5549
  assert.ok(config);
5172
5550
  if (!config)
5173
5551
  return;
@@ -5177,47 +5555,69 @@ esac
5177
5555
  config.workers[0].pane_id = '%13';
5178
5556
  config.workers[1].pane_id = '%14';
5179
5557
  await saveTeamConfig(config, cwd);
5180
- await shutdownTeam('team-shutdown-shared-session', cwd, { force: true });
5181
- const teamRoot = join(cwd, '.omx', 'state', 'team', 'team-shutdown-shared-session');
5182
- assert.equal(existsSync(teamRoot), false);
5183
- assert.equal(await readMonitorSnapshot('team-shutdown-shared-session', cwd), null);
5558
+ await shutdownTeam(teamName, cwd, { force: true });
5184
5559
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5185
- assert.doesNotMatch(tmuxLog, /list-panes -t %13 -F #\{pane_pid\}/);
5186
- assert.doesNotMatch(tmuxLog, /list-panes -t %14 -F #\{pane_pid\}/);
5187
5560
  assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5188
- assert.doesNotMatch(tmuxLog, /kill-session -t leader:0/);
5189
5561
  assert.match(tmuxLog, /kill-pane -t %12/);
5190
5562
  assert.match(tmuxLog, /kill-pane -t %13/);
5191
- assert.match(tmuxLog, /kill-pane -t %14/);
5563
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %14/);
5192
5564
  assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
5193
- assert.match(tmuxLog, /select-pane -t %11/);
5194
5565
  });
5195
5566
  }
5196
5567
  finally {
5197
5568
  await rm(cwd, { recursive: true, force: true });
5198
5569
  }
5199
5570
  });
5200
- it('shutdownTeam restores a standalone HUD pane after tearing down the team HUD', async () => {
5201
- const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-restore-hud-'));
5571
+ it('shutdownTeam preserves worker-looking panes when the team owner tag cannot be read', async () => {
5572
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-worker-owner-read-error-'));
5573
+ const teamName = 'team-worker-owner-read-error';
5202
5574
  try {
5203
5575
  await withMockTmuxFixture({
5204
- dirPrefix: 'omx-runtime-shutdown-restore-hud-bin-',
5576
+ dirPrefix: 'omx-runtime-shutdown-worker-owner-read-error-bin-',
5205
5577
  tmuxScript: (tmuxLogPath) => `#!/bin/sh
5206
5578
  set -eu
5207
- printf '%s\n' "$*" >> "${tmuxLogPath}"
5579
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5208
5580
  case "$1" in
5209
5581
  -V)
5210
5582
  echo "tmux 3.4"
5211
5583
  exit 0
5212
5584
  ;;
5213
5585
  list-panes)
5214
- exit 1
5586
+ case "$*" in
5587
+ *"-F #{pane_dead} #{pane_pid}"*)
5588
+ exit 1
5589
+ ;;
5590
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5591
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-worker-owner-read-error/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-worker-owner-read-error/worker-2 codex\\n"
5592
+ exit 0
5593
+ ;;
5594
+ *)
5595
+ exit 1
5596
+ ;;
5597
+ esac
5215
5598
  ;;
5216
5599
  split-window)
5217
- printf '%%44\n'
5600
+ printf '%%44\\n'
5218
5601
  exit 0
5219
5602
  ;;
5220
- kill-pane|kill-session|select-pane)
5603
+ show-option)
5604
+ case "$*" in
5605
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5606
+ echo "team:team-worker-owner-read-error"
5607
+ ;;
5608
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5609
+ echo "team:team-worker-owner-read-error"
5610
+ ;;
5611
+ *"-p -t %14 @omx_team_pane_owner_id"*)
5612
+ exit 2
5613
+ ;;
5614
+ *)
5615
+ exit 1
5616
+ ;;
5617
+ esac
5618
+ exit 0
5619
+ ;;
5620
+ kill-pane|resize-pane|select-pane|run-shell)
5221
5621
  exit 0
5222
5622
  ;;
5223
5623
  *)
@@ -5226,31 +5626,989 @@ case "$1" in
5226
5626
  esac
5227
5627
  `,
5228
5628
  }, async ({ tmuxLogPath }) => {
5229
- await initTeamState('team-shutdown-restore-hud', 'shutdown restore hud test', 'executor', 2, cwd);
5230
- const config = await readTeamConfig('team-shutdown-restore-hud', cwd);
5629
+ await initTeamState(teamName, 'shutdown worker owner read error test', 'executor', 2, cwd);
5630
+ const config = await readTeamConfig(teamName, cwd);
5231
5631
  assert.ok(config);
5232
5632
  if (!config)
5233
5633
  return;
5234
5634
  config.tmux_session = 'leader:0';
5235
5635
  config.leader_pane_id = '%11';
5236
5636
  config.hud_pane_id = '%12';
5237
- config.workers[0].pane_id = '%12';
5238
- config.workers[1].pane_id = '%13';
5637
+ config.workers[0].pane_id = '%13';
5638
+ config.workers[1].pane_id = '%14';
5239
5639
  await saveTeamConfig(config, cwd);
5240
- await shutdownTeam('team-shutdown-restore-hud', cwd, { force: true });
5640
+ await shutdownTeam(teamName, cwd, { force: true });
5241
5641
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5242
- assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5243
5642
  assert.match(tmuxLog, /kill-pane -t %12/);
5244
5643
  assert.match(tmuxLog, /kill-pane -t %13/);
5245
- assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\{pane_id\}`));
5246
- assert.match(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %44 -y \d+ >/);
5247
- assert.match(tmuxLog, /run-shell tmux resize-pane -t %44 -y \d+ >/);
5248
- assert.match(tmuxLog, /hud --watch/);
5249
- assert.match(tmuxLog, /OMX_TMUX_HUD_LEADER_PANE='%11'/);
5250
- assert.match(tmuxLog, /select-pane -t %11/);
5644
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %14/);
5645
+ });
5646
+ }
5647
+ finally {
5648
+ await rm(cwd, { recursive: true, force: true });
5649
+ }
5650
+ });
5651
+ it('shutdownTeam does not use a detected worker as fallback leader when the shared-session leader is stale', async () => {
5652
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-stale-leader-worker-'));
5653
+ const teamName = 'team-stale-leader-worker';
5654
+ try {
5655
+ await withMockTmuxFixture({
5656
+ dirPrefix: 'omx-runtime-shutdown-stale-leader-worker-bin-',
5657
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5658
+ set -eu
5659
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5660
+ case "$1" in
5661
+ -V)
5662
+ echo "tmux 3.4"
5663
+ exit 0
5664
+ ;;
5665
+ list-panes)
5666
+ case "$*" in
5667
+ *"-F #{pane_dead} #{pane_pid}"*)
5668
+ exit 1
5669
+ ;;
5670
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5671
+ printf "%%11\\tzsh\\tvim notes.md\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv 'OMX_TEAM_INTERNAL_WORKER=team-stale-leader-worker/worker-1' codex\\n"
5672
+ exit 0
5673
+ ;;
5674
+ *)
5675
+ exit 1
5676
+ ;;
5677
+ esac
5678
+ ;;
5679
+ show-option)
5680
+ case "$*" in
5681
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5682
+ echo "team:team-stale-leader-worker"
5683
+ ;;
5684
+ *)
5685
+ exit 1
5686
+ ;;
5687
+ esac
5688
+ exit 0
5689
+ ;;
5690
+ kill-pane|select-pane|run-shell)
5691
+ exit 0
5692
+ ;;
5693
+ *)
5694
+ exit 0
5695
+ ;;
5696
+ esac
5697
+ `,
5698
+ }, async ({ tmuxLogPath }) => {
5699
+ await initTeamState(teamName, 'shutdown stale leader worker test', 'executor', 1, cwd);
5700
+ const config = await readTeamConfig(teamName, cwd);
5701
+ assert.ok(config);
5702
+ if (!config)
5703
+ return;
5704
+ config.tmux_session = 'leader:0';
5705
+ config.leader_pane_id = '%10';
5706
+ config.hud_pane_id = '%12';
5707
+ config.workers[0].pane_id = '';
5708
+ await saveTeamConfig(config, cwd);
5709
+ await shutdownTeam(teamName, cwd, { force: true });
5710
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5711
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %10/);
5712
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5713
+ assert.match(tmuxLog, /kill-pane -t %12/);
5714
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %13/);
5715
+ assert.doesNotMatch(tmuxLog, /split-window/);
5716
+ assert.doesNotMatch(tmuxLog, /select-pane -t %11/);
5717
+ });
5718
+ }
5719
+ finally {
5720
+ await rm(cwd, { recursive: true, force: true });
5721
+ }
5722
+ });
5723
+ it('shutdownTeam does not restore HUD onto a live leader pane without matching ownership tag', async () => {
5724
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-reused-leader-pane-'));
5725
+ const teamName = 'team-reused-leader-pane';
5726
+ try {
5727
+ await withMockTmuxFixture({
5728
+ dirPrefix: 'omx-runtime-shutdown-reused-leader-pane-bin-',
5729
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5730
+ set -eu
5731
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5732
+ case "$1" in
5733
+ -V)
5734
+ echo "tmux 3.4"
5735
+ exit 0
5736
+ ;;
5737
+ list-panes)
5738
+ case "$*" in
5739
+ *"-F #{pane_dead} #{pane_pid}"*)
5740
+ exit 1
5741
+ ;;
5742
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5743
+ printf "%%11\\tzsh\\tvim unrelated-notes.md\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv 'OMX_TEAM_INTERNAL_WORKER=team-reused-leader-pane/worker-1' codex\\n"
5744
+ exit 0
5745
+ ;;
5746
+ *)
5747
+ exit 1
5748
+ ;;
5749
+ esac
5750
+ ;;
5751
+ show-option)
5752
+ case "$*" in
5753
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5754
+ echo "team:other-team"
5755
+ ;;
5756
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5757
+ echo "team:other-team"
5758
+ ;;
5759
+ *"-p -t %13 @omx_team_pane_owner_id"*)
5760
+ echo "team:team-reused-leader-pane"
5761
+ ;;
5762
+ *)
5763
+ exit 1
5764
+ ;;
5765
+ esac
5766
+ exit 0
5767
+ ;;
5768
+ kill-pane|select-pane|run-shell)
5769
+ exit 0
5770
+ ;;
5771
+ *)
5772
+ exit 0
5773
+ ;;
5774
+ esac
5775
+ `,
5776
+ env: { OMX_SESSION_ID: 'expected-team-session' },
5777
+ }, async ({ tmuxLogPath }) => {
5778
+ await initTeamState(teamName, 'shutdown reused leader pane test', 'executor', 1, cwd);
5779
+ const config = await readTeamConfig(teamName, cwd);
5780
+ assert.ok(config);
5781
+ if (!config)
5782
+ return;
5783
+ config.tmux_session = 'leader:0';
5784
+ config.leader_pane_id = '%11';
5785
+ config.hud_pane_id = '%12';
5786
+ config.workers[0].pane_id = '';
5787
+ await saveTeamConfig(config, cwd);
5788
+ await shutdownTeam(teamName, cwd, { force: true });
5789
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5790
+ assert.match(tmuxLog, /show-option -qv -p -t %11 @omx_team_pane_owner_id/);
5791
+ assert.match(tmuxLog, /show-option -qv -p -t %12 @omx_team_pane_owner_id/);
5792
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5793
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %12/);
5794
+ assert.match(tmuxLog, /kill-pane -t %13/);
5795
+ assert.doesNotMatch(tmuxLog, /split-window/);
5796
+ assert.doesNotMatch(tmuxLog, /select-pane -t %11/);
5797
+ });
5798
+ }
5799
+ finally {
5800
+ await rm(cwd, { recursive: true, force: true });
5801
+ }
5802
+ });
5803
+ it('shutdownTeam skips prekill and keeps the leader pane alive on native Windows split-pane shutdown', async () => {
5804
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-win32-split-'));
5805
+ try {
5806
+ await withNativeWindowsPlatform(async () => {
5807
+ await withMockTmuxFixture({
5808
+ dirPrefix: 'omx-runtime-shutdown-win32-split-bin-',
5809
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5810
+ set -eu
5811
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5812
+ case "$1" in
5813
+ -V)
5814
+ echo "tmux 3.4"
5815
+ exit 0
5816
+ ;;
5817
+ list-panes)
5818
+ case "$*" in
5819
+ *"-F #{pane_dead} #{pane_pid}"*)
5820
+ exit 1
5821
+ ;;
5822
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5823
+ printf "%%11\\tpwsh\\tpwsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-win32-split/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-win32-split/worker-2 codex\\n"
5824
+ exit 0
5825
+ ;;
5826
+ *)
5827
+ exit 1
5828
+ ;;
5829
+ esac
5830
+ ;;
5831
+ split-window)
5832
+ printf '%%44\\n'
5833
+ exit 0
5834
+ ;;
5835
+ show-option)
5836
+ case "$*" in
5837
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5838
+ echo "team:team-shutdown-win32-split"
5839
+ ;;
5840
+ *"-p -t %12 @omx_team_pane_owner_id"*)
5841
+ echo "team:team-shutdown-win32-split"
5842
+ ;;
5843
+ *)
5844
+ exit 1
5845
+ ;;
5846
+ esac
5847
+ exit 0
5848
+ ;;
5849
+ kill-pane|resize-pane|select-pane)
5850
+ exit 0
5851
+ ;;
5852
+ *)
5853
+ exit 0
5854
+ ;;
5855
+ esac
5856
+ `,
5857
+ env: { OMX_SESSION_ID: 'team-shutdown-win32-split-session' },
5858
+ }, async ({ tmuxLogPath }) => {
5859
+ await initTeamState('team-shutdown-win32-split', 'shutdown win32 split test', 'executor', 2, cwd);
5860
+ const config = await readTeamConfig('team-shutdown-win32-split', cwd);
5861
+ assert.ok(config);
5862
+ if (!config)
5863
+ return;
5864
+ config.tmux_session = 'leader:0';
5865
+ config.leader_pane_id = '%11';
5866
+ config.hud_pane_id = '%12';
5867
+ config.workers[0].pane_id = '%13';
5868
+ config.workers[1].pane_id = '%14';
5869
+ await saveTeamConfig(config, cwd);
5870
+ await shutdownTeam('team-shutdown-win32-split', cwd, { force: true });
5871
+ const teamRoot = join(cwd, '.omx', 'state', 'team', 'team-shutdown-win32-split');
5872
+ assert.equal(existsSync(teamRoot), false);
5873
+ assert.equal(await readMonitorSnapshot('team-shutdown-win32-split', cwd), null);
5874
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5875
+ assert.doesNotMatch(tmuxLog, /list-panes -t %13 -F #\{pane_pid\}/);
5876
+ assert.doesNotMatch(tmuxLog, /list-panes -t %14 -F #\{pane_pid\}/);
5877
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5878
+ assert.doesNotMatch(tmuxLog, /kill-session -t leader:0/);
5879
+ assert.match(tmuxLog, /kill-pane -t %12/);
5880
+ assert.match(tmuxLog, /kill-pane -t %13/);
5881
+ assert.match(tmuxLog, /kill-pane -t %14/);
5882
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
5883
+ assert.match(tmuxLog, new RegExp(`resize-pane -t %44 -y ${HUD_TMUX_TEAM_HEIGHT_LINES}`));
5884
+ assert.match(tmuxLog, /select-pane -t %11/);
5885
+ });
5886
+ });
5887
+ }
5888
+ finally {
5889
+ await rm(cwd, { recursive: true, force: true });
5890
+ }
5891
+ });
5892
+ it('shutdownTeam preserves an unrelated HUD when the leader is live but persisted shared-session HUD is stale', async () => {
5893
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-win32-stale-topology-'));
5894
+ const teamName = 'team-win32-stale-topo';
5895
+ try {
5896
+ await withNativeWindowsPlatform(async () => {
5897
+ await withMockTmuxFixture({
5898
+ dirPrefix: 'omx-runtime-shutdown-win32-stale-topology-bin-',
5899
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5900
+ set -eu
5901
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5902
+ case "$1" in
5903
+ -V)
5904
+ echo "tmux 3.4"
5905
+ exit 0
5906
+ ;;
5907
+ list-panes)
5908
+ case "$*" in
5909
+ *"-F #{pane_dead} #{pane_pid}"*)
5910
+ exit 1
5911
+ ;;
5912
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5913
+ printf "%%11\\tpwsh\\tpwsh\\n%%22\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%23\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-win32-stale-topo/worker-1 codex\\n%%24\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-win32-stale-topo/worker-2 codex\\n"
5914
+ exit 0
5915
+ ;;
5916
+ *)
5917
+ exit 1
5918
+ ;;
5919
+ esac
5920
+ ;;
5921
+ split-window)
5922
+ printf '%%44\\n'
5923
+ exit 0
5924
+ ;;
5925
+ show-option)
5926
+ case "$*" in
5927
+ *"-p -t %11 @omx_team_pane_owner_id"*)
5928
+ echo "team:team-win32-stale-topo"
5929
+ ;;
5930
+ *)
5931
+ exit 1
5932
+ ;;
5933
+ esac
5934
+ exit 0
5935
+ ;;
5936
+ kill-pane|resize-pane|select-pane)
5937
+ exit 0
5938
+ ;;
5939
+ *)
5940
+ exit 0
5941
+ ;;
5942
+ esac
5943
+ `,
5944
+ }, async ({ tmuxLogPath }) => {
5945
+ await initTeamState(teamName, 'shutdown win32 stale topology test', 'executor', 2, cwd);
5946
+ const config = await readTeamConfig(teamName, cwd);
5947
+ assert.ok(config);
5948
+ if (!config)
5949
+ return;
5950
+ config.tmux_session = 'leader:0';
5951
+ config.leader_pane_id = '%11';
5952
+ config.hud_pane_id = '%12';
5953
+ config.workers[0].pane_id = '%23';
5954
+ config.workers[1].pane_id = '%24';
5955
+ await saveTeamConfig(config, cwd);
5956
+ await shutdownTeam(teamName, cwd, { force: true });
5957
+ const teamRoot = join(cwd, '.omx', 'state', 'team', teamName);
5958
+ assert.equal(existsSync(teamRoot), false);
5959
+ assert.equal(await readMonitorSnapshot(teamName, cwd), null);
5960
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
5961
+ assert.doesNotMatch(tmuxLog, /list-panes -t %11 -F #\{pane_pid\}/);
5962
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
5963
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %22/);
5964
+ assert.match(tmuxLog, /kill-pane -t %23/);
5965
+ assert.match(tmuxLog, /kill-pane -t %24/);
5966
+ assert.doesNotMatch(tmuxLog, /split-window/);
5967
+ assert.doesNotMatch(tmuxLog, /select-pane -t %11/);
5968
+ });
5969
+ });
5970
+ }
5971
+ finally {
5972
+ await rm(cwd, { recursive: true, force: true });
5973
+ }
5974
+ });
5975
+ it('shutdownTeam skips prekill and keeps the leader pane alive on shared-session shutdown', async () => {
5976
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-shared-session-'));
5977
+ try {
5978
+ await withMockTmuxFixture({
5979
+ dirPrefix: 'omx-runtime-shutdown-shared-session-bin-',
5980
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
5981
+ set -eu
5982
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
5983
+ case "$1" in
5984
+ -V)
5985
+ echo "tmux 3.4"
5986
+ exit 0
5987
+ ;;
5988
+ list-panes)
5989
+ case "$*" in
5990
+ *"-F #{pane_dead} #{pane_pid}"*)
5991
+ exit 1
5992
+ ;;
5993
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
5994
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\tnode /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-shared-session/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-shared-session/worker-2 codex\\n"
5995
+ exit 0
5996
+ ;;
5997
+ *)
5998
+ exit 1
5999
+ ;;
6000
+ esac
6001
+ ;;
6002
+ split-window)
6003
+ printf '%%44\\n'
6004
+ exit 0
6005
+ ;;
6006
+ show-option)
6007
+ case "$*" in
6008
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6009
+ echo "team:team-shutdown-shared-session"
6010
+ ;;
6011
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6012
+ echo "team:team-shutdown-shared-session"
6013
+ ;;
6014
+ *)
6015
+ exit 1
6016
+ ;;
6017
+ esac
6018
+ exit 0
6019
+ ;;
6020
+ kill-pane|resize-pane|select-pane)
6021
+ exit 0
6022
+ ;;
6023
+ *)
6024
+ exit 0
6025
+ ;;
6026
+ esac
6027
+ `,
6028
+ env: { OMX_SESSION_ID: 'team-shutdown-shared-session-owner' },
6029
+ }, async ({ tmuxLogPath }) => {
6030
+ await initTeamState('team-shutdown-shared-session', 'shutdown shared session test', 'executor', 2, cwd);
6031
+ const config = await readTeamConfig('team-shutdown-shared-session', cwd);
6032
+ assert.ok(config);
6033
+ if (!config)
6034
+ return;
6035
+ config.tmux_session = 'leader:0';
6036
+ config.leader_pane_id = '%11';
6037
+ config.hud_pane_id = '%12';
6038
+ config.workers[0].pane_id = '%13';
6039
+ config.workers[1].pane_id = '%14';
6040
+ await saveTeamConfig(config, cwd);
6041
+ await shutdownTeam('team-shutdown-shared-session', cwd, { force: true });
6042
+ const teamRoot = join(cwd, '.omx', 'state', 'team', 'team-shutdown-shared-session');
6043
+ assert.equal(existsSync(teamRoot), false);
6044
+ assert.equal(await readMonitorSnapshot('team-shutdown-shared-session', cwd), null);
6045
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6046
+ assert.doesNotMatch(tmuxLog, /list-panes -t %13 -F #\{pane_pid\}/);
6047
+ assert.doesNotMatch(tmuxLog, /list-panes -t %14 -F #\{pane_pid\}/);
6048
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
6049
+ assert.doesNotMatch(tmuxLog, /kill-session -t leader:0/);
6050
+ assert.match(tmuxLog, /kill-pane -t %12/);
6051
+ assert.match(tmuxLog, /kill-pane -t %13/);
6052
+ assert.match(tmuxLog, /kill-pane -t %14/);
6053
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
6054
+ assert.match(tmuxLog, /select-pane -t %11/);
6055
+ });
6056
+ }
6057
+ finally {
6058
+ await rm(cwd, { recursive: true, force: true });
6059
+ }
6060
+ });
6061
+ it('shutdownTeam restores a standalone HUD pane after tearing down the team HUD', async () => {
6062
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-restore-hud-'));
6063
+ const leaderPaneCwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-restore-hud-leader-cwd-'));
6064
+ try {
6065
+ await withMockTmuxFixture({
6066
+ dirPrefix: 'omx-runtime-shutdown-restore-hud-bin-',
6067
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6068
+ set -eu
6069
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
6070
+ case "$1" in
6071
+ -V)
6072
+ echo "tmux 3.4"
6073
+ exit 0
6074
+ ;;
6075
+ display-message)
6076
+ case "$*" in
6077
+ *"#{pane_current_path}"*)
6078
+ echo "${leaderPaneCwd}"
6079
+ ;;
6080
+ esac
6081
+ exit 0
6082
+ ;;
6083
+ list-panes)
6084
+ case "$*" in
6085
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6086
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-shutdown-restore-hud/worker-2 codex\\n"
6087
+ exit 0
6088
+ ;;
6089
+ *)
6090
+ exit 1
6091
+ ;;
6092
+ esac
6093
+ ;;
6094
+ split-window)
6095
+ printf '%%44\n'
6096
+ exit 0
6097
+ ;;
6098
+ show-option)
6099
+ case "$*" in
6100
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6101
+ echo "team:team-shutdown-restore-hud"
6102
+ ;;
6103
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6104
+ echo "team:team-shutdown-restore-hud"
6105
+ ;;
6106
+ *)
6107
+ exit 1
6108
+ ;;
6109
+ esac
6110
+ exit 0
6111
+ ;;
6112
+ kill-pane|kill-session|select-pane)
6113
+ exit 0
6114
+ ;;
6115
+ *)
6116
+ exit 0
6117
+ ;;
6118
+ esac
6119
+ `,
6120
+ env: { OMX_SESSION_ID: 'team-shutdown-restore-hud-session' },
6121
+ }, async ({ tmuxLogPath }) => {
6122
+ await initTeamState('team-shutdown-restore-hud', 'shutdown restore hud test', 'executor', 2, cwd);
6123
+ const config = await readTeamConfig('team-shutdown-restore-hud', cwd);
6124
+ assert.ok(config);
6125
+ if (!config)
6126
+ return;
6127
+ config.tmux_session = 'leader:0';
6128
+ config.leader_pane_id = '%11';
6129
+ config.hud_pane_id = '%12';
6130
+ config.workers[0].pane_id = '%12';
6131
+ config.workers[1].pane_id = '%13';
6132
+ await saveTeamConfig(config, cwd);
6133
+ await shutdownTeam('team-shutdown-restore-hud', cwd, { force: true });
6134
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6135
+ const count = (pattern) => [...tmuxLog.matchAll(pattern)].length;
6136
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
6137
+ assert.match(tmuxLog, /kill-pane -t %12/);
6138
+ assert.match(tmuxLog, /kill-pane -t %13/);
6139
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\{pane_id\}`));
6140
+ assert.equal(count(/kill-pane -t %12/g), 1);
6141
+ assert.equal(count(new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`, 'g')), 1);
6142
+ assert.match(tmuxLog, /run-shell -b sleep \d+; tmux resize-pane -t %44 -y \d+ >/);
6143
+ assert.match(tmuxLog, /run-shell tmux resize-pane -t %44 -y \d+ >/);
6144
+ assert.match(tmuxLog, /hud --watch/);
6145
+ assert.match(tmuxLog, /OMX_TMUX_HUD_LEADER_PANE='%11'/);
6146
+ assert.match(tmuxLog, /OMX_SESSION_ID='team-shutdown-restore-hud-session'/);
6147
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\} -c ${escapeRegExp(leaderPaneCwd)} `));
6148
+ assert.doesNotMatch(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\} -c ${escapeRegExp(cwd)} `));
6149
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %44/);
6150
+ assert.match(tmuxLog, /select-pane -t %11/);
6151
+ });
6152
+ }
6153
+ finally {
6154
+ await rm(cwd, { recursive: true, force: true });
6155
+ await rm(leaderPaneCwd, { recursive: true, force: true });
6156
+ }
6157
+ });
6158
+ it('shutdownTeam preserves unpersisted legacy worker-looking panes without owner tags', async () => {
6159
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-unpersisted-legacy-worker-'));
6160
+ const teamName = 'team-unpersisted-legacy-worker';
6161
+ try {
6162
+ await withMockTmuxFixture({
6163
+ dirPrefix: 'omx-runtime-shutdown-unpersisted-legacy-worker-bin-',
6164
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6165
+ set -eu
6166
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
6167
+ case "$1" in
6168
+ -V)
6169
+ echo "tmux 3.4"
6170
+ exit 0
6171
+ ;;
6172
+ display-message)
6173
+ case "$*" in
6174
+ *"#{pane_current_path}"*)
6175
+ echo "${cwd}"
6176
+ ;;
6177
+ esac
6178
+ exit 0
6179
+ ;;
6180
+ list-panes)
6181
+ case "$*" in
6182
+ *"-F #{pane_dead} #{pane_pid}"*)
6183
+ exit 1
6184
+ ;;
6185
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6186
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-unpersisted-legacy-worker/worker-1 codex\\n%%14\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-unpersisted-legacy-worker/worker-2 codex\\n"
6187
+ exit 0
6188
+ ;;
6189
+ *"-t %11 -F #{pane_id}"*"#{pane_current_command}"*)
6190
+ printf "%%11\\tzsh\\tzsh\\n"
6191
+ exit 0
6192
+ ;;
6193
+ *)
6194
+ exit 1
6195
+ ;;
6196
+ esac
6197
+ ;;
6198
+ split-window)
6199
+ printf '%%44\\n'
6200
+ exit 0
6201
+ ;;
6202
+ show-option)
6203
+ case "$*" in
6204
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6205
+ echo "team:team-unpersisted-legacy-worker"
6206
+ ;;
6207
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6208
+ echo "team:team-unpersisted-legacy-worker"
6209
+ ;;
6210
+ *)
6211
+ exit 1
6212
+ ;;
6213
+ esac
6214
+ exit 0
6215
+ ;;
6216
+ kill-pane|resize-pane|select-pane|run-shell)
6217
+ exit 0
6218
+ ;;
6219
+ *)
6220
+ exit 0
6221
+ ;;
6222
+ esac
6223
+ `,
6224
+ }, async ({ tmuxLogPath }) => {
6225
+ await initTeamState(teamName, 'shutdown unpersisted legacy worker test', 'executor', 2, cwd);
6226
+ const config = await readTeamConfig(teamName, cwd);
6227
+ assert.ok(config);
6228
+ if (!config)
6229
+ return;
6230
+ config.tmux_session = 'leader:0';
6231
+ config.leader_pane_id = '%11';
6232
+ config.hud_pane_id = '%12';
6233
+ config.workers[0].pane_id = '%13';
6234
+ config.workers[1].pane_id = '%99';
6235
+ await saveTeamConfig(config, cwd);
6236
+ await shutdownTeam(teamName, cwd, { force: true });
6237
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6238
+ assert.match(tmuxLog, /kill-pane -t %12/);
6239
+ assert.match(tmuxLog, /kill-pane -t %13/);
6240
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %14/);
6241
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %99/);
6242
+ });
6243
+ }
6244
+ finally {
6245
+ await rm(cwd, { recursive: true, force: true });
6246
+ }
6247
+ });
6248
+ it('shutdownTeam reclaims a matching-owner live HUD when the persisted HUD id is stale', async () => {
6249
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-stale-hud-live-owner-'));
6250
+ const teamName = 'team-stale-hud-live-owner';
6251
+ try {
6252
+ await withMockTmuxFixture({
6253
+ dirPrefix: 'omx-runtime-shutdown-stale-hud-live-owner-bin-',
6254
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6255
+ set -eu
6256
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
6257
+ case "$1" in
6258
+ -V)
6259
+ echo "tmux 3.4"
6260
+ exit 0
6261
+ ;;
6262
+ display-message)
6263
+ case "$*" in
6264
+ *"#{pane_current_path}"*)
6265
+ echo "${cwd}"
6266
+ ;;
6267
+ esac
6268
+ exit 0
6269
+ ;;
6270
+ list-panes)
6271
+ case "$*" in
6272
+ *"-F #{pane_dead} #{pane_pid}"*)
6273
+ exit 1
6274
+ ;;
6275
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6276
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-stale-hud-live-owner/worker-1 codex\\n"
6277
+ exit 0
6278
+ ;;
6279
+ *"-t %11 -F #{pane_id}"*"#{pane_current_command}"*)
6280
+ printf "%%11\\tzsh\\tzsh\\n"
6281
+ exit 0
6282
+ ;;
6283
+ *)
6284
+ exit 1
6285
+ ;;
6286
+ esac
6287
+ ;;
6288
+ split-window)
6289
+ printf '%%44\\n'
6290
+ exit 0
6291
+ ;;
6292
+ show-option)
6293
+ case "$*" in
6294
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6295
+ echo "team:team-stale-hud-live-owner"
6296
+ ;;
6297
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6298
+ echo "team:team-stale-hud-live-owner"
6299
+ ;;
6300
+ *)
6301
+ exit 1
6302
+ ;;
6303
+ esac
6304
+ exit 0
6305
+ ;;
6306
+ kill-pane|resize-pane|select-pane|run-shell)
6307
+ exit 0
6308
+ ;;
6309
+ *)
6310
+ exit 0
6311
+ ;;
6312
+ esac
6313
+ `,
6314
+ }, async ({ tmuxLogPath }) => {
6315
+ await initTeamState(teamName, 'shutdown stale hud live owner test', 'executor', 1, cwd);
6316
+ const config = await readTeamConfig(teamName, cwd);
6317
+ assert.ok(config);
6318
+ if (!config)
6319
+ return;
6320
+ config.tmux_session = 'leader:0';
6321
+ config.leader_pane_id = '%11';
6322
+ config.hud_pane_id = '%99';
6323
+ config.workers[0].pane_id = '%13';
6324
+ await saveTeamConfig(config, cwd);
6325
+ await shutdownTeam(teamName, cwd, { force: true });
6326
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6327
+ const count = (pattern) => [...tmuxLog.matchAll(pattern)].length;
6328
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %11/);
6329
+ assert.match(tmuxLog, /kill-pane -t %12/);
6330
+ assert.match(tmuxLog, /kill-pane -t %13/);
6331
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %99/);
6332
+ assert.equal(count(/kill-pane -t %12/g), 1);
6333
+ assert.equal(count(new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`, 'g')), 1);
6334
+ assert.match(tmuxLog, /select-pane -t %11/);
6335
+ });
6336
+ }
6337
+ finally {
6338
+ await rm(cwd, { recursive: true, force: true });
6339
+ }
6340
+ });
6341
+ it('shutdownTeam reclaims a legacy leader-owned HUD pane without a team owner tag', async () => {
6342
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-legacy-hud-owner-'));
6343
+ const teamName = 'team-legacy-hud-owner';
6344
+ try {
6345
+ await withMockTmuxFixture({
6346
+ dirPrefix: 'omx-runtime-shutdown-legacy-hud-owner-bin-',
6347
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6348
+ set -eu
6349
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
6350
+ case "$1" in
6351
+ -V)
6352
+ echo "tmux 3.4"
6353
+ exit 0
6354
+ ;;
6355
+ display-message)
6356
+ case "$*" in
6357
+ *"#{pane_current_path}"*)
6358
+ echo "${cwd}"
6359
+ ;;
6360
+ esac
6361
+ exit 0
6362
+ ;;
6363
+ list-panes)
6364
+ case "$*" in
6365
+ *"-F #{pane_dead} #{pane_pid}"*)
6366
+ exit 1
6367
+ ;;
6368
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6369
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-legacy-hud-owner/worker-1 codex\\n"
6370
+ exit 0
6371
+ ;;
6372
+ *"-t %11 -F #{pane_id}"*"#{pane_current_command}"*)
6373
+ printf "%%11\\tzsh\\tzsh\\n"
6374
+ exit 0
6375
+ ;;
6376
+ *)
6377
+ exit 1
6378
+ ;;
6379
+ esac
6380
+ ;;
6381
+ split-window)
6382
+ printf '%%44\\n'
6383
+ exit 0
6384
+ ;;
6385
+ show-option)
6386
+ case "$*" in
6387
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6388
+ echo "team:team-legacy-hud-owner"
6389
+ ;;
6390
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6391
+ exit 1
6392
+ ;;
6393
+ *)
6394
+ exit 1
6395
+ ;;
6396
+ esac
6397
+ exit 0
6398
+ ;;
6399
+ kill-pane|resize-pane|select-pane|run-shell)
6400
+ exit 0
6401
+ ;;
6402
+ *)
6403
+ exit 0
6404
+ ;;
6405
+ esac
6406
+ `,
6407
+ }, async ({ tmuxLogPath }) => {
6408
+ await initTeamState(teamName, 'shutdown legacy hud owner test', 'executor', 1, cwd);
6409
+ const config = await readTeamConfig(teamName, cwd);
6410
+ assert.ok(config);
6411
+ if (!config)
6412
+ return;
6413
+ config.tmux_session = 'leader:0';
6414
+ config.leader_pane_id = '%11';
6415
+ config.hud_pane_id = '%12';
6416
+ config.workers[0].pane_id = '%13';
6417
+ await saveTeamConfig(config, cwd);
6418
+ await shutdownTeam(teamName, cwd, { force: true });
6419
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6420
+ assert.match(tmuxLog, /kill-pane -t %12/);
6421
+ assert.match(tmuxLog, /kill-pane -t %13/);
6422
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
6423
+ assert.match(tmuxLog, /select-pane -t %11/);
6424
+ });
6425
+ }
6426
+ finally {
6427
+ await rm(cwd, { recursive: true, force: true });
6428
+ }
6429
+ });
6430
+ it('shutdownTeam restores standalone HUD for legacy leaders with only an instance tag', async () => {
6431
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-legacy-leader-instance-'));
6432
+ const teamName = 'team-legacy-leader-instance';
6433
+ const legacySessionId = 'legacy-leader-session';
6434
+ try {
6435
+ await withMockTmuxFixture({
6436
+ dirPrefix: 'omx-runtime-shutdown-legacy-leader-instance-bin-',
6437
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6438
+ set -eu
6439
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
6440
+ case "$1" in
6441
+ -V)
6442
+ echo "tmux 3.4"
6443
+ exit 0
6444
+ ;;
6445
+ display-message)
6446
+ case "$*" in
6447
+ *"#{pane_current_path}"*)
6448
+ echo "${cwd}"
6449
+ ;;
6450
+ esac
6451
+ exit 0
6452
+ ;;
6453
+ list-panes)
6454
+ case "$*" in
6455
+ *"-F #{pane_dead} #{pane_pid}"*)
6456
+ exit 1
6457
+ ;;
6458
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6459
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-legacy-leader-instance/worker-1 codex\\n"
6460
+ exit 0
6461
+ ;;
6462
+ *"-t %11 -F #{pane_id}"*"#{pane_current_command}"*)
6463
+ printf "%%11\\tzsh\\tzsh\\n"
6464
+ exit 0
6465
+ ;;
6466
+ *)
6467
+ exit 1
6468
+ ;;
6469
+ esac
6470
+ ;;
6471
+ split-window)
6472
+ printf '%%44\\n'
6473
+ exit 0
6474
+ ;;
6475
+ show-option)
6476
+ case "$*" in
6477
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6478
+ exit 1
6479
+ ;;
6480
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6481
+ exit 1
6482
+ ;;
6483
+ *"-p -t %13 @omx_team_pane_owner_id"*)
6484
+ echo "team:team-legacy-leader-instance"
6485
+ ;;
6486
+ *"-p -t %11 @omx_pane_instance_id"*)
6487
+ echo "${legacySessionId}"
6488
+ ;;
6489
+ *)
6490
+ exit 1
6491
+ ;;
6492
+ esac
6493
+ exit 0
6494
+ ;;
6495
+ kill-pane|resize-pane|select-pane|run-shell)
6496
+ exit 0
6497
+ ;;
6498
+ *)
6499
+ exit 0
6500
+ ;;
6501
+ esac
6502
+ `,
6503
+ env: { OMX_SESSION_ID: legacySessionId },
6504
+ }, async ({ tmuxLogPath }) => {
6505
+ await initTeamState(teamName, 'shutdown legacy leader instance test', 'executor', 1, cwd);
6506
+ const config = await readTeamConfig(teamName, cwd);
6507
+ assert.ok(config);
6508
+ if (!config)
6509
+ return;
6510
+ config.tmux_session = 'leader:0';
6511
+ config.leader_pane_id = '%11';
6512
+ config.hud_pane_id = '%12';
6513
+ config.workers[0].pane_id = '%13';
6514
+ await saveTeamConfig(config, cwd);
6515
+ await shutdownTeam(teamName, cwd, { force: true });
6516
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6517
+ assert.match(tmuxLog, /show-option -qv -p -t %11 @omx_team_pane_owner_id/);
6518
+ assert.match(tmuxLog, /show-option -qv -p -t %11 @omx_pane_instance_id/);
6519
+ assert.match(tmuxLog, /kill-pane -t %12/);
6520
+ assert.match(tmuxLog, /kill-pane -t %13/);
6521
+ assert.match(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
6522
+ assert.match(tmuxLog, /select-pane -t %11/);
6523
+ });
6524
+ }
6525
+ finally {
6526
+ await rm(cwd, { recursive: true, force: true });
6527
+ }
6528
+ });
6529
+ it('shutdownTeam preserves a persisted HUD pane when the team owner tag cannot be read', async () => {
6530
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-shutdown-hud-owner-read-error-'));
6531
+ const teamName = 'team-hud-owner-read-error';
6532
+ const originalWarn = console.warn;
6533
+ const warnings = [];
6534
+ try {
6535
+ console.warn = (message, ...optionalParams) => {
6536
+ warnings.push([message, ...optionalParams].map(String).join(' '));
6537
+ };
6538
+ await withMockTmuxFixture({
6539
+ dirPrefix: 'omx-runtime-shutdown-hud-owner-read-error-bin-',
6540
+ tmuxScript: (tmuxLogPath) => `#!/bin/sh
6541
+ set -eu
6542
+ printf '%s\\n' "$*" >> "${tmuxLogPath}"
6543
+ case "$1" in
6544
+ -V)
6545
+ echo "tmux 3.4"
6546
+ exit 0
6547
+ ;;
6548
+ list-panes)
6549
+ case "$*" in
6550
+ *"-F #{pane_dead} #{pane_pid}"*)
6551
+ exit 1
6552
+ ;;
6553
+ *"-t leader:0 -F #{pane_id}"*"#{pane_current_command}"*)
6554
+ printf "%%11\\tzsh\\tzsh\\n%%12\\tnode\\texec env OMX_TMUX_HUD_OWNER=1 OMX_TMUX_HUD_LEADER_PANE='%%11' node /tmp/bin/omx.js hud --watch\\n%%13\\tcodex\\tenv OMX_TEAM_INTERNAL_WORKER=team-hud-owner-read-error/worker-1 codex\\n"
6555
+ exit 0
6556
+ ;;
6557
+ *)
6558
+ exit 1
6559
+ ;;
6560
+ esac
6561
+ ;;
6562
+ split-window)
6563
+ printf '%%44\\n'
6564
+ exit 0
6565
+ ;;
6566
+ show-option)
6567
+ case "$*" in
6568
+ *"-p -t %11 @omx_team_pane_owner_id"*)
6569
+ echo "team:team-hud-owner-read-error"
6570
+ ;;
6571
+ *"-p -t %12 @omx_team_pane_owner_id"*)
6572
+ exit 2
6573
+ ;;
6574
+ *"-p -t %13 @omx_team_pane_owner_id"*)
6575
+ echo "team:team-hud-owner-read-error"
6576
+ ;;
6577
+ *)
6578
+ exit 1
6579
+ ;;
6580
+ esac
6581
+ exit 0
6582
+ ;;
6583
+ kill-pane|resize-pane|select-pane|run-shell)
6584
+ exit 0
6585
+ ;;
6586
+ *)
6587
+ exit 0
6588
+ ;;
6589
+ esac
6590
+ `,
6591
+ }, async ({ tmuxLogPath }) => {
6592
+ await initTeamState(teamName, 'shutdown hud owner read error test', 'executor', 1, cwd);
6593
+ const config = await readTeamConfig(teamName, cwd);
6594
+ assert.ok(config);
6595
+ if (!config)
6596
+ return;
6597
+ config.tmux_session = 'leader:0';
6598
+ config.leader_pane_id = '%11';
6599
+ config.hud_pane_id = '%12';
6600
+ config.workers[0].pane_id = '%13';
6601
+ await saveTeamConfig(config, cwd);
6602
+ await shutdownTeam(teamName, cwd, { force: true });
6603
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
6604
+ assert.doesNotMatch(tmuxLog, /kill-pane -t %12/);
6605
+ assert.match(tmuxLog, /kill-pane -t %13/);
6606
+ assert.doesNotMatch(tmuxLog, new RegExp(`split-window -v -l ${HUD_TMUX_TEAM_HEIGHT_LINES} -t %11 -d -P -F #\\{pane_id\\}`));
6607
+ assert.match(warnings.join('\n'), /skipped shared-session HUD pane %12 because team owner tag could not be read: tmux show-option exited 2/);
5251
6608
  });
5252
6609
  }
5253
6610
  finally {
6611
+ console.warn = originalWarn;
5254
6612
  await rm(cwd, { recursive: true, force: true });
5255
6613
  }
5256
6614
  });