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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
readSkillActiveState,
|
|
14
14
|
readVisibleSkillActiveStateForStateDir,
|
|
15
15
|
type SkillActiveStateLike,
|
|
16
|
+
type SkillActiveEntry,
|
|
16
17
|
} from "../state/skill-active.js";
|
|
17
18
|
import {
|
|
18
19
|
isTrustedSubagentThread,
|
|
@@ -40,7 +41,7 @@ import {
|
|
|
40
41
|
} from "../team/state.js";
|
|
41
42
|
import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
|
|
42
43
|
import { findGitLayout } from "../utils/git-layout.js";
|
|
43
|
-
import { getBaseStateDir, getStateFilePath, getStatePath } from "../mcp/state-paths.js";
|
|
44
|
+
import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
|
|
44
45
|
import {
|
|
45
46
|
detectKeywords,
|
|
46
47
|
detectPrimaryKeyword,
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
SLOPPY_FALLBACK_PHRASE_PATTERNS,
|
|
62
63
|
buildNativePostToolUseOutput,
|
|
63
64
|
buildNativePreToolUseOutput,
|
|
65
|
+
commandInvokesApplyPatch,
|
|
64
66
|
detectMcpTransportFailure,
|
|
65
67
|
hasAnyPattern,
|
|
66
68
|
} from "./codex-native-pre-post.js";
|
|
@@ -86,6 +88,9 @@ import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDec
|
|
|
86
88
|
import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
|
|
87
89
|
import { readRunState } from "../runtime/run-state.js";
|
|
88
90
|
import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
|
|
91
|
+
import {
|
|
92
|
+
buildCodexGoalTerminalCleanupNotice,
|
|
93
|
+
} from "../goal-workflows/codex-goal-snapshot.js";
|
|
89
94
|
import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
|
|
90
95
|
import {
|
|
91
96
|
parseUltragoalSteeringDirective,
|
|
@@ -146,6 +151,8 @@ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
|
146
151
|
const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
|
|
147
152
|
const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
|
|
148
153
|
const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
|
|
154
|
+
const NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE = "native-subagent-capacity-blocker.json";
|
|
155
|
+
const NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS = 30 * 60_000;
|
|
149
156
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
150
157
|
const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
|
|
151
158
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
@@ -548,6 +555,23 @@ function readHookEventName(payload: CodexHookPayload): CodexHookEventName | null
|
|
|
548
555
|
return null;
|
|
549
556
|
}
|
|
550
557
|
|
|
558
|
+
function sanitizeCodexHookOutput(
|
|
559
|
+
hookEventName: CodexHookEventName | null,
|
|
560
|
+
output: Record<string, unknown> | null,
|
|
561
|
+
): Record<string, unknown> | null {
|
|
562
|
+
if (!output || hookEventName !== "PreToolUse") return output;
|
|
563
|
+
const systemMessage = safeString(output.systemMessage).trim();
|
|
564
|
+
if (systemMessage) return { systemMessage };
|
|
565
|
+
|
|
566
|
+
const reason = safeString(output.reason).trim();
|
|
567
|
+
const hookSpecificOutput = output.hookSpecificOutput;
|
|
568
|
+
const additionalContext = hookSpecificOutput && typeof hookSpecificOutput === "object"
|
|
569
|
+
? safeString((hookSpecificOutput as { additionalContext?: unknown }).additionalContext).trim()
|
|
570
|
+
: "";
|
|
571
|
+
const derivedSystemMessage = [reason, additionalContext].filter(Boolean).join("\n\n");
|
|
572
|
+
return derivedSystemMessage ? { systemMessage: derivedSystemMessage } : {};
|
|
573
|
+
}
|
|
574
|
+
|
|
551
575
|
export function mapCodexHookEventToOmxEvent(
|
|
552
576
|
hookEventName: CodexHookEventName | null,
|
|
553
577
|
): string | null {
|
|
@@ -2226,6 +2250,76 @@ function isStopExempt(payload: CodexHookPayload): boolean {
|
|
|
2226
2250
|
);
|
|
2227
2251
|
}
|
|
2228
2252
|
|
|
2253
|
+
async function readModeStateWithStopSource(
|
|
2254
|
+
mode: "autopilot" | "ultrawork" | "ultraqa",
|
|
2255
|
+
cwd: string,
|
|
2256
|
+
sessionId?: string,
|
|
2257
|
+
): Promise<{ state: Record<string, unknown>; path: string } | null> {
|
|
2258
|
+
const paths = await getAuthoritativeActiveStatePaths(mode, cwd, sessionId?.trim() || undefined).catch(() => [] as string[]);
|
|
2259
|
+
const path = paths[0];
|
|
2260
|
+
if (!path) return null;
|
|
2261
|
+
const state = await readJsonIfExists(path);
|
|
2262
|
+
return state ? { state, path } : null;
|
|
2263
|
+
}
|
|
2264
|
+
async function readRawSkillActiveState(path: string): Promise<SkillActiveStateLike | null> {
|
|
2265
|
+
try {
|
|
2266
|
+
const parsed = JSON.parse(await readFile(path, "utf-8"));
|
|
2267
|
+
return parsed && typeof parsed === "object" ? parsed as SkillActiveStateLike : null;
|
|
2268
|
+
} catch {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
|
|
2274
|
+
function canonicalStopDisagreement(modeState: Record<string, unknown>, canonicalState: SkillActiveStateLike | null, mode: string, sessionId?: string): string {
|
|
2275
|
+
if (!canonicalState) return "canonical_state_missing";
|
|
2276
|
+
const normalizedSessionId = safeString(sessionId).trim();
|
|
2277
|
+
const activeEntry = listRawActiveSkillEntries(canonicalState).find((entry) => {
|
|
2278
|
+
if (entry.skill !== mode) return false;
|
|
2279
|
+
const entrySessionId = safeString(entry.session_id ?? canonicalState.session_id).trim();
|
|
2280
|
+
return normalizedSessionId ? entrySessionId === normalizedSessionId || entrySessionId === "" : true;
|
|
2281
|
+
});
|
|
2282
|
+
if (!activeEntry) return "canonical_inactive";
|
|
2283
|
+
if (mode === "autopilot") {
|
|
2284
|
+
const phase = safeString(modeState.current_phase ?? modeState.currentPhase).trim();
|
|
2285
|
+
const canonicalPhase = safeString(activeEntry.phase ?? canonicalState.phase).trim();
|
|
2286
|
+
if (phase && canonicalPhase && normalizeAutopilotPhase(phase) !== normalizeAutopilotPhase(canonicalPhase)) {
|
|
2287
|
+
return `canonical_phase:${canonicalPhase}`;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return "canonical_agrees";
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function listRawActiveSkillEntries(state: SkillActiveStateLike | null): SkillActiveEntry[] {
|
|
2294
|
+
if (!state) return [];
|
|
2295
|
+
const entries: SkillActiveEntry[] = [];
|
|
2296
|
+
if (Array.isArray(state.active_skills)) {
|
|
2297
|
+
for (const candidate of state.active_skills) {
|
|
2298
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
2299
|
+
const raw = candidate as unknown as Record<string, unknown>;
|
|
2300
|
+
const skill = safeString(raw.skill).trim();
|
|
2301
|
+
if (!skill || raw.active === false) continue;
|
|
2302
|
+
entries.push({
|
|
2303
|
+
...raw,
|
|
2304
|
+
skill,
|
|
2305
|
+
phase: safeString(raw.phase).trim() || undefined,
|
|
2306
|
+
session_id: safeString(raw.session_id).trim() || undefined,
|
|
2307
|
+
thread_id: safeString(raw.thread_id).trim() || undefined,
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
const topLevelSkill = safeString(state.skill).trim();
|
|
2312
|
+
if (state.active === true && topLevelSkill) {
|
|
2313
|
+
entries.push({
|
|
2314
|
+
skill: topLevelSkill,
|
|
2315
|
+
phase: safeString(state.phase).trim() || undefined,
|
|
2316
|
+
session_id: safeString(state.session_id).trim() || undefined,
|
|
2317
|
+
thread_id: safeString(state.thread_id).trim() || undefined,
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
return entries;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2229
2323
|
async function buildModeBasedStopOutput(
|
|
2230
2324
|
mode: "autopilot" | "ultrawork" | "ultraqa",
|
|
2231
2325
|
cwd: string,
|
|
@@ -2237,17 +2331,38 @@ async function buildModeBasedStopOutput(
|
|
|
2237
2331
|
if (mode === "autopilot" && await readAutopilotDeepInterviewQuestionWaitState(cwd, sessionId)) {
|
|
2238
2332
|
return null;
|
|
2239
2333
|
}
|
|
2240
|
-
const
|
|
2334
|
+
const sourcedState = await readModeStateWithStopSource(mode, cwd, sessionId);
|
|
2335
|
+
const state = sourcedState?.state ?? null;
|
|
2241
2336
|
if (!state || !shouldContinueRun(state)) return null;
|
|
2337
|
+
const rootCanonicalState = await readRawSkillActiveState(getSkillActiveStatePathsForStateDir(getBaseStateDir(cwd)).rootPath);
|
|
2338
|
+
const canonicalDisagreement = rootCanonicalState
|
|
2339
|
+
? canonicalStopDisagreement(state, rootCanonicalState, mode, sessionId)
|
|
2340
|
+
: "canonical_state_missing";
|
|
2341
|
+
if (canonicalDisagreement === "canonical_inactive") return null;
|
|
2242
2342
|
const phase = formatPhase(state.current_phase);
|
|
2343
|
+
if (!rootCanonicalState || mode !== "autopilot") {
|
|
2344
|
+
const systemMessage = mode === "autopilot" && phase.toLowerCase().replace(/_/g, "-") === "code-review"
|
|
2345
|
+
? "OMX autopilot is still active (phase: code-review). Run the required $code-review step before completing or clearing Autopilot state."
|
|
2346
|
+
: `OMX ${mode} is still active (phase: ${phase}).`;
|
|
2347
|
+
return {
|
|
2348
|
+
decision: "block",
|
|
2349
|
+
reason: `OMX ${mode} is still active (phase: ${phase}); continue the task and gather fresh verification evidence before stopping.`,
|
|
2350
|
+
stopReason: `${mode}_${phase}`,
|
|
2351
|
+
systemMessage,
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
const statePath = sourcedState ? formatStopStatePath(cwd, sourcedState.path) : "unknown";
|
|
2355
|
+
const diagnostic = `state: ${statePath}; canonical: ${canonicalDisagreement}`;
|
|
2243
2356
|
const systemMessage = mode === "autopilot" && phase.toLowerCase().replace(/_/g, "-") === "code-review"
|
|
2244
|
-
?
|
|
2245
|
-
: `OMX ${mode} is still active (phase: ${phase}).`;
|
|
2357
|
+
? `OMX autopilot is still active (phase: code-review; ${diagnostic}). Run the required $code-review step before completing or clearing Autopilot state.`
|
|
2358
|
+
: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}).`;
|
|
2246
2359
|
return {
|
|
2247
2360
|
decision: "block",
|
|
2248
|
-
reason: `OMX ${mode} is still active (phase: ${phase}); continue the task and gather fresh verification evidence before stopping.`,
|
|
2361
|
+
reason: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}); continue the task and gather fresh verification evidence before stopping.`,
|
|
2249
2362
|
stopReason: `${mode}_${phase}`,
|
|
2250
2363
|
systemMessage,
|
|
2364
|
+
statePath,
|
|
2365
|
+
canonicalDisagreement,
|
|
2251
2366
|
};
|
|
2252
2367
|
}
|
|
2253
2368
|
|
|
@@ -2293,6 +2408,63 @@ function reportsBlockedUltragoalCompletedAggregateMicrogoalLoop(goal: Record<str
|
|
|
2293
2408
|
&& /\b(?:unreconcilable|mismatch|loop|already complete|already completed|blocks?)\b/i.test(evidence);
|
|
2294
2409
|
}
|
|
2295
2410
|
|
|
2411
|
+
function looksLikeNewGoalPrompt(text: string): boolean {
|
|
2412
|
+
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);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
async function findCompletedGoalWorkflowCleanupNotice(cwd: string): Promise<string | null> {
|
|
2416
|
+
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
2417
|
+
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
2418
|
+
const ultragoals = Array.isArray(ultragoal?.goals) ? ultragoal.goals.map(safeObject) : [];
|
|
2419
|
+
if (safeString(aggregateCompletion.status) === "complete" || (ultragoals.length > 0 && ultragoals.every((goal) => safeString(goal.status) === "complete"))) {
|
|
2420
|
+
return buildCodexGoalTerminalCleanupNotice("Ultragoal completion");
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
const performanceRoot = join(cwd, ".omx", "goals", "performance");
|
|
2424
|
+
for (const entry of await readdir(performanceRoot, { withFileTypes: true }).catch(() => [])) {
|
|
2425
|
+
if (!entry.isDirectory()) continue;
|
|
2426
|
+
const state = await readJsonIfExists(join(performanceRoot, entry.name, "state.json"));
|
|
2427
|
+
if (state?.workflow === "performance-goal" && safeString(state.status) === "complete") {
|
|
2428
|
+
return buildCodexGoalTerminalCleanupNotice("Performance-goal completion");
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
const autoresearchRoot = join(cwd, ".omx", "goals", "autoresearch");
|
|
2433
|
+
for (const entry of await readdir(autoresearchRoot, { withFileTypes: true }).catch(() => [])) {
|
|
2434
|
+
if (!entry.isDirectory()) continue;
|
|
2435
|
+
const mission = await readJsonIfExists(join(autoresearchRoot, entry.name, "mission.json"));
|
|
2436
|
+
if (mission?.workflow === "autoresearch-goal" && safeString(mission.status) === "complete") {
|
|
2437
|
+
return buildCodexGoalTerminalCleanupNotice("Autoresearch-goal completion");
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
return null;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
async function buildCompletedGoalCleanupPromptWarning(cwd: string, prompt: string): Promise<string | null> {
|
|
2445
|
+
if (!looksLikeNewGoalPrompt(prompt)) return null;
|
|
2446
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2447
|
+
if (!notice) return null;
|
|
2448
|
+
return `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
async function buildCompletedGoalCleanupStopOutput(payload: CodexHookPayload, cwd: string): Promise<Record<string, unknown> | null> {
|
|
2452
|
+
const text = [
|
|
2453
|
+
safeString(payload.last_user_message ?? payload.lastUserMessage),
|
|
2454
|
+
safeString(payload.last_assistant_message ?? payload.lastAssistantMessage),
|
|
2455
|
+
].join("\n");
|
|
2456
|
+
if (!looksLikeNewGoalPrompt(text)) return null;
|
|
2457
|
+
const notice = await findCompletedGoalWorkflowCleanupNotice(cwd);
|
|
2458
|
+
if (!notice) return null;
|
|
2459
|
+
const systemMessage = `${notice} Do not continue into create_goal until cleanup is explicit; hooks only nudge and must not mutate Codex goal state.`;
|
|
2460
|
+
return {
|
|
2461
|
+
decision: "block",
|
|
2462
|
+
reason: systemMessage,
|
|
2463
|
+
stopReason: "completed_codex_goal_cleanup_required",
|
|
2464
|
+
systemMessage,
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2296
2468
|
async function findActiveGoalWorkflowReconciliationRequirement(cwd: string): Promise<{ workflow: string; command: string; remediation?: string } | null> {
|
|
2297
2469
|
const ultragoal = await readJsonIfExists(join(cwd, ".omx", "ultragoal", "goals.json"));
|
|
2298
2470
|
const aggregateCompletion = safeObject(ultragoal?.aggregateCompletion);
|
|
@@ -2544,6 +2716,144 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
|
|
|
2544
2716
|
return safeString(payload.turn_id ?? payload.turnId).trim();
|
|
2545
2717
|
}
|
|
2546
2718
|
|
|
2719
|
+
interface NativeSubagentCapacityBlocker {
|
|
2720
|
+
schema_version: 1;
|
|
2721
|
+
reason: "agent_thread_limit_reached";
|
|
2722
|
+
session_id?: string;
|
|
2723
|
+
thread_id?: string;
|
|
2724
|
+
turn_id?: string;
|
|
2725
|
+
tool_name?: string;
|
|
2726
|
+
error_summary: string;
|
|
2727
|
+
observed_at: string;
|
|
2728
|
+
expires_at: string;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
function nativeSubagentCapacityBlockerPath(stateDir: string): string {
|
|
2732
|
+
return join(stateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE);
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
function stringifyUnknown(value: unknown): string {
|
|
2736
|
+
if (typeof value === "string") return value;
|
|
2737
|
+
if (value === undefined || value === null) return "";
|
|
2738
|
+
try {
|
|
2739
|
+
return JSON.stringify(value);
|
|
2740
|
+
} catch {
|
|
2741
|
+
return String(value);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
function payloadEvidenceText(payload: CodexHookPayload): string {
|
|
2746
|
+
return [
|
|
2747
|
+
safeString(payload.tool_name),
|
|
2748
|
+
stringifyUnknown(payload.tool_response),
|
|
2749
|
+
stringifyUnknown(payload.response),
|
|
2750
|
+
stringifyUnknown(payload.error),
|
|
2751
|
+
stringifyUnknown(payload.message),
|
|
2752
|
+
].filter(Boolean).join("\n");
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
function isNativeSubagentCapacityFailure(payload: CodexHookPayload): boolean {
|
|
2756
|
+
const evidence = payloadEvidenceText(payload);
|
|
2757
|
+
if (!/\bagent thread limit reached\b/i.test(evidence)) return false;
|
|
2758
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2759
|
+
return !toolName || /(?:spawn_agent|multi_agent|subagent|collab|agent)/i.test(toolName);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
function summarizeCapacityFailure(text: string): string {
|
|
2763
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
2764
|
+
if (!normalized) return "agent thread limit reached";
|
|
2765
|
+
const match = normalized.match(/[^.?!\n\r]*agent thread limit reached[^.?!\n\r]*/i);
|
|
2766
|
+
return (match?.[0] ?? normalized).slice(0, 500);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
async function recordNativeSubagentCapacityBlocker(
|
|
2770
|
+
cwd: string,
|
|
2771
|
+
stateDir: string,
|
|
2772
|
+
payload: CodexHookPayload,
|
|
2773
|
+
): Promise<void> {
|
|
2774
|
+
if (!isNativeSubagentCapacityFailure(payload)) return;
|
|
2775
|
+
const nowMs = Date.now();
|
|
2776
|
+
const blocker: NativeSubagentCapacityBlocker = {
|
|
2777
|
+
schema_version: 1,
|
|
2778
|
+
reason: "agent_thread_limit_reached",
|
|
2779
|
+
...(readPayloadSessionId(payload) ? { session_id: readPayloadSessionId(payload) } : {}),
|
|
2780
|
+
...(readPayloadThreadId(payload) ? { thread_id: readPayloadThreadId(payload) } : {}),
|
|
2781
|
+
...(readPayloadTurnId(payload) ? { turn_id: readPayloadTurnId(payload) } : {}),
|
|
2782
|
+
...(safeString(payload.tool_name).trim() ? { tool_name: safeString(payload.tool_name).trim() } : {}),
|
|
2783
|
+
error_summary: summarizeCapacityFailure(payloadEvidenceText(payload)),
|
|
2784
|
+
observed_at: new Date(nowMs).toISOString(),
|
|
2785
|
+
expires_at: new Date(nowMs + NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS).toISOString(),
|
|
2786
|
+
};
|
|
2787
|
+
await mkdir(stateDir, { recursive: true });
|
|
2788
|
+
await writeFile(nativeSubagentCapacityBlockerPath(stateDir), JSON.stringify({
|
|
2789
|
+
...blocker,
|
|
2790
|
+
cwd,
|
|
2791
|
+
}, null, 2));
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
function isFreshNativeSubagentCapacityBlocker(
|
|
2795
|
+
blocker: Record<string, unknown> | null,
|
|
2796
|
+
cwd: string,
|
|
2797
|
+
payload: CodexHookPayload,
|
|
2798
|
+
nowMs = Date.now(),
|
|
2799
|
+
): blocker is NativeSubagentCapacityBlocker & Record<string, unknown> {
|
|
2800
|
+
if (!blocker) return false;
|
|
2801
|
+
if (safeString(blocker.reason) !== "agent_thread_limit_reached") return false;
|
|
2802
|
+
const expiresAtMs = Date.parse(safeString(blocker.expires_at));
|
|
2803
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) return false;
|
|
2804
|
+
const blockerCwd = safeString(blocker.cwd).trim();
|
|
2805
|
+
if (blockerCwd) {
|
|
2806
|
+
try {
|
|
2807
|
+
if (resolve(blockerCwd) !== resolve(cwd)) return false;
|
|
2808
|
+
} catch {
|
|
2809
|
+
return false;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
const blockerSessionId = safeString(blocker.session_id).trim();
|
|
2813
|
+
const payloadSessionId = readPayloadSessionId(payload);
|
|
2814
|
+
return !blockerSessionId || !payloadSessionId || blockerSessionId === payloadSessionId;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
function inputContainsCloseAgentRequest(value: unknown): boolean {
|
|
2818
|
+
if (typeof value === "string") return /\bclose_agent\b/i.test(value);
|
|
2819
|
+
if (!value || typeof value !== "object") return false;
|
|
2820
|
+
try {
|
|
2821
|
+
return /\bclose_agent\b/i.test(JSON.stringify(value));
|
|
2822
|
+
} catch {
|
|
2823
|
+
return false;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
function isCloseAgentToolUse(payload: CodexHookPayload): boolean {
|
|
2828
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2829
|
+
if (/\bclose_agent\b/i.test(toolName)) return true;
|
|
2830
|
+
if (/multi_tool_use\.parallel/i.test(toolName) && inputContainsCloseAgentRequest(payload.tool_input)) return true;
|
|
2831
|
+
return inputContainsCloseAgentRequest(payload.tool_input) && /multi_agent|agent|tool_use/i.test(toolName);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
async function buildNativeSubagentCapacityCloseGuardOutput(
|
|
2835
|
+
payload: CodexHookPayload,
|
|
2836
|
+
cwd: string,
|
|
2837
|
+
stateDir: string,
|
|
2838
|
+
): Promise<Record<string, unknown> | null> {
|
|
2839
|
+
if (!isCloseAgentToolUse(payload)) return null;
|
|
2840
|
+
const blocker = await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir));
|
|
2841
|
+
if (!isFreshNativeSubagentCapacityBlocker(blocker, cwd, payload)) return null;
|
|
2842
|
+
|
|
2843
|
+
const evidence = safeString(blocker.error_summary).trim() || "agent thread limit reached";
|
|
2844
|
+
return {
|
|
2845
|
+
decision: "block",
|
|
2846
|
+
reason: "Native subagent capacity was exhausted recently; model-level close_agent cleanup is blocked because close_agent can hang indefinitely on stale handles.",
|
|
2847
|
+
hookSpecificOutput: {
|
|
2848
|
+
hookEventName: "PreToolUse",
|
|
2849
|
+
additionalContext:
|
|
2850
|
+
`OMX blocked ${safeString(payload.tool_name).trim() || "close_agent"} before it could start: a recent native subagent capacity failure was recorded (${evidence}). `
|
|
2851
|
+
+ "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. "
|
|
2852
|
+
+ "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.",
|
|
2853
|
+
},
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2547
2857
|
async function resolveInternalSessionIdForPayload(
|
|
2548
2858
|
cwd: string,
|
|
2549
2859
|
payloadSessionId: string,
|
|
@@ -2602,6 +2912,7 @@ const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
|
2602
2912
|
".omx/plans",
|
|
2603
2913
|
".omx/specs",
|
|
2604
2914
|
".omx/state",
|
|
2915
|
+
".beads",
|
|
2605
2916
|
] as const;
|
|
2606
2917
|
|
|
2607
2918
|
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
@@ -2652,6 +2963,10 @@ function canAutopilotSkillMirrorSupplyRalplanPhase(phase: string): boolean {
|
|
|
2652
2963
|
return phase === "" || normalizeAutopilotPhase(phase) === "ralplan";
|
|
2653
2964
|
}
|
|
2654
2965
|
|
|
2966
|
+
function isAutopilotReviewReworkPhase(phase: string): boolean {
|
|
2967
|
+
return normalizeAutopilotPhase(phase) === "rework";
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2655
2970
|
function hasExplicitExecutionHandoffSkill(
|
|
2656
2971
|
state: SkillActiveStateLike | null,
|
|
2657
2972
|
sessionId: string,
|
|
@@ -2691,6 +3006,138 @@ function isAllowedRalplanArtifactPath(cwd: string, rawPath: string): boolean {
|
|
|
2691
3006
|
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2692
3007
|
}
|
|
2693
3008
|
|
|
3009
|
+
interface RalplanBeadsCommandClassification {
|
|
3010
|
+
present: boolean;
|
|
3011
|
+
allowed: boolean;
|
|
3012
|
+
reason?: string;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
function shellTokenizeLiteralCommand(command: string): string[] | null {
|
|
3016
|
+
const tokens: string[] = [];
|
|
3017
|
+
let current = "";
|
|
3018
|
+
let quote: "'" | '"' | null = null;
|
|
3019
|
+
let escaping = false;
|
|
3020
|
+
|
|
3021
|
+
for (const char of command.trim()) {
|
|
3022
|
+
if (escaping) {
|
|
3023
|
+
current += char;
|
|
3024
|
+
escaping = false;
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (quote === '"' && char === "\\") {
|
|
3028
|
+
escaping = true;
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
if (quote) {
|
|
3032
|
+
if (char === quote) quote = null;
|
|
3033
|
+
else current += char;
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
if (char === "'" || char === '"') {
|
|
3037
|
+
quote = char;
|
|
3038
|
+
continue;
|
|
3039
|
+
}
|
|
3040
|
+
if (/\s/.test(char)) {
|
|
3041
|
+
if (current) {
|
|
3042
|
+
tokens.push(current);
|
|
3043
|
+
current = "";
|
|
3044
|
+
}
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
if (/[;&|<>`$(){}\n\r]/.test(char)) return null;
|
|
3048
|
+
current += char;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
if (escaping || quote) return null;
|
|
3052
|
+
if (current) tokens.push(current);
|
|
3053
|
+
return tokens;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
function findLiteralBdExecutableIndex(tokens: string[]): number {
|
|
3057
|
+
if (tokens[0] === "bd") return 0;
|
|
3058
|
+
if (tokens[0] === "command" || tokens[0] === "builtin" || tokens[0] === "exec" || tokens[0] === "nohup") {
|
|
3059
|
+
return tokens[1] === "bd" ? 1 : -1;
|
|
3060
|
+
}
|
|
3061
|
+
if (tokens[0] !== "env") return -1;
|
|
3062
|
+
|
|
3063
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
3064
|
+
const token = tokens[index] ?? "";
|
|
3065
|
+
if (token === "bd") return index;
|
|
3066
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) continue;
|
|
3067
|
+
if (token.startsWith("-")) continue;
|
|
3068
|
+
return -1;
|
|
3069
|
+
}
|
|
3070
|
+
return -1;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
function isAllowedRalplanBeadsDbPath(cwd: string, rawPath: string): boolean {
|
|
3074
|
+
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
3075
|
+
if (!trimmed || trimmed.includes("\0")) return false;
|
|
3076
|
+
let relativePath: string;
|
|
3077
|
+
try {
|
|
3078
|
+
const absolute = resolve(cwd, trimmed);
|
|
3079
|
+
relativePath = relative(cwd, absolute).replace(/\\/g, "/");
|
|
3080
|
+
} catch {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
return relativePath.startsWith(".beads/") && relativePath.length > ".beads/".length;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
function classifyRalplanBeadsMetadataCommand(cwd: string, command: string): RalplanBeadsCommandClassification {
|
|
3087
|
+
const trimmedCommand = command.trim();
|
|
3088
|
+
const startsWithBd = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"$`]*"|'[^']*'|[^\s"'$`;&|<>]+)\s+)*bd(?:\s|$)/.test(trimmedCommand);
|
|
3089
|
+
const hasCompoundBd = /[;&|()]\s*bd(?:\s|$)/.test(command);
|
|
3090
|
+
const tokens = shellTokenizeLiteralCommand(command);
|
|
3091
|
+
const bdExecutableIndex = tokens ? findLiteralBdExecutableIndex(tokens) : -1;
|
|
3092
|
+
if (!startsWithBd && !hasCompoundBd && bdExecutableIndex === -1) return { present: false, allowed: false };
|
|
3093
|
+
|
|
3094
|
+
if (!tokens || bdExecutableIndex !== 0) {
|
|
3095
|
+
return { present: true, allowed: false, reason: "Beads tracker command must be a single literal bd invocation" };
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
let dbPath = "";
|
|
3099
|
+
let dbValueIndex = -1;
|
|
3100
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
3101
|
+
const token = tokens[index] ?? "";
|
|
3102
|
+
if (token === "--db") {
|
|
3103
|
+
dbPath = tokens[index + 1] ?? "";
|
|
3104
|
+
dbValueIndex = index + 1;
|
|
3105
|
+
break;
|
|
3106
|
+
}
|
|
3107
|
+
if (token.startsWith("--db=")) {
|
|
3108
|
+
dbPath = token.slice("--db=".length);
|
|
3109
|
+
dbValueIndex = index;
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
if (!dbPath) {
|
|
3115
|
+
return { present: true, allowed: false, reason: "Beads tracker command is missing a literal --db .beads/<db> target" };
|
|
3116
|
+
}
|
|
3117
|
+
if (!isAllowedRalplanBeadsDbPath(cwd, dbPath)) {
|
|
3118
|
+
return { present: true, allowed: false, reason: `Beads tracker db target ${dbPath} is outside repo-local .beads metadata` };
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
const operationTokens = tokens
|
|
3122
|
+
.slice(dbValueIndex + 1)
|
|
3123
|
+
.filter((token) => token && !token.startsWith("-"));
|
|
3124
|
+
const operation = operationTokens[0] ?? "";
|
|
3125
|
+
const suboperation = operationTokens[1] ?? "";
|
|
3126
|
+
if (["create", "update", "edit", "close", "reopen", "status", "dep"].includes(operation)) {
|
|
3127
|
+
return { present: true, allowed: true };
|
|
3128
|
+
}
|
|
3129
|
+
if (operation === "comments" && suboperation === "add") {
|
|
3130
|
+
return { present: true, allowed: true };
|
|
3131
|
+
}
|
|
3132
|
+
return {
|
|
3133
|
+
present: true,
|
|
3134
|
+
allowed: false,
|
|
3135
|
+
reason: operation
|
|
3136
|
+
? `Beads tracker operation ${operation}${suboperation ? ` ${suboperation}` : ""} is not allowed during planning`
|
|
3137
|
+
: "Beads tracker command is missing an allowed metadata operation",
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
2694
3141
|
function readPreToolUseCommand(payload: CodexHookPayload): string {
|
|
2695
3142
|
const toolInput = safeObject(payload.tool_input);
|
|
2696
3143
|
return safeString(toolInput.command).trim();
|
|
@@ -2751,6 +3198,31 @@ function isNullDeviceRedirectTarget(target: string): boolean {
|
|
|
2751
3198
|
return normalized === "/dev/null" || normalized === "nul";
|
|
2752
3199
|
}
|
|
2753
3200
|
|
|
3201
|
+
// Collects same-command literal variable assignments (`NAME="value"`), skipping
|
|
3202
|
+
// any value that involves expansion (`$`, backticks) so unresolved/dynamic
|
|
3203
|
+
// targets stay conservatively blocked.
|
|
3204
|
+
function extractCommandLiteralAssignments(command: string): Map<string, string> {
|
|
3205
|
+
const assignments = new Map<string, string>();
|
|
3206
|
+
const pattern = /(?:^|[\n;&|(]|&&|\|\|)\s*([A-Za-z_][A-Za-z0-9_]*)=(?:"([^"$`]*)"|'([^']*)'|([^\s"'$`;&|<>]+))/g;
|
|
3207
|
+
for (const match of command.matchAll(pattern)) {
|
|
3208
|
+
const name = safeString(match[1]).trim();
|
|
3209
|
+
if (!name) continue;
|
|
3210
|
+
const value = match[2] ?? match[3] ?? match[4] ?? "";
|
|
3211
|
+
assignments.set(name, value);
|
|
3212
|
+
}
|
|
3213
|
+
return assignments;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// Resolves a redirect/tee target of the form `$NAME`/`${NAME}` against
|
|
3217
|
+
// same-command literal assignments; non-variable or unresolved targets are
|
|
3218
|
+
// returned unchanged so they remain subject to the allowed-path check.
|
|
3219
|
+
function resolveCommandRedirectTarget(target: string, assignments: Map<string, string>): string {
|
|
3220
|
+
const variableMatch = target.match(/^\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?$/);
|
|
3221
|
+
if (!variableMatch) return target;
|
|
3222
|
+
const resolved = assignments.get(safeString(variableMatch[1]));
|
|
3223
|
+
return resolved !== undefined ? resolved : target;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
2754
3226
|
function extractDeepInterviewCommandRedirectTargets(command: string): string[] {
|
|
2755
3227
|
const targets: string[] = [];
|
|
2756
3228
|
for (const match of command.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
|
|
@@ -2761,7 +3233,7 @@ function extractDeepInterviewCommandRedirectTargets(command: string): string[] {
|
|
|
2761
3233
|
}
|
|
2762
3234
|
|
|
2763
3235
|
function commandHasDeepInterviewWriteIntent(command: string): boolean {
|
|
2764
|
-
return
|
|
3236
|
+
return commandInvokesApplyPatch(command)
|
|
2765
3237
|
|| extractDeepInterviewCommandRedirectTargets(command).length > 0
|
|
2766
3238
|
|| /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
|
|
2767
3239
|
|| /\bsed\s+(?:[^\n;&|]*\s)?-i(?:\b|['"])/.test(command)
|
|
@@ -2770,13 +3242,42 @@ function commandHasDeepInterviewWriteIntent(command: string): boolean {
|
|
|
2770
3242
|
}
|
|
2771
3243
|
|
|
2772
3244
|
function extractDeepInterviewCommandWriteTargets(command: string): string[] {
|
|
2773
|
-
const
|
|
3245
|
+
const assignments = extractCommandLiteralAssignments(command);
|
|
3246
|
+
const targets = extractDeepInterviewCommandRedirectTargets(command)
|
|
3247
|
+
.map((target) => resolveCommandRedirectTarget(target, assignments));
|
|
2774
3248
|
for (const match of command.matchAll(/\btee\s+(?:-a\s+)?(["']?)([^\s&|;<>]+)\1/g)) {
|
|
2775
3249
|
const candidate = safeString(match[2]).trim();
|
|
2776
|
-
if (candidate) targets.push(candidate);
|
|
3250
|
+
if (candidate) targets.push(resolveCommandRedirectTarget(candidate, assignments));
|
|
2777
3251
|
}
|
|
2778
3252
|
return targets;
|
|
2779
3253
|
}
|
|
3254
|
+
function formatPlanningWriteBlockDetail(
|
|
3255
|
+
operationClass: string,
|
|
3256
|
+
target: string | undefined,
|
|
3257
|
+
allowedPrefixes: readonly string[],
|
|
3258
|
+
): string {
|
|
3259
|
+
const targetDetail = target ? `target ${target}` : "target <unresolved>";
|
|
3260
|
+
return `${operationClass} ${targetDetail} is not under allowed planning artifact paths (${allowedPrefixes.join(", ")})`;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
function isUnresolvedVariableTarget(target: string): boolean {
|
|
3264
|
+
const normalized = target.trim().replace(/^['"]|['"]$/g, "");
|
|
3265
|
+
return /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/.test(normalized);
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
|
|
3269
|
+
function describeImplementationToolBlock(
|
|
3270
|
+
toolName: string,
|
|
3271
|
+
blockedPath: string | undefined,
|
|
3272
|
+
pathCount: number,
|
|
3273
|
+
): string {
|
|
3274
|
+
if (pathCount === 0) {
|
|
3275
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target extraction failed" : `${toolName} path`;
|
|
3276
|
+
return `${operationClass} target <unresolved>; only planning artifact paths are allowed (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
3277
|
+
}
|
|
3278
|
+
const operationClass = isApplyPatchToolName(toolName) ? "apply_patch target" : `${toolName} path`;
|
|
3279
|
+
return formatPlanningWriteBlockDetail(operationClass, blockedPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
3280
|
+
}
|
|
2780
3281
|
|
|
2781
3282
|
function isAllowedDeepInterviewBashWrite(cwd: string, command: string): boolean {
|
|
2782
3283
|
if (!commandHasDeepInterviewWriteIntent(command)) return true;
|
|
@@ -2871,6 +3372,7 @@ async function readActiveRalplanStateForPreToolUse(
|
|
|
2871
3372
|
));
|
|
2872
3373
|
if (!hasActiveAutopilotSkill) return null;
|
|
2873
3374
|
const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
|
|
3375
|
+
if (isAutopilotReviewReworkPhase(autopilotStatePhase)) return null;
|
|
2874
3376
|
if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase)) return null;
|
|
2875
3377
|
const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2876
3378
|
entry.skill === "autopilot"
|
|
@@ -2882,10 +3384,37 @@ async function readActiveRalplanStateForPreToolUse(
|
|
|
2882
3384
|
}
|
|
2883
3385
|
|
|
2884
3386
|
function isAllowedRalplanBashWrite(cwd: string, command: string): boolean {
|
|
3387
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
3388
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
3389
|
+
const hasAllowedTargets = targets.length > 0
|
|
3390
|
+
&& targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
3391
|
+
|
|
3392
|
+
if (beadsCommand.present) {
|
|
3393
|
+
return beadsCommand.allowed && (targets.length === 0 || hasAllowedTargets);
|
|
3394
|
+
}
|
|
2885
3395
|
if (!commandHasDeepInterviewWriteIntent(command)) return true;
|
|
2886
3396
|
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command)) return true;
|
|
3397
|
+
return hasAllowedTargets;
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
function buildRalplanBashBlockedDetail(cwd: string, command: string): string {
|
|
2887
3401
|
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2888
|
-
|
|
3402
|
+
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
3403
|
+
if (blockedTarget && isUnresolvedVariableTarget(blockedTarget)) {
|
|
3404
|
+
return `unresolved Bash write target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
3405
|
+
}
|
|
3406
|
+
if (blockedTarget) {
|
|
3407
|
+
const operationClass = /\btee\s+(?:-a\s+)?/.test(command) ? "Bash tee write" : "Bash redirect write";
|
|
3408
|
+
return `${operationClass} target ${blockedTarget} is not under allowed planning artifact paths or metadata paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
|
|
3409
|
+
}
|
|
3410
|
+
const beadsCommand = classifyRalplanBeadsMetadataCommand(cwd, command);
|
|
3411
|
+
if (beadsCommand.present && !beadsCommand.allowed) {
|
|
3412
|
+
return beadsCommand.reason ?? "Beads tracker command is not an allowed planning metadata mutation";
|
|
3413
|
+
}
|
|
3414
|
+
if (beadsCommand.present) {
|
|
3415
|
+
return "Beads tracker command also performs an implementation write outside allowed planning metadata";
|
|
3416
|
+
}
|
|
3417
|
+
return "Bash write intent did not identify an allowed planning artifact path or metadata path";
|
|
2889
3418
|
}
|
|
2890
3419
|
|
|
2891
3420
|
async function buildRalplanPreToolUseBoundaryOutput(
|
|
@@ -2908,21 +3437,18 @@ async function buildRalplanPreToolUseBoundaryOutput(
|
|
|
2908
3437
|
if (toolName === "Bash") {
|
|
2909
3438
|
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2910
3439
|
if (blocked) {
|
|
2911
|
-
|
|
2912
|
-
const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
|
|
2913
|
-
blockedDetail = blockedTarget
|
|
2914
|
-
? `write target ${blockedTarget} is not under allowed planning artifact paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`
|
|
2915
|
-
: "Bash write intent did not identify an allowed planning artifact path";
|
|
3440
|
+
blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
|
|
2916
3441
|
}
|
|
2917
3442
|
} else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2918
|
-
|
|
3443
|
+
const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
|
|
3444
|
+
if (toolPathCandidates.length === 0) {
|
|
2919
3445
|
blocked = true;
|
|
2920
|
-
blockedDetail =
|
|
3446
|
+
blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
|
|
2921
3447
|
} else {
|
|
2922
|
-
const blockedPath =
|
|
3448
|
+
const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2923
3449
|
blocked = blockedPath !== undefined;
|
|
2924
3450
|
if (blockedPath !== undefined) {
|
|
2925
|
-
blockedDetail =
|
|
3451
|
+
blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
|
|
2926
3452
|
}
|
|
2927
3453
|
}
|
|
2928
3454
|
}
|
|
@@ -2942,7 +3468,7 @@ async function buildRalplanPreToolUseBoundaryOutput(
|
|
|
2942
3468
|
hookEventName: "PreToolUse",
|
|
2943
3469
|
additionalContext:
|
|
2944
3470
|
`${planningModeDescription}. `
|
|
2945
|
-
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`,
|
|
3471
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
|
|
2946
3472
|
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
2947
3473
|
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
2948
3474
|
},
|
|
@@ -4221,28 +4747,50 @@ export async function dispatchCodexNativeHook(
|
|
|
4221
4747
|
if (hookEventName === "SessionStart" && nativeSessionId) {
|
|
4222
4748
|
const transcriptPath = safeString(payload.transcript_path ?? payload.transcriptPath).trim();
|
|
4223
4749
|
const subagentSessionStart = readNativeSubagentSessionStartMetadata(transcriptPath);
|
|
4224
|
-
if (subagentSessionStart
|
|
4750
|
+
if (subagentSessionStart) {
|
|
4751
|
+
// A native child/subagent SessionStart carries a parent_thread_id in its
|
|
4752
|
+
// transcript session_meta. Treat it as a child-agent lifecycle event for
|
|
4753
|
+
// notification suppression and subagent tracking even when the canonical
|
|
4754
|
+
// leader session has not been reconciled yet (#2831). A child start must
|
|
4755
|
+
// never promote itself into a root/leader session or emit an independent
|
|
4756
|
+
// session-start notification at session/minimal verbosity.
|
|
4225
4757
|
isSubagentSessionStart = true;
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
canonicalSessionId,
|
|
4229
|
-
currentSessionState,
|
|
4230
|
-
subagentSessionStart,
|
|
4231
|
-
);
|
|
4232
|
-
if (belongsToCanonicalSession) {
|
|
4233
|
-
resolvedNativeSessionId = nativeSessionId;
|
|
4234
|
-
await recordNativeSubagentSessionStart(
|
|
4758
|
+
if (canonicalSessionId) {
|
|
4759
|
+
const belongsToCanonicalSession = await nativeSubagentSessionStartBelongsToCanonicalSession(
|
|
4235
4760
|
cwd,
|
|
4236
4761
|
canonicalSessionId,
|
|
4237
|
-
|
|
4762
|
+
currentSessionState,
|
|
4238
4763
|
subagentSessionStart,
|
|
4239
|
-
transcriptPath,
|
|
4240
4764
|
);
|
|
4765
|
+
if (belongsToCanonicalSession) {
|
|
4766
|
+
resolvedNativeSessionId = nativeSessionId;
|
|
4767
|
+
await recordNativeSubagentSessionStart(
|
|
4768
|
+
cwd,
|
|
4769
|
+
canonicalSessionId,
|
|
4770
|
+
nativeSessionId,
|
|
4771
|
+
subagentSessionStart,
|
|
4772
|
+
transcriptPath,
|
|
4773
|
+
);
|
|
4774
|
+
} else {
|
|
4775
|
+
skipCanonicalSessionStartContext = true;
|
|
4776
|
+
resolvedNativeSessionId =
|
|
4777
|
+
safeString(currentSessionState?.native_session_id).trim() || nativeSessionId;
|
|
4778
|
+
await recordIgnoredNativeSubagentSessionStart(
|
|
4779
|
+
cwd,
|
|
4780
|
+
canonicalSessionId,
|
|
4781
|
+
nativeSessionId,
|
|
4782
|
+
subagentSessionStart,
|
|
4783
|
+
transcriptPath,
|
|
4784
|
+
);
|
|
4785
|
+
}
|
|
4241
4786
|
} else {
|
|
4787
|
+
// No canonical leader session is resolved in this worktree yet. Still
|
|
4788
|
+
// register the child thread under its parent so its later Stop is
|
|
4789
|
+
// recognized as subagent-scoped, skip leader SessionStart context, and
|
|
4790
|
+
// do not reconcile the child as a new root session.
|
|
4242
4791
|
skipCanonicalSessionStartContext = true;
|
|
4243
|
-
resolvedNativeSessionId =
|
|
4244
|
-
|
|
4245
|
-
await recordIgnoredNativeSubagentSessionStart(
|
|
4792
|
+
resolvedNativeSessionId = nativeSessionId;
|
|
4793
|
+
await recordNativeSubagentSessionStart(
|
|
4246
4794
|
cwd,
|
|
4247
4795
|
canonicalSessionId,
|
|
4248
4796
|
nativeSessionId,
|
|
@@ -4311,7 +4859,8 @@ export async function dispatchCodexNativeHook(
|
|
|
4311
4859
|
|
|
4312
4860
|
if (hookEventName === "UserPromptSubmit") {
|
|
4313
4861
|
const prompt = readPromptText(payload);
|
|
4314
|
-
goalWorkflowAdditionalContext = await
|
|
4862
|
+
goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
|
|
4863
|
+
?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
|
|
4315
4864
|
ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
|
|
4316
4865
|
? 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)}`)
|
|
4317
4866
|
: null;
|
|
@@ -4484,8 +5033,10 @@ export async function dispatchCodexNativeHook(
|
|
|
4484
5033
|
: "";
|
|
4485
5034
|
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4486
5035
|
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
5036
|
+
?? await buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir)
|
|
4487
5037
|
?? buildNativePreToolUseOutput(payload);
|
|
4488
5038
|
} else if (hookEventName === "PostToolUse") {
|
|
5039
|
+
await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
|
|
4489
5040
|
if (detectMcpTransportFailure(payload)) {
|
|
4490
5041
|
await markTeamTransportFailure(cwd, payload);
|
|
4491
5042
|
}
|
|
@@ -4494,7 +5045,7 @@ export async function dispatchCodexNativeHook(
|
|
|
4494
5045
|
} else if (hookEventName === "Stop") {
|
|
4495
5046
|
outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
|
|
4496
5047
|
skipRalphStopBlock: isSubagentStop,
|
|
4497
|
-
});
|
|
5048
|
+
}) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
|
|
4498
5049
|
}
|
|
4499
5050
|
|
|
4500
5051
|
return {
|
|
@@ -4607,6 +5158,9 @@ function buildMalformedStdinHookOutput(
|
|
|
4607
5158
|
const systemMessage =
|
|
4608
5159
|
`${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
4609
5160
|
const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput);
|
|
5161
|
+
if (inferredHookEventName === "PreToolUse") {
|
|
5162
|
+
return { systemMessage };
|
|
5163
|
+
}
|
|
4610
5164
|
if (inferredHookEventName === "Stop" || (!inferredHookEventName && hasNativeStopRuntimeSurface(cwd))) {
|
|
4611
5165
|
return {
|
|
4612
5166
|
decision: "block",
|
|
@@ -4649,11 +5203,14 @@ async function buildOversizedStdinHookOutput(
|
|
|
4649
5203
|
rawHookEventName: CodexHookEventName | null,
|
|
4650
5204
|
cwd: string,
|
|
4651
5205
|
): Promise<Record<string, unknown>> {
|
|
5206
|
+
const systemMessage =
|
|
5207
|
+
`OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
5208
|
+
if (rawHookEventName === "PreToolUse") {
|
|
5209
|
+
return { systemMessage };
|
|
5210
|
+
}
|
|
4652
5211
|
if (rawHookEventName === "Stop") {
|
|
4653
5212
|
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
4654
5213
|
}
|
|
4655
|
-
const systemMessage =
|
|
4656
|
-
`OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
4657
5214
|
return {
|
|
4658
5215
|
continue: false,
|
|
4659
5216
|
stopReason: "native_hook_stdin_oversized",
|
|
@@ -4767,8 +5324,8 @@ export async function runCodexNativeHookCli(): Promise<void> {
|
|
|
4767
5324
|
|
|
4768
5325
|
const result = await dispatchCodexNativeHook(payload);
|
|
4769
5326
|
if (result.outputJson) {
|
|
4770
|
-
writeNativeHookJsonStdout(result.outputJson);
|
|
4771
|
-
} else if (result.hookEventName
|
|
5327
|
+
writeNativeHookJsonStdout(sanitizeCodexHookOutput(result.hookEventName, result.outputJson) ?? {});
|
|
5328
|
+
} else if (result.hookEventName !== "PreCompact" && result.hookEventName !== "PostCompact") {
|
|
4772
5329
|
writeNativeHookJsonStdout({});
|
|
4773
5330
|
}
|
|
4774
5331
|
} catch (error) {
|