oh-my-codex 0.7.6 → 0.8.1
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/README.de.md +315 -0
- package/README.es.md +296 -17
- package/README.fr.md +315 -0
- package/README.it.md +315 -0
- package/README.ja.md +297 -18
- package/README.ko.md +296 -17
- package/README.md +110 -13
- package/README.pt.md +296 -17
- package/README.ru.md +296 -17
- package/README.tr.md +315 -0
- package/README.vi.md +297 -18
- package/README.zh-TW.md +362 -0
- package/README.zh.md +293 -17
- package/dist/catalog/__tests__/generator.test.js +2 -0
- package/dist/catalog/__tests__/generator.test.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +7 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/ask.test.d.ts +2 -0
- package/dist/cli/__tests__/ask.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ask.test.js +236 -0
- package/dist/cli/__tests__/ask.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +45 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -0
- package/dist/cli/__tests__/index.test.js +85 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.d.ts +2 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +15 -0
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -0
- package/dist/cli/__tests__/ralph.test.js +19 -43
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +2 -0
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +219 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/version.test.d.ts +2 -0
- package/dist/cli/__tests__/version.test.d.ts.map +1 -0
- package/dist/cli/__tests__/version.test.js +21 -0
- package/dist/cli/__tests__/version.test.js.map +1 -0
- package/dist/cli/ask.d.ts +13 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +174 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/constants.d.ts +10 -0
- package/dist/cli/constants.d.ts.map +1 -0
- package/dist/cli/constants.js +10 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/doctor.js +16 -5
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +8 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +150 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ralph.d.ts +3 -11
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +64 -45
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +17 -18
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +257 -0
- package/dist/cli/team.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js +55 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/emulator.test.js +6 -0
- package/dist/hooks/__tests__/emulator.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +44 -22
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +23 -7
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +59 -0
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +264 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +61 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +17 -7
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/openclaw-setup-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js +61 -0
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.d.ts +2 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js +34 -0
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.d.ts +2 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.js +35 -0
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -0
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +18 -16
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/codebase-map.d.ts.map +1 -1
- package/dist/hooks/codebase-map.js +6 -2
- package/dist/hooks/codebase-map.js.map +1 -1
- package/dist/hooks/emulator.d.ts.map +1 -1
- package/dist/hooks/emulator.js +2 -0
- package/dist/hooks/emulator.js.map +1 -1
- package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk.js +2 -1
- package/dist/hooks/extensibility/sdk.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +6 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +2 -24
- package/dist/hud/index.js.map +1 -1
- package/dist/mcp/__tests__/path-traversal.test.js +9 -227
- package/dist/mcp/__tests__/path-traversal.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-schema.test.js +16 -20
- package/dist/mcp/__tests__/state-server-schema.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server-team-tools.test.js +30 -487
- package/dist/mcp/__tests__/state-server-team-tools.test.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js +18 -8
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/memory-server.js +72 -11
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +4 -1
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +179 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +221 -1111
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/mcp/team-server.d.ts.map +1 -1
- package/dist/mcp/team-server.js +9 -3
- package/dist/mcp/team-server.js.map +1 -1
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js +8 -3
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/notifications/__tests__/dispatch-cooldown.test.d.ts +5 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.js +100 -0
- package/dist/notifications/__tests__/dispatch-cooldown.test.js.map +1 -0
- package/dist/notifications/__tests__/temp-mode.test.d.ts +2 -0
- package/dist/notifications/__tests__/temp-mode.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/temp-mode.test.js +172 -0
- package/dist/notifications/__tests__/temp-mode.test.js.map +1 -0
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +67 -7
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/dispatch-cooldown.d.ts +36 -0
- package/dist/notifications/dispatch-cooldown.d.ts.map +1 -0
- package/dist/notifications/dispatch-cooldown.js +109 -0
- package/dist/notifications/dispatch-cooldown.js.map +1 -0
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +4 -4
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/notifications/index.d.ts +5 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +39 -8
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +6 -2
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.d.ts.map +1 -1
- package/dist/notifications/session-registry.js +2 -2
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/temp-contract.d.ts +22 -0
- package/dist/notifications/temp-contract.d.ts.map +1 -0
- package/dist/notifications/temp-contract.js +147 -0
- package/dist/notifications/temp-contract.js.map +1 -0
- package/dist/notifications/tmux.js +2 -2
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/notifications/types.d.ts +18 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/config.test.js +81 -0
- package/dist/openclaw/__tests__/config.test.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +40 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/config.d.ts +4 -0
- package/dist/openclaw/config.d.ts.map +1 -1
- package/dist/openclaw/config.js +110 -16
- package/dist/openclaw/config.js.map +1 -1
- package/dist/openclaw/dispatcher.d.ts +9 -3
- package/dist/openclaw/dispatcher.d.ts.map +1 -1
- package/dist/openclaw/dispatcher.js +42 -9
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/openclaw/types.d.ts +5 -1
- package/dist/openclaw/types.d.ts.map +1 -1
- package/dist/ralph/__tests__/persistence.test.js +28 -1
- package/dist/ralph/__tests__/persistence.test.js.map +1 -1
- package/dist/ralph/persistence.d.ts +21 -0
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +85 -2
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/state/paths.d.ts +3 -0
- package/dist/state/paths.d.ts.map +1 -0
- package/dist/state/paths.js +2 -0
- package/dist/state/paths.js.map +1 -0
- package/dist/team/__tests__/api-interop.test.d.ts +2 -0
- package/dist/team/__tests__/api-interop.test.d.ts.map +1 -0
- package/dist/team/__tests__/api-interop.test.js +1052 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -0
- package/dist/team/__tests__/idle-nudge.test.d.ts +2 -0
- package/dist/team/__tests__/idle-nudge.test.d.ts.map +1 -0
- package/dist/team/__tests__/idle-nudge.test.js +225 -0
- package/dist/team/__tests__/idle-nudge.test.js.map +1 -0
- package/dist/team/__tests__/mcp-comm.test.js +30 -0
- package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +33 -26
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.d.ts +2 -0
- package/dist/team/__tests__/state-root.test.d.ts.map +1 -0
- package/dist/team/__tests__/state-root.test.js +9 -0
- package/dist/team/__tests__/state-root.test.js.map +1 -0
- package/dist/team/__tests__/state.test.js +52 -17
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-ops-contract.test.d.ts +2 -0
- package/dist/team/__tests__/team-ops-contract.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-ops-contract.test.js +90 -0
- package/dist/team/__tests__/team-ops-contract.test.js.map +1 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js +176 -0
- package/dist/team/__tests__/tmux-claude-workers-demo.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +8 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +29 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worktree.test.js +54 -1
- package/dist/team/__tests__/worktree.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +19 -0
- package/dist/team/api-interop.d.ts.map +1 -0
- package/dist/team/api-interop.js +578 -0
- package/dist/team/api-interop.js.map +1 -0
- package/dist/team/mcp-comm.d.ts.map +1 -1
- package/dist/team/mcp-comm.js +32 -2
- package/dist/team/mcp-comm.js.map +1 -1
- package/dist/team/orchestrator.d.ts +1 -10
- package/dist/team/orchestrator.d.ts.map +1 -1
- package/dist/team/orchestrator.js +8 -0
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/runtime-cli.js +14 -8
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +103 -30
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +33 -12
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/approvals.d.ts +25 -0
- package/dist/team/state/approvals.d.ts.map +1 -0
- package/dist/team/state/approvals.js +31 -0
- package/dist/team/state/approvals.js.map +1 -0
- package/dist/team/state/config.d.ts +2 -0
- package/dist/team/state/config.d.ts.map +1 -0
- package/dist/team/state/config.js +2 -0
- package/dist/team/state/config.js.map +1 -0
- package/dist/team/state/dispatch-lock.d.ts +3 -0
- package/dist/team/state/dispatch-lock.d.ts.map +1 -0
- package/dist/team/state/dispatch-lock.js +81 -0
- package/dist/team/state/dispatch-lock.js.map +1 -0
- package/dist/team/state/dispatch.d.ts +61 -0
- package/dist/team/state/dispatch.d.ts.map +1 -0
- package/dist/team/state/dispatch.js +158 -0
- package/dist/team/state/dispatch.js.map +1 -0
- package/dist/team/state/events.d.ts +2 -0
- package/dist/team/state/events.d.ts.map +1 -0
- package/dist/team/state/events.js +2 -0
- package/dist/team/state/events.js.map +1 -0
- package/dist/team/state/index.d.ts +11 -0
- package/dist/team/state/index.d.ts.map +1 -0
- package/dist/team/state/index.js +11 -0
- package/dist/team/state/index.js.map +1 -0
- package/dist/team/state/io.d.ts +2 -0
- package/dist/team/state/io.d.ts.map +1 -0
- package/dist/team/state/io.js +2 -0
- package/dist/team/state/io.js.map +1 -0
- package/dist/team/state/locks.d.ts +16 -0
- package/dist/team/state/locks.d.ts.map +1 -0
- package/dist/team/state/locks.js +201 -0
- package/dist/team/state/locks.js.map +1 -0
- package/dist/team/state/mailbox.d.ts +39 -0
- package/dist/team/state/mailbox.d.ts.map +1 -0
- package/dist/team/state/mailbox.js +58 -0
- package/dist/team/state/mailbox.js.map +1 -0
- package/dist/team/state/monitor.d.ts +96 -0
- package/dist/team/state/monitor.d.ts.map +1 -0
- package/dist/team/state/monitor.js +163 -0
- package/dist/team/state/monitor.js.map +1 -0
- package/dist/team/state/shutdown.d.ts +2 -0
- package/dist/team/state/shutdown.d.ts.map +1 -0
- package/dist/team/state/shutdown.js +2 -0
- package/dist/team/state/shutdown.js.map +1 -0
- package/dist/team/state/summary.d.ts +2 -0
- package/dist/team/state/summary.d.ts.map +1 -0
- package/dist/team/state/summary.js +2 -0
- package/dist/team/state/summary.js.map +1 -0
- package/dist/team/state/tasks.d.ts +49 -0
- package/dist/team/state/tasks.d.ts.map +1 -0
- package/dist/team/state/tasks.js +182 -0
- package/dist/team/state/tasks.js.map +1 -0
- package/dist/team/state/types.d.ts +281 -0
- package/dist/team/state/types.d.ts.map +1 -0
- package/dist/team/state/types.js +3 -0
- package/dist/team/state/types.js.map +1 -0
- package/dist/team/state/workers.d.ts +2 -0
- package/dist/team/state/workers.d.ts.map +1 -0
- package/dist/team/state/workers.js +2 -0
- package/dist/team/state/workers.js.map +1 -0
- package/dist/team/state-root.d.ts +5 -0
- package/dist/team/state-root.d.ts.map +1 -0
- package/dist/team/state-root.js +8 -0
- package/dist/team/state-root.js.map +1 -0
- package/dist/team/state.d.ts +4 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +200 -881
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +11 -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 +58 -26
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +43 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/safe-json.d.ts +3 -0
- package/dist/utils/safe-json.d.ts.map +1 -0
- package/dist/utils/safe-json.js +19 -0
- package/dist/utils/safe-json.js.map +1 -0
- package/dist/utils/sleep.d.ts +3 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +15 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/visual/__tests__/verdict.test.d.ts +2 -0
- package/dist/visual/__tests__/verdict.test.d.ts.map +1 -0
- package/dist/visual/__tests__/verdict.test.js +81 -0
- package/dist/visual/__tests__/verdict.test.js.map +1 -0
- package/dist/visual/constants.d.ts +4 -0
- package/dist/visual/constants.d.ts.map +1 -0
- package/dist/visual/constants.js +3 -0
- package/dist/visual/constants.js.map +1 -0
- package/dist/visual/verdict.d.ts +17 -0
- package/dist/visual/verdict.d.ts.map +1 -0
- package/dist/visual/verdict.js +61 -0
- package/dist/visual/verdict.js.map +1 -0
- package/package.json +10 -3
- package/scripts/ask-claude.sh +17 -0
- package/scripts/ask-gemini.sh +14 -0
- package/scripts/demo-claude-workers.sh +241 -0
- package/scripts/demo-team-e2e.sh +179 -0
- package/scripts/fixtures/ask-advisor-stub.js +12 -0
- package/scripts/notify-hook/team-dispatch.js +234 -12
- package/scripts/notify-hook/team-leader-nudge.js +42 -2
- package/scripts/notify-hook/team-worker.js +63 -4
- package/scripts/notify-hook/visual-verdict.js +50 -1
- package/scripts/notify-hook.js +1 -0
- package/scripts/run-provider-advisor.js +179 -0
- package/skills/ask-claude/SKILL.md +61 -0
- package/skills/ask-gemini/SKILL.md +61 -0
- package/skills/autopilot/SKILL.md +32 -2
- package/skills/configure-notifications/SKILL.md +188 -186
- package/skills/deep-interview/SKILL.md +247 -0
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/ralph/SKILL.md +42 -11
- package/skills/ralplan/SKILL.md +17 -0
- package/skills/team/SKILL.md +64 -5
- package/skills/visual-verdict/SKILL.md +76 -0
- package/skills/web-clone/SKILL.md +366 -0
- package/skills/worker/SKILL.md +42 -11
- package/templates/AGENTS.md +9 -0
- package/templates/catalog-manifest.json +39 -18
- package/skills/configure-discord/SKILL.md +0 -256
- package/skills/configure-openclaw/SKILL.md +0 -267
- package/skills/configure-slack/SKILL.md +0 -226
- package/skills/configure-telegram/SKILL.md +0 -232
|
@@ -21,6 +21,76 @@ async function writeJsonAtomic(path, value) {
|
|
|
21
21
|
|
|
22
22
|
// Keep stale-timeout semantics aligned with src/team/state.ts LOCK_STALE_MS.
|
|
23
23
|
const DISPATCH_LOCK_STALE_MS = 5 * 60 * 1000;
|
|
24
|
+
const DEFAULT_ISSUE_DISPATCH_COOLDOWN_MS = 15 * 60 * 1000;
|
|
25
|
+
const ISSUE_DISPATCH_COOLDOWN_ENV = 'OMX_TEAM_DISPATCH_ISSUE_COOLDOWN_MS';
|
|
26
|
+
const DEFAULT_DISPATCH_TRIGGER_COOLDOWN_MS = 30 * 1000;
|
|
27
|
+
const DISPATCH_TRIGGER_COOLDOWN_ENV = 'OMX_TEAM_DISPATCH_TRIGGER_COOLDOWN_MS';
|
|
28
|
+
const LEADER_PANE_MISSING_DEFERRED_REASON = 'leader_pane_missing_deferred';
|
|
29
|
+
const LEADER_NOTIFICATION_DEFERRED_TYPE = 'leader_notification_deferred';
|
|
30
|
+
|
|
31
|
+
function resolveIssueDispatchCooldownMs(env = process.env) {
|
|
32
|
+
const raw = safeString(env[ISSUE_DISPATCH_COOLDOWN_ENV]).trim();
|
|
33
|
+
if (raw === '') return DEFAULT_ISSUE_DISPATCH_COOLDOWN_MS;
|
|
34
|
+
const parsed = Number.parseInt(raw, 10);
|
|
35
|
+
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_ISSUE_DISPATCH_COOLDOWN_MS;
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveDispatchTriggerCooldownMs(env = process.env) {
|
|
40
|
+
const raw = safeString(env[DISPATCH_TRIGGER_COOLDOWN_ENV]).trim();
|
|
41
|
+
if (raw === '') return DEFAULT_DISPATCH_TRIGGER_COOLDOWN_MS;
|
|
42
|
+
const parsed = Number.parseInt(raw, 10);
|
|
43
|
+
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_DISPATCH_TRIGGER_COOLDOWN_MS;
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractIssueKey(triggerMessage) {
|
|
48
|
+
const match = safeString(triggerMessage).match(/\b([A-Z][A-Z0-9]+-\d+)\b/i);
|
|
49
|
+
return match?.[1]?.toUpperCase() || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function issueCooldownStatePath(teamDirPath) {
|
|
53
|
+
return join(teamDirPath, 'dispatch', 'issue-cooldown.json');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function triggerCooldownStatePath(teamDirPath) {
|
|
57
|
+
return join(teamDirPath, 'dispatch', 'trigger-cooldown.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readIssueCooldownState(teamDirPath) {
|
|
61
|
+
const fallback = { by_issue: {} };
|
|
62
|
+
const parsed = await readJson(issueCooldownStatePath(teamDirPath), fallback);
|
|
63
|
+
if (!parsed || typeof parsed !== 'object' || typeof parsed.by_issue !== 'object' || parsed.by_issue === null) {
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function readTriggerCooldownState(teamDirPath) {
|
|
70
|
+
const fallback = { by_trigger: {} };
|
|
71
|
+
const parsed = await readJson(triggerCooldownStatePath(teamDirPath), fallback);
|
|
72
|
+
if (!parsed || typeof parsed !== 'object' || typeof parsed.by_trigger !== 'object' || parsed.by_trigger === null) {
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeTriggerKey(value) {
|
|
79
|
+
return safeString(value).replace(/\s+/g, ' ').trim();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseTriggerCooldownEntry(entry) {
|
|
83
|
+
if (typeof entry === 'number') {
|
|
84
|
+
return { at: entry, lastRequestId: '' };
|
|
85
|
+
}
|
|
86
|
+
if (!entry || typeof entry !== 'object') {
|
|
87
|
+
return { at: NaN, lastRequestId: '' };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
at: Number(entry.at),
|
|
91
|
+
lastRequestId: safeString(entry.last_request_id).trim(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
24
94
|
|
|
25
95
|
async function withDispatchLock(teamDirPath, fn) {
|
|
26
96
|
const lockDir = join(teamDirPath, 'dispatch', '.lock');
|
|
@@ -117,18 +187,15 @@ async function withMailboxLock(teamDirPath, workerName, fn) {
|
|
|
117
187
|
}
|
|
118
188
|
|
|
119
189
|
function defaultInjectTarget(request, config) {
|
|
190
|
+
if (request.to_worker === 'leader-fixed') {
|
|
191
|
+
if (config.leader_pane_id) return { type: 'pane', value: config.leader_pane_id };
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
120
194
|
if (request.pane_id) return { type: 'pane', value: request.pane_id };
|
|
121
195
|
if (typeof request.worker_index === 'number' && Array.isArray(config?.workers)) {
|
|
122
196
|
const worker = config.workers.find((candidate) => Number(candidate?.index) === request.worker_index);
|
|
123
197
|
if (worker?.pane_id) return { type: 'pane', value: worker.pane_id };
|
|
124
198
|
}
|
|
125
|
-
// Leader-fixed fallback: use config.leader_pane_id when request has no
|
|
126
|
-
// pane_id or worker_index (leader is not a worker). Without this, leader
|
|
127
|
-
// dispatch falls through to the session target which hits the active pane
|
|
128
|
-
// (likely a worker). Fixes #433.
|
|
129
|
-
if (request.to_worker === 'leader-fixed' && config.leader_pane_id) {
|
|
130
|
-
return { type: 'pane', value: config.leader_pane_id };
|
|
131
|
-
}
|
|
132
199
|
if (typeof request.worker_index === 'number' && config.tmux_session) {
|
|
133
200
|
return { type: 'pane', value: `${config.tmux_session}.${request.worker_index}` };
|
|
134
201
|
}
|
|
@@ -136,6 +203,30 @@ function defaultInjectTarget(request, config) {
|
|
|
136
203
|
return null;
|
|
137
204
|
}
|
|
138
205
|
|
|
206
|
+
async function appendLeaderNotificationDeferredEvent({
|
|
207
|
+
stateDir,
|
|
208
|
+
teamName,
|
|
209
|
+
request,
|
|
210
|
+
reason,
|
|
211
|
+
nowIso,
|
|
212
|
+
}) {
|
|
213
|
+
const eventsDir = join(stateDir, 'team', teamName, 'events');
|
|
214
|
+
const eventsPath = join(eventsDir, 'events.ndjson');
|
|
215
|
+
const event = {
|
|
216
|
+
event_id: `leader-deferred-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
|
217
|
+
team: teamName,
|
|
218
|
+
type: LEADER_NOTIFICATION_DEFERRED_TYPE,
|
|
219
|
+
worker: request.to_worker,
|
|
220
|
+
to_worker: request.to_worker,
|
|
221
|
+
reason,
|
|
222
|
+
created_at: nowIso,
|
|
223
|
+
request_id: request.request_id,
|
|
224
|
+
...(request.message_id ? { message_id: request.message_id } : {}),
|
|
225
|
+
};
|
|
226
|
+
await mkdir(eventsDir, { recursive: true }).catch(() => {});
|
|
227
|
+
await appendFile(eventsPath, JSON.stringify(event) + '\n').catch(() => {});
|
|
228
|
+
}
|
|
229
|
+
|
|
139
230
|
function resolveWorkerCliForRequest(request, config) {
|
|
140
231
|
const workers = Array.isArray(config?.workers) ? config.workers : [];
|
|
141
232
|
const idx = Number.isFinite(request?.worker_index) ? Number(request.worker_index) : null;
|
|
@@ -156,6 +247,19 @@ function capturedPaneContainsTrigger(captured, trigger) {
|
|
|
156
247
|
return normalizeCaptureText(captured).includes(normalizeCaptureText(trigger));
|
|
157
248
|
}
|
|
158
249
|
|
|
250
|
+
function capturedPaneContainsTriggerNearTail(captured, trigger, nonEmptyTailLines = 24) {
|
|
251
|
+
if (!captured || !trigger) return false;
|
|
252
|
+
const normalizedTrigger = normalizeCaptureText(trigger);
|
|
253
|
+
if (!normalizedTrigger) return false;
|
|
254
|
+
const lines = safeString(captured)
|
|
255
|
+
.split('\n')
|
|
256
|
+
.map((line) => line.replace(/\r/g, '').trim())
|
|
257
|
+
.filter((line) => line.length > 0);
|
|
258
|
+
if (lines.length === 0) return false;
|
|
259
|
+
const tail = lines.slice(-Math.max(1, nonEmptyTailLines)).join(' ');
|
|
260
|
+
return normalizeCaptureText(tail).includes(normalizedTrigger);
|
|
261
|
+
}
|
|
262
|
+
|
|
159
263
|
// Ported from src/team/tmux-session.ts:949-963 — detects active CLI task indicators.
|
|
160
264
|
function paneHasActiveTask(captured) {
|
|
161
265
|
const lines = safeString(captured)
|
|
@@ -163,6 +267,7 @@ function paneHasActiveTask(captured) {
|
|
|
163
267
|
.map((line) => line.replace(/\r/g, '').trim())
|
|
164
268
|
.filter((line) => line.length > 0);
|
|
165
269
|
const tail = lines.slice(-40);
|
|
270
|
+
if (tail.some((line) => /\b\d+\s+background terminal running\b/i.test(line))) return true;
|
|
166
271
|
if (tail.some((line) => /esc to interrupt/i.test(line))) return true;
|
|
167
272
|
if (tail.some((line) => /\bbackground terminal running\b/i.test(line))) return true;
|
|
168
273
|
if (tail.some((line) => /^•\s.+\(.+•\s*esc to interrupt\)$/i.test(line))) return true;
|
|
@@ -171,6 +276,40 @@ function paneHasActiveTask(captured) {
|
|
|
171
276
|
return false;
|
|
172
277
|
}
|
|
173
278
|
|
|
279
|
+
function paneIsBootstrapping(captured) {
|
|
280
|
+
const lines = safeString(captured)
|
|
281
|
+
.split('\n')
|
|
282
|
+
.map((line) => line.replace(/\r/g, '').trim())
|
|
283
|
+
.filter((line) => line.length > 0);
|
|
284
|
+
return lines.some((line) =>
|
|
285
|
+
/\b(loading|initializing|starting up)\b/i.test(line)
|
|
286
|
+
|| /\bmodel:\s*loading\b/i.test(line)
|
|
287
|
+
|| /\bconnecting\s+to\b/i.test(line),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function paneLooksReady(captured) {
|
|
292
|
+
const content = safeString(captured).trimEnd();
|
|
293
|
+
if (content === '') return false;
|
|
294
|
+
|
|
295
|
+
const lines = content
|
|
296
|
+
.split('\n')
|
|
297
|
+
.map((line) => line.replace(/\r/g, ''))
|
|
298
|
+
.map((line) => line.trimEnd())
|
|
299
|
+
.filter((line) => line.trim() !== '');
|
|
300
|
+
|
|
301
|
+
if (paneIsBootstrapping(content)) return false;
|
|
302
|
+
|
|
303
|
+
const lastLine = lines.length > 0 ? lines[lines.length - 1] : '';
|
|
304
|
+
if (/^\s*[›>❯]\s*/u.test(lastLine)) return true;
|
|
305
|
+
|
|
306
|
+
const hasCodexPromptLine = lines.some((line) => /^\s*›\s*/u.test(line));
|
|
307
|
+
const hasClaudePromptLine = lines.some((line) => /^\s*❯\s*/u.test(line));
|
|
308
|
+
if (hasCodexPromptLine || hasClaudePromptLine) return true;
|
|
309
|
+
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
174
313
|
const INJECT_VERIFY_DELAY_MS = 250;
|
|
175
314
|
const INJECT_VERIFY_ROUNDS = 3;
|
|
176
315
|
|
|
@@ -237,16 +376,29 @@ async function injectDispatchRequest(request, config, cwd) {
|
|
|
237
376
|
for (let round = 0; round < INJECT_VERIFY_ROUNDS; round++) {
|
|
238
377
|
await new Promise((r) => setTimeout(r, INJECT_VERIFY_DELAY_MS));
|
|
239
378
|
try {
|
|
240
|
-
// Primary: trigger text no longer in narrow input area
|
|
379
|
+
// Primary: trigger text no longer in narrow input area.
|
|
380
|
+
// Secondary guard: also inspect the recent non-empty tail of wide capture.
|
|
381
|
+
// This avoids false confirmations when Codex leaves the unsent draft just
|
|
382
|
+
// above a large blank area (narrow capture misses it) while still avoiding
|
|
383
|
+
// full-scrollback false positives.
|
|
241
384
|
const narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
|
|
242
|
-
if (!capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message)) {
|
|
243
|
-
return { ok: true, reason: 'tmux_send_keys_confirmed', pane: resolution.paneTarget };
|
|
244
|
-
}
|
|
245
|
-
// Secondary: worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
|
|
246
385
|
const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
|
|
386
|
+
// Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
|
|
247
387
|
if (paneHasActiveTask(wideCap.stdout)) {
|
|
248
388
|
return { ok: true, reason: 'tmux_send_keys_confirmed_active_task', pane: resolution.paneTarget };
|
|
249
389
|
}
|
|
390
|
+
// Do not declare success while a *worker* pane is still bootstrapping / not
|
|
391
|
+
// input-ready. Otherwise a pre-ready send can be marked "confirmed" and later
|
|
392
|
+
// appear as a stuck unsent draft once the UI finishes loading.
|
|
393
|
+
// Keep leader-fixed behavior unchanged to avoid regressing leader notification flow.
|
|
394
|
+
if (request.to_worker !== 'leader-fixed' && !paneLooksReady(wideCap.stdout)) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
|
|
398
|
+
const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
|
|
399
|
+
if (!triggerInNarrow && !triggerNearTail) {
|
|
400
|
+
return { ok: true, reason: 'tmux_send_keys_confirmed', pane: resolution.paneTarget };
|
|
401
|
+
}
|
|
250
402
|
} catch {
|
|
251
403
|
// capture failed; fall through to retry C-m
|
|
252
404
|
}
|
|
@@ -302,6 +454,8 @@ export async function drainPendingTeamDispatch({
|
|
|
302
454
|
let processed = 0;
|
|
303
455
|
let skipped = 0;
|
|
304
456
|
let failed = 0;
|
|
457
|
+
const issueCooldownMs = resolveIssueDispatchCooldownMs();
|
|
458
|
+
const triggerCooldownMs = resolveDispatchTriggerCooldownMs();
|
|
305
459
|
|
|
306
460
|
for (const teamName of teams) {
|
|
307
461
|
if (processed >= maxPerTick) break;
|
|
@@ -315,6 +469,11 @@ export async function drainPendingTeamDispatch({
|
|
|
315
469
|
await withDispatchLock(teamDirPath, async () => {
|
|
316
470
|
const requests = await readJson(requestsPath, []);
|
|
317
471
|
if (!Array.isArray(requests)) return;
|
|
472
|
+
const issueCooldownState = await readIssueCooldownState(teamDirPath);
|
|
473
|
+
const triggerCooldownState = await readTriggerCooldownState(teamDirPath);
|
|
474
|
+
const issueCooldownByIssue = issueCooldownState.by_issue || {};
|
|
475
|
+
const triggerCooldownByKey = triggerCooldownState.by_trigger || {};
|
|
476
|
+
const nowMs = Date.now();
|
|
318
477
|
|
|
319
478
|
let mutated = false;
|
|
320
479
|
for (const request of requests) {
|
|
@@ -325,7 +484,66 @@ export async function drainPendingTeamDispatch({
|
|
|
325
484
|
continue;
|
|
326
485
|
}
|
|
327
486
|
|
|
487
|
+
if (request.to_worker === 'leader-fixed' && !safeString(config?.leader_pane_id).trim()) {
|
|
488
|
+
const nowIso = new Date().toISOString();
|
|
489
|
+
request.updated_at = nowIso;
|
|
490
|
+
request.last_reason = LEADER_PANE_MISSING_DEFERRED_REASON;
|
|
491
|
+
request.status = 'pending';
|
|
492
|
+
skipped += 1;
|
|
493
|
+
mutated = true;
|
|
494
|
+
await appendDispatchLog(logsDir, {
|
|
495
|
+
type: 'dispatch_deferred',
|
|
496
|
+
team: teamName,
|
|
497
|
+
request_id: request.request_id,
|
|
498
|
+
worker: request.to_worker,
|
|
499
|
+
to_worker: request.to_worker,
|
|
500
|
+
message_id: request.message_id || null,
|
|
501
|
+
reason: LEADER_PANE_MISSING_DEFERRED_REASON,
|
|
502
|
+
status: 'pending',
|
|
503
|
+
tmux_injection_attempted: false,
|
|
504
|
+
});
|
|
505
|
+
await appendLeaderNotificationDeferredEvent({
|
|
506
|
+
stateDir,
|
|
507
|
+
teamName,
|
|
508
|
+
request,
|
|
509
|
+
reason: LEADER_PANE_MISSING_DEFERRED_REASON,
|
|
510
|
+
nowIso,
|
|
511
|
+
});
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const issueKey = extractIssueKey(request.trigger_message);
|
|
516
|
+
if (issueCooldownMs > 0 && issueKey) {
|
|
517
|
+
const lastInjectedMs = Number(issueCooldownByIssue[issueKey]);
|
|
518
|
+
if (Number.isFinite(lastInjectedMs) && lastInjectedMs > 0 && nowMs - lastInjectedMs < issueCooldownMs) {
|
|
519
|
+
skipped += 1;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const triggerKey = normalizeTriggerKey(request.trigger_message);
|
|
525
|
+
if (triggerCooldownMs > 0 && triggerKey) {
|
|
526
|
+
const parsed = parseTriggerCooldownEntry(triggerCooldownByKey[triggerKey]);
|
|
527
|
+
const withinCooldown = Number.isFinite(parsed.at) && parsed.at > 0 && nowMs - parsed.at < triggerCooldownMs;
|
|
528
|
+
const sameRequestRetry = parsed.lastRequestId !== '' && parsed.lastRequestId === safeString(request.request_id).trim();
|
|
529
|
+
if (withinCooldown && !sameRequestRetry) {
|
|
530
|
+
skipped += 1;
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
328
535
|
const result = await injector(request, config, resolve(cwd));
|
|
536
|
+
if (issueKey && issueCooldownMs > 0) {
|
|
537
|
+
issueCooldownByIssue[issueKey] = Date.now();
|
|
538
|
+
mutated = true;
|
|
539
|
+
}
|
|
540
|
+
if (triggerKey && triggerCooldownMs > 0) {
|
|
541
|
+
triggerCooldownByKey[triggerKey] = {
|
|
542
|
+
at: Date.now(),
|
|
543
|
+
last_request_id: safeString(request.request_id).trim(),
|
|
544
|
+
};
|
|
545
|
+
mutated = true;
|
|
546
|
+
}
|
|
329
547
|
const nowIso = new Date().toISOString();
|
|
330
548
|
request.attempt_count = Number.isFinite(request.attempt_count) ? Math.max(0, request.attempt_count + 1) : 1;
|
|
331
549
|
request.updated_at = nowIso;
|
|
@@ -401,6 +619,10 @@ export async function drainPendingTeamDispatch({
|
|
|
401
619
|
}
|
|
402
620
|
|
|
403
621
|
if (mutated) {
|
|
622
|
+
issueCooldownState.by_issue = issueCooldownByIssue;
|
|
623
|
+
await writeJsonAtomic(issueCooldownStatePath(teamDirPath), issueCooldownState);
|
|
624
|
+
triggerCooldownState.by_trigger = triggerCooldownByKey;
|
|
625
|
+
await writeJsonAtomic(triggerCooldownStatePath(teamDirPath), triggerCooldownState);
|
|
404
626
|
await writeJsonAtomic(requestsPath, requests);
|
|
405
627
|
}
|
|
406
628
|
});
|
|
@@ -10,6 +10,8 @@ import { readJsonIfExists, getScopedStateDirsForCurrentSession } from './state-i
|
|
|
10
10
|
import { runProcess } from './process-runner.js';
|
|
11
11
|
import { logTmuxHookEvent } from './log.js';
|
|
12
12
|
import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
|
|
13
|
+
const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
|
|
14
|
+
const LEADER_NOTIFICATION_DEFERRED_TYPE = 'leader_notification_deferred';
|
|
13
15
|
|
|
14
16
|
export function resolveLeaderNudgeIntervalMs() {
|
|
15
17
|
const raw = safeString(process.env.OMX_TEAM_LEADER_NUDGE_MS || '');
|
|
@@ -109,6 +111,26 @@ export async function emitTeamNudgeEvent(cwd, teamName, reason, nowIso) {
|
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
async function emitLeaderNudgeDeferredEvent(cwd, teamName, reason, nowIso) {
|
|
115
|
+
const eventsDir = join(cwd, '.omx', 'state', 'team', teamName, 'events');
|
|
116
|
+
const eventsPath = join(eventsDir, 'events.ndjson');
|
|
117
|
+
try {
|
|
118
|
+
await mkdir(eventsDir, { recursive: true });
|
|
119
|
+
const event = {
|
|
120
|
+
event_id: `leader-deferred-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
|
121
|
+
team: teamName,
|
|
122
|
+
type: LEADER_NOTIFICATION_DEFERRED_TYPE,
|
|
123
|
+
worker: 'leader-fixed',
|
|
124
|
+
to_worker: 'leader-fixed',
|
|
125
|
+
reason,
|
|
126
|
+
created_at: nowIso,
|
|
127
|
+
};
|
|
128
|
+
await appendFile(eventsPath, JSON.stringify(event) + '\n');
|
|
129
|
+
} catch {
|
|
130
|
+
// Best effort
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
112
134
|
export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputedLeaderStale }) {
|
|
113
135
|
const intervalMs = resolveLeaderNudgeIntervalMs();
|
|
114
136
|
const idleCooldownMs = resolveLeaderAllIdleNudgeCooldownMs();
|
|
@@ -163,8 +185,8 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
163
185
|
} catch {
|
|
164
186
|
// ignore
|
|
165
187
|
}
|
|
166
|
-
|
|
167
|
-
|
|
188
|
+
if (!tmuxSession && !leaderPaneId) continue;
|
|
189
|
+
const tmuxTarget = leaderPaneId;
|
|
168
190
|
|
|
169
191
|
const paneStatus = tmuxSession
|
|
170
192
|
? await checkWorkerPanesAlive(tmuxSession)
|
|
@@ -234,6 +256,24 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
|
|
|
234
256
|
const capped = text.length > 180 ? `${text.slice(0, 177)}...` : text;
|
|
235
257
|
const markedText = `${capped} ${DEFAULT_MARKER}`;
|
|
236
258
|
|
|
259
|
+
if (!tmuxTarget) {
|
|
260
|
+
await emitLeaderNudgeDeferredEvent(cwd, teamName, LEADER_PANE_MISSING_NO_INJECTION_REASON, nowIso);
|
|
261
|
+
try {
|
|
262
|
+
await logTmuxHookEvent(logsDir, {
|
|
263
|
+
timestamp: nowIso,
|
|
264
|
+
type: LEADER_NOTIFICATION_DEFERRED_TYPE,
|
|
265
|
+
team: teamName,
|
|
266
|
+
worker: 'leader-fixed',
|
|
267
|
+
to_worker: 'leader-fixed',
|
|
268
|
+
reason: LEADER_PANE_MISSING_NO_INJECTION_REASON,
|
|
269
|
+
leader_pane_id: null,
|
|
270
|
+
tmux_session: tmuxSession || null,
|
|
271
|
+
tmux_injection_attempted: false,
|
|
272
|
+
});
|
|
273
|
+
} catch { /* ignore */ }
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
237
277
|
try {
|
|
238
278
|
await runProcess('tmux', ['send-keys', '-t', tmuxTarget, '-l', markedText], 3000);
|
|
239
279
|
await new Promise(r => setTimeout(r, 100));
|
|
@@ -194,6 +194,43 @@ export async function readTeamWorkersForIdleCheck(stateDir, teamName) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
async function emitLeaderPaneMissingDeferred({
|
|
198
|
+
stateDir,
|
|
199
|
+
logsDir,
|
|
200
|
+
teamName,
|
|
201
|
+
workerName,
|
|
202
|
+
tmuxSession,
|
|
203
|
+
leaderPaneId,
|
|
204
|
+
reason = 'leader_pane_missing_no_injection',
|
|
205
|
+
}) {
|
|
206
|
+
const nowIso = new Date().toISOString();
|
|
207
|
+
await logTmuxHookEvent(logsDir, {
|
|
208
|
+
timestamp: nowIso,
|
|
209
|
+
type: 'leader_notification_deferred',
|
|
210
|
+
team: teamName,
|
|
211
|
+
worker: workerName,
|
|
212
|
+
to_worker: 'leader-fixed',
|
|
213
|
+
reason,
|
|
214
|
+
leader_pane_id: leaderPaneId || null,
|
|
215
|
+
tmux_session: tmuxSession || null,
|
|
216
|
+
tmux_injection_attempted: false,
|
|
217
|
+
}).catch(() => {});
|
|
218
|
+
|
|
219
|
+
const eventsDir = join(stateDir, 'team', teamName, 'events');
|
|
220
|
+
const eventsPath = join(eventsDir, 'events.ndjson');
|
|
221
|
+
await mkdir(eventsDir, { recursive: true }).catch(() => {});
|
|
222
|
+
const event = {
|
|
223
|
+
event_id: `leader-deferred-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
|
224
|
+
team: teamName,
|
|
225
|
+
type: 'leader_notification_deferred',
|
|
226
|
+
worker: workerName,
|
|
227
|
+
to_worker: 'leader-fixed',
|
|
228
|
+
reason,
|
|
229
|
+
created_at: nowIso,
|
|
230
|
+
};
|
|
231
|
+
await appendFile(eventsPath, JSON.stringify(event) + '\n').catch(() => {});
|
|
232
|
+
}
|
|
233
|
+
|
|
197
234
|
export async function updateWorkerHeartbeat(stateDir, teamName, workerName) {
|
|
198
235
|
const heartbeatPath = join(stateDir, 'team', teamName, 'workers', workerName, 'heartbeat.json');
|
|
199
236
|
let turnCount = 0;
|
|
@@ -228,8 +265,6 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
228
265
|
const teamInfo = await readTeamWorkersForIdleCheck(stateDir, teamName);
|
|
229
266
|
if (!teamInfo) return;
|
|
230
267
|
const { workers, tmuxSession, leaderPaneId } = teamInfo;
|
|
231
|
-
const tmuxTarget = leaderPaneId || tmuxSession;
|
|
232
|
-
if (!tmuxTarget) return;
|
|
233
268
|
|
|
234
269
|
// Check cooldown to prevent notification spam
|
|
235
270
|
const idleStatePath = join(stateDir, 'team', teamName, 'all-workers-idle.json');
|
|
@@ -252,8 +287,21 @@ export async function maybeNotifyLeaderAllWorkersIdle({ cwd, stateDir, logsDir,
|
|
|
252
287
|
);
|
|
253
288
|
if (!allIdle) return;
|
|
254
289
|
|
|
290
|
+
if (!leaderPaneId) {
|
|
291
|
+
await emitLeaderPaneMissingDeferred({
|
|
292
|
+
stateDir,
|
|
293
|
+
logsDir,
|
|
294
|
+
teamName,
|
|
295
|
+
workerName,
|
|
296
|
+
tmuxSession,
|
|
297
|
+
leaderPaneId,
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
255
302
|
const N = workers.length;
|
|
256
303
|
const message = `[OMX] All ${N} worker${N === 1 ? '' : 's'} idle. Ready for next instructions. ${DEFAULT_MARKER}`;
|
|
304
|
+
const tmuxTarget = leaderPaneId;
|
|
257
305
|
|
|
258
306
|
try {
|
|
259
307
|
await runProcess('tmux', ['send-keys', '-t', tmuxTarget, '-l', message], 3000);
|
|
@@ -382,8 +430,19 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
|
|
|
382
430
|
const teamInfo = await readTeamWorkersForIdleCheck(stateDir, teamName);
|
|
383
431
|
if (!teamInfo) return;
|
|
384
432
|
const { tmuxSession, leaderPaneId } = teamInfo;
|
|
385
|
-
|
|
386
|
-
if (!
|
|
433
|
+
|
|
434
|
+
if (!leaderPaneId) {
|
|
435
|
+
await emitLeaderPaneMissingDeferred({
|
|
436
|
+
stateDir,
|
|
437
|
+
logsDir,
|
|
438
|
+
teamName,
|
|
439
|
+
workerName,
|
|
440
|
+
tmuxSession,
|
|
441
|
+
leaderPaneId,
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const tmuxTarget = leaderPaneId;
|
|
387
446
|
|
|
388
447
|
// Build notification message with context
|
|
389
448
|
const parts = [`[OMX] ${workerName} idle`];
|
|
@@ -25,6 +25,40 @@ const VERDICT_PATTERNS = [
|
|
|
25
25
|
*/
|
|
26
26
|
const VERDICT_CANDIDATE_RE = /(?:\*\*Status\*\*\s*:|Verdict\s*:)/i;
|
|
27
27
|
|
|
28
|
+
function extractJsonCandidates(rawMessage) {
|
|
29
|
+
const message = safeString(rawMessage).trim();
|
|
30
|
+
if (!message) return [];
|
|
31
|
+
|
|
32
|
+
const candidates = [message];
|
|
33
|
+
const fencePattern = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
34
|
+
for (const match of message.matchAll(fencePattern)) {
|
|
35
|
+
const block = safeString(match[1]).trim();
|
|
36
|
+
if (block) candidates.push(block);
|
|
37
|
+
}
|
|
38
|
+
return candidates;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function maybePersistRuntimeVisualFeedback({ cwd, output, sessionId }) {
|
|
42
|
+
if (!cwd || !output) return;
|
|
43
|
+
|
|
44
|
+
const candidates = extractJsonCandidates(output);
|
|
45
|
+
if (candidates.length === 0) return;
|
|
46
|
+
|
|
47
|
+
const { buildVisualLoopFeedback } = await import('../../dist/visual/verdict.js');
|
|
48
|
+
const { recordRalphVisualFeedback } = await import('../../dist/ralph/persistence.js');
|
|
49
|
+
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(candidate);
|
|
53
|
+
const feedback = buildVisualLoopFeedback(parsed);
|
|
54
|
+
await recordRalphVisualFeedback(cwd, feedback, sessionId || undefined);
|
|
55
|
+
return;
|
|
56
|
+
} catch {
|
|
57
|
+
// Try next candidate
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
/**
|
|
29
63
|
* Attempt to extract a structured verdict from free-form text.
|
|
30
64
|
* Returns `{ verdict, raw }` on success, `null` otherwise.
|
|
@@ -50,12 +84,27 @@ export function parseVisualVerdict(text) {
|
|
|
50
84
|
*
|
|
51
85
|
* Module import failure is handled by the caller in notify-hook.js.
|
|
52
86
|
*/
|
|
53
|
-
export async function maybePersistVisualVerdict({ payload, stateDir, logsDir, sessionId, turnId }) {
|
|
87
|
+
export async function maybePersistVisualVerdict({ cwd, payload, stateDir, logsDir, sessionId, turnId }) {
|
|
54
88
|
const output = safeString(
|
|
55
89
|
payload?.['last-assistant-message'] || payload?.last_assistant_message || '',
|
|
56
90
|
);
|
|
57
91
|
if (!output) return;
|
|
58
92
|
|
|
93
|
+
// Runtime visual feedback (JSON/fenced JSON) for ralph-progress persistence.
|
|
94
|
+
// Non-fatal and observable via warn-level structured logging.
|
|
95
|
+
try {
|
|
96
|
+
await maybePersistRuntimeVisualFeedback({ cwd, output, sessionId });
|
|
97
|
+
} catch (err) {
|
|
98
|
+
await logNotifyHookEvent(logsDir, {
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
level: 'warn',
|
|
101
|
+
type: 'visual_runtime_feedback_persist_failure',
|
|
102
|
+
error: err?.message || String(err),
|
|
103
|
+
session_id: sessionId,
|
|
104
|
+
turn_id: turnId,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
59
108
|
const parsed = parseVisualVerdict(output);
|
|
60
109
|
|
|
61
110
|
if (!parsed) {
|