oh-my-codex 0.18.13 → 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.
Files changed (277) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +31 -11
  4. package/dist/agents/__tests__/native-config.test.js +25 -0
  5. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  6. package/dist/agents/native-config.d.ts.map +1 -1
  7. package/dist/agents/native-config.js +5 -1
  8. package/dist/agents/native-config.js.map +1 -1
  9. package/dist/autopilot/__tests__/fsm.test.js +7 -0
  10. package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
  11. package/dist/autopilot/__tests__/planner-routing.test.d.ts +2 -0
  12. package/dist/autopilot/__tests__/planner-routing.test.d.ts.map +1 -0
  13. package/dist/autopilot/__tests__/planner-routing.test.js +74 -0
  14. package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -0
  15. package/dist/autopilot/__tests__/ralplan-gate.test.js +315 -0
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  17. package/dist/autopilot/completion-gate.js +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +2 -2
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autopilot/fsm.js +3 -0
  22. package/dist/autopilot/fsm.js.map +1 -1
  23. package/dist/autopilot/planner-routing.d.ts +21 -0
  24. package/dist/autopilot/planner-routing.d.ts.map +1 -0
  25. package/dist/autopilot/planner-routing.js +59 -0
  26. package/dist/autopilot/planner-routing.js.map +1 -0
  27. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  28. package/dist/autopilot/ralplan-gate.js +30 -0
  29. package/dist/autopilot/ralplan-gate.js.map +1 -1
  30. package/dist/autoresearch/goal.d.ts.map +1 -1
  31. package/dist/autoresearch/goal.js +10 -1
  32. package/dist/autoresearch/goal.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch-goal.test.js +33 -0
  34. package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -0
  36. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts +2 -0
  38. package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts.map +1 -0
  39. package/dist/cli/__tests__/doctor-artifact-ownership.test.js +158 -0
  40. package/dist/cli/__tests__/doctor-artifact-ownership.test.js.map +1 -0
  41. package/dist/cli/__tests__/doctor-spark-routing.test.js +13 -0
  42. package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
  43. package/dist/cli/__tests__/doctor-warning-copy.test.js +1 -0
  44. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  45. package/dist/cli/__tests__/index.test.js +5 -0
  46. package/dist/cli/__tests__/index.test.js.map +1 -1
  47. package/dist/cli/__tests__/nested-help-routing.test.js +4 -0
  48. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  49. package/dist/cli/__tests__/package-bin-contract.test.js +7 -1
  50. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  51. package/dist/cli/__tests__/performance-goal.test.js +22 -0
  52. package/dist/cli/__tests__/performance-goal.test.js.map +1 -1
  53. package/dist/cli/__tests__/resume.test.js +96 -0
  54. package/dist/cli/__tests__/resume.test.js.map +1 -1
  55. package/dist/cli/__tests__/session-scoped-runtime.test.js +162 -0
  56. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  57. package/dist/cli/__tests__/session-search.test.js +36 -0
  58. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  59. package/dist/cli/__tests__/setup-agents-overwrite.test.js +35 -1
  60. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  61. package/dist/cli/__tests__/setup-install-mode.test.js +33 -0
  62. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  63. package/dist/cli/__tests__/team.test.js +17 -3
  64. package/dist/cli/__tests__/team.test.js.map +1 -1
  65. package/dist/cli/__tests__/ultragoal.test.js +37 -0
  66. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  67. package/dist/cli/__tests__/update.test.js +1 -0
  68. package/dist/cli/__tests__/update.test.js.map +1 -1
  69. package/dist/cli/autoresearch-goal.d.ts.map +1 -1
  70. package/dist/cli/autoresearch-goal.js +2 -1
  71. package/dist/cli/autoresearch-goal.js.map +1 -1
  72. package/dist/cli/doctor.d.ts +22 -0
  73. package/dist/cli/doctor.d.ts.map +1 -1
  74. package/dist/cli/doctor.js +263 -4
  75. package/dist/cli/doctor.js.map +1 -1
  76. package/dist/cli/index.d.ts +1 -1
  77. package/dist/cli/index.d.ts.map +1 -1
  78. package/dist/cli/index.js +65 -4
  79. package/dist/cli/index.js.map +1 -1
  80. package/dist/cli/performance-goal.d.ts.map +1 -1
  81. package/dist/cli/performance-goal.js +4 -2
  82. package/dist/cli/performance-goal.js.map +1 -1
  83. package/dist/cli/plugin-marketplace.d.ts +1 -0
  84. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  85. package/dist/cli/plugin-marketplace.js +67 -5
  86. package/dist/cli/plugin-marketplace.js.map +1 -1
  87. package/dist/cli/project-runtime-codex-homes.d.ts +2 -0
  88. package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -1
  89. package/dist/cli/project-runtime-codex-homes.js +112 -5
  90. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  91. package/dist/cli/setup.d.ts.map +1 -1
  92. package/dist/cli/setup.js +15 -6
  93. package/dist/cli/setup.js.map +1 -1
  94. package/dist/cli/ultragoal.d.ts.map +1 -1
  95. package/dist/cli/ultragoal.js +5 -1
  96. package/dist/cli/ultragoal.js.map +1 -1
  97. package/dist/cli/update.d.ts.map +1 -1
  98. package/dist/cli/update.js +3 -0
  99. package/dist/cli/update.js.map +1 -1
  100. package/dist/config/__tests__/models.test.js +21 -1
  101. package/dist/config/__tests__/models.test.js.map +1 -1
  102. package/dist/config/models.d.ts +5 -0
  103. package/dist/config/models.d.ts.map +1 -1
  104. package/dist/config/models.js +23 -0
  105. package/dist/config/models.js.map +1 -1
  106. package/dist/goal-workflows/codex-goal-snapshot.d.ts +2 -0
  107. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  108. package/dist/goal-workflows/codex-goal-snapshot.js +10 -0
  109. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  110. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +7 -0
  111. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  112. package/dist/hooks/__tests__/keyword-detector.test.js +46 -0
  113. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  114. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +100 -0
  115. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  116. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +75 -0
  117. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  118. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +225 -0
  119. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  120. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +75 -0
  121. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  122. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +200 -0
  123. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  124. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +187 -12
  125. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  126. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +225 -0
  127. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  128. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  129. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  130. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +75 -0
  131. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  132. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  133. package/dist/hooks/keyword-detector.js +4 -0
  134. package/dist/hooks/keyword-detector.js.map +1 -1
  135. package/dist/hud/__tests__/reconcile.test.js +137 -2
  136. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  137. package/dist/hud/__tests__/state.test.js +107 -0
  138. package/dist/hud/__tests__/state.test.js.map +1 -1
  139. package/dist/hud/__tests__/tmux.test.js +17 -0
  140. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  141. package/dist/hud/reconcile.d.ts.map +1 -1
  142. package/dist/hud/reconcile.js +21 -7
  143. package/dist/hud/reconcile.js.map +1 -1
  144. package/dist/hud/render.d.ts.map +1 -1
  145. package/dist/hud/render.js +9 -0
  146. package/dist/hud/render.js.map +1 -1
  147. package/dist/hud/state.d.ts.map +1 -1
  148. package/dist/hud/state.js +43 -5
  149. package/dist/hud/state.js.map +1 -1
  150. package/dist/hud/types.d.ts +6 -0
  151. package/dist/hud/types.d.ts.map +1 -1
  152. package/dist/hud/types.js.map +1 -1
  153. package/dist/modes/__tests__/base-autopilot-gates.test.js +48 -0
  154. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  155. package/dist/modes/base.d.ts.map +1 -1
  156. package/dist/modes/base.js +1 -0
  157. package/dist/modes/base.js.map +1 -1
  158. package/dist/performance-goal/artifacts.d.ts.map +1 -1
  159. package/dist/performance-goal/artifacts.js +10 -1
  160. package/dist/performance-goal/artifacts.js.map +1 -1
  161. package/dist/ralplan/__tests__/consensus-gate.test.js +112 -0
  162. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  163. package/dist/ralplan/consensus-gate.d.ts +23 -0
  164. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  165. package/dist/ralplan/consensus-gate.js +133 -21
  166. package/dist/ralplan/consensus-gate.js.map +1 -1
  167. package/dist/scripts/__tests__/codex-native-hook.test.js +831 -2
  168. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  169. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  170. package/dist/scripts/codex-native-hook.js +486 -21
  171. package/dist/scripts/codex-native-hook.js.map +1 -1
  172. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  173. package/dist/scripts/notify-hook/team-tmux-guard.js +87 -8
  174. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  175. package/dist/scripts/notify-hook/tmux-injection.js +1 -1
  176. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  177. package/dist/scripts/run-compiled-ci.d.ts +2 -0
  178. package/dist/scripts/run-compiled-ci.d.ts.map +1 -0
  179. package/dist/scripts/run-compiled-ci.js +77 -0
  180. package/dist/scripts/run-compiled-ci.js.map +1 -0
  181. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  182. package/dist/scripts/sync-plugin-mirror.js +1 -0
  183. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  184. package/dist/session-history/search.d.ts.map +1 -1
  185. package/dist/session-history/search.js +24 -9
  186. package/dist/session-history/search.js.map +1 -1
  187. package/dist/state/__tests__/operations.test.js +107 -3
  188. package/dist/state/__tests__/operations.test.js.map +1 -1
  189. package/dist/state/operations.d.ts.map +1 -1
  190. package/dist/state/operations.js +57 -2
  191. package/dist/state/operations.js.map +1 -1
  192. package/dist/team/__tests__/delivery-e2e-smoke.test.js +22 -1
  193. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  194. package/dist/team/__tests__/model-contract.test.js +57 -1
  195. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  196. package/dist/team/__tests__/runtime.test.js +1468 -110
  197. package/dist/team/__tests__/runtime.test.js.map +1 -1
  198. package/dist/team/__tests__/scaling.test.js +63 -0
  199. package/dist/team/__tests__/scaling.test.js.map +1 -1
  200. package/dist/team/__tests__/state.test.js +24 -1
  201. package/dist/team/__tests__/state.test.js.map +1 -1
  202. package/dist/team/__tests__/tmux-session.test.js +512 -0
  203. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  204. package/dist/team/__tests__/worker-bootstrap.test.js +42 -9
  205. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  206. package/dist/team/model-contract.d.ts +16 -0
  207. package/dist/team/model-contract.d.ts.map +1 -1
  208. package/dist/team/model-contract.js +41 -1
  209. package/dist/team/model-contract.js.map +1 -1
  210. package/dist/team/runtime.d.ts.map +1 -1
  211. package/dist/team/runtime.js +116 -30
  212. package/dist/team/runtime.js.map +1 -1
  213. package/dist/team/scaling.d.ts.map +1 -1
  214. package/dist/team/scaling.js +9 -1
  215. package/dist/team/scaling.js.map +1 -1
  216. package/dist/team/state/types.d.ts +2 -0
  217. package/dist/team/state/types.d.ts.map +1 -1
  218. package/dist/team/state/types.js.map +1 -1
  219. package/dist/team/state.d.ts +3 -0
  220. package/dist/team/state.d.ts.map +1 -1
  221. package/dist/team/state.js +20 -0
  222. package/dist/team/state.js.map +1 -1
  223. package/dist/team/tmux-session.d.ts +38 -3
  224. package/dist/team/tmux-session.d.ts.map +1 -1
  225. package/dist/team/tmux-session.js +275 -36
  226. package/dist/team/tmux-session.js.map +1 -1
  227. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  228. package/dist/team/worker-bootstrap.js +26 -1
  229. package/dist/team/worker-bootstrap.js.map +1 -1
  230. package/dist/ultragoal/__tests__/artifacts.test.js +238 -0
  231. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  232. package/dist/ultragoal/__tests__/docs-contract.test.js +3 -0
  233. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  234. package/dist/ultragoal/artifacts.d.ts +15 -0
  235. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  236. package/dist/ultragoal/artifacts.js +167 -4
  237. package/dist/ultragoal/artifacts.js.map +1 -1
  238. package/dist/utils/__tests__/agents-md.test.js +39 -1
  239. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  240. package/dist/utils/__tests__/agents-model-table.test.js +28 -0
  241. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  242. package/dist/utils/agents-md.d.ts +4 -0
  243. package/dist/utils/agents-md.d.ts.map +1 -1
  244. package/dist/utils/agents-md.js +25 -0
  245. package/dist/utils/agents-md.js.map +1 -1
  246. package/dist/utils/agents-model-table.d.ts +7 -3
  247. package/dist/utils/agents-model-table.d.ts.map +1 -1
  248. package/dist/utils/agents-model-table.js +16 -8
  249. package/dist/utils/agents-model-table.js.map +1 -1
  250. package/dist/verification/__tests__/ci-rust-gates.test.js +4 -0
  251. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  252. package/package.json +2 -2
  253. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  254. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +59 -8
  255. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +9 -5
  256. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +3 -0
  257. package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +3 -0
  258. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
  259. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +22 -4
  260. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +2 -2
  261. package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +61 -0
  262. package/skills/autopilot/SKILL.md +9 -5
  263. package/skills/autoresearch-goal/SKILL.md +3 -0
  264. package/skills/ecomode/SKILL.md +1 -1
  265. package/skills/ecomode/references/agent-tiers.md +61 -0
  266. package/skills/performance-goal/SKILL.md +3 -0
  267. package/skills/ralplan/SKILL.md +1 -1
  268. package/skills/ultragoal/SKILL.md +22 -4
  269. package/skills/ultrawork/SKILL.md +2 -2
  270. package/skills/ultrawork/references/agent-tiers.md +61 -0
  271. package/src/scripts/__tests__/codex-native-hook.test.ts +975 -40
  272. package/src/scripts/codex-native-hook.ts +529 -22
  273. package/src/scripts/notify-hook/team-tmux-guard.ts +92 -8
  274. package/src/scripts/notify-hook/tmux-injection.ts +1 -1
  275. package/src/scripts/run-compiled-ci.ts +85 -0
  276. package/src/scripts/sync-plugin-mirror.ts +1 -0
  277. package/templates/AGENTS.md +6 -0
