oh-my-codex 0.3.8 → 0.3.10

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 (131) hide show
  1. package/dist/cli/__tests__/doctor-team.test.js +58 -0
  2. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  3. package/dist/cli/__tests__/index.test.js +3 -3
  4. package/dist/cli/__tests__/index.test.js.map +1 -1
  5. package/dist/cli/__tests__/lifecycle-notifications.test.d.ts +2 -0
  6. package/dist/cli/__tests__/lifecycle-notifications.test.d.ts.map +1 -0
  7. package/dist/cli/__tests__/lifecycle-notifications.test.js +48 -0
  8. package/dist/cli/__tests__/lifecycle-notifications.test.js.map +1 -0
  9. package/dist/cli/doctor.js +28 -0
  10. package/dist/cli/doctor.js.map +1 -1
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +41 -1
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/config/__tests__/models.test.d.ts +2 -0
  15. package/dist/config/__tests__/models.test.d.ts.map +1 -0
  16. package/dist/config/__tests__/models.test.js +69 -0
  17. package/dist/config/__tests__/models.test.js.map +1 -0
  18. package/dist/config/models.d.ts +24 -0
  19. package/dist/config/models.d.ts.map +1 -0
  20. package/dist/config/models.js +53 -0
  21. package/dist/config/models.js.map +1 -0
  22. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +221 -36
  23. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  24. package/dist/mcp/__tests__/state-paths.test.js +21 -1
  25. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  26. package/dist/mcp/__tests__/state-server-team-tools.test.js +53 -1
  27. package/dist/mcp/__tests__/state-server-team-tools.test.js.map +1 -1
  28. package/dist/mcp/state-paths.d.ts +1 -0
  29. package/dist/mcp/state-paths.d.ts.map +1 -1
  30. package/dist/mcp/state-paths.js +34 -1
  31. package/dist/mcp/state-paths.js.map +1 -1
  32. package/dist/mcp/state-server.d.ts.map +1 -1
  33. package/dist/mcp/state-server.js +46 -11
  34. package/dist/mcp/state-server.js.map +1 -1
  35. package/dist/notifications/__tests__/config.test.d.ts +2 -0
  36. package/dist/notifications/__tests__/config.test.d.ts.map +1 -0
  37. package/dist/notifications/__tests__/config.test.js +186 -0
  38. package/dist/notifications/__tests__/config.test.js.map +1 -0
  39. package/dist/notifications/__tests__/dispatcher.test.d.ts +2 -0
  40. package/dist/notifications/__tests__/dispatcher.test.d.ts.map +1 -0
  41. package/dist/notifications/__tests__/dispatcher.test.js +202 -0
  42. package/dist/notifications/__tests__/dispatcher.test.js.map +1 -0
  43. package/dist/notifications/__tests__/formatter.test.d.ts +2 -0
  44. package/dist/notifications/__tests__/formatter.test.d.ts.map +1 -0
  45. package/dist/notifications/__tests__/formatter.test.js +103 -0
  46. package/dist/notifications/__tests__/formatter.test.js.map +1 -0
  47. package/dist/notifications/__tests__/notifier.test.d.ts +2 -0
  48. package/dist/notifications/__tests__/notifier.test.d.ts.map +1 -0
  49. package/dist/notifications/__tests__/notifier.test.js +104 -0
  50. package/dist/notifications/__tests__/notifier.test.js.map +1 -0
  51. package/dist/notifications/__tests__/profiles.test.d.ts +2 -0
  52. package/dist/notifications/__tests__/profiles.test.d.ts.map +1 -0
  53. package/dist/notifications/__tests__/profiles.test.js +404 -0
  54. package/dist/notifications/__tests__/profiles.test.js.map +1 -0
  55. package/dist/notifications/__tests__/reply-listener.test.d.ts +2 -0
  56. package/dist/notifications/__tests__/reply-listener.test.d.ts.map +1 -0
  57. package/dist/notifications/__tests__/reply-listener.test.js +58 -0
  58. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -0
  59. package/dist/notifications/__tests__/session-registry.test.d.ts +2 -0
  60. package/dist/notifications/__tests__/session-registry.test.d.ts.map +1 -0
  61. package/dist/notifications/__tests__/session-registry.test.js +147 -0
  62. package/dist/notifications/__tests__/session-registry.test.js.map +1 -0
  63. package/dist/notifications/__tests__/tmux-detector.test.d.ts +2 -0
  64. package/dist/notifications/__tests__/tmux-detector.test.d.ts.map +1 -0
  65. package/dist/notifications/__tests__/tmux-detector.test.js +77 -0
  66. package/dist/notifications/__tests__/tmux-detector.test.js.map +1 -0
  67. package/dist/notifications/__tests__/tmux.test.d.ts +2 -0
  68. package/dist/notifications/__tests__/tmux.test.d.ts.map +1 -0
  69. package/dist/notifications/__tests__/tmux.test.js +86 -0
  70. package/dist/notifications/__tests__/tmux.test.js.map +1 -0
  71. package/dist/notifications/config.d.ts +44 -0
  72. package/dist/notifications/config.d.ts.map +1 -0
  73. package/dist/notifications/config.js +407 -0
  74. package/dist/notifications/config.js.map +1 -0
  75. package/dist/notifications/dispatcher.d.ts +15 -0
  76. package/dist/notifications/dispatcher.d.ts.map +1 -0
  77. package/dist/notifications/dispatcher.js +410 -0
  78. package/dist/notifications/dispatcher.js.map +1 -0
  79. package/dist/notifications/formatter.d.ts +14 -0
  80. package/dist/notifications/formatter.d.ts.map +1 -0
  81. package/dist/notifications/formatter.js +134 -0
  82. package/dist/notifications/formatter.js.map +1 -0
  83. package/dist/notifications/index.d.ts +32 -0
  84. package/dist/notifications/index.d.ts.map +1 -0
  85. package/dist/notifications/index.js +93 -0
  86. package/dist/notifications/index.js.map +1 -0
  87. package/dist/notifications/reply-listener.d.ts +47 -0
  88. package/dist/notifications/reply-listener.d.ts.map +1 -0
  89. package/dist/notifications/reply-listener.js +656 -0
  90. package/dist/notifications/reply-listener.js.map +1 -0
  91. package/dist/notifications/session-registry.d.ts +26 -0
  92. package/dist/notifications/session-registry.d.ts.map +1 -0
  93. package/dist/notifications/session-registry.js +275 -0
  94. package/dist/notifications/session-registry.js.map +1 -0
  95. package/dist/notifications/tmux-detector.d.ts +17 -0
  96. package/dist/notifications/tmux-detector.d.ts.map +1 -0
  97. package/dist/notifications/tmux-detector.js +77 -0
  98. package/dist/notifications/tmux-detector.js.map +1 -0
  99. package/dist/notifications/tmux.d.ts +28 -0
  100. package/dist/notifications/tmux.d.ts.map +1 -0
  101. package/dist/notifications/tmux.js +203 -0
  102. package/dist/notifications/tmux.js.map +1 -0
  103. package/dist/notifications/types.d.ts +181 -0
  104. package/dist/notifications/types.d.ts.map +1 -0
  105. package/dist/notifications/types.js +9 -0
  106. package/dist/notifications/types.js.map +1 -0
  107. package/dist/team/__tests__/runtime.test.js +54 -2
  108. package/dist/team/__tests__/runtime.test.js.map +1 -1
  109. package/dist/team/__tests__/state.test.js +30 -0
  110. package/dist/team/__tests__/state.test.js.map +1 -1
  111. package/dist/team/__tests__/worker-bootstrap.test.js +59 -1
  112. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  113. package/dist/team/runtime.d.ts +2 -2
  114. package/dist/team/runtime.d.ts.map +1 -1
  115. package/dist/team/runtime.js +50 -23
  116. package/dist/team/runtime.js.map +1 -1
  117. package/dist/team/state.d.ts +1 -1
  118. package/dist/team/state.d.ts.map +1 -1
  119. package/dist/team/state.js +5 -0
  120. package/dist/team/state.js.map +1 -1
  121. package/dist/team/tmux-session.d.ts.map +1 -1
  122. package/dist/team/tmux-session.js +53 -10
  123. package/dist/team/tmux-session.js.map +1 -1
  124. package/dist/team/worker-bootstrap.d.ts +12 -0
  125. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  126. package/dist/team/worker-bootstrap.js +37 -0
  127. package/dist/team/worker-bootstrap.js.map +1 -1
  128. package/package.json +1 -1
  129. package/prompts/planner.md +3 -3
  130. package/scripts/notify-hook.js +137 -7
  131. package/skills/team/SKILL.md +3 -1
