oh-my-codex 0.16.2 → 0.16.3

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 (149) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/dist/cli/__tests__/doctor-warning-copy.test.js +36 -0
  4. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  5. package/dist/cli/__tests__/index.test.js +173 -3
  6. package/dist/cli/__tests__/index.test.js.map +1 -1
  7. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  8. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  9. package/dist/cli/__tests__/ralph.test.js +1 -0
  10. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  11. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -0
  12. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  13. package/dist/cli/__tests__/setup-install-mode.test.js +79 -2
  14. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  15. package/dist/cli/__tests__/team.test.js +161 -0
  16. package/dist/cli/__tests__/team.test.js.map +1 -1
  17. package/dist/cli/__tests__/uninstall.test.js +39 -1
  18. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  19. package/dist/cli/__tests__/update.test.js +109 -19
  20. package/dist/cli/__tests__/update.test.js.map +1 -1
  21. package/dist/cli/doctor.d.ts.map +1 -1
  22. package/dist/cli/doctor.js +17 -0
  23. package/dist/cli/doctor.js.map +1 -1
  24. package/dist/cli/index.d.ts.map +1 -1
  25. package/dist/cli/index.js +34 -4
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/cli/setup.d.ts.map +1 -1
  28. package/dist/cli/setup.js +68 -4
  29. package/dist/cli/setup.js.map +1 -1
  30. package/dist/cli/team.d.ts.map +1 -1
  31. package/dist/cli/team.js +54 -15
  32. package/dist/cli/team.js.map +1 -1
  33. package/dist/cli/uninstall.d.ts.map +1 -1
  34. package/dist/cli/uninstall.js +67 -5
  35. package/dist/cli/uninstall.js.map +1 -1
  36. package/dist/cli/update.d.ts +10 -2
  37. package/dist/cli/update.d.ts.map +1 -1
  38. package/dist/cli/update.js +99 -5
  39. package/dist/cli/update.js.map +1 -1
  40. package/dist/config/__tests__/codex-hooks.test.js +181 -4
  41. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  42. package/dist/config/__tests__/generator-idempotent.test.js +59 -1
  43. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  44. package/dist/config/__tests__/generator-notify.test.js +39 -3
  45. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  46. package/dist/config/codex-hooks.d.ts +38 -4
  47. package/dist/config/codex-hooks.d.ts.map +1 -1
  48. package/dist/config/codex-hooks.js +181 -17
  49. package/dist/config/codex-hooks.js.map +1 -1
  50. package/dist/config/generator.d.ts +9 -0
  51. package/dist/config/generator.d.ts.map +1 -1
  52. package/dist/config/generator.js +176 -38
  53. package/dist/config/generator.js.map +1 -1
  54. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +29 -1
  55. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  56. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +10 -0
  57. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  58. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +1 -0
  59. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  60. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts +2 -0
  61. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts.map +1 -0
  62. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +52 -0
  63. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -0
  64. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +148 -0
  65. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  66. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +3 -0
  67. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  68. package/dist/hooks/__tests__/wiki-docs-contract.test.js +1 -2
  69. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -1
  70. package/dist/planning/__tests__/artifacts.test.js +64 -0
  71. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  72. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +2 -0
  73. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +1 -0
  74. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +90 -0
  75. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -0
  76. package/dist/planning/artifacts.d.ts +7 -2
  77. package/dist/planning/artifacts.d.ts.map +1 -1
  78. package/dist/planning/artifacts.js +62 -8
  79. package/dist/planning/artifacts.js.map +1 -1
  80. package/dist/planning/context-pack-status.d.ts +6 -0
  81. package/dist/planning/context-pack-status.d.ts.map +1 -1
  82. package/dist/planning/context-pack-status.js +25 -0
  83. package/dist/planning/context-pack-status.js.map +1 -1
  84. package/dist/ralph/persistence.d.ts +1 -1
  85. package/dist/ralph/persistence.d.ts.map +1 -1
  86. package/dist/ralph/persistence.js +8 -2
  87. package/dist/ralph/persistence.js.map +1 -1
  88. package/dist/scripts/__tests__/codex-native-hook.test.js +139 -11
  89. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  90. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  91. package/dist/scripts/codex-native-hook.js +9 -22
  92. package/dist/scripts/codex-native-hook.js.map +1 -1
  93. package/dist/scripts/notify-dispatcher.d.ts +7 -0
  94. package/dist/scripts/notify-dispatcher.d.ts.map +1 -0
  95. package/dist/scripts/notify-dispatcher.js +58 -0
  96. package/dist/scripts/notify-dispatcher.js.map +1 -0
  97. package/dist/scripts/notify-fallback-watcher.js +4 -0
  98. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  99. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  100. package/dist/scripts/notify-hook/ralph-session-resume.js +96 -8
  101. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  102. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  103. package/dist/scripts/notify-hook/state-io.js +6 -2
  104. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  105. package/dist/scripts/notify-hook/visual-verdict.js +3 -3
  106. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  107. package/dist/scripts/notify-hook.js +124 -0
  108. package/dist/scripts/notify-hook.js.map +1 -1
  109. package/dist/team/__tests__/approved-execution.test.js +45 -1
  110. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  111. package/dist/team/__tests__/runtime.test.js +173 -19
  112. package/dist/team/__tests__/runtime.test.js.map +1 -1
  113. package/dist/team/__tests__/worker-bootstrap.test.js +37 -0
  114. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  115. package/dist/team/approved-execution.d.ts +1 -0
  116. package/dist/team/approved-execution.d.ts.map +1 -1
  117. package/dist/team/approved-execution.js +50 -0
  118. package/dist/team/approved-execution.js.map +1 -1
  119. package/dist/team/delivery-log.d.ts.map +1 -1
  120. package/dist/team/delivery-log.js +8 -1
  121. package/dist/team/delivery-log.js.map +1 -1
  122. package/dist/team/runtime.d.ts.map +1 -1
  123. package/dist/team/runtime.js +104 -18
  124. package/dist/team/runtime.js.map +1 -1
  125. package/dist/team/state/mailbox.d.ts +1 -0
  126. package/dist/team/state/mailbox.d.ts.map +1 -1
  127. package/dist/team/state/mailbox.js +10 -1
  128. package/dist/team/state/mailbox.js.map +1 -1
  129. package/dist/team/state-root.js +1 -1
  130. package/dist/team/state-root.js.map +1 -1
  131. package/dist/team/state.d.ts.map +1 -1
  132. package/dist/team/state.js +2 -2
  133. package/dist/team/state.js.map +1 -1
  134. package/dist/team/worker-bootstrap.d.ts +7 -2
  135. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  136. package/dist/team/worker-bootstrap.js +17 -4
  137. package/dist/team/worker-bootstrap.js.map +1 -1
  138. package/package.json +1 -1
  139. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  140. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
  141. package/skills/omx-setup/SKILL.md +1 -1
  142. package/src/scripts/__tests__/codex-native-hook.test.ts +158 -11
  143. package/src/scripts/codex-native-hook.ts +8 -21
  144. package/src/scripts/notify-dispatcher.ts +74 -0
  145. package/src/scripts/notify-fallback-watcher.ts +6 -2
  146. package/src/scripts/notify-hook/ralph-session-resume.ts +117 -8
  147. package/src/scripts/notify-hook/state-io.ts +4 -2
  148. package/src/scripts/notify-hook/visual-verdict.ts +3 -3
  149. package/src/scripts/notify-hook.ts +116 -0