@@ -12,7 +12,7 @@ import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionS
12
12
  import { appendTeamEvent, readTeamLeaderAttention, readTeamConfig, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
13
13
  import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
14
14
  import { findGitLayout } from "../utils/git-layout.js";
15
- import { getBaseStateDir, getStateFilePath, getStatePath } from "../mcp/state-paths.js";
15
+ import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
16
16
  import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, } from "../hooks/keyword-detector.js";
17
17
  import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
18
18
  import { readTeamModeConfig } from "../config/team-mode.js";
@@ -30,6 +30,7 @@ import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDec
30
30
  import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
31
31
  import { readRunState } from "../runtime/run-state.js";
32
32
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
33
+ import { buildCodexGoalTerminalCleanupNotice, } from "../goal-workflows/codex-goal-snapshot.js";
33
34
  import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
34
35
  import { parseUltragoalSteeringDirective, steerUltragoal, } from "../ultragoal/artifacts.js";
35
36
  import { triagePrompt } from "../hooks/triage-heuristic.js";
@@ -45,6 +46,8 @@ const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
45
46
  const TEAM_STOP_BLOCKING_TASK_STATUSES = new Set(["pending", "in_progress", "blocked"]);
46
47
  const TEAM_WORKER_TERMINAL_RUN_STATES = new Set(["done", "complete", "completed", "failed", "stopped", "cancelled"]);