@@ -26,7 +26,7 @@ Plans that are too vague waste executor time guessing. Plans that are too detail
26
26
 
27
27
  - Never write code files (.ts, .js, .py, .go, etc.). Only output plans to `.omx/plans/*.md` and drafts to `.omx/drafts/*.md`.
28
28
  - Never generate a plan until the user explicitly requests it ("make it into a work plan", "generate the plan").
29
- - Never start implementation. Always hand off to `/oh-my-codex:start-work`.
29
+ - Never start implementation. Always hand off by presenting actionable next-step commands (see Output Format).
30
30
  - Ask ONE question at a time using AskUserQuestion tool. Never batch multiple questions.
31
31
  - Never ask the user about codebase facts (use explore agent to look them up).
32
32
  - Default to 3-6 step plans. Avoid architecture redesign unless the task requires it.
@@ -41,7 +41,7 @@ Plans that are too vague waste executor time guessing. Plans that are too detail
41
41
  4) When user triggers plan generation ("make it into a work plan"), consult analyst (Metis) first for gap analysis.
42
42
  5) Generate plan with: Context, Work Objectives, Guardrails (Must Have / Must NOT Have), Task Flow, Detailed TODOs with acceptance criteria, Success Criteria.
43
43
  6) Display confirmation summary and wait for explicit user approval.
