oh-my-codex 0.18.13 → 0.18.15
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/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +41 -18
- package/dist/agents/__tests__/native-config.test.js +62 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +15 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.js +7 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.d.ts +2 -0
- package/dist/autopilot/__tests__/planner-routing.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/planner-routing.test.js +74 -0
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +315 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/completion-gate.js +1 -1
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +2 -2
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autopilot/fsm.js +3 -0
- package/dist/autopilot/fsm.js.map +1 -1
- package/dist/autopilot/planner-routing.d.ts +21 -0
- package/dist/autopilot/planner-routing.d.ts.map +1 -0
- package/dist/autopilot/planner-routing.js +59 -0
- package/dist/autopilot/planner-routing.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +30 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/autoresearch/goal.d.ts.map +1 -1
- package/dist/autoresearch/goal.js +10 -1
- package/dist/autoresearch/goal.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js +33 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js +3 -0
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.js +158 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.js +33 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js +13 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +132 -2
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +5 -0
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +40 -0
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +4 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +7 -1
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/performance-goal.test.js +22 -0
- package/dist/cli/__tests__/performance-goal.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts +2 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts.map +1 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +64 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +96 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +162 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js +36 -0
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +70 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +33 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +17 -3
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +37 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +81 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/url.test.d.ts +2 -0
- package/dist/cli/__tests__/url.test.d.ts.map +1 -0
- package/dist/cli/__tests__/url.test.js +82 -0
- package/dist/cli/__tests__/url.test.js.map +1 -0
- package/dist/cli/autoresearch-goal.d.ts.map +1 -1
- package/dist/cli/autoresearch-goal.js +2 -1
- package/dist/cli/autoresearch-goal.js.map +1 -1
- package/dist/cli/autoresearch-intake.d.ts.map +1 -1
- package/dist/cli/autoresearch-intake.js +2 -0
- package/dist/cli/autoresearch-intake.js.map +1 -1
- package/dist/cli/doctor.d.ts +39 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +460 -40
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +71 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/performance-goal.d.ts.map +1 -1
- package/dist/cli/performance-goal.js +4 -2
- package/dist/cli/performance-goal.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +88 -67
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.d.ts +2 -0
- package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -1
- package/dist/cli/project-runtime-codex-homes.js +112 -5
- package/dist/cli/project-runtime-codex-homes.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +15 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +5 -1
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +23 -4
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/url.d.ts +8 -0
- package/dist/cli/url.d.ts.map +1 -0
- package/dist/cli/url.js +69 -0
- package/dist/cli/url.js.map +1 -0
- package/dist/config/__tests__/codex-hooks.test.js +20 -4
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +21 -1
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +1 -4
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/models.d.ts +5 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +23 -0
- package/dist/config/models.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +2 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +10 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +7 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +46 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +100 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +225 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +563 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +187 -12
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +225 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +4 -0
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +137 -2
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +107 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +17 -0
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +21 -7
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +9 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +43 -5
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/types.d.ts +6 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +24 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +53 -0
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +2 -0
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +32 -6
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +11 -5
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-autopilot-gates.test.js +48 -0
- package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +1 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/http-client.test.js +9 -0
- package/dist/notifications/__tests__/http-client.test.js.map +1 -1
- package/dist/notifications/http-client.js +1 -1
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/performance-goal/artifacts.d.ts.map +1 -1
- package/dist/performance-goal/artifacts.js +10 -1
- package/dist/performance-goal/artifacts.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +112 -0
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +133 -21
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1251 -13
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +601 -24
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +1 -0
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +279 -86
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +87 -8
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/run-compiled-ci.d.ts +2 -0
- package/dist/scripts/run-compiled-ci.d.ts.map +1 -0
- package/dist/scripts/run-compiled-ci.js +77 -0
- package/dist/scripts/run-compiled-ci.js.map +1 -0
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +1 -0
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/session-history/search.d.ts.map +1 -1
- package/dist/session-history/search.js +24 -9
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/args.test.d.ts +2 -0
- package/dist/sidecar/__tests__/args.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/args.test.js +18 -0
- package/dist/sidecar/__tests__/args.test.js.map +1 -0
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +12 -4
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +107 -3
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +57 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +22 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +57 -1
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +1468 -110
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +63 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +24 -1
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +512 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +42 -9
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +16 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +41 -1
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +116 -30
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +9 -1
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/types.d.ts +2 -0
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +3 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +20 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts +38 -3
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +275 -36
- 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 +26 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +388 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +3 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +23 -0
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +275 -12
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/__tests__/url-reader.test.d.ts +2 -0
- package/dist/url-reader/__tests__/url-reader.test.d.ts.map +1 -0
- package/dist/url-reader/__tests__/url-reader.test.js +433 -0
- package/dist/url-reader/__tests__/url-reader.test.js.map +1 -0
- package/dist/url-reader/index.d.ts +4 -0
- package/dist/url-reader/index.d.ts.map +1 -0
- package/dist/url-reader/index.js +522 -0
- package/dist/url-reader/index.js.map +1 -0
- package/dist/url-reader/types.d.ts +41 -0
- package/dist/url-reader/types.d.ts.map +1 -0
- package/dist/url-reader/types.js +2 -0
- package/dist/url-reader/types.js.map +1 -0
- package/dist/utils/__tests__/agents-md.test.js +39 -1
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +28 -0
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts +4 -0
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +28 -0
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts +7 -3
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +16 -8
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/platform-command.d.ts +7 -1
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +12 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +4 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/vscode/__tests__/index.test.d.ts +2 -0
- package/dist/vscode/__tests__/index.test.d.ts.map +1 -0
- package/dist/vscode/__tests__/index.test.js +236 -0
- package/dist/vscode/__tests__/index.test.js.map +1 -0
- package/dist/vscode/index.d.ts +76 -0
- package/dist/vscode/index.d.ts.map +1 -0
- package/dist/vscode/index.js +196 -0
- package/dist/vscode/index.js.map +1 -0
- package/package.json +3 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +59 -8
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +9 -5
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +22 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +61 -0
- package/skills/autopilot/SKILL.md +9 -5
- package/skills/autoresearch-goal/SKILL.md +3 -0
- package/skills/ecomode/SKILL.md +1 -1
- package/skills/ecomode/references/agent-tiers.md +61 -0
- package/skills/performance-goal/SKILL.md +3 -0
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +22 -4
- package/skills/ultrawork/SKILL.md +2 -2
- package/skills/ultrawork/references/agent-tiers.md +61 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +1560 -181
- package/src/scripts/codex-native-hook.ts +663 -24
- package/src/scripts/notify-hook/team-leader-nudge.ts +298 -91
- package/src/scripts/notify-hook/team-tmux-guard.ts +92 -8
- package/src/scripts/notify-hook/tmux-injection.ts +1 -1
- package/src/scripts/run-compiled-ci.ts +85 -0
- package/src/scripts/sync-plugin-mirror.ts +1 -0
- package/templates/AGENTS.md +6 -0
|
@@ -12,7 +12,7 @@ import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionS
|
|
|
12
12
|
import { appendTeamEvent, readTeamLeaderAttention, readTeamConfig, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
|
|
13
13
|
import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
|
|
14
14
|
import { findGitLayout } from "../utils/git-layout.js";
|
|
15
|
-
import { getBaseStateDir, getStateFilePath, getStatePath } from "../mcp/state-paths.js";
|
|
15
|
+
import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
|
|
16
16
|
import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, } from "../hooks/keyword-detector.js";
|
|
17
17
|
import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
|
|
18
18
|
import { readTeamModeConfig } from "../config/team-mode.js";
|
|
@@ -30,6 +30,7 @@ import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDec
|
|
|
30
30
|
import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
|
|
31
31
|
import { readRunState } from "../runtime/run-state.js";
|
|
32
32
|
import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
|
|
33
|
+
import { buildCodexGoalTerminalCleanupNotice, } from "../goal-workflows/codex-goal-snapshot.js";
|
|
33
34
|
import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
|
|
34
35
|
import { parseUltragoalSteeringDirective, steerUltragoal, } from "../ultragoal/artifacts.js";
|
|
35
36
|
import { triagePrompt } from "../hooks/triage-heuristic.js";
|
|
@@ -45,6 +46,8 @@ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
|
45
46
|
const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
|
|
46
47
|
const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
|
|
47
48
|
const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
|
|
49
|
+
const NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE = "native-subagent-capacity-blocker.json";
|
|
50
|
+
const NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS = 30 * 60_000;
|
|
48
51
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
49
52
|
const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
|
|
50
53
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
@@ -381,6 +384,20 @@ function readHookEventName(payload) {
|
|
|
381
384
|
}
|
|
382
385
|
return null;
|
|
383
386
|
}
|
|
387
|
+
function sanitizeCodexHookOutput(hookEventName, output) {
|
|
388
|
+
if (!output || hookEventName !== "PreToolUse")
|
|
389
|
+
return output;
|
|
390
|
+
const systemMessage = safeString(output.systemMessage).trim();
|
|
391
|
+
if (systemMessage)
|
|
392
|
+
return { systemMessage };
|
|
393
|
+
const reason = safeString(output.reason).trim();
|
|
394
|
+
const hookSpecificOutput = output.hookSpecificOutput;
|
|
395
|
+
const additionalContext = hookSpecificOutput && typeof hookSpecificOutput === "object"
|
|
396
|
+
? safeString(hookSpecificOutput.additionalContext).trim()
|
|
397
|
+
: "";
|
|
398
|
+
const derivedSystemMessage = [reason, additionalContext].filter(Boolean).join("\n\n");
|
|
399
|
+
return derivedSystemMessage ? { systemMessage: derivedSystemMessage } : {};
|
|
400
|
+
}
|
|
384
401
|
export function mapCodexHookEventToOmxEvent(hookEventName) {
|
|
385
402
|
switch (hookEventName) {
|
|
386
403
|
case "SessionStart":
|
|
@@ -1812,6 +1829,76 @@ function isStopExempt(payload) {
|
|
|
1812
1829
|
|| value.includes("compact")
|
|
1813
1830
|
|| value.includes("limit"));
|
|
1814
1831
|
}
|
|
1832
|
+
async function readModeStateWithStopSource(mode, cwd, sessionId) {
|
|
1833
|
+
const paths = await getAuthoritativeActiveStatePaths(mode, cwd, sessionId?.trim() || undefined).catch(() => []);
|
|
1834
|
+
const path = paths[0];
|
|
1835
|
+
if (!path)
|
|
1836
|
+
return null;
|
|
1837
|
+
const state = await readJsonIfExists(path);
|
|
1838
|
+
return state ? { state, path } : null;
|
|
1839
|
+
}
|
|
1840
|
+
async function readRawSkillActiveState(path) {
|
|
1841
|
+
try {
|
|
1842
|
+
const parsed = JSON.parse(await readFile(path, "utf-8"));
|
|
1843
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1844
|
+
}
|
|
1845
|
+
catch {
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function canonicalStopDisagreement(modeState, canonicalState, mode, sessionId) {
|
|
1850
|
+
if (!canonicalState)
|
|
1851
|
+
return "canonical_state_missing";
|
|
1852
|
+
const normalizedSessionId = safeString(sessionId).trim();
|
|
1853
|
+
const activeEntry = listRawActiveSkillEntries(canonicalState).find((entry) => {
|
|
1854
|
+
if (entry.skill !== mode)
|
|
1855
|
+
return false;
|
|
1856
|
+
const entrySessionId = safeString(entry.session_id ?? canonicalState.session_id).trim();
|
|
1857
|
+
return normalizedSessionId ? entrySessionId === normalizedSessionId || entrySessionId === "" : true;
|
|
1858
|
+
});
|
|
1859
|
+
if (!activeEntry)
|
|
1860
|
+
return "canonical_inactive";
|
|
1861
|
+
if (mode === "autopilot") {
|
|
1862
|
+
const phase = safeString(modeState.current_phase ?? modeState.currentPhase).trim();
|
|
1863
|
+
const canonicalPhase = safeString(activeEntry.phase ?? canonicalState.phase).trim();
|
|
1864
|
+
if (phase && canonicalPhase && normalizeAutopilotPhase(phase) !== normalizeAutopilotPhase(canonicalPhase)) {
|
|
1865
|
+
return `canonical_phase:${canonicalPhase}`;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return "canonical_agrees";
|
|
1869
|
+
}
|
|
1870
|
+
function listRawActiveSkillEntries(state) {
|
|
1871
|
+
if (!state)
|
|
1872
|
+
return [];
|
|
1873
|
+
const entries = [];
|
|
1874
|
+
if (Array.isArray(state.active_skills)) {
|
|
1875
|
+
for (const candidate of state.active_skills) {
|
|
1876
|
+
if (!candidate || typeof candidate !== "object")
|
|
1877
|
+
continue;
|
|
1878
|
+
const raw = candidate;
|
|
1879
|
+
const skill = safeString(raw.skill).trim();
|
|
1880
|
+
if (!skill || raw.active === false)
|
|
1881
|
+
continue;
|
|
1882
|
+
entries.push({
|
|
1883
|
+
...raw,
|
|
1884
|
+
skill,
|
|
1885
|
+
phase: safeString(raw.phase).trim() || undefined,
|
|
1886
|
+
session_id: safeString(raw.session_id).trim() || undefined,
|
|
1887
|
+
thread_id: safeString(raw.thread_id).trim() || undefined,
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
const topLevelSkill = safeString(state.skill).trim();
|
|
1892
|
+
if (state.active === true && topLevelSkill) {
|
|
1893
|
+
entries.push({
|
|
1894
|
+
skill: topLevelSkill,
|
|
1895
|
+
phase: safeString(state.phase).trim() || undefined,
|
|
1896
|
+
session_id: safeString(state.session_id).trim() || undefined,
|
|
1897
|
+
thread_id: safeString(state.thread_id).trim() || undefined,
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
return entries;
|
|
1901
|
+
}
|
|
1815
1902
|
async function buildModeBasedStopOutput(mode, cwd, sessionId) {
|
|
1816
1903
|
if (await readCanonicalTerminalRunStateForStop(cwd, sessionId, mode)) {
|
|
1817
1904
|
return null;
|
|
@@ -1819,18 +1906,40 @@ async function buildModeBasedStopOutput(mode, cwd, sessionId) {
|
|
|
1819
1906
|
if (mode === "autopilot" && await readAutopilotDeepInterviewQuestionWaitState(cwd, sessionId)) {
|
|
1820
1907
|
return null;
|
|
1821
1908
|
}
|
|
1822
|
-
const
|
|
1909
|
+
const sourcedState = await readModeStateWithStopSource(mode, cwd, sessionId);
|
|
1910
|
+
const state = sourcedState?.state ?? null;
|
|
1823
1911
|
if (!state || !shouldContinueRun(state))
|
|
1824
1912
|
return null;
|
|
1913
|
+
const rootCanonicalState = await readRawSkillActiveState(getSkillActiveStatePathsForStateDir(getBaseStateDir(cwd)).rootPath);
|
|
1914
|
+
const canonicalDisagreement = rootCanonicalState
|
|
1915
|
+
? canonicalStopDisagreement(state, rootCanonicalState, mode, sessionId)
|
|
1916
|
+
: "canonical_state_missing";
|
|
1917
|
+
if (canonicalDisagreement === "canonical_inactive")
|
|
1918
|
+
return null;
|
|
1825
1919
|
const phase = formatPhase(state.current_phase);
|
|
1920
|
+
if (!rootCanonicalState || mode !== "autopilot") {
|
|
1921
|
+
const systemMessage = mode === "autopilot" && phase.toLowerCase().replace(/_/g, "-") === "code-review"
|
|
1922
|
+
? "OMX autopilot is still active (phase: code-review). Run the required $code-review step before completing or clearing Autopilot state."
|
|
1923
|
+
: `OMX ${mode} is still active (phase: ${phase}).`;
|
|
1924
|
+
return {
|
|
1925
|
+
decision: "block",
|
|
1926
|
+
reason: `OMX ${mode} is still active (phase: ${phase}); continue the task and gather fresh verification evidence before stopping.`,
|
|
1927
|
+
stopReason: `${mode}_${phase}`,
|
|
1928
|
+
systemMessage,
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
const statePath = sourcedState ? formatStopStatePath(cwd, sourcedState.path) : "unknown";
|
|
1932
|
+
const diagnostic = `state: ${statePath}; canonical: ${canonicalDisagreement}`;
|
|
1826
1933
|
const systemMessage = mode === "autopilot" && phase.toLowerCase().replace(/_/g, "-") === "code-review"
|
|
1827
|
-
?
|
|
1828
|
-
: `OMX ${mode} is still active (phase: ${phase}).`;
|
|
1934
|
+
? `OMX autopilot is still active (phase: code-review; ${diagnostic}). Run the required $code-review step before completing or clearing Autopilot state.`
|
|
1935
|
+
: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}).`;
|
|
1829
1936
|
return {
|
|
1830
1937
|
decision: "block",
|
|
1831
|
-
reason: `OMX ${mode} is still active (phase: ${phase}); continue the task and gather fresh verification evidence before stopping.`,
|
|
1938
|
+
reason: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}); continue the task and gather fresh verification evidence before stopping.`,
|
|
1832
1939
|
stopReason: `${mode}_${phase}`,
|
|
1833
1940
|
systemMessage,
|
|
1941
|
+
statePath,
|
|
1942
|
+
canonicalDisagreement,
|
|
1834
1943
|
};
|
|
1835
1944
|
}
|
|
1836
1945
|
export function looksLikeGoalCompletionPrompt(text) {
|
|
@@ -1873,6 +1982,112 @@ function reportsBlockedUltragoalCompletedAggregateMicrogoalLoop(goal) {
|
|
|
1873
1982
|
&& /microgoal/i.test(evidence)
|
|
1874
1983
|
&& /\b(?:unreconcilable|mismatch|loop|already complete|already completed|blocks?)\b/i.test(evidence);
|
|
1875
1984
|
}
|
|
1985
|
+
function sentenceWindowAround(text, start, end) {
|
|
1986
|
+
const rawBefore = text.slice(Math.max(0, start - 80), start);
|
|
1987
|
+
const rawAfter = text.slice(end, Math.min(text.length, end + 80));
|
|
1988
|
+
const before = rawBefore.slice(Math.max(rawBefore.lastIndexOf("\n"), rawBefore.lastIndexOf("."), rawBefore.lastIndexOf("!"), rawBefore.lastIndexOf("?")) + 1);
|
|
1989
|
+
const sentenceEndOffsets = [rawAfter.indexOf("\n"), rawAfter.indexOf("."), rawAfter.indexOf("!"), rawAfter.indexOf("?")].filter((index) => index >= 0);
|
|
1990
|
+
const after = sentenceEndOffsets.length > 0 ? rawAfter.slice(0, Math.min(...sentenceEndOffsets)) : rawAfter;
|
|
1991
|
+
return `${before}${text.slice(start, end)}${after}`;
|
|
1992
|
+
}
|
|
1993
|
+
function isNegatedGoalAttemptWindow(window) {
|
|
1994
|
+
return /\b(?:do\s+not|don't|never|must\s+not|should\s+not|cannot|can't|not|no)\b.{0,50}\b(?:start|create|begin|new|another|goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b/i.test(window)
|
|
1995
|
+
|| /\b(?:without|instead\s+of)\b.{0,30}\b(?:start|create|begin|new|another)?\b.{0,30}\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b/i.test(window)
|
|
1996
|
+
|| /\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b.{0,30}\b(?:is|was|already)\b.{0,30}\b(?:documented|complete|completed|done|unavailable|not\s+needed)\b/i.test(window);
|
|
1997
|
+
}
|
|
1998
|
+
function looksLikeGoalCreationAttempt(text) {
|
|
1999
|
+
const candidatePattern = /\b(?:start|create|begin|new|another|goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b/gi;
|
|
2000
|
+
for (const match of text.matchAll(candidatePattern)) {
|
|
2001
|
+
const start = match.index ?? 0;
|
|
2002
|
+
const end = start + match[0].length;
|
|
2003
|
+
const window = sentenceWindowAround(text, start, end);
|
|
2004
|
+
if (isNegatedGoalAttemptWindow(window))
|
|
2005
|
+
continue;
|
|
2006
|
+
if (/(?:\b(?:start|create|begin|new|another)\b.{0,80}\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b|\b(?:goal|ultragoal|performance[-\s]goal|autoresearch[-\s]goal)\b.{0,80}\b(?:start|create|begin|new|another)\b)/i.test(window))
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
function looksLikeCreateGoalAttempt(text) {
|
|
2012
|
+
const candidatePattern = /\bcreate_goal\s*(?:\(|\b)/gi;
|
|
2013
|
+
for (const match of text.matchAll(candidatePattern)) {
|
|
2014
|
+
const start = match.index ?? 0;
|
|
2015
|
+
const end = start + match[0].length;
|
|
2016
|
+
const window = sentenceWindowAround(text, start, end);
|
|
2017
|
+
if (/(?:\bno\s+create_goal\s+attempt\b|\b(?:do\s+not|don't|never|must\s+not|should\s+not|cannot|can't|not)\b.{0,40}\bcreate_goal\b|\bwithout\s+create_goal\b|\bfailed\s+to\s+call\s+create_goal\b|\bcreate_goal\s+(?:is|was)\s+(?:unavailable|not\s+available|not\s+called)\b)/i.test(window)) {
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
if (/\b(?:call|calling|called|invoke|invoking|start|starting|create|creating|begin|beginning|attempt|attempting|try|trying|continue\s+to|proceed\s+to)\b.{0,80}\bcreate_goal\b/i.test(window)
|
|
2021
|
+
|| /\bcreate_goal\b.{0,80}\b(?:payload|call|tool|now|next|follows|follow|start|starting|create|creating|begin|beginning|attempt|attempting)\b/i.test(window)
|
|
2022
|
+
|| /\bcreate_goal\s*\(/i.test(match[0])) {
|
|
2023
|
+
return true;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
function looksLikeCompletedGoalCleanupAttempt(text) {
|
|
2029
|
+
return looksLikeGoalCreationAttempt(text) || looksLikeCreateGoalAttempt(text);
|
|
2030
|
+
}
|
|
2031
|
+
function hasFreshNativeGoalCleanupEvidence(text) {
|
|
2032
|
+
return /\b(?:get_goal|Codex goal|native goal|active goal|thread goal)\b.{0,120}\b(?:reports?|returns?|shows?|status|state|still|attached|pending|cleanup|required|requires?)\b.{0,120}\b(?:complete|completed|attached|pending|cleanup|required|clear)\b/i.test(text)
|
|
2033
|
+
|| /\b(?:complete|completed|attached|pending|cleanup|required|clear)\b.{0,120}\b(?:get_goal|Codex goal|native goal|active goal|thread goal)\b/i.test(text)
|
|
2034
|
+
|| /\b(?:pending[-_ ]cleanup|native[-_ ]goal[-_ ]cleanup|completed[-_ ]codex[-_ ]goal[-_ ]cleanup)\b/i.test(text);
|
|
2035
|
+
}
|
|
2036
|
+
async function findCompletedGoalWorkflowCleanupNotice(cwd) {
|
|
2037
|
+
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
2038
|
+
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
2039
|
+
const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
|
|
2040
|
+
if (safeString(aggregateCompletion.status) === "complete" || (ultragoals.length > 0 && ultragoals.every((goal) => safeString(goal.status) === "complete"))) {
|
|
2041
|
+
return buildCodexGoalTerminalCleanupNotice("Ultragoal completion");
|
|
2042
|
+
}
|
|
2043
|
+
const performanceRoot = join(cwd, ".omx", "goals", "performance");
|
|
2044
|
+
for (const entry of await readdir(performanceRoot, { withFileTypes: true }).catch(() => [])) {
|
|
2045
|
+
if (!entry.isDirectory())
|
|
2046
|
+
continue;
|
|
2047
|
+
const state = await readJsonIfExists(join(performanceRoot, entry.name, "state.json"));
|
|
2048
|
+
if (state?.workflow === "performance-goal" && safeString(state.status) === "complete") {
|
|
2049
|
+
return buildCodexGoalTerminalCleanupNotice("Performance-goal completion");
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
const autoresearchRoot = join(cwd, ".omx", "goals", "autoresearch");
|
|
2053
|
+
for (const entry of await readdir(autoresearchRoot, { withFileTypes: true }).catch(() => [])) {
|
|
2054
|
+
if (!entry.isDirectory())
|
|
2055
|
+
continue;
|
|
2056
|
+
const mission = await readJsonIfExists(join(autoresearchRoot, entry.name, "mission.json"));
|
|
2057
|
+
if (mission?.workflow === "autoresearch-goal" && safeString(mission.status) === "complete") {
|
|
2058
|
+
return buildCodexGoalTerminalCleanupNotice("Autoresearch-goal completion");
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return null;
|
|
2062
|
+
}
|
|
2063
|
+
async function buildCompletedGoalCleanupPromptWarning(cwd, prompt) {
|
|
2064
|
+
if (!looksLikeCompletedGoalCleanupAttempt(prompt))
|
|
2065
|
+
return null;
|
|
2066
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2067
|
+
if (!notice)
|
|
2068
|
+
return null;
|
|
2069
|
+
return `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2070
|
+
}
|
|
2071
|
+
async function buildCompletedGoalCleanupStopOutput(payload, cwd) {
|
|
2072
|
+
const text = [
|
|
2073
|
+
safeString(payload.last_user_message ?? payload.lastUserMessage),
|
|
2074
|
+
safeString(payload.last_assistant_message ?? payload.lastAssistantMessage),
|
|
2075
|
+
].join("\n");
|
|
2076
|
+
if (!looksLikeCompletedGoalCleanupAttempt(text))
|
|
2077
|
+
return null;
|
|
2078
|
+
if (!hasFreshNativeGoalCleanupEvidence(text))
|
|
2079
|
+
return null;
|
|
2080
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2081
|
+
if (!notice)
|
|
2082
|
+
return null;
|
|
2083
|
+
const systemMessage = `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2084
|
+
return {
|
|
2085
|
+
decision: "block",
|
|
2086
|
+
reason: systemMessage,
|
|
2087
|
+
stopReason: "completed_codex_goal_cleanup_required",
|
|
2088
|
+
systemMessage,
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
1876
2091
|
async function findActiveGoalWorkflowReconciliationRequirement(cwd) {
|
|
1877
2092
|
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
1878
2093
|
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
@@ -2088,6 +2303,125 @@ function readPayloadThreadId(payload) {
|
|
|
2088
2303
|
function readPayloadTurnId(payload) {
|
|
2089
2304
|
return safeString(payload.turn_id ?? payload.turnId).trim();
|
|
2090
2305
|
}
|
|
2306
|
+
function nativeSubagentCapacityBlockerPath(stateDir) {
|
|
2307
|
+
return join(stateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE);
|
|
2308
|
+
}
|
|
2309
|
+
function stringifyUnknown(value) {
|
|
2310
|
+
if (typeof value === "string")
|
|
2311
|
+
return value;
|
|
2312
|
+
if (value === undefined || value === null)
|
|
2313
|
+
return "";
|
|
2314
|
+
try {
|
|
2315
|
+
return JSON.stringify(value);
|
|
2316
|
+
}
|
|
2317
|
+
catch {
|
|
2318
|
+
return String(value);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
function payloadEvidenceText(payload) {
|
|
2322
|
+
return [
|
|
2323
|
+
safeString(payload.tool_name),
|
|
2324
|
+
stringifyUnknown(payload.tool_response),
|
|
2325
|
+
stringifyUnknown(payload.response),
|
|
2326
|
+
stringifyUnknown(payload.error),
|
|
2327
|
+
stringifyUnknown(payload.message),
|
|
2328
|
+
].filter(Boolean).join("\n");
|
|
2329
|
+
}
|
|
2330
|
+
function isNativeSubagentCapacityFailure(payload) {
|
|
2331
|
+
const evidence = payloadEvidenceText(payload);
|
|
2332
|
+
if (!/\bagent thread limit reached\b/i.test(evidence))
|
|
2333
|
+
return false;
|
|
2334
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2335
|
+
return !toolName || /(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName);
|
|
2336
|
+
}
|
|
2337
|
+
function summarizeCapacityFailure(text) {
|
|
2338
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2339
|
+
if (!normalized)
|
|
2340
|
+
return "agent thread limit reached";
|
|
2341
|
+
const match = normalized.match(/[^.?!\n\r]*agent thread limit reached[^.?!\n\r]*/i);
|
|
2342
|
+
return (match?.[0] ?? normalized).slice(0, 500);
|
|
2343
|
+
}
|
|
2344
|
+
async function recordNativeSubagentCapacityBlocker(cwd, stateDir, payload) {
|
|
2345
|
+
if (!isNativeSubagentCapacityFailure(payload))
|
|
2346
|
+
return;
|
|
2347
|
+
const nowMs = Date.now();
|
|
2348
|
+
const blocker = {
|
|
2349
|
+
schema_version: 1,
|
|
2350
|
+
reason: "agent_thread_limit_reached",
|
|
2351
|
+
...(readPayloadSessionId(payload) ? { session_id: readPayloadSessionId(payload) } : {}),
|
|
2352
|
+
...(readPayloadThreadId(payload) ? { thread_id: readPayloadThreadId(payload) } : {}),
|
|
2353
|
+
...(readPayloadTurnId(payload) ? { turn_id: readPayloadTurnId(payload) } : {}),
|
|
2354
|
+
...(safeString(payload.tool_name).trim() ? { tool_name: safeString(payload.tool_name).trim() } : {}),
|
|
2355
|
+
error_summary: summarizeCapacityFailure(payloadEvidenceText(payload)),
|
|
2356
|
+
observed_at: new Date(nowMs).toISOString(),
|
|
2357
|
+
expires_at: new Date(nowMs + NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS).toISOString(),
|
|
2358
|
+
};
|
|
2359
|
+
await mkdir(stateDir, { recursive: true });
|
|
2360
|
+
await writeFile(nativeSubagentCapacityBlockerPath(stateDir), JSON.stringify({
|
|
2361
|
+
...blocker,
|
|
2362
|
+
cwd,
|
|
2363
|
+
}, null, 2));
|
|
2364
|
+
}
|
|
2365
|
+
function isFreshNativeSubagentCapacityBlocker(blocker, cwd, payload, nowMs = Date.now()) {
|
|
2366
|
+
if (!blocker)
|
|
2367
|
+
return false;
|
|
2368
|
+
if (safeString(blocker.reason) !== "agent_thread_limit_reached")
|
|
2369
|
+
return false;
|
|
2370
|
+
const expiresAtMs = Date.parse(safeString(blocker.expires_at));
|
|
2371
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs)
|
|
2372
|
+
return false;
|
|
2373
|
+
const blockerCwd = safeString(blocker.cwd).trim();
|
|
2374
|
+
if (blockerCwd) {
|
|
2375
|
+
try {
|
|
2376
|
+
if (resolve(blockerCwd) !== resolve(cwd))
|
|
2377
|
+
return false;
|
|
2378
|
+
}
|
|
2379
|
+
catch {
|
|
2380
|
+
return false;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
const blockerSessionId = safeString(blocker.session_id).trim();
|
|
2384
|
+
const payloadSessionId = readPayloadSessionId(payload);
|
|
2385
|
+
return !blockerSessionId || !payloadSessionId || blockerSessionId === payloadSessionId;
|
|
2386
|
+
}
|
|
2387
|
+
function inputContainsCloseAgentRequest(value) {
|
|
2388
|
+
if (typeof value === "string")
|
|
2389
|
+
return /\bclose_agent\b/i.test(value);
|
|
2390
|
+
if (!value || typeof value !== "object")
|
|
2391
|
+
return false;
|
|
2392
|
+
try {
|
|
2393
|
+
return /\bclose_agent\b/i.test(JSON.stringify(value));
|
|
2394
|
+
}
|
|
2395
|
+
catch {
|
|
2396
|
+
return false;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
function isCloseAgentToolUse(payload) {
|
|
2400
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2401
|
+
if (/\bclose_agent\b/i.test(toolName))
|
|
2402
|
+
return true;
|
|
2403
|
+
if (/multi_tool_use\.parallel/i.test(toolName) && inputContainsCloseAgentRequest(payload.tool_input))
|
|
2404
|
+
return true;
|
|
2405
|
+
return inputContainsCloseAgentRequest(payload.tool_input) && /multi_agent|agent|tool_use/i.test(toolName);
|
|
2406
|
+
}
|
|
2407
|
+
async function buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir) {
|
|
2408
|
+
if (!isCloseAgentToolUse(payload))
|
|
2409
|
+
return null;
|
|
2410
|
+
const blocker = await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir));
|
|
2411
|
+
if (!isFreshNativeSubagentCapacityBlocker(blocker, cwd, payload))
|
|
2412
|
+
return null;
|
|
2413
|
+
const evidence = safeString(blocker.error_summary).trim() || "agent thread limit reached";
|
|
2414
|
+
return {
|
|
2415
|
+
decision: "block",
|
|
2416
|
+
reason: "Native subagent capacity was exhausted recently; model-level close_agent cleanup is blocked because close_agent can hang indefinitely on stale handles.",
|
|
2417
|
+
hookSpecificOutput: {
|
|
2418
|
+
hookEventName: "PreToolUse",
|
|
2419
|
+
additionalContext: `OMX blocked ${safeString(payload.tool_name).trim() || "close_agent"} before it could start: a recent native subagent capacity failure was recorded (${evidence}). `
|
|
2420
|
+
+ "Do not call multi_agent_v1.close_agent, and do not batch close_agent through multi_tool_use.parallel, as stale native handles can hang the whole turn. "
|
|
2421
|
+
+ "Treat this as a bounded capacity blocker: persist/report the blocker evidence, avoid further native subagent cleanup from the model turn, and recover via runtime-level cleanup or a fresh Codex session.",
|
|
2422
|
+
},
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2091
2425
|
async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir) {
|
|
2092
2426
|
const currentSession = stateDir
|
|
2093
2427
|
? await readUsableSessionStateFromStateDir(cwd, stateDir)
|
|
@@ -2096,12 +2430,15 @@ async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDi
|
|
|
2096
2430
|
if (!canonicalSessionId)
|
|
2097
2431
|
return payloadSessionId;
|
|
2098
2432
|
const nativeSessionId = safeString(currentSession?.native_session_id).trim();
|
|
2433
|
+
const ownerOmxSessionId = safeString(currentSession?.owner_omx_session_id).trim();
|
|
2099
2434
|
if (!payloadSessionId)
|
|
2100
2435
|
return canonicalSessionId;
|
|
2101
2436
|
if (payloadSessionId === canonicalSessionId)
|
|
2102
2437
|
return canonicalSessionId;
|
|
2103
2438
|
if (nativeSessionId && payloadSessionId === nativeSessionId)
|
|
2104
2439
|
return canonicalSessionId;
|
|
2440
|
+
if (ownerOmxSessionId && payloadSessionId === ownerOmxSessionId)
|
|
2441
|
+
return canonicalSessionId;
|
|
2105
2442
|
return payloadSessionId;
|
|
2106
2443
|
}
|
|
2107
2444
|
async function readUsableSessionStateFromStateDir(cwd, stateDir) {
|
|
@@ -2134,6 +2471,7 @@ const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
|
2134
2471
|
".omx/plans",
|
|
2135
2472
|
".omx/specs",
|
|
2136
2473
|
".omx/state",
|
|
2474
|
+
".beads",
|
|
2137
2475
|
];
|
|
2138
2476
|
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
2139
2477
|
"Write",
|
|
@@ -2182,6 +2520,9 @@ function isAutopilotRalplanLikePhase(phase) {
|
|
|
2182
2520
|
function canAutopilotSkillMirrorSupplyRalplanPhase(phase) {
|
|
2183
2521
|
return phase === "" || normalizeAutopilotPhase(phase) === "ralplan";
|
|
2184
2522
|
}
|
|
2523
|
+
function isAutopilotReviewReworkPhase(phase) {
|
|
2524
|
+
return normalizeAutopilotPhase(phase) === "rework";
|
|
2525
|
+
}
|
|
2185
2526
|
function hasExplicitExecutionHandoffSkill(state, sessionId, threadId) {
|
|
2186
2527
|
return listActiveSkills(state ?? {}).some((entry) => (RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
|
|
2187
2528
|
&& matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)));
|
|
@@ -2208,6 +2549,134 @@ function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
|
2208
2549
|
function isAllowedRalplanArtifactPath(cwd, rawPath) {
|
|
2209
2550
|
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2210
2551
|
}
|
|
2552
|
+
function shellTokenizeLiteralCommand(command) {
|
|
2553
|
+
const tokens = [];
|
|
2554
|
+
let current = "";
|
|
2555
|
+
let quote = null;
|
|
2556
|
+
let escaping = false;
|
|
2557
|
+
for (const char of command.trim()) {
|
|
2558
|
+
if (escaping) {
|
|
2559
|
+
current += char;
|
|
2560
|
+
escaping = false;
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
if (quote === '"' && char === "\\") {
|
|
2564
|
+
escaping = true;
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
if (quote) {
|
|
2568
|
+
if (char === quote)
|
|
2569
|
+
quote = null;
|
|
2570
|
+
else
|
|
2571
|
+
current += char;
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
if (char === "'" || char === '"') {
|
|
2575
|
+
quote = char;
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
if (/\s/.test(char)) {
|
|
2579
|
+
if (current) {
|
|
2580
|
+
tokens.push(current);
|
|
2581
|
+
current = "";
|
|
2582
|
+
}
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2585
|
+
if (/[;&|<>`$(){}\n\r]/.test(char))
|
|
2586
|
+
return null;
|
|
2587
|
+
current += char;
|
|
2588
|
+
}
|
|
2589
|
+
if (escaping || quote)
|
|
2590
|
+
return null;
|
|
2591
|
+
if (current)
|
|
2592
|
+
tokens.push(current);
|
|
2593
|
+
return tokens;
|
|
2594
|
+
}
|
|
2595
|
+
function findLiteralBdExecutableIndex(tokens) {
|
|
2596
|
+
if (tokens[0] === "bd")
|
|
2597
|
+
return 0;
|
|
2598
|
+
if (tokens[0] === "command" || tokens[0] === "builtin" || tokens[0] === "exec" || tokens[0] === "nohup") {
|
|
2599
|
+
return tokens[1] === "bd" ? 1 : -1;
|
|
2600
|
+
}
|
|
2601
|
+
if (tokens[0] !== "env")
|
|
2602
|
+
return -1;
|
|
2603
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
2604
|
+
const token = tokens[index] ?? "";
|
|
2605
|
+
if (token === "bd")
|
|
2606
|
+
return index;
|
|
2607
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token))
|
|
2608
|
+
continue;
|
|
2609
|
+
if (token.startsWith("-"))
|
|
2610
|
+
continue;
|
|
2611
|
+
return -1;
|
|
2612
|
+
}
|
|
2613
|
+
return -1;
|
|
2614
|
+
}
|
|
2615
|
+
function isAllowedRalplanBeadsDbPath(cwd, rawPath) {
|
|
2616
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
2617
|
+
if (!trimmed || trimmed.includes("\0"))
|
|
2618
|
+
return false;
|
|
2619
|
+
let relativePath;
|
|
2620
|
+
try {
|
|
2621
|
+
const absolute = resolve(cwd, trimmed);
|
|
2622
|
+
relativePath = relative(cwd, absolute).replace(/\\/g, "/");
|
|
2623
|
+
}
|
|
2624
|
+
catch {
|
|
2625
|
+
return false;
|
|
2626
|
+
}
|
|
2627
|
+
return relativePath.startsWith(".beads/") && relativePath.length > ".beads/".length;
|
|
2628
|
+
}
|
|
2629
|
+
function classifyRalplanBeadsMetadataCommand(cwd, command) {
|
|
2630
|
+
const trimmedCommand = command.trim();
|
|
2631
|
+
const startsWithBd = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"$`]*"|'[^']*'|[^\s"'$`;&|<>]+)\s+)*bd(?:\s|$)/.test(trimmedCommand);
|
|
2632
|
+
const hasCompoundBd = /[;&|()]\s*bd(?:\s|$)/.test(command);
|
|
2633
|
+
const tokens = shellTokenizeLiteralCommand(command);
|
|
2634
|
+
const bdExecutableIndex = tokens ? findLiteralBdExecutableIndex(tokens) : -1;
|
|
2635
|
+
if (!startsWithBd && !hasCompoundBd && bdExecutableIndex === -1)
|
|
2636
|
+
return { present: false, allowed: false };
|
|
2637
|
+
if (!tokens || bdExecutableIndex !== 0) {
|
|
2638
|
+
return { present: true, allowed: false, reason: "Beads tracker command must be a single literal bd invocation" };
|
|
2639
|
+
}
|
|
2640
|
+
let dbPath = "";
|
|
2641
|
+
let dbValueIndex = -1;
|
|
2642
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
2643
|
+
const token = tokens[index] ?? "";
|
|
2644
|
+
if (token === "--db") {
|
|
2645
|
+
dbPath = tokens[index + 1] ?? "";
|
|
2646
|
+
dbValueIndex = index + 1;
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
if (token.startsWith("--db=")) {
|
|
2650
|
+
dbPath = token.slice("--db=".length);
|
|
2651
|
+
dbValueIndex = index;
|
|
2652
|
+
break;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
if (!dbPath) {
|
|
2656
|
+
return { present: true, allowed: false, reason: "Beads tracker command is missing a literal --db .beads/<db> target" };
|
|
2657
|
+
}
|
|
2658
|
+
if (!isAllowedRalplanBeadsDbPath(cwd, dbPath)) {
|
|
2659
|
+
return { present: true, allowed: false, reason: `Beads tracker db target ${dbPath} is outside repo-local .beads metadata` };
|
|
2660
|
+
}
|
|
2661
|
+
const operationTokens = tokens
|
|
2662
|
+
.slice(dbValueIndex + 1)
|
|
2663
|
+
.filter((token) => token && !token.startsWith("-"));
|
|
2664
|
+
const operation = operationTokens[0] ?? "";
|
|
2665
|
+
const suboperation = operationTokens[1] ?? "";
|
|
2666
|
+
if (["create", "update", "edit", "close", "reopen", "status", "dep"].includes(operation)) {
|
|
2667
|
+
return { present: true, allowed: true };
|
|
2668
|
+
}
|
|
2669
|
+
if (operation === "comments" && suboperation === "add") {
|
|
2670
|
+
return { present: true, allowed: true };
|
|
2671
|
+
}
|
|
2672
|
+
return {
|
|
2673
|
+
present: true,
|
|
2674
|
+
allowed: false,
|
|
2675
|
+
reason: operation
|
|
2676
|
+
? `Beads tracker operation ${operation}${suboperation ? ` ${suboperation}` : ""} is not allowed during planning`
|
|
2677
|
+
: "Beads tracker command is missing an allowed metadata operation",
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2211
2680
|
function readPreToolUseCommand(payload) {
|
|
2212
2681
|
const toolInput = safeObject(payload.tool_input);
|
|
2213
2682
|
return safeString(toolInput.command).trim();
|
|
@@ -2314,6 +2783,22 @@ function extractDeepInterviewCommandWriteTargets(command) {
|
|
|
2314
2783
|
}
|
|
2315
2784
|
return targets;
|
|
2316
2785
|
}
|
|
2786
|
+
function formatPlanningWriteBlockDetail(operationClass, target, allowedPrefixes) {
|
|
2787
|
+
const targetDetail = target ? `target ${target}` : "target <unresolved>";
|
|
2788
|
+
return `${operationClass} ${targetDetail} is not under allowed planning artifact paths (${allowedPrefixes.join(", ")})`;
|
|
2789
|
+
}
|
|
2790
|
+
function isUnresolvedVariableTarget(target) {
|
|
2791
|
+
const normalized = target.trim().replace(/^['"]|['"]$/g, "");
|
|
2792
|
+
return /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(normalized);
|
|
2793
|
+
}
|
|
2794
|
+
function describeImplementationToolBlock(toolName, blockedPath, pathCount) {
|
|
2795
|
+
if (pathCount === 0) {
|
|
2796
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
|
|
2797
|
+
return `${operationClass} target <unresolved>; only planning artifact paths are allowed (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2798
|
+
}
|
|
2799
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
|
|
2800
|
+
return formatPlanningWriteBlockDetail(operationClass, blockedPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2801
|
+
}
|
|
2317
2802
|
function isAllowedDeepInterviewBashWrite(cwd, command) {
|
|
2318
2803
|
if (!commandHasDeepInterviewWriteIntent(command))
|
|
2319
2804
|
return true;
|
|
@@ -2400,6 +2885,8 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
|
|
|
2400
2885
|
if (!hasActiveAutopilotSkill)
|
|
2401
2886
|
return null;
|
|
2402
2887
|
const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
|
|
2888
|
+
if (isAutopilotReviewReworkPhase(autopilotStatePhase))
|
|
2889
|
+
return null;
|
|
2403
2890
|
if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase))
|
|
2404
2891
|
return null;
|
|
2405
2892
|
const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
|
|
@@ -2410,12 +2897,37 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
|
|
|
2410
2897
|
return hasActiveAutopilotSkill ? autopilotState : null;
|
|
2411
2898
|
}
|
|
2412
2899
|
function isAllowedRalplanBashWrite(cwd, command) {
|
|
2900
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
2901
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2902
|
+
const hasAllowedTargets = targets.length > 0
|
|
2903
|
+
&& targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
2904
|
+
if (beadsCommand.present) {
|
|
2905
|
+
return beadsCommand.allowed && (targets.length === 0 || hasAllowedTargets);
|
|
2906
|
+
}
|
|
2413
2907
|
if (!commandHasDeepInterviewWriteIntent(command))
|
|
2414
2908
|
return true;
|
|
2415
2909
|
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
|
|
2416
2910
|
return true;
|
|
2911
|
+
return hasAllowedTargets;
|
|
2912
|
+
}
|
|
2913
|
+
function buildRalplanBashBlockedDetail(cwd, command) {
|
|
2417
2914
|
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2418
|
-
|
|
2915
|
+
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
2916
|
+
if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
|
|
2917
|
+
return `unresolved Bash write target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2918
|
+
}
|
|
2919
|
+
if (blockedTarget) {
|
|
2920
|
+
const operationClass = /\btee\s+(?:-a\s+)?/.test(command) ? "Bash tee write" : "Bash redirect write";
|
|
2921
|
+
return `${operationClass} target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2922
|
+
}
|
|
2923
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
2924
|
+
if (beadsCommand.present && !beadsCommand.allowed) {
|
|
2925
|
+
return beadsCommand.reason ?? "Beads tracker command is not an allowed planning metadata mutation";
|
|
2926
|
+
}
|
|
2927
|
+
if (beadsCommand.present) {
|
|
2928
|
+
return "Beads tracker command also performs an implementation write outside allowed planning metadata";
|
|
2929
|
+
}
|
|
2930
|
+
return "Bash write intent did not identify an allowed planning artifact path or metadata path";
|
|
2419
2931
|
}
|
|
2420
2932
|
async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
|
|
2421
2933
|
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
@@ -2431,23 +2943,20 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
2431
2943
|
if (toolName === "Bash") {
|
|
2432
2944
|
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2433
2945
|
if (blocked) {
|
|
2434
|
-
|
|
2435
|
-
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
2436
|
-
blockedDetail = blockedTarget
|
|
2437
|
-
? `write target ${blockedTarget} is not under allowed planning artifact paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`
|
|
2438
|
-
: "Bash write intent did not identify an allowed planning artifact path";
|
|
2946
|
+
blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
|
|
2439
2947
|
}
|
|
2440
2948
|
}
|
|
2441
2949
|
else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2442
|
-
|
|
2950
|
+
const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
|
|
2951
|
+
if (toolPathCandidates.length === 0) {
|
|
2443
2952
|
blocked = true;
|
|
2444
|
-
blockedDetail =
|
|
2953
|
+
blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
|
|
2445
2954
|
}
|
|
2446
2955
|
else {
|
|
2447
|
-
const blockedPath =
|
|
2956
|
+
const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2448
2957
|
blocked = blockedPath !== undefined;
|
|
2449
2958
|
if (blockedPath !== undefined) {
|
|
2450
|
-
blockedDetail =
|
|
2959
|
+
blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
|
|
2451
2960
|
}
|
|
2452
2961
|
}
|
|
2453
2962
|
}
|
|
@@ -2465,7 +2974,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
2465
2974
|
hookSpecificOutput: {
|
|
2466
2975
|
hookEventName: "PreToolUse",
|
|
2467
2976
|
additionalContext: `${planningModeDescription}. `
|
|
2468
|
-
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`,
|
|
2977
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
|
|
2469
2978
|
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
2470
2979
|
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
2471
2980
|
},
|
|
@@ -2533,6 +3042,20 @@ function modeStateMatchesSkillStopContext(state, cwd, sessionId) {
|
|
|
2533
3042
|
}
|
|
2534
3043
|
return true;
|
|
2535
3044
|
}
|
|
3045
|
+
function modeStateHasExplicitMatchingCwd(state, cwd) {
|
|
3046
|
+
const stateCwd = safeString(state.cwd
|
|
3047
|
+
?? state.workingDirectory
|
|
3048
|
+
?? state.working_directory
|
|
3049
|
+
?? state.project_path).trim();
|
|
3050
|
+
if (!stateCwd)
|
|
3051
|
+
return false;
|
|
3052
|
+
try {
|
|
3053
|
+
return resolve(stateCwd) === resolve(cwd);
|
|
3054
|
+
}
|
|
3055
|
+
catch {
|
|
3056
|
+
return false;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
2536
3059
|
async function readBlockingSkillForStop(cwd, stateDir, sessionId, threadId, requiredSkill) {
|
|
2537
3060
|
const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
|
|
2538
3061
|
const visibleEntries = canonicalState ? listActiveSkills(canonicalState) : [];
|
|
@@ -2613,14 +3136,59 @@ function rootModeStateIsCanonicalForStopContext(state, cwd, sessionId, threadId)
|
|
|
2613
3136
|
return false;
|
|
2614
3137
|
return true;
|
|
2615
3138
|
}
|
|
3139
|
+
function hasExplicitSessionScope(state) {
|
|
3140
|
+
return safeString(state.owner_omx_session_id
|
|
3141
|
+
?? state.session_id
|
|
3142
|
+
?? state.codex_session_id
|
|
3143
|
+
?? state.owner_codex_session_id).trim() !== "";
|
|
3144
|
+
}
|
|
3145
|
+
async function readStateTimestampMs(state, path) {
|
|
3146
|
+
return parseTimestampMs(state.updated_at)
|
|
3147
|
+
?? parseTimestampMs(state.completed_at)
|
|
3148
|
+
?? parseTimestampMs(state.created_at)
|
|
3149
|
+
?? await stat(path).then((info) => info.mtimeMs, () => null);
|
|
3150
|
+
}
|
|
3151
|
+
async function unscopedRootRalplanStateIsNewerTerminalPlanningCompletion(rootState, rootPath, sessionPath, cwd, threadId) {
|
|
3152
|
+
if (hasExplicitSessionScope(rootState))
|
|
3153
|
+
return false;
|
|
3154
|
+
const stateThreadId = safeString(rootState.owner_codex_thread_id ?? rootState.thread_id).trim();
|
|
3155
|
+
if (threadId && stateThreadId && stateThreadId !== threadId)
|
|
3156
|
+
return false;
|
|
3157
|
+
if (!modeStateHasExplicitMatchingCwd(rootState, cwd))
|
|
3158
|
+
return false;
|
|
3159
|
+
const phase = safeString(rootState.current_phase ?? rootState.currentPhase).trim().toLowerCase();
|
|
3160
|
+
if (phase !== "complete" && phase !== "completed")
|
|
3161
|
+
return false;
|
|
3162
|
+
const planningComplete = rootState.planning_complete === true || rootState.planningComplete === true;
|
|
3163
|
+
const latestPlanPath = safeString(rootState.latest_plan_path ?? rootState.latestPlanPath).trim();
|
|
3164
|
+
if (!planningComplete || !latestPlanPath)
|
|
3165
|
+
return false;
|
|
3166
|
+
const sessionState = await readJsonIfExists(sessionPath);
|
|
3167
|
+
if (!sessionState)
|
|
3168
|
+
return false;
|
|
3169
|
+
const sessionPhase = safeString(sessionState.current_phase ?? sessionState.currentPhase).trim().toLowerCase();
|
|
3170
|
+
if (sessionPhase && TERMINAL_MODE_PHASES.has(sessionPhase))
|
|
3171
|
+
return false;
|
|
3172
|
+
const rootTimestamp = await readStateTimestampMs(rootState, rootPath);
|
|
3173
|
+
const sessionTimestamp = await readStateTimestampMs(sessionState, sessionPath);
|
|
3174
|
+
if (rootTimestamp === null || sessionTimestamp === null)
|
|
3175
|
+
return false;
|
|
3176
|
+
return rootTimestamp > sessionTimestamp;
|
|
3177
|
+
}
|
|
2616
3178
|
async function shouldIgnoreSessionSkillBlockerForCanonicalInactiveRoot(cwd, stateDir, skill, sessionId, threadId) {
|
|
2617
|
-
const
|
|
3179
|
+
const rootModeStatePath = join(stateDir, `${skill}-state.json`);
|
|
3180
|
+
const rootModeState = await readJsonIfExists(rootModeStatePath);
|
|
2618
3181
|
if (!rootModeState)
|
|
2619
3182
|
return false;
|
|
2620
|
-
if (!rootModeStateIsCanonicalForStopContext(rootModeState, cwd, sessionId, threadId))
|
|
2621
|
-
return false;
|
|
2622
3183
|
if (!isTerminalOrInactiveModeState(rootModeState))
|
|
2623
3184
|
return false;
|
|
3185
|
+
const canonicalRoot = rootModeStateIsCanonicalForStopContext(rootModeState, cwd, sessionId, threadId)
|
|
3186
|
+
&& (skill !== "ralplan" || modeStateHasExplicitMatchingCwd(rootModeState, cwd));
|
|
3187
|
+
const freshUnscopedRoot = canonicalRoot || skill !== "ralplan"
|
|
3188
|
+
? false
|
|
3189
|
+
: await unscopedRootRalplanStateIsNewerTerminalPlanningCompletion(rootModeState, rootModeStatePath, join(stateDir, "sessions", sessionId, `${skill}-state.json`), cwd, threadId);
|
|
3190
|
+
if (!canonicalRoot && !freshUnscopedRoot)
|
|
3191
|
+
return false;
|
|
2624
3192
|
const { rootPath } = getSkillActiveStatePathsForStateDir(stateDir);
|
|
2625
3193
|
const rootSkillState = await readSkillActiveState(rootPath);
|
|
2626
3194
|
return rootSkillStateHasNoActiveSkillForStopContext(rootSkillState, skill, sessionId, threadId);
|
|
@@ -3392,7 +3960,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3392
3960
|
&& shouldSuppressSubagentLifecycleHookDispatch();
|
|
3393
3961
|
if (hookEventName === "UserPromptSubmit") {
|
|
3394
3962
|
const prompt = readPromptText(payload);
|
|
3395
|
-
goalWorkflowAdditionalContext = await
|
|
3963
|
+
goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
|
|
3964
|
+
?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
|
|
3396
3965
|
ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
|
|
3397
3966
|
? await applyUserPromptUltragoalSteering(cwd, prompt).catch((error) => `OMX native UserPromptSubmit rejected bounded .omx/ultragoal steering for G002-cli-and-prompt-submit-bridge: ${error instanceof Error ? error.message : String(error)}`)
|
|
3398
3967
|
: null;
|
|
@@ -3550,9 +4119,11 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3550
4119
|
: "";
|
|
3551
4120
|
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
3552
4121
|
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4122
|
+
?? await buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir)
|
|
3553
4123
|
?? buildNativePreToolUseOutput(payload);
|
|
3554
4124
|
}
|
|
3555
4125
|
else if (hookEventName === "PostToolUse") {
|
|
4126
|
+
await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
|
|
3556
4127
|
if (detectMcpTransportFailure(payload)) {
|
|
3557
4128
|
await markTeamTransportFailure(cwd, payload);
|
|
3558
4129
|
}
|
|
@@ -3562,7 +4133,7 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3562
4133
|
else if (hookEventName === "Stop") {
|
|
3563
4134
|
outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
|
|
3564
4135
|
skipRalphStopBlock: isSubagentStop,
|
|
3565
|
-
});
|
|
4136
|
+
}) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
|
|
3566
4137
|
}
|
|
3567
4138
|
return {
|
|
3568
4139
|
hookEventName,
|
|
@@ -3659,6 +4230,9 @@ function buildMalformedStdinHookOutput(parseError, rawInput, cwd = process.cwd()
|
|
|
3659
4230
|
const reason = "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
|
|
3660
4231
|
const systemMessage = `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
3661
4232
|
const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput);
|
|
4233
|
+
if (inferredHookEventName === "PreToolUse") {
|
|
4234
|
+
return { systemMessage };
|
|
4235
|
+
}
|
|
3662
4236
|
if (inferredHookEventName === "Stop" || (!inferredHookEventName && hasNativeStopRuntimeSurface(cwd))) {
|
|
3663
4237
|
return {
|
|
3664
4238
|
decision: "block",
|
|
@@ -3694,10 +4268,13 @@ async function buildOversizedStopActiveWorkflowOutput(cwd) {
|
|
|
3694
4268
|
};
|
|
3695
4269
|
}
|
|
3696
4270
|
async function buildOversizedStdinHookOutput(rawHookEventName, cwd) {
|
|
4271
|
+
const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
4272
|
+
if (rawHookEventName === "PreToolUse") {
|
|
4273
|
+
return { systemMessage };
|
|
4274
|
+
}
|
|
3697
4275
|
if (rawHookEventName === "Stop") {
|
|
3698
4276
|
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
3699
4277
|
}
|
|
3700
|
-
const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
3701
4278
|
return {
|
|
3702
4279
|
continue: false,
|
|
3703
4280
|
stopReason: "native_hook_stdin_oversized",
|
|
@@ -3777,9 +4354,9 @@ export async function runCodexNativeHookCli() {
|
|
|
3777
4354
|
}
|
|
3778
4355
|
const result = await dispatchCodexNativeHook(payload);
|
|
3779
4356
|
if (result.outputJson) {
|
|
3780
|
-
writeNativeHookJsonStdout(result.outputJson);
|
|
4357
|
+
writeNativeHookJsonStdout(sanitizeCodexHookOutput(result.hookEventName, result.outputJson) ?? {});
|
|
3781
4358
|
}
|
|
3782
|
-
else if (result.hookEventName
|
|
4359
|
+
else if (result.hookEventName !== "PreCompact" && result.hookEventName !== "PostCompact") {
|
|
3783
4360
|
writeNativeHookJsonStdout({});
|
|
3784
4361
|
}
|
|
3785
4362
|
}
|