47
48
  const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
49
+ const NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE = "native-subagent-capacity-blocker.json";
50
+ const NATIVE_SUBAGENT_CAPACITY_BLOCKER_TTL_MS = 30 * 60_000;
48
51
  const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
49
52
  const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
50
53
  const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
@@ -381,6 +384,20 @@ function readHookEventName(payload) {
381
384
  }
382
385
  return null;
383
386
  }
387
+ function sanitizeCodexHookOutput(hookEventName, output) {
388
+ if (!output || hookEventName !== "PreToolUse")
389
+ return output;
390
+ const systemMessage = safeString(output.systemMessage).trim();
391
+ if (systemMessage)
392
+ return { systemMessage };
393
+ const reason = safeString(output.reason).trim();
394
+ const hookSpecificOutput = output.hookSpecificOutput;
395
+ const additionalContext = hookSpecificOutput && typeof hookSpecificOutput === "object"
396
+ ? safeString(hookSpecificOutput.additionalContext).trim()
397
+ : "";
398
+ const derivedSystemMessage = [reason, additionalContext].filter(Boolean).join("\n\n");
399
+ return derivedSystemMessage ? { systemMessage: derivedSystemMessage } : {};
400
+ }
384
401
  export function mapCodexHookEventToOmxEvent(hookEventName) {
385
402
  switch (hookEventName) {
386
403
  case "SessionStart":
@@ -1812,6 +1829,76 @@ function isStopExempt(payload) {
1812
1829
  || value.includes("compact")
1813
1830
  || value.includes("limit"));
1814
1831
  }
1832
+ async function readModeStateWithStopSource(mode, cwd, sessionId) {
1833
+ const paths = await getAuthoritativeActiveStatePaths(mode, cwd, sessionId?.trim() || undefined).catch(() => []);
1834
+ const path = paths[0];
1835
+ if (!path)
1836
+ return null;
1837
+ const state = await readJsonIfExists(path);
1838
+ return state ? { state, path } : null;
1839
+ }
1840
+ async function readRawSkillActiveState(path) {
1841
+ try {
1842
+ const parsed = JSON.parse(await readFile(path, "utf-8"));
1843
+ return parsed && typeof parsed === "object" ? parsed : null;
1844
+ }
1845
+ catch {
1846
+ return null;
1847
+ }
1848
+ }
1849
+ function canonicalStopDisagreement(modeState, canonicalState, mode, sessionId) {
1850
+ if (!canonicalState)
1851
+ return "canonical_state_missing";
1852
+ const normalizedSessionId = safeString(sessionId).trim();
1853
+ const activeEntry = listRawActiveSkillEntries(canonicalState).find((entry) => {
1854
+ if (entry.skill !== mode)
1855
+ return false;
1856
+ const entrySessionId = safeString(entry.session_id ?? canonicalState.session_id).trim();
1857
+ return normalizedSessionId ? entrySessionId === normalizedSessionId || entrySessionId === "" : true;
1858
+ });
1859
+ if (!activeEntry)
1860
+ return "canonical_inactive";
1861
+ if (mode === "autopilot") {
1862
+ const phase = safeString(modeState.current_phase ?? modeState.currentPhase).trim();
1863
+ const canonicalPhase = safeString(activeEntry.phase ?? canonicalState.phase).trim();
1864
+ if (phase && canonicalPhase && normalizeAutopilotPhase(phase) !== normalizeAutopilotPhase(canonicalPhase)) {
1865
+ return `canonical_phase:${canonicalPhase}`;
1866
+ }
1867
+ }
1868
+ return "canonical_agrees";
1869
+ }
1870
+ function listRawActiveSkillEntries(state) {
1871
+ if (!state)
1872
+ return [];
1873
+ const entries = [];
1874
+ if (Array.isArray(state.active_skills)) {
1875
+ for (const candidate of state.active_skills) {
1876
+ if (!candidate || typeof candidate !== "object")
1877
+ continue;
1878
+ const raw = candidate;
1879
+ const skill = safeString(raw.skill).trim();
1880
+ if (!skill || raw.active === false)
1881
+ continue;
1882
+ entries.push({
1883
+ ...raw,
1884
+ skill,
1885
+ phase: safeString(raw.phase).trim() || undefined,
1886
+ session_id: safeString(raw.session_id).trim() || undefined,
1887
+ thread_id: safeString(raw.thread_id).trim() || undefined,
1888
+ });
1889
+ }
1890
+ }
1891
+ const topLevelSkill = safeString(state.skill).trim();
1892
+ if (state.active === true && topLevelSkill) {
1893
+ entries.push({
1894
+ skill: topLevelSkill,
1895
+ phase: safeString(state.phase).trim() || undefined,
1896
+ session_id: safeString(state.session_id).trim() || undefined,
1897
+ thread_id: safeString(state.thread_id).trim() || undefined,
1898
+ });
1899
+ }
1900
+ return entries;
1901
+ }
1815
1902
  async function buildModeBasedStopOutput(mode, cwd, sessionId) {
1816
1903
  if (await readCanonicalTerminalRunStateForStop(cwd, sessionId, mode)) {
1817
1904
  return null;
@@ -1819,18 +1906,40 @@ async function buildModeBasedStopOutput(mode, cwd, sessionId) {
1819
1906
  if (mode === "autopilot" && await readAutopilotDeepInterviewQuestionWaitState(cwd, sessionId)) {
1820
1907
  return null;
1821
1908
  }
1822
- const state = await readModeStateForActiveDecision(mode, sessionId?.trim() || undefined, cwd);
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
- ? "OMX autopilot is still active (phase: code-review). Run the required $code-review step before completing or clearing Autopilot state."
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();
@@ -2314,6 +2730,22 @@ function extractDeepInterviewCommandWriteTargets(command) {
2314
2730
  }
2315
2731
  return targets;
2316
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
+ }
2317
2749
  function isAllowedDeepInterviewBashWrite(cwd, command) {
2318
2750
  if (!commandHasDeepInterviewWriteIntent(command))
2319
2751
  return true;
@@ -2400,6 +2832,8 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
2400
2832
  if (!hasActiveAutopilotSkill)
2401
2833
  return null;
2402
2834
  const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
2835
+ if (isAutopilotReviewReworkPhase(autopilotStatePhase))
2836
+ return null;
2403
2837
  if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase))
2404
2838
  return null;
2405
2839
  const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "autopilot"
@@ -2410,12 +2844,37 @@ async function readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, thr
2410
2844
  return hasActiveAutopilotSkill ? autopilotState : null;
2411
2845
  }
2412
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
+ }
2413
2854
  if (!commandHasDeepInterviewWriteIntent(command))
