oh-my-codex 0.18.12 → 0.18.14
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 +38 -11
- package/dist/agents/__tests__/native-config.test.js +25 -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 +5 -1
- 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 +936 -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 +62 -18
- 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__/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-invalid-config.test.js +35 -0
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
- 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 +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +59 -1
- package/dist/cli/__tests__/index.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__/resume.test.js +313 -1
- 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-help.test.js +3 -2
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js +100 -2
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +35 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +39 -5
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js +74 -0
- package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +45 -0
- package/dist/cli/__tests__/setup-scope.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 +6 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- 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/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +272 -5
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +10 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +274 -11
- 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 +67 -5
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.d.ts +8 -0
- package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -0
- package/dist/cli/project-runtime-codex-homes.js +134 -0
- package/dist/cli/project-runtime-codex-homes.js.map +1 -0
- package/dist/cli/session-search.d.ts.map +1 -1
- package/dist/cli/session-search.js +8 -1
- package/dist/cli/session-search.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +183 -10
- 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 +5 -0
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +38 -24
- 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 +6 -0
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +34 -49
- 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 +200 -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/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/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/pipeline/__tests__/orchestrator.test.js +125 -0
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +109 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +7 -0
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +552 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +25 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +289 -75
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1149 -47
- 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 +542 -33
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts +1 -0
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +130 -0
- package/dist/scripts/codex-native-pre-post.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/__tests__/search.test.js +166 -0
- package/dist/session-history/__tests__/search.test.js.map +1 -1
- package/dist/session-history/search.d.ts +7 -0
- package/dist/session-history/search.d.ts.map +1 -1
- package/dist/session-history/search.js +99 -25
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/collector.test.js +60 -0
- package/dist/sidecar/__tests__/collector.test.js.map +1 -1
- package/dist/sidecar/collector.d.ts.map +1 -1
- package/dist/sidecar/collector.js +3 -6
- package/dist/sidecar/collector.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 +238 -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 +15 -0
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +167 -4
- package/dist/ultragoal/artifacts.js.map +1 -1
- 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 +25 -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/verification/__tests__/ci-rust-gates.test.js +8 -2
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +71 -3
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/package.json +2 -2
- 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 +1417 -119
- package/src/scripts/codex-native-hook.ts +597 -40
- package/src/scripts/codex-native-pre-post.ts +137 -0
- 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,12 +12,12 @@ 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";
|
|
19
19
|
import { detectNativeStopStallPattern, loadAutoNudgeConfig, normalizeAutoNudgeSignatureText, resolveEffectiveAutoNudgeResponse, } from "./notify-hook/auto-nudge.js";
|
|
20
|
-
import { SLOPPY_FALLBACK_GROUNDING_PATTERNS, SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS, SLOPPY_FALLBACK_PHRASE_PATTERNS, buildNativePostToolUseOutput, buildNativePreToolUseOutput, detectMcpTransportFailure, hasAnyPattern, } from "./codex-native-pre-post.js";
|
|
20
|
+
import { SLOPPY_FALLBACK_GROUNDING_PATTERNS, SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS, SLOPPY_FALLBACK_PHRASE_PATTERNS, buildNativePostToolUseOutput, buildNativePreToolUseOutput, commandInvokesApplyPatch, detectMcpTransportFailure, hasAnyPattern, } from "./codex-native-pre-post.js";
|
|
21
21
|
import { handleTeamWorkerPostToolUseSuccess } from "./notify-hook/team-worker-posttooluse.js";
|
|
22
22
|
import { maybeNudgeLeaderForAllowedWorkerStop } from "./notify-hook/team-worker-stop.js";
|
|
23
23
|
import { resolveCodexExecutionSurface, } from "./codex-execution-surface.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,62 @@ 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 looksLikeNewGoalPrompt(text) {
|
|
1986
|
+
return /(?:\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(text);
|
|
1987
|
+
}
|
|
1988
|
+
async function findCompletedGoalWorkflowCleanupNotice(cwd) {
|
|
1989
|
+
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
1990
|
+
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
1991
|
+
const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
|
|
1992
|
+
if (safeString(aggregateCompletion.status) === "complete" || (ultragoals.length > 0 && ultragoals.every((goal) => safeString(goal.status) === "complete"))) {
|
|
1993
|
+
return buildCodexGoalTerminalCleanupNotice("Ultragoal completion");
|
|
1994
|
+
}
|
|
1995
|
+
const performanceRoot = join(cwd, ".omx", "goals", "performance");
|
|
1996
|
+
for (const entry of await readdir(performanceRoot, { withFileTypes: true }).catch(() => [])) {
|
|
1997
|
+
if (!entry.isDirectory())
|
|
1998
|
+
continue;
|
|
1999
|
+
const state = await readJsonIfExists(join(performanceRoot, entry.name, "state.json"));
|
|
2000
|
+
if (state?.workflow === "performance-goal" && safeString(state.status) === "complete") {
|
|
2001
|
+
return buildCodexGoalTerminalCleanupNotice("Performance-goal completion");
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
const autoresearchRoot = join(cwd, ".omx", "goals", "autoresearch");
|
|
2005
|
+
for (const entry of await readdir(autoresearchRoot, { withFileTypes: true }).catch(() => [])) {
|
|
2006
|
+
if (!entry.isDirectory())
|
|
2007
|
+
continue;
|
|
2008
|
+
const mission = await readJsonIfExists(join(autoresearchRoot, entry.name, "mission.json"));
|
|
2009
|
+
if (mission?.workflow === "autoresearch-goal" && safeString(mission.status) === "complete") {
|
|
2010
|
+
return buildCodexGoalTerminalCleanupNotice("Autoresearch-goal completion");
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
2015
|
+
async function buildCompletedGoalCleanupPromptWarning(cwd, prompt) {
|
|
2016
|
+
if (!looksLikeNewGoalPrompt(prompt))
|
|
2017
|
+
return null;
|
|
2018
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2019
|
+
if (!notice)
|
|
2020
|
+
return null;
|
|
2021
|
+
return `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2022
|
+
}
|
|
2023
|
+
async function buildCompletedGoalCleanupStopOutput(payload, cwd) {
|
|
2024
|
+
const text = [
|
|
2025
|
+
safeString(payload.last_user_message ?? payload.lastUserMessage),
|
|
2026
|
+
safeString(payload.last_assistant_message ?? payload.lastAssistantMessage),
|
|
2027
|
+
].join("\n");
|
|
2028
|
+
if (!looksLikeNewGoalPrompt(text))
|
|
2029
|
+
return null;
|
|
2030
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2031
|
+
if (!notice)
|
|
2032
|
+
return null;
|
|
2033
|
+
const systemMessage = `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2034
|
+
return {
|
|
2035
|
+
decision: "block",
|
|
2036
|
+
reason: systemMessage,
|
|
2037
|
+
stopReason: "completed_codex_goal_cleanup_required",
|
|
2038
|
+
systemMessage,
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
1876
2041
|
async function findActiveGoalWorkflowReconciliationRequirement(cwd) {
|
|
1877
2042
|
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
1878
2043
|
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
@@ -2088,6 +2253,125 @@ function readPayloadThreadId(payload) {
|
|
|
2088
2253
|
function readPayloadTurnId(payload) {
|
|
2089
2254
|
return safeString(payload.turn_id ?? payload.turnId).trim();
|
|
2090
2255
|
}
|
|
2256
|
+
function nativeSubagentCapacityBlockerPath(stateDir) {
|
|
2257
|
+
return join(stateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE);
|
|
2258
|
+
}
|
|
2259
|
+
function stringifyUnknown(value) {
|
|
2260
|
+
if (typeof value === "string")
|
|
2261
|
+
return value;
|
|
2262
|
+
if (value === undefined || value === null)
|
|
2263
|
+
return "";
|
|
2264
|
+
try {
|
|
2265
|
+
return JSON.stringify(value);
|
|
2266
|
+
}
|
|
2267
|
+
catch {
|
|
2268
|
+
return String(value);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
function payloadEvidenceText(payload) {
|
|
2272
|
+
return [
|
|
2273
|
+
safeString(payload.tool_name),
|
|
2274
|
+
stringifyUnknown(payload.tool_response),
|
|
2275
|
+
stringifyUnknown(payload.response),
|
|
2276
|
+
stringifyUnknown(payload.error),
|
|
2277
|
+
stringifyUnknown(payload.message),
|
|
2278
|
+
].filter(Boolean).join("\n");
|
|
2279
|
+
}
|
|
2280
|
+
function isNativeSubagentCapacityFailure(payload) {
|
|
2281
|
+
const evidence = payloadEvidenceText(payload);
|
|
2282
|
+
if (!/\bagent thread limit reached\b/i.test(evidence))
|
|
2283
|
+
return false;
|
|
2284
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2285
|
+
return !toolName || /(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName);
|
|
2286
|
+
}
|
|
2287
|
+
function summarizeCapacityFailure(text) {
|
|
2288
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2289
|
+
if (!normalized)
|
|
2290
|
+
return "agent thread limit reached";
|
|
2291
|
+
const match = normalized.match(/[^.?!\n\r]*agent thread limit reached[^.?!\n\r]*/i);
|
|
2292
|
+
return (match?.[0] ?? normalized).slice(0, 500);
|
|
2293
|
+
}
|
|
2294
|
+
async function recordNativeSubagentCapacityBlocker(cwd, stateDir, payload) {
|
|
2295
|
+
if (!isNativeSubagentCapacityFailure(payload))
|
|
2296
|
+
return;
|
|
2297
|
+
const nowMs = Date.now();
|
|
2298
|
+
const blocker = {
|
|
2299
|
+
schema_version: 1,
|
|
2300
|
+
reason: "agent_thread_limit_reached",
|
|
2301
|
+
...(readPayloadSessionId(payload) ? { session_id: readPayloadSessionId(payload) } : {}),
|
|
2302
|
+
...(readPayloadThreadId(payload) ? { thread_id: readPayloadThreadId(payload) } : {}),
|
|
2303
|
+
...(readPayloadTurnId(payload) ? { turn_id: readPayloadTurnId(payload) } : {}),
|
|
2304
|
+
...(safeString(payload.tool_name).trim() ? { tool_name: safeString(payload.tool_name).trim() } : {}),
|
|
2305
|
+
error_summary: summarizeCapacityFailure(payloadEvidenceText(payload)),
|
|
2306
|
+
observed_at: new Date(nowMs).toISOString(),
|
|
2307
|
+
expires_at: new Date(nowMs + NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS).toISOString(),
|
|
2308
|
+
};
|
|
2309
|
+
await mkdir(stateDir, { recursive: true });
|
|
2310
|
+
await writeFile(nativeSubagentCapacityBlockerPath(stateDir), JSON.stringify({
|
|
2311
|
+
...blocker,
|
|
2312
|
+
cwd,
|
|
2313
|
+
}, null, 2));
|
|
2314
|
+
}
|
|
2315
|
+
function isFreshNativeSubagentCapacityBlocker(blocker, cwd, payload, nowMs = Date.now()) {
|
|
2316
|
+
if (!blocker)
|
|
2317
|
+
return false;
|
|
2318
|
+
if (safeString(blocker.reason) !== "agent_thread_limit_reached")
|
|
2319
|
+
return false;
|
|
2320
|
+
const expiresAtMs = Date.parse(safeString(blocker.expires_at));
|
|
2321
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs)
|
|
2322
|
+
return false;
|
|
2323
|
+
const blockerCwd = safeString(blocker.cwd).trim();
|
|
2324
|
+
if (blockerCwd) {
|
|
2325
|
+
try {
|
|
2326
|
+
if (resolve(blockerCwd) !== resolve(cwd))
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
catch {
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
const blockerSessionId = safeString(blocker.session_id).trim();
|
|
2334
|
+
const payloadSessionId = readPayloadSessionId(payload);
|
|
2335
|
+
return !blockerSessionId || !payloadSessionId || blockerSessionId === payloadSessionId;
|
|
2336
|
+
}
|
|
2337
|
+
function inputContainsCloseAgentRequest(value) {
|
|
2338
|
+
if (typeof value === "string")
|
|
2339
|
+
return /\bclose_agent\b/i.test(value);
|
|
2340
|
+
if (!value || typeof value !== "object")
|
|
2341
|
+
return false;
|
|
2342
|
+
try {
|
|
2343
|
+
return /\bclose_agent\b/i.test(JSON.stringify(value));
|
|
2344
|
+
}
|
|
2345
|
+
catch {
|
|
2346
|
+
return false;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function isCloseAgentToolUse(payload) {
|
|
2350
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2351
|
+
if (/\bclose_agent\b/i.test(toolName))
|
|
2352
|
+
return true;
|
|
2353
|
+
if (/multi_tool_use\.parallel/i.test(toolName) && inputContainsCloseAgentRequest(payload.tool_input))
|
|
2354
|
+
return true;
|
|
2355
|
+
return inputContainsCloseAgentRequest(payload.tool_input) && /multi_agent|agent|tool_use/i.test(toolName);
|
|
2356
|
+
}
|
|
2357
|
+
async function buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir) {
|
|
2358
|
+
if (!isCloseAgentToolUse(payload))
|
|
2359
|
+
return null;
|
|
2360
|
+
const blocker = await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir));
|
|
2361
|
+
if (!isFreshNativeSubagentCapacityBlocker(blocker, cwd, payload))
|
|
2362
|
+
return null;
|
|
2363
|
+
const evidence = safeString(blocker.error_summary).trim() || "agent thread limit reached";
|
|
2364
|
+
return {
|
|
2365
|
+
decision: "block",
|
|
2366
|
+
reason: "Native subagent capacity was exhausted recently; model-level close_agent cleanup is blocked because close_agent can hang indefinitely on stale handles.",
|
|
2367
|
+
hookSpecificOutput: {
|
|
2368
|
+
hookEventName: "PreToolUse",
|
|
2369
|
+
additionalContext: `OMX blocked ${safeString(payload.tool_name).trim() || "close_agent"} before it could start: a recent native subagent capacity failure was recorded (${evidence}). `
|
|
2370
|
+
+ "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. "
|
|
2371
|
+
+ "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.",
|
|
2372
|
+
},
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2091
2375
|
async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir) {
|
|
2092
2376
|
const currentSession = stateDir
|
|
2093
2377
|
? await readUsableSessionStateFromStateDir(cwd, stateDir)
|
|
@@ -2134,6 +2418,7 @@ const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
|
2134
2418
|
".omx/plans",
|
|
2135
2419
|
".omx/specs",
|
|
2136
2420
|
".omx/state",
|
|
2421
|
+
".beads",
|
|
2137
2422
|
];
|
|
2138
2423
|
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
2139
2424
|
"Write",
|
|
@@ -2182,6 +2467,9 @@ function isAutopilotRalplanLikePhase(phase) {
|
|
|
2182
2467
|
function canAutopilotSkillMirrorSupplyRalplanPhase(phase) {
|
|
2183
2468
|
return phase === "" || normalizeAutopilotPhase(phase) === "ralplan";
|
|
2184
2469
|
}
|
|
2470
|
+
function isAutopilotReviewReworkPhase(phase) {
|
|
2471
|
+
return normalizeAutopilotPhase(phase) === "rework";
|
|
2472
|
+
}
|
|
2185
2473
|
function hasExplicitExecutionHandoffSkill(state, sessionId, threadId) {
|
|
2186
2474
|
return listActiveSkills(state ?? {}).some((entry) => (RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
|
|
2187
2475
|
&& matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)));
|
|
@@ -2208,6 +2496,134 @@ function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
|
|
|
2208
2496
|
function isAllowedRalplanArtifactPath(cwd, rawPath) {
|
|
2209
2497
|
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2210
2498
|
}
|
|
2499
|
+
function shellTokenizeLiteralCommand(command) {
|
|
2500
|
+
const tokens = [];
|
|
2501
|
+
let current = "";
|
|
2502
|
+
let quote = null;
|
|
2503
|
+
let escaping = false;
|
|
2504
|
+
for (const char of command.trim()) {
|
|
2505
|
+
if (escaping) {
|
|
2506
|
+
current += char;
|
|
2507
|
+
escaping = false;
|
|
2508
|
+
continue;
|
|
2509
|
+
}
|
|
2510
|
+
if (quote === '"' && char === "\\") {
|
|
2511
|
+
escaping = true;
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
if (quote) {
|
|
2515
|
+
if (char === quote)
|
|
2516
|
+
quote = null;
|
|
2517
|
+
else
|
|
2518
|
+
current += char;
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2521
|
+
if (char === "'" || char === '"') {
|
|
2522
|
+
quote = char;
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
2525
|
+
if (/\s/.test(char)) {
|
|
2526
|
+
if (current) {
|
|
2527
|
+
tokens.push(current);
|
|
2528
|
+
current = "";
|
|
2529
|
+
}
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
if (/[;&|<>`$(){}\n\r]/.test(char))
|
|
2533
|
+
return null;
|
|
2534
|
+
current += char;
|
|
2535
|
+
}
|
|
2536
|
+
if (escaping || quote)
|
|
2537
|
+
return null;
|
|
2538
|
+
if (current)
|
|
2539
|
+
tokens.push(current);
|
|
2540
|
+
return tokens;
|
|
2541
|
+
}
|
|
2542
|
+
function findLiteralBdExecutableIndex(tokens) {
|
|
2543
|
+
if (tokens[0] === "bd")
|
|
2544
|
+
return 0;
|
|
2545
|
+
if (tokens[0] === "command" || tokens[0] === "builtin" || tokens[0] === "exec" || tokens[0] === "nohup") {
|
|
2546
|
+
return tokens[1] === "bd" ? 1 : -1;
|
|
2547
|
+
}
|
|
2548
|
+
if (tokens[0] !== "env")
|
|
2549
|
+
return -1;
|
|
2550
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
2551
|
+
const token = tokens[index] ?? "";
|
|
2552
|
+
if (token === "bd")
|
|
2553
|
+
return index;
|
|
2554
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token))
|
|
2555
|
+
continue;
|
|
2556
|
+
if (token.startsWith("-"))
|
|
2557
|
+
continue;
|
|
2558
|
+
return -1;
|
|
2559
|
+
}
|
|
2560
|
+
return -1;
|
|
2561
|
+
}
|
|
2562
|
+
function isAllowedRalplanBeadsDbPath(cwd, rawPath) {
|
|
2563
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
2564
|
+
if (!trimmed || trimmed.includes("\0"))
|
|
2565
|
+
return false;
|
|
2566
|
+
let relativePath;
|
|
2567
|
+
try {
|
|
2568
|
+
const absolute = resolve(cwd, trimmed);
|
|
2569
|
+
relativePath = relative(cwd, absolute).replace(/\\/g, "/");
|
|
2570
|
+
}
|
|
2571
|
+
catch {
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
return relativePath.startsWith(".beads/") && relativePath.length > ".beads/".length;
|
|
2575
|
+
}
|
|
2576
|
+
function classifyRalplanBeadsMetadataCommand(cwd, command) {
|
|
2577
|
+
const trimmedCommand = command.trim();
|
|
2578
|
+
const startsWithBd = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"$`]*"|'[^']*'|[^\s"'$`;&|<>]+)\s+)*bd(?:\s|$)/.test(trimmedCommand);
|
|
2579
|
+
const hasCompoundBd = /[;&|()]\s*bd(?:\s|$)/.test(command);
|
|
2580
|
+
const tokens = shellTokenizeLiteralCommand(command);
|
|
2581
|
+
const bdExecutableIndex = tokens ? findLiteralBdExecutableIndex(tokens) : -1;
|
|
2582
|
+
if (!startsWithBd && !hasCompoundBd && bdExecutableIndex === -1)
|
|
2583
|
+
return { present: false, allowed: false };
|
|
2584
|
+
if (!tokens || bdExecutableIndex !== 0) {
|
|
2585
|
+
return { present: true, allowed: false, reason: "Beads tracker command must be a single literal bd invocation" };
|
|
2586
|
+
}
|
|
2587
|
+
let dbPath = "";
|
|
2588
|
+
let dbValueIndex = -1;
|
|
2589
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
2590
|
+
const token = tokens[index] ?? "";
|
|
2591
|
+
if (token === "--db") {
|
|
2592
|
+
dbPath = tokens[index + 1] ?? "";
|
|
2593
|
+
dbValueIndex = index + 1;
|
|
2594
|
+
break;
|
|
2595
|
+
}
|
|
2596
|
+
if (token.startsWith("--db=")) {
|
|
2597
|
+
dbPath = token.slice("--db=".length);
|
|
2598
|
+
dbValueIndex = index;
|
|
2599
|
+
break;
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
if (!dbPath) {
|
|
2603
|
+
return { present: true, allowed: false, reason: "Beads tracker command is missing a literal --db .beads/<db> target" };
|
|
2604
|
+
}
|
|
2605
|
+
if (!isAllowedRalplanBeadsDbPath(cwd, dbPath)) {
|
|
2606
|
+
return { present: true, allowed: false, reason: `Beads tracker db target ${dbPath} is outside repo-local .beads metadata` };
|
|
2607
|
+
}
|
|
2608
|
+
const operationTokens = tokens
|
|
2609
|
+
.slice(dbValueIndex + 1)
|
|
2610
|
+
.filter((token) => token && !token.startsWith("-"));
|
|
2611
|
+
const operation = operationTokens[0] ?? "";
|
|
2612
|
+
const suboperation = operationTokens[1] ?? "";
|
|
2613
|
+
if (["create", "update", "edit", "close", "reopen", "status", "dep"].includes(operation)) {
|
|
2614
|
+
return { present: true, allowed: true };
|
|
2615
|
+
}
|
|
2616
|
+
if (operation === "comments" && suboperation === "add") {
|
|
2617
|
+
return { present: true, allowed: true };
|
|
2618
|
+
}
|
|
2619
|
+
return {
|
|
2620
|
+
present: true,
|
|
2621
|
+
allowed: false,
|
|
2622
|
+
reason: operation
|
|
2623
|
+
? `Beads tracker operation ${operation}${suboperation ? ` ${suboperation}` : ""} is not allowed during planning`
|
|
2624
|
+
: "Beads tracker command is missing an allowed metadata operation",
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2211
2627
|
function readPreToolUseCommand(payload) {
|
|
2212
2628
|
const toolInput = safeObject(payload.tool_input);
|
|
2213
2629
|
return safeString(toolInput.command).trim();
|
|
@@ -2261,6 +2677,31 @@ function isNullDeviceRedirectTarget(target) {
|
|
|
2261
2677
|
const normalized = target.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
2262
2678
|
return normalized === "/dev/null" || normalized === "nul";
|
|
2263
2679
|
}
|
|
2680
|
+
// Collects same-command literal variable assignments (`NAME="value"`), skipping
|
|
2681
|
+
// any value that involves expansion (`$`, backticks) so unresolved/dynamic
|
|
2682
|
+
// targets stay conservatively blocked.
|
|
2683
|
+
function extractCommandLiteralAssignments(command) {
|
|
2684
|
+
const assignments = new Map();
|
|
2685
|
+
const pattern = /(?:^|[\n;&|(]|&&|\|\|)\s*([A-Za-z_][A-Za-z0-9_]*)=(?:"([^"$`]*)"|'([^']*)'|([^\s"'$`;&|<>]+))/g;
|
|
2686
|
+
for (const match of command.matchAll(pattern)) {
|
|
2687
|
+
const name = safeString(match[1]).trim();
|
|
2688
|
+
if (!name)
|
|
2689
|
+
continue;
|
|
2690
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
2691
|
+
assignments.set(name, value);
|
|
2692
|
+
}
|
|
2693
|
+
return assignments;
|
|
2694
|
+
}
|
|
2695
|
+
// Resolves a redirect/tee target of the form `$NAME`/`${NAME}` against
|
|
2696
|
+
// same-command literal assignments; non-variable or unresolved targets are
|
|
2697
|
+
// returned unchanged so they remain subject to the allowed-path check.
|
|
2698
|
+
function resolveCommandRedirectTarget(target, assignments) {
|
|
2699
|
+
const variableMatch = target.match(/^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/);
|
|
2700
|
+
if (!variableMatch)
|
|
2701
|
+
return target;
|
|
2702
|
+
const resolved = assignments.get(safeString(variableMatch[1]));
|
|
2703
|
+
return resolved !== undefined ? resolved : target;
|
|
2704
|
+
}
|
|
2264
2705
|
function extractDeepInterviewCommandRedirectTargets(command) {
|
|
2265
2706
|
const targets = [];
|
|
2266
2707
|
for (const match of command.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
|
|
@@ -2271,7 +2712,7 @@ function extractDeepInterviewCommandRedirectTargets(command) {
|
|
|
2271
2712
|
return targets;
|
|
2272
2713
|
}
|
|
2273
2714
|
function commandHasDeepInterviewWriteIntent(command) {
|
|
2274
|
-
return
|
|
2715
|
+
return commandInvokesApplyPatch(command)
|
|
2275
2716
|
|| extractDeepInterviewCommandRedirectTargets(command).length > 0
|
|
2276
2717
|
|| /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
|
|
2277
2718
|
|| /\bsed\s+(?:[^\n;&|]*\s)?-i(?:\b|['"])/.test(command)
|
|
@@ -2279,14 +2720,32 @@ function commandHasDeepInterviewWriteIntent(command) {
|
|
|
2279
2720
|
|| /\b(?:git\s+(?:checkout|switch|restore|reset|apply|am|merge|rebase)|npm\s+(?:install|i|ci)|pnpm\s+(?:install|i)|yarn\s+(?:install|add))\b/.test(command);
|
|
2280
2721
|
}
|
|
2281
2722
|
function extractDeepInterviewCommandWriteTargets(command) {
|
|
2282
|
-
const
|
|
2723
|
+
const assignments = extractCommandLiteralAssignments(command);
|
|
2724
|
+
const targets = extractDeepInterviewCommandRedirectTargets(command)
|
|
2725
|
+
.map((target) => resolveCommandRedirectTarget(target, assignments));
|
|
2283
2726
|
for (const match of command.matchAll(/\btee\s+(?:-a\s+)?(["']?)([^\s&|;<>]+)\1/g)) {
|
|
2284
2727
|
const candidate = safeString(match[2]).trim();
|
|
2285
2728
|
if (candidate)
|
|
2286
|
-
targets.push(candidate);
|
|
2729
|
+
targets.push(resolveCommandRedirectTarget(candidate, assignments));
|
|
2287
2730
|
}
|
|
2288
2731
|
return targets;
|
|
2289
2732
|
}
|
|
2733
|
+
function formatPlanningWriteBlockDetail(operationClass, target, allowedPrefixes) {
|
|
2734
|
+
const targetDetail = target ? `target ${target}` : "target <unresolved>";
|
|
2735
|
+
return `${operationClass} ${targetDetail} is not under allowed planning artifact paths (${allowedPrefixes.join(", ")})`;
|
|
2736
|
+
}
|
|
2737
|
+
function isUnresolvedVariableTarget(target) {
|
|
2738
|
+
const normalized = target.trim().replace(/^['"]|['"]$/g, "");
|
|
2739
|
+
return /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(normalized);
|
|
2740
|
+
}
|
|
2741
|
+
function describeImplementationToolBlock(toolName, blockedPath, pathCount) {
|
|
2742
|
+
if (pathCount === 0) {
|
|
2743
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
|
|
2744
|
+
return `${operationClass} target <unresolved>; only planning artifact paths are allowed (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2745
|
+
}
|
|
2746
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
|
|
2747
|
+
return formatPlanningWriteBlockDetail(operationClass, blockedPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2748
|
+
}
|
|
2290
2749
|
function isAllowedDeepInterviewBashWrite(cwd, command) {
|
|
2291
2750
|
if (!commandHasDeepInterviewWriteIntent(command))
|
|
2292
2751
|
return true;
|
|
@@ -2373,6 +2832,8 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
|
|
|
2373
2832
|
if (!hasActiveAutopilotSkill)
|
|
2374
2833
|
return null;
|
|
2375
2834
|
const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
|
|
2835
|
+
if (isAutopilotReviewReworkPhase(autopilotStatePhase))
|
|
2836
|
+
return null;
|
|
2376
2837
|
if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase))
|
|
2377
2838
|
return null;
|
|
2378
2839
|
const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
|
|
@@ -2383,12 +2844,37 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
|
|
|
2383
2844
|
return hasActiveAutopilotSkill ? autopilotState : null;
|
|
2384
2845
|
}
|
|
2385
2846
|
function isAllowedRalplanBashWrite(cwd, command) {
|
|
2847
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
2848
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2849
|
+
const hasAllowedTargets = targets.length > 0
|
|
2850
|
+
&& targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
2851
|
+
if (beadsCommand.present) {
|
|
2852
|
+
return beadsCommand.allowed && (targets.length === 0 || hasAllowedTargets);
|
|
2853
|
+
}
|
|
2386
2854
|
if (!commandHasDeepInterviewWriteIntent(command))
|
|
2387
2855
|
return true;
|
|
2388
2856
|
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
|
|
2389
2857
|
return true;
|
|
2858
|
+
return hasAllowedTargets;
|
|
2859
|
+
}
|
|
2860
|
+
function buildRalplanBashBlockedDetail(cwd, command) {
|
|
2390
2861
|
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2391
|
-
|
|
2862
|
+
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
2863
|
+
if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
|
|
2864
|
+
return `unresolved Bash write target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2865
|
+
}
|
|
2866
|
+
if (blockedTarget) {
|
|
2867
|
+
const operationClass = /\btee\s+(?:-a\s+)?/.test(command) ? "Bash tee write" : "Bash redirect write";
|
|
2868
|
+
return `${operationClass} target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
2869
|
+
}
|
|
2870
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
2871
|
+
if (beadsCommand.present && !beadsCommand.allowed) {
|
|
2872
|
+
return beadsCommand.reason ?? "Beads tracker command is not an allowed planning metadata mutation";
|
|
2873
|
+
}
|
|
2874
|
+
if (beadsCommand.present) {
|
|
2875
|
+
return "Beads tracker command also performs an implementation write outside allowed planning metadata";
|
|
2876
|
+
}
|
|
2877
|
+
return "Bash write intent did not identify an allowed planning artifact path or metadata path";
|
|
2392
2878
|
}
|
|
2393
2879
|
async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
|
|
2394
2880
|
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
@@ -2404,23 +2890,20 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
2404
2890
|
if (toolName === "Bash") {
|
|
2405
2891
|
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2406
2892
|
if (blocked) {
|
|
2407
|
-
|
|
2408
|
-
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
2409
|
-
blockedDetail = blockedTarget
|
|
2410
|
-
? `write target ${blockedTarget} is not under allowed planning artifact paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`
|
|
2411
|
-
: "Bash write intent did not identify an allowed planning artifact path";
|
|
2893
|
+
blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
|
|
2412
2894
|
}
|
|
2413
2895
|
}
|
|
2414
2896
|
else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2415
|
-
|
|
2897
|
+
const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
|
|
2898
|
+
if (toolPathCandidates.length === 0) {
|
|
2416
2899
|
blocked = true;
|
|
2417
|
-
blockedDetail =
|
|
2900
|
+
blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
|
|
2418
2901
|
}
|
|
2419
2902
|
else {
|
|
2420
|
-
const blockedPath =
|
|
2903
|
+
const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2421
2904
|
blocked = blockedPath !== undefined;
|
|
2422
2905
|
if (blockedPath !== undefined) {
|
|
2423
|
-
blockedDetail =
|
|
2906
|
+
blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
|
|
2424
2907
|
}
|
|
2425
2908
|
}
|
|
2426
2909
|
}
|
|
@@ -2438,7 +2921,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
|
|
|
2438
2921
|
hookSpecificOutput: {
|
|
2439
2922
|
hookEventName: "PreToolUse",
|
|
2440
2923
|
additionalContext: `${planningModeDescription}. `
|
|
2441
|
-
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`,
|
|
2924
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
|
|
2442
2925
|
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
2443
2926
|
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
2444
2927
|
},
|
|
@@ -3293,18 +3776,35 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3293
3776
|
if (hookEventName === "SessionStart" && nativeSessionId) {
|
|
3294
3777
|
const transcriptPath = safeString(payload.transcript_path ?? payload.transcriptPath).trim();
|
|
3295
3778
|
const subagentSessionStart = readNativeSubagentSessionStartMetadata(transcriptPath);
|
|
3296
|
-
if (subagentSessionStart
|
|
3779
|
+
if (subagentSessionStart) {
|
|
3780
|
+
// A native child/subagent SessionStart carries a parent_thread_id in its
|
|
3781
|
+
// transcript session_meta. Treat it as a child-agent lifecycle event for
|
|
3782
|
+
// notification suppression and subagent tracking even when the canonical
|
|
3783
|
+
// leader session has not been reconciled yet (#2831). A child start must
|
|
3784
|
+
// never promote itself into a root/leader session or emit an independent
|
|
3785
|
+
// session-start notification at session/minimal verbosity.
|
|
3297
3786
|
isSubagentSessionStart = true;
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3787
|
+
if (canonicalSessionId) {
|
|
3788
|
+
const belongsToCanonicalSession = await nativeSubagentSessionStartBelongsToCanonicalSession(cwd, canonicalSessionId, currentSessionState, subagentSessionStart);
|
|
3789
|
+
if (belongsToCanonicalSession) {
|
|
3790
|
+
resolvedNativeSessionId = nativeSessionId;
|
|
3791
|
+
await recordNativeSubagentSessionStart(cwd, canonicalSessionId, nativeSessionId, subagentSessionStart, transcriptPath);
|
|
3792
|
+
}
|
|
3793
|
+
else {
|
|
3794
|
+
skipCanonicalSessionStartContext = true;
|
|
3795
|
+
resolvedNativeSessionId =
|
|
3796
|
+
safeString(currentSessionState?.native_session_id).trim() || nativeSessionId;
|
|
3797
|
+
await recordIgnoredNativeSubagentSessionStart(cwd, canonicalSessionId, nativeSessionId, subagentSessionStart, transcriptPath);
|
|
3798
|
+
}
|
|
3302
3799
|
}
|
|
3303
3800
|
else {
|
|
3801
|
+
// No canonical leader session is resolved in this worktree yet. Still
|
|
3802
|
+
// register the child thread under its parent so its later Stop is
|
|
3803
|
+
// recognized as subagent-scoped, skip leader SessionStart context, and
|
|
3804
|
+
// do not reconcile the child as a new root session.
|
|
3304
3805
|
skipCanonicalSessionStartContext = true;
|
|
3305
|
-
resolvedNativeSessionId =
|
|
3306
|
-
|
|
3307
|
-
await recordIgnoredNativeSubagentSessionStart(cwd, canonicalSessionId, nativeSessionId, subagentSessionStart, transcriptPath);
|
|
3806
|
+
resolvedNativeSessionId = nativeSessionId;
|
|
3807
|
+
await recordNativeSubagentSessionStart(cwd, canonicalSessionId, nativeSessionId, subagentSessionStart, transcriptPath);
|
|
3308
3808
|
}
|
|
3309
3809
|
}
|
|
3310
3810
|
else {
|
|
@@ -3348,7 +3848,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3348
3848
|
&& shouldSuppressSubagentLifecycleHookDispatch();
|
|
3349
3849
|
if (hookEventName === "UserPromptSubmit") {
|
|
3350
3850
|
const prompt = readPromptText(payload);
|
|
3351
|
-
goalWorkflowAdditionalContext = await
|
|
3851
|
+
goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
|
|
3852
|
+
?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
|
|
3352
3853
|
ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
|
|
3353
3854
|
? 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)}`)
|
|
3354
3855
|
: null;
|
|
@@ -3506,9 +4007,11 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3506
4007
|
: "";
|
|
3507
4008
|
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
3508
4009
|
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4010
|
+
?? await buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir)
|
|
3509
4011
|
?? buildNativePreToolUseOutput(payload);
|
|
3510
4012
|
}
|
|
3511
4013
|
else if (hookEventName === "PostToolUse") {
|
|
4014
|
+
await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
|
|
3512
4015
|
if (detectMcpTransportFailure(payload)) {
|
|
3513
4016
|
await markTeamTransportFailure(cwd, payload);
|
|
3514
4017
|
}
|
|
@@ -3518,7 +4021,7 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
|
|
|
3518
4021
|
else if (hookEventName === "Stop") {
|
|
3519
4022
|
outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
|
|
3520
4023
|
skipRalphStopBlock: isSubagentStop,
|
|
3521
|
-
});
|
|
4024
|
+
}) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
|
|
3522
4025
|
}
|
|
3523
4026
|
return {
|
|
3524
4027
|
hookEventName,
|
|
@@ -3615,6 +4118,9 @@ function buildMalformedStdinHookOutput(parseError, rawInput, cwd = process.cwd()
|
|
|
3615
4118
|
const reason = "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
|
|
3616
4119
|
const systemMessage = `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
3617
4120
|
const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput);
|
|
4121
|
+
if (inferredHookEventName === "PreToolUse") {
|
|
4122
|
+
return { systemMessage };
|
|
4123
|
+
}
|
|
3618
4124
|
if (inferredHookEventName === "Stop" || (!inferredHookEventName && hasNativeStopRuntimeSurface(cwd))) {
|
|
3619
4125
|
return {
|
|
3620
4126
|
decision: "block",
|
|
@@ -3650,10 +4156,13 @@ async function buildOversizedStopActiveWorkflowOutput(cwd) {
|
|
|
3650
4156
|
};
|
|
3651
4157
|
}
|
|
3652
4158
|
async function buildOversizedStdinHookOutput(rawHookEventName, cwd) {
|
|
4159
|
+
const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
4160
|
+
if (rawHookEventName === "PreToolUse") {
|
|
4161
|
+
return { systemMessage };
|
|
4162
|
+
}
|
|
3653
4163
|
if (rawHookEventName === "Stop") {
|
|
3654
4164
|
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
3655
4165
|
}
|
|
3656
|
-
const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
3657
4166
|
return {
|
|
3658
4167
|
continue: false,
|
|
3659
4168
|
stopReason: "native_hook_stdin_oversized",
|
|
@@ -3733,9 +4242,9 @@ export async function runCodexNativeHookCli() {
|
|
|
3733
4242
|
}
|
|
3734
4243
|
const result = await dispatchCodexNativeHook(payload);
|
|
3735
4244
|
if (result.outputJson) {
|
|
3736
|
-
writeNativeHookJsonStdout(result.outputJson);
|
|
4245
|
+
writeNativeHookJsonStdout(sanitizeCodexHookOutput(result.hookEventName, result.outputJson) ?? {});
|
|
3737
4246
|
}
|
|
3738
|
-
else if (result.hookEventName
|
|
4247
|
+
else if (result.hookEventName !== "PreCompact" && result.hookEventName !== "PostCompact") {
|
|
3739
4248
|
writeNativeHookJsonStdout({});
|
|
3740
4249
|
}
|
|
3741
4250
|
}
|