oh-my-codex 0.3.9 → 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.
- package/dist/cli/__tests__/doctor-team.test.js +58 -0
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +3 -3
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/lifecycle-notifications.test.d.ts +2 -0
- package/dist/cli/__tests__/lifecycle-notifications.test.d.ts.map +1 -0
- package/dist/cli/__tests__/lifecycle-notifications.test.js +48 -0
- package/dist/cli/__tests__/lifecycle-notifications.test.js.map +1 -0
- package/dist/cli/doctor.js +28 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +41 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/__tests__/models.test.d.ts +2 -0
- package/dist/config/__tests__/models.test.d.ts.map +1 -0
- package/dist/config/__tests__/models.test.js +69 -0
- package/dist/config/__tests__/models.test.js.map +1 -0
- package/dist/config/models.d.ts +24 -0
- package/dist/config/models.d.ts.map +1 -0
- package/dist/config/models.js +53 -0
- package/dist/config/models.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +221 -36
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +21 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-team-tools.test.js +53 -1
- package/dist/mcp/__tests__/state-server-team-tools.test.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +1 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +34 -1
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +46 -11
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/notifications/__tests__/config.test.d.ts +2 -0
- package/dist/notifications/__tests__/config.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/config.test.js +186 -0
- package/dist/notifications/__tests__/config.test.js.map +1 -0
- package/dist/notifications/__tests__/dispatcher.test.d.ts +2 -0
- package/dist/notifications/__tests__/dispatcher.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/dispatcher.test.js +202 -0
- package/dist/notifications/__tests__/dispatcher.test.js.map +1 -0
- package/dist/notifications/__tests__/formatter.test.d.ts +2 -0
- package/dist/notifications/__tests__/formatter.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/formatter.test.js +103 -0
- package/dist/notifications/__tests__/formatter.test.js.map +1 -0
- package/dist/notifications/__tests__/notifier.test.d.ts +2 -0
- package/dist/notifications/__tests__/notifier.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/notifier.test.js +104 -0
- package/dist/notifications/__tests__/notifier.test.js.map +1 -0
- package/dist/notifications/__tests__/profiles.test.d.ts +2 -0
- package/dist/notifications/__tests__/profiles.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/profiles.test.js +404 -0
- package/dist/notifications/__tests__/profiles.test.js.map +1 -0
- package/dist/notifications/__tests__/reply-listener.test.d.ts +2 -0
- package/dist/notifications/__tests__/reply-listener.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/reply-listener.test.js +58 -0
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -0
- package/dist/notifications/__tests__/session-registry.test.d.ts +2 -0
- package/dist/notifications/__tests__/session-registry.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/session-registry.test.js +147 -0
- package/dist/notifications/__tests__/session-registry.test.js.map +1 -0
- package/dist/notifications/__tests__/tmux-detector.test.d.ts +2 -0
- package/dist/notifications/__tests__/tmux-detector.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/tmux-detector.test.js +77 -0
- package/dist/notifications/__tests__/tmux-detector.test.js.map +1 -0
- package/dist/notifications/__tests__/tmux.test.d.ts +2 -0
- package/dist/notifications/__tests__/tmux.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/tmux.test.js +86 -0
- package/dist/notifications/__tests__/tmux.test.js.map +1 -0
- package/dist/notifications/config.d.ts +44 -0
- package/dist/notifications/config.d.ts.map +1 -0
- package/dist/notifications/config.js +407 -0
- package/dist/notifications/config.js.map +1 -0
- package/dist/notifications/dispatcher.d.ts +15 -0
- package/dist/notifications/dispatcher.d.ts.map +1 -0
- package/dist/notifications/dispatcher.js +410 -0
- package/dist/notifications/dispatcher.js.map +1 -0
- package/dist/notifications/formatter.d.ts +14 -0
- package/dist/notifications/formatter.d.ts.map +1 -0
- package/dist/notifications/formatter.js +134 -0
- package/dist/notifications/formatter.js.map +1 -0
- package/dist/notifications/index.d.ts +32 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +93 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/reply-listener.d.ts +47 -0
- package/dist/notifications/reply-listener.d.ts.map +1 -0
- package/dist/notifications/reply-listener.js +656 -0
- package/dist/notifications/reply-listener.js.map +1 -0
- package/dist/notifications/session-registry.d.ts +26 -0
- package/dist/notifications/session-registry.d.ts.map +1 -0
- package/dist/notifications/session-registry.js +275 -0
- package/dist/notifications/session-registry.js.map +1 -0
- package/dist/notifications/tmux-detector.d.ts +17 -0
- package/dist/notifications/tmux-detector.d.ts.map +1 -0
- package/dist/notifications/tmux-detector.js +77 -0
- package/dist/notifications/tmux-detector.js.map +1 -0
- package/dist/notifications/tmux.d.ts +28 -0
- package/dist/notifications/tmux.d.ts.map +1 -0
- package/dist/notifications/tmux.js +203 -0
- package/dist/notifications/tmux.js.map +1 -0
- package/dist/notifications/types.d.ts +181 -0
- package/dist/notifications/types.d.ts.map +1 -0
- package/dist/notifications/types.js +9 -0
- package/dist/notifications/types.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +54 -2
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +30 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +2 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/runtime.d.ts +2 -2
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +19 -12
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state.d.ts +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +5 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +53 -10
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +4 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/package.json +1 -1
- package/scripts/notify-hook.js +137 -7
package/scripts/notify-hook.js
CHANGED
|
@@ -437,7 +437,63 @@ function resolveLeaderNudgeIntervalMs() {
|
|
|
437
437
|
return 120_000;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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', ['
|
|
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) {
|