2414
2855
  return true;
2415
2856
  if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
2416
2857
  return true;
2858
+ return hasAllowedTargets;
2859
+ }
2860
+ function buildRalplanBashBlockedDetail(cwd, command) {
2417
2861
  const targets = extractDeepInterviewCommandWriteTargets(command);
2418
- return targets.length > 0 && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
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";
2419
2878
  }
2420
2879
  async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
2421
2880
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
@@ -2431,23 +2890,20 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
2431
2890
  if (toolName === "Bash") {
2432
2891
  blocked = !isAllowedRalplanBashWrite(cwd, command);
2433
2892
  if (blocked) {
2434
- const targets = extractDeepInterviewCommandWriteTargets(command);
2435
- const blockedTarget = targets.find((target) => !isAllowedRalplanArtifactPath(cwd, target));
2436
- blockedDetail = blockedTarget
2437
- ? `write target ${blockedTarget} is not under allowed planning artifact paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`
2438
- : "Bash write intent did not identify an allowed planning artifact path";
2893
+ blockedDetail = buildRalplanBashBlockedDetail(cwd, command);
2439
2894
  }
2440
2895
  }
2441
2896
  else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
2442
- if (pathCandidates.length === 0) {
2897
+ const toolPathCandidates = collectImplementationToolPathCandidates(payload, toolName, pathCandidates);
2898
+ if (toolPathCandidates.length === 0) {
2443
2899
  blocked = true;
2444
- blockedDetail = `${toolName} did not include a file path; only planning artifact paths are allowed`;
2900
+ blockedDetail = describeImplementationToolBlock(toolName, undefined, toolPathCandidates.length);
2445
2901
  }
2446
2902
  else {
2447
- const blockedPath = pathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
2903
+ const blockedPath = toolPathCandidates.find((candidate) => !isAllowedRalplanArtifactPath(cwd, candidate));
2448
2904
  blocked = blockedPath !== undefined;
2449
2905
  if (blockedPath !== undefined) {
2450
- blockedDetail = `path ${blockedPath} is not under allowed planning artifact paths (${RALPLAN_ALLOWED_WRITE_PREFIXES.join(", ")})`;
2906
+ blockedDetail = describeImplementationToolBlock(toolName, blockedPath, toolPathCandidates.length);
2451
2907
  }
2452
2908
  }
2453
2909
  }