@@ -296,6 +296,10 @@ if (stateRoot && teamName && workerName) {
296
296
  }
297
297
  `;
298
298
  await writeFile(binaryPath, `#!/usr/bin/env node
299
+ if (process.argv[2] === '--version') {
300
+ console.log('codex 0.0.0-test');
301
+ process.exit(0);
302
+ }
299
303
  ${bootstrap}
300
304
  ${scriptBody}
301
305
  `, { mode: 0o755 });
@@ -329,6 +333,22 @@ async function withPromptModeCodexEnv(binDir, extraEnv, run) {
329
333
  }
330
334
  }
331
335
  }
336
+ function fakeCodexShellScript(body) {
337
+ return `#!/bin/sh
338
+ if [ "\${1:-}" = "--version" ] || [ "\${1:-}" = "-V" ]; then
339
+ echo "codex 0.0.0-test"
340
+ exit 0
341
+ fi
342
+ ${body}`;
343
+ }
344
+ function fakeCodexNodeScript(body) {
345
+ return `#!/usr/bin/env node
346
+ if (process.argv.includes('--version') || process.argv.includes('-V')) {
347
+ console.log('codex 0.0.0-test');
348
+ process.exit(0);
349
+ }
350
+ ${body}`;
351
+ }
332
352
  function teamStateTestPath(cwd, ...parts) {
333
353
  const stateRoot = process.env.OMX_TEAM_STATE_ROOT ?? join(cwd, '.omx', 'state');
334
354
  return join(stateRoot, ...parts);
@@ -727,7 +747,7 @@ describe('runtime', () => {
727
747
  set -eu
728
748
  printf '%s\\n' "$*" >> "${tmuxLogPath}"
729
749
  case "$1" in
730
- -V)
750
+ --version|-V)
731
751
  echo "tmux 3.4"
732
752
  exit 0
733
753
  ;;
@@ -787,7 +807,7 @@ esac
787
807
  binaries: [
788
808
  {
789
809
  name: 'codex',
790
- content: '#!/bin/sh\nsleep 30\n',
810
+ content: fakeCodexShellScript('sleep 30\n'),
791
811
  },
792
812
  ],
793
813
  }, async () => {
@@ -942,7 +962,7 @@ esac
942
962
  binaries: [
943
963
  {
944
964
  name: 'codex',
945
- content: '#!/bin/sh\nsleep 30\n',
965
+ content: fakeCodexShellScript('sleep 30\n'),
946
966
  },
947
967
  ],
948
968
  }, async ({ tmuxLogPath }) => {
@@ -1452,7 +1472,7 @@ EOF
1452
1472
  ;;
1453
1473
  esac
1454
1474
  `,