44
- 7) On approval, hand off to `/oh-my-codex:start-work {plan-name}`.
44
+ 7) On approval, present concrete next-step commands the user can copy-paste to begin execution (e.g. `$ralph "execute plan: {plan-name}"` or `$team 3:executor "execute plan: {plan-name}"`).
45
45
 
46
46
  ## Tool Usage
47
47
 
@@ -71,7 +71,7 @@ Plans that are too vague waste executor time guessing. Plans that are too detail
71
71
  2. [Deliverable 2]
72
72
 
73
73
  **Does this plan capture your intent?**
74
- - "proceed" - Begin implementation via /oh-my-codex:start-work
74
+ - "proceed" - Show executable next-step commands
75
75
  - "adjust [X]" - Return to interview to modify
76
76
  - "restart" - Discard and start fresh
77
77
 
@@ -437,7 +437,63 @@ function resolveLeaderNudgeIntervalMs() {
437
437
  return 120_000;
438
438
  }
439
439
 
440
- async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir }) {
440
+ function resolveLeaderStalenessThresholdMs() {
441
+ const raw = safeString(process.env.OMX_TEAM_LEADER_STALE_MS || '');
442
+ const parsed = asNumber(raw);
443
+ // Default: 3 minutes. Guard against unreasonable values.
444
+ if (parsed !== null && parsed >= 10_000 && parsed <= 30 * 60_000) return parsed;
445
+ return 180_000;
446
+ }
447
+
448
+ async function checkWorkerPanesAlive(tmuxTarget) {
449
+ // Check if the team tmux session has worker panes running.
450
+ // tmuxTarget is either "omx-team-foo" or "session:window".
451
+ const sessionName = tmuxTarget.split(':')[0];
452
+ try {
453
+ const result = await runProcess('tmux', ['list-panes', '-t', sessionName, '-F', '#{pane_id} #{pane_pid}'], 2000);
454
+ const lines = (result.stdout || '')
455
+ .split('\n')
456
+ .map(l => l.trim())
457
+ .filter(Boolean);
458
+ return { alive: lines.length > 0, paneCount: lines.length };
459
+ } catch {
460
+ return { alive: false, paneCount: 0 };
461
+ }
462
+ }
463
+
464
+ async function isLeaderStale(stateDir, thresholdMs, nowMs) {
465
+ // Check HUD state (updated by the notify hook on each leader turn) for staleness.
466
+ const hudStatePath = join(stateDir, 'hud-state.json');
467
+ const hudState = await readJsonIfExists(hudStatePath, null);
468
+ if (!hudState || typeof hudState !== 'object') return true;
469
+ const lastTurnAt = safeString(hudState.last_turn_at || '');
470
+ if (!lastTurnAt) return true;
471
+ const lastMs = Date.parse(lastTurnAt);
472
+ if (!Number.isFinite(lastMs)) return true;
473
+ return (nowMs - lastMs) >= thresholdMs;
474
+ }
475
+
476
+ async function emitTeamNudgeEvent(cwd, teamName, reason, nowIso) {
477
+ // Write a team_leader_nudge event to the team's events.ndjson log.
478
+ const eventsDir = join(cwd, '.omx', 'state', 'team', teamName, 'events');
479
+ const eventsPath = join(eventsDir, 'events.ndjson');
480
+ try {
481
+ await mkdir(eventsDir, { recursive: true });
482
+ const event = {
483
+ event_id: `nudge-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
484
+ team: teamName,
485
+ type: 'team_leader_nudge',
486
+ worker: 'leader-fixed',
487
+ reason,
488
+ created_at: nowIso,
489
+ };
490
+ await appendFile(eventsPath, JSON.stringify(event) + '\n');
491
+ } catch {
492
+ // Best effort
493
+ }
494
+ }
495
+
496
+ async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputedLeaderStale }) {
441
497
  const intervalMs = resolveLeaderNudgeIntervalMs();
442
498
  const nowMs = Date.now();
443
499
  const nowIso = new Date().toISOString();
@@ -467,6 +523,9 @@ async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir }) {
467
523
  // Non-critical
468
524
  }
469
525
 
526
+ // Use pre-computed staleness (captured before HUD state was updated this turn)
527
+ const leaderStale = typeof preComputedLeaderStale === 'boolean' ? preComputedLeaderStale : false;
528
+
470
529
  for (const teamName of activeTeamNames) {
471
530
  // Resolve tmux target (session:window) from manifest/config. Best effort.
472
531
  let tmuxTarget = '';
@@ -483,6 +542,9 @@ async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir }) {
483
542
  }
484
543
  if (!tmuxTarget) continue;
485
544
 
545
+ // Check if worker panes are still alive in tmux
546
+ const paneStatus = await checkWorkerPanesAlive(tmuxTarget);
547
+
486
548
  let mailbox = null;
487
549
  try {
488
550
  const mailboxPath = join(omxDir, 'state', 'team', teamName, 'mailbox', 'leader-fixed.json');
@@ -503,17 +565,51 @@ async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir }) {
503
565
 
504
566
  const hasNewMessage = newestId && newestId !== prevMsgId;
505
567
  const dueByTime = !Number.isFinite(prevAtMs) || (nowMs - prevAtMs >= intervalMs);
506
- if (!hasNewMessage && !dueByTime) continue;
507
568
 
569
+ // New condition: worker panes alive + leader stale = always nudge
570
+ const stalePanesNudge = paneStatus.alive && leaderStale;
571
+
572
+ if (!hasNewMessage && !dueByTime && !stalePanesNudge) continue;
573
+
574
+ // Build contextual nudge message
508
575
  const msgCount = messages.length;
509
- const text = hasNewMessage
510
- ? `Team ${teamName}: ${msgCount} msg(s) for leader. Run: omx team status ${teamName}`
511
- : `Team ${teamName} active. Run: omx team status ${teamName}`;
576
+ let nudgeReason = '';
577
+ let text = '';
578
+ if (stalePanesNudge && hasNewMessage) {
579
+ nudgeReason = 'stale_leader_with_messages';
580
+ text = `Team ${teamName}: leader stale, ${paneStatus.paneCount} pane(s) active, ${msgCount} msg(s) pending. Run: omx team status ${teamName}`;
581
+ } else if (stalePanesNudge) {
582
+ nudgeReason = 'stale_leader_panes_alive';
583
+ text = `Team ${teamName}: leader stale, ${paneStatus.paneCount} worker pane(s) still active. Run: omx team status ${teamName}`;
584
+ } else if (hasNewMessage) {
585
+ nudgeReason = 'new_mailbox_message';
586
+ text = `Team ${teamName}: ${msgCount} msg(s) for leader. Run: omx team status ${teamName}`;
587
+ } else {
588
+ nudgeReason = 'periodic_check';
589
+ text = `Team ${teamName} active. Run: omx team status ${teamName}`;
590
+ }
512
591
  const capped = text.length > 180 ? `${text.slice(0, 177)}...` : text;
513
592
 
514
593
  try {
515
- await runProcess('tmux', ['display-message', '-t', tmuxTarget, '--', capped], 1200);
594
+ await runProcess('tmux', ['send-keys', '-t', tmuxTarget, capped, 'C-m', 'C-m'], 1200);
516
595
  nudgeState.last_nudged_by_team[teamName] = { at: nowIso, last_message_id: newestId || prevMsgId || '' };
596
+
597
+ // Emit team event for the nudge
598
+ await emitTeamNudgeEvent(cwd, teamName, nudgeReason, nowIso);
599
+
600
+ // Log the nudge
601
+ try {
602
+ await logTmuxHookEvent(logsDir, {
603
+ timestamp: nowIso,
604
+ type: 'team_leader_nudge',
605
+ team: teamName,
606
+ tmux_target: tmuxTarget,
607
+ reason: nudgeReason,
608
+ pane_count: paneStatus.paneCount,
609
+ leader_stale: leaderStale,
610
+ message_count: msgCount,
611
+ });
612
+ } catch { /* ignore */ }
517
613
  } catch (err) {
518
614
  // Best effort. Log only in debug mode to avoid noise.
519
615
  try {
@@ -522,6 +618,7 @@ async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir }) {
522
618
  type: 'team_leader_nudge',
523
619
  team: teamName,
524
620
  tmux_target: tmuxTarget,
621
+ reason: nudgeReason,
525
622
  error: safeString(err && err.message ? err.message : err),
526
623
  });
527
624
  } catch { /* ignore */ }
@@ -968,6 +1065,17 @@ async function main() {
968
1065
  }
969
1066
  }
970
1067
 
1068
+ // 3.5. Pre-compute leader staleness BEFORE updating HUD state (used by nudge in step 6)
1069
+ let preComputedLeaderStale = false;
1070
+ if (!isTeamWorker) {
1071
+ try {
1072
+ const stalenessMs = resolveLeaderStalenessThresholdMs();
1073
+ preComputedLeaderStale = await isLeaderStale(stateDir, stalenessMs, Date.now());
1074
+ } catch {
1075
+ // Non-critical
1076
+ }
1077
+ }
1078
+
971
1079
  // 4. Write HUD state summary for `omx hud` (lead session only)
972
1080
  if (!isTeamWorker) {
973
1081
  const hudStatePath = join(stateDir, 'hud-state.json');
@@ -1026,11 +1134,33 @@ async function main() {
1026
1134
  // 6. Team leader nudge (lead session only): remind the leader to check teammate/mailbox state.
1027
1135
  if (!isTeamWorker) {
1028
1136
  try {
1029
- await maybeNudgeTeamLeader({ cwd, stateDir, logsDir });
1137
+ await maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputedLeaderStale });
1030
1138
  } catch {
1031
1139
  // Non-critical
1032
1140
  }
1033
1141
  }
1142
+
1143
+ // 7. Dispatch session-idle lifecycle notification (lead session only, best effort)
1144
+ if (!isTeamWorker) {
1145
+ try {
1146
+ const { notifyLifecycle } = await import('../dist/notifications/index.js');
1147
+ const sessionJsonPath = join(stateDir, 'session.json');
1148
+ let notifySessionId = '';
1149
+ try {
1150
+ const sessionData = JSON.parse(await readFile(sessionJsonPath, 'utf-8'));
1151
+ notifySessionId = safeString(sessionData && sessionData.session_id ? sessionData.session_id : '');
1152
+ } catch { /* no session file */ }
1153
+
1154
+ if (notifySessionId) {
1155
+ await notifyLifecycle('session-idle', {
1156
+ sessionId: notifySessionId,
1157
+ projectPath: cwd,
1158
+ });
1159
+ }
1160
+ } catch {
1161
+ // Non-fatal: notification module may not be built or config may not exist
1162
+ }
1163
+ }
1034
1164
  }
1035
1165
 
1036
1166
  async function readdir(dir) {
@@ -63,7 +63,9 @@ If duplicates exist, remove extras before `omx team` to prevent HUD ending up in
63
63
  - `.omx/state/team/<team>/config.json`
64
64
  - `.omx/state/team/<team>/manifest.v2.json`
65
65
  - `.omx/state/team/<team>/tasks/task-<id>.json`
66
- 4. Apply worker overlay to project `AGENTS.md`
66
+ 4. Compose team-scoped worker instructions file at:
67
+ - `.omx/state/team/<team>/worker-agents.md`
68
+ - Uses project `AGENTS.md` content (if present) + worker overlay, without mutating project `AGENTS.md`
67
69
  5. Split current tmux window into worker panes
68
70
  6. Launch workers with `OMX_TEAM_WORKER=<team>/worker-<n>`
69
71
  7. Wait for worker readiness (`capture-pane` polling)