@@ -2465,7 +2921,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
2465
2921
  hookSpecificOutput: {
2466
2922
  hookEventName: "PreToolUse",
2467
2923
  additionalContext: `${planningModeDescription}. `
2468
- + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. "
2924
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
2469
2925
  + "Do not edit implementation files or run implementation-focused writes from planning phases. "
2470
2926
  + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
2471
2927
  },
@@ -3392,7 +3848,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
3392
3848
  && shouldSuppressSubagentLifecycleHookDispatch();
3393
3849
  if (hookEventName === "UserPromptSubmit") {
3394
3850
  const prompt = readPromptText(payload);
3395
- goalWorkflowAdditionalContext = await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
3851
+ goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
3852
+ ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
3396
3853
  ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
3397
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)}`)
3398
3855
  : null;
@@ -3550,9 +4007,11 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
3550
4007
  : "";
3551
4008
  outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
3552
4009
  ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
4010
+ ?? await buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDir)
3553
4011
  ?? buildNativePreToolUseOutput(payload);
3554
4012
  }
3555
4013
  else if (hookEventName === "PostToolUse") {
4014
+ await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
3556
4015
  if (detectMcpTransportFailure(payload)) {
3557
4016
  await markTeamTransportFailure(cwd, payload);
3558
4017
  }
@@ -3562,7 +4021,7 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
3562
4021
  else if (hookEventName === "Stop") {
3563
4022
  outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
3564
4023
  skipRalphStopBlock: isSubagentStop,
3565
- });
4024
+ }) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
3566
4025
  }
3567
4026
  return {
3568
4027
  hookEventName,
@@ -3659,6 +4118,9 @@ function buildMalformedStdinHookOutput(parseError, rawInput, cwd = process.cwd()
3659
4118
  const reason = "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
3660
4119
  const systemMessage = `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