1455
- binaries: [{ name: 'codex', content: '#!/bin/sh\nexit 0\n' }],
1475
+ binaries: [{ name: 'codex', content: fakeCodexShellScript('exit 0\n') }],
1456
1476
  }, async ({ tmuxLogPath }) => {
1457
1477
  delete process.env.TMUX;
1458
1478
  process.env.TMUX_PANE = '%1';
@@ -1581,7 +1601,7 @@ EOF
1581
1601
  ;;
1582
1602
  esac
1583
1603
  `,
1584
- binaries: [{ name: 'codex', content: '#!/bin/sh\nexit 0\n' }],
1604
+ binaries: [{ name: 'codex', content: fakeCodexShellScript('exit 0\n') }],
1585
1605
  }, async () => {
1586
1606
  delete process.env.TMUX;
1587
1607
  process.env.TMUX_PANE = '%1';
@@ -1698,7 +1718,7 @@ case "$1" in
1698
1718
  ;;
1699
1719
  esac
1700
1720
  `,
1701
- binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1721
+ binaries: [{ name: 'codex', content: fakeCodexNodeScript('process.stdin.resume();\n') }],
1702
1722
  }, async () => {
1703
1723
  delete process.env.TMUX;
1704
1724
  process.env.TMUX_PANE = '%1';
@@ -1820,7 +1840,7 @@ case "$1" in
1820
1840
  ;;
1821
1841
  esac
1822
1842
  `,
1823
- binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1843
+ binaries: [{ name: 'codex', content: fakeCodexNodeScript('process.stdin.resume();\n') }],
1824
1844
  }, async () => {
1825
1845
  delete process.env.TMUX;
1826
1846
  process.env.TMUX_PANE = '%1';
@@ -1966,7 +1986,7 @@ case "$1" in
1966
1986
  ;;
1967
1987
  esac
1968
1988
  `,
1969
- binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
1989
+ binaries: [{ name: 'codex', content: fakeCodexNodeScript('process.stdin.resume();\n') }],
1970
1990
  }, async () => {
1971
1991
  delete process.env.TMUX;
1972
1992
  process.env.TMUX_PANE = '%1';
@@ -2108,11 +2128,10 @@ esac
2108
2128
  binaries: [
2109
2129
  {
2110
2130
  name: 'codex',
2111
- content: `#!/usr/bin/env node
2112
- process.stdin.resume();
2131
+ content: fakeCodexNodeScript(`process.stdin.resume();
2113
2132
  setTimeout(() => process.exit(0), 30000);
2114
2133
  process.on('SIGTERM', () => process.exit(0));
2115
- `,
2134
+ `),
2116
2135
  },
2117
2136
  ],