3661
4120
  const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput);
4121
+ if (inferredHookEventName === "PreToolUse") {
4122
+ return { systemMessage };
4123
+ }
3662
4124
  if (inferredHookEventName === "Stop" || (!inferredHookEventName && hasNativeStopRuntimeSurface(cwd))) {
3663
4125
  return {
3664
4126
  decision: "block",
@@ -3694,10 +4156,13 @@ async function buildOversizedStopActiveWorkflowOutput(cwd) {
3694
4156
  };
3695
4157
  }
3696
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
+ }
3697
4163
  if (rawHookEventName === "Stop") {
3698
4164
  return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
3699
4165
  }
3700
- const systemMessage = `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
3701
4166
  return {
3702
4167
  continue: false,
3703
4168
  stopReason: "native_hook_stdin_oversized",
@@ -3777,9 +4242,9 @@ export async function runCodexNativeHookCli() {
3777
4242
  }
3778
4243
  const result = await dispatchCodexNativeHook(payload);
3779
4244
  if (result.outputJson) {
3780
- writeNativeHookJsonStdout(result.outputJson);
4245
+ writeNativeHookJsonStdout(sanitizeCodexHookOutput(result.hookEventName, result.outputJson) ?? {});
3781
4246
  }
3782
- else if (result.hookEventName === "Stop") {
4247
+ else if (result.hookEventName !== "PreCompact" && result.hookEventName !== "PostCompact") {
3783
4248
  writeNativeHookJsonStdout({});
3784
4249
  }
3785
4250
  }