2118
2137
  }, async () => {
@@ -2215,7 +2234,7 @@ process.on('SIGTERM', () => process.exit(0));
2215
2234
  set -eu
2216
2235
  order_file="${cwd}/dead-pane-order.log"
2217
2236
  case "$1" in
2218
- -V)
2237
+ --version|-V)
2219
2238
  echo "tmux 3.4"
2220
2239
  exit 0
2221
2240
  ;;
@@ -2268,14 +2287,14 @@ case "$1" in
2268
2287
  ;;
2269
2288
  esac
2270
2289
  `,
2271
- binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
2290
+ binaries: [{ name: 'codex', content: fakeCodexNodeScript('setTimeout(() => process.exit(0), 100);\n') }],
2272
2291
  }, async () => {
2273
2292
  delete process.env.TMUX;
2274
2293
  process.env.TMUX_PANE = '%1';
2275
2294
  process.env.OMX_TEAM_WORKER_LAUNCH_MODE = 'interactive';
2276
2295
  process.env.OMX_TEAM_WORKER_CLI = 'codex';
2277
- process.env.OMX_TEAM_READY_TIMEOUT_MS = '300';
2278
- process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '50';
2296
+ process.env.OMX_TEAM_READY_TIMEOUT_MS = '5000';
2297
+ process.env.OMX_TEAM_STARTUP_EVIDENCE_TIMEOUT_MS = '500';
2279
2298
  process.env.OMX_TEAM_STARTUP_DISPATCH_RETRIES = '1';
2280
2299
  receiptFailer = setInterval(() => {
2281
2300
  void (async () => {
@@ -2418,7 +2437,7 @@ case "$1" in
2418
2437
  ;;
2419
2438
  esac
2420
2439
  `,
2421
- binaries: [{ name: 'codex', content: '#!/usr/bin/env node\nprocess.stdin.resume();\n' }],
2440
+ binaries: [{ name: 'codex', content: fakeCodexNodeScript('process.stdin.resume();\n') }],
2422
2441
  }, async () => {
2423
2442
  delete process.env.TMUX;
2424
2443
  process.env.TMUX_PANE = '%1';
@@ -2564,11 +2583,10 @@ esac
2564
2583
  binaries: [
2565
2584
  {
2566
2585
  name: 'codex',
2567
- content: `#!/usr/bin/env node
2568
- process.stdin.resume();
2586
+ content: fakeCodexNodeScript(`process.stdin.resume();
2569
2587
  setTimeout(() => process.exit(0), 30000);
2570
2588
  process.on('SIGTERM', () => process.exit(0));
2571
- `,
2589
+ `),
2572
2590
  },
2573
2591
  ],
2574
2592
  }, async () => {
@@ -2842,6 +2860,10 @@ sleep 5
2842
2860
  const fakeCodexPath = join(binDir, 'codex');
2843
2861
  await mkdir(binDir, { recursive: true });
2844
2862
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
2863
+ if (process.argv[2] === '--version') {
2864
+ console.log('codex 0.0.0-test');
2865
+ process.exit(0);
2866
+ }
2845
2867
  process.exit(0);
2846
2868
  `, { mode: 0o755 });
2847
2869
  const prevPath = process.env.PATH;
@@ -2894,6 +2916,10 @@ process.exit(0);
2894
2916
  await writeFile(join(promptsDir, 'test-engineer.md'), '<identity>Test Engineer</identity>');
2895
2917
  await writeFile(join(promptsDir, 'writer.md'), '<identity>You are Writer.</identity>');
2896
2918
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
2919
+ if (process.argv[2] === '--version') {
2920
+ console.log('codex 0.0.0-test');
2921
+ process.exit(0);
2922
+ }
2897
2923
  const fs = require('fs');
2898
2924
  const path = require('path');
2899
2925
  const worker = String(process.env.OMX_TEAM_WORKER || 'unknown').replace(/[^a-zA-Z0-9_-]+/g, '__');
@@ -3016,6 +3042,10 @@ process.on('SIGTERM', () => process.exit(0));
3016
3042
  await mkdir(promptsDir, { recursive: true });
3017
3043
  await writeFile(join(promptsDir, 'writer.md'), '<identity>You are Writer.</identity>');
3018
3044
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
3045
+ if (process.argv[2] === '--version') {
3046
+ console.log('codex 0.0.0-test');
3047
+ process.exit(0);
3048
+ }
3019
3049
  const fs = require('fs');
3020
3050
  const path = require('path');
3021
3051
  const worker = String(process.env.OMX_TEAM_WORKER || 'unknown').replace(/[^a-zA-Z0-9_-]+/g, '__');
@@ -3106,6 +3136,10 @@ process.on('SIGTERM', () => process.exit(0));
3106
3136
  const fakeCodexPath = join(binDir, 'codex');
3107
3137
  await mkdir(binDir, { recursive: true });
3108
3138
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
3139
+ if (process.argv[2] === '--version') {
3140
+ console.log('codex 0.0.0-test');
3141
+ process.exit(0);
3142
+ }
3109
3143
  process.stdin.resume();
3110
3144
  setTimeout(() => process.exit(0), 5000);
3111
3145
  process.on('SIGTERM', () => process.exit(0));
@@ -3273,6 +3307,10 @@ exit 0
3273
3307
  const envLogPath = join(logDir, 'env.json');
3274
3308
  await mkdir(binDir, { recursive: true });
3275
3309
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
3310
+ if (process.argv[2] === '--version') {
3311
+ console.log('codex 0.0.0-test');
3312
+ process.exit(0);
3313
+ }
3276
3314
  const fs = require('fs');
3277
3315
  const path = require('path');
3278
3316
  const logDir = process.env.OMX_TEST_LOG_DIR;
@@ -3359,6 +3397,10 @@ process.on('SIGTERM', () => process.exit(0));
3359
3397
  const fakeCodexPath = join(binDir, 'codex');
3360
3398
  await mkdir(binDir, { recursive: true });
3361
3399
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
3400
+ if (process.argv[2] === '--version') {
3401
+ console.log('codex 0.0.0-test');
3402
+ process.exit(0);
3403
+ }
3362
3404
  process.stdin.resume();
3363
3405
  setTimeout(() => process.exit(0), 5000);
3364
3406
  process.on('SIGTERM', () => process.exit(0));
@@ -3415,6 +3457,10 @@ process.on('SIGTERM', () => process.exit(0));
3415
3457
  const logDir = await mkdtemp(join(tmpdir(), 'omx-runtime-prompt-logs-'));
3416
3458
  const envLogPath = join(logDir, 'env.json');
3417
3459
  await writeFile(fakeCodexPath, `#!/usr/bin/env node
3460
+ if (process.argv[2] === '--version') {
3461
+ console.log('codex 0.0.0-test');
3462
+ process.exit(0);
3463
+ }
3418
3464
  const fs = require('fs');
3419
3465
  const path = require('path');
3420
3466
  const logDir = process.env.OMX_TEST_LOG_DIR;
@@ -5450,6 +5496,53 @@ esac
5450
5496
  await rm(cwd, { recursive: true, force: true });
5451
5497
  }
5452
5498
  });
5499
+ it('startTeam injects approved handoff context into ready approved worker inboxes', async () => {
5500
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-handoff-'));
5501
+ const binDir = join(cwd, 'bin');
5502
+ const fakeCodexPath = join(binDir, 'codex');
5503
+ const approvedTask = 'Execute approved issue 1314 handoff plan';
5504
+ await mkdir(binDir, { recursive: true });
5505
+ await mkdir(join(cwd, '.omx', 'plans'), { recursive: true });
5506
+ const prdPath = join(cwd, '.omx', 'plans', 'prd-issue-1314-handoff.md');
5507
+ const testSpecPath = join(cwd, '.omx', 'plans', 'test-spec-issue-1314-handoff.md');
5508
+ await writeFile(prdPath, [
5509
+ '# Approved plan',
5510
+ '',
5511
+ buildContextPackOutcome(canonicalContextPackRelativePath('issue-1314-handoff')),
5512
+ '',
5513
+ `Launch via omx team 1:executor "${approvedTask}"`,
5514
+ ].join('\n'));
5515
+ await writeFile(testSpecPath, '# Test spec\n');
5516
+ await writeReadyContextPack(cwd, 'issue-1314-handoff', prdPath, testSpecPath);
5517
+ await writeFile(join(cwd, '.omx', 'plans', 'repo-context-issue-1314-handoff.md'), 'Read the approved repository slice first.\n');
5518
+ await writeFakePromptWorkerBinary(fakeCodexPath, `setTimeout(() => {}, 5000);`);
5519
+ let runtime = null;
5520
+ try {
5521
+ runtime = await withPromptModeCodexEnv(binDir, {}, () => withoutTeamWorkerEnv(() => startTeam('team-approved-handoff', 'approved handoff context test', 'executor', 1, [{ subject: 's', description: 'd', owner: 'worker-1' }], cwd, {
5522
+ approvedExecution: {
5523
+ prd_path: prdPath,
5524
+ task: approvedTask,
5525
+ command: `omx team 1:executor "${approvedTask}"`,
5526
+ },
5527
+ })));
5528
+ const inbox = await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'workers', 'worker-1', 'inbox.md'), 'utf-8');
5529
+ assert.match(inbox, /## Approved Handoff Context/);
5530
+ assert.match(inbox, new RegExp(`Approved plan: ${prdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
5531
+ assert.match(inbox, new RegExp(`Test specs: ${testSpecPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
5532
+ assert.match(inbox, /Approved repository context summary source: .*repo-context-issue-1314-handoff\.md/);
5533
+ assert.match(inbox, /Read the approved repository slice first\./);
5534
+ assert.match(inbox, /Build refs \(read first\): src\/build-1\.ts/);
5535
+ assert.match(inbox, /Verify refs: src\/verify-2\.ts/);
5536
+ assert.match(inbox, /Scope refs: src\/scope-0\.ts/);
5537
+ assert.doesNotMatch(inbox, /query the canonical pack|Context pack index/);
5538
+ }
5539
+ finally {
5540
+ if (runtime) {
5541
+ await shutdownTeam(runtime.teamName, cwd, { force: true }).catch(() => { });
5542
+ }
5543
+ await rm(cwd, { recursive: true, force: true });
5544
+ }
5545
+ });
5453
5546
  it('startTeam keeps explicit plan-only approved bindings on the generic path', async () => {
5454
5547
  const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-binding-plan-only-'));
5455
5548
  const binDir = join(cwd, 'bin');
@@ -5470,6 +5563,8 @@ esac
5470
5563
  })));
5471
5564
  const bindingPath = join(runtime.config.team_state_root ?? join(cwd, '.omx', 'state'), 'team', runtime.teamName, 'approved-execution.json');
5472
5565
  assert.equal(existsSync(bindingPath), false);
5566
+ const inbox = await readFile(join(cwd, '.omx', 'state', 'team', runtime.teamName, 'workers', 'worker-1', 'inbox.md'), 'utf-8');
5567
+ assert.doesNotMatch(inbox, /## Approved Handoff Context/);
5473
5568
  }
5474
5569
  finally {
5475
5570
  if (runtime) {
@@ -5654,6 +5749,65 @@ esac
5654
5749
  await rm(cwd, { recursive: true, force: true });
5655
5750
  }
5656
5751
  });
5752
+ it('assignTask injects approved handoff context when the persisted approved binding remains ready', async () => {
5753
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-runtime-approved-followup-'));
5754
+ const approvedTask = 'Execute approved issue 1320 plan';
5755
+ try {
5756
+ await initTeamState('team-approved-followup', 'assignment test', 'executor', 1, cwd);
5757
+ const manifestPath = teamStateTestPath(cwd, 'team', 'team-approved-followup', 'manifest.v2.json');
5758
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf-8'));
5759
+ manifest.policy = { ...(manifest.policy || {}), dispatch_ack_timeout_ms: 250 };
5760
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
5761
+ const plansDir = join(cwd, '.omx', 'plans');
5762
+ await mkdir(plansDir, { recursive: true });
5763
+ const prdPath = join(plansDir, 'prd-issue-1320.md');
5764
+ const testSpecPath = join(plansDir, 'test-spec-issue-1320.md');
5765
+ await writeFile(prdPath, [
5766
+ '# Approved plan',
5767
+ '',
5768
+ buildContextPackOutcome(canonicalContextPackRelativePath('issue-1320')),
5769
+ '',
5770
+ `Launch via omx team 1:executor "${approvedTask}"`,
5771
+ ].join('\n'));
5772
+ await writeFile(testSpecPath, '# Test spec\n');
5773
+ await writeReadyContextPack(cwd, 'issue-1320', prdPath, testSpecPath);
5774
+ await writeFile(join(plansDir, 'repo-context-issue-1320.md'), 'Follow the approved repository slice before broader repo exploration.\n');
5775
+ await writePersistedApprovedTeamExecutionBinding('team-approved-followup', cwd, {
5776
+ prd_path: prdPath,
5777
+ task: approvedTask,
5778
+ command: `omx team 1:executor "${approvedTask}"`,
5779
+ });
5780
+ const task = await createTask('team-approved-followup', { subject: 'Implement approved follow-up', description: 'Implement approved follow-up', status: 'pending' }, cwd);
5781
+ const assignPromise = assignTask('team-approved-followup', 'worker-1', task.id, cwd);
5782
+ const deadline = Date.now() + 2_000;
5783
+ let delivered = false;
5784
+ while (Date.now() < deadline && !delivered) {
5785
+ const requests = await listDispatchRequests('team-approved-followup', cwd, { kind: 'inbox', to_worker: 'worker-1' });
5786
+ if (!requests.some((request) => request.status === 'pending')) {
5787
+ await new Promise((resolve) => setTimeout(resolve, 20));
5788
+ continue;
5789
+ }
5790
+ await markPendingInboxDispatchesDelivered('team-approved-followup', cwd, {
5791
+ toWorker: 'worker-1',
5792
+ lastReason: 'test_delivered_receipt',
5793
+ });
5794
+ delivered = true;
5795
+ }
5796
+ assert.ok(delivered, 'expected follow-up inbox dispatch request to be queued');
5797
+ await assignPromise;
5798
+ const inbox = await readFile(join(cwd, '.omx', 'state', 'team', 'team-approved-followup', 'workers', 'worker-1', 'inbox.md'), 'utf-8');
5799
+ assert.match(inbox, /## Approved Handoff Context/);
5800
+ assert.match(inbox, new RegExp(`Approved plan: ${prdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
5801
+ assert.match(inbox, /Build refs \(read first\): src\/build-1\.ts/);
5802
+ assert.match(inbox, /Verify refs: src\/verify-2\.ts/);
5803
+ assert.match(inbox, /Scope refs: src\/scope-0\.ts/);
5804
+ assert.match(inbox, /Follow the approved repository slice before broader repo exploration\./);
5805
+ assert.doesNotMatch(inbox, /query the canonical pack|Context pack index/);
5806
+ }
5807
+ finally {
5808
+ await rm(cwd, { recursive: true, force: true });
5809
+ }
5810
+ });
5657
5811
  it('monitorTeam does not re-notify already-notified mailbox messages (issue #116)', async () => {
5658
5812
  // Regression: deliverPendingMailboxMessages used to re-notify every 15 s via shouldRetry.
5659
5813
  // After the fix it must NOT re-notify messages that already have notified_at set.