oh-my-codex 0.18.1 → 0.18.2

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 (204) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +4 -2
  4. package/dist/agents/__tests__/definitions.test.js +14 -0
  5. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  6. package/dist/agents/__tests__/native-config.test.js +19 -0
  7. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  8. package/dist/agents/definitions.d.ts.map +1 -1
  9. package/dist/agents/definitions.js +30 -0
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/agents/native-config.d.ts +1 -0
  12. package/dist/agents/native-config.d.ts.map +1 -1
  13. package/dist/agents/native-config.js +4 -0
  14. package/dist/agents/native-config.js.map +1 -1
  15. package/dist/catalog/__tests__/generator.test.js +4 -0
  16. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +61 -5
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +161 -21
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/launch-fallback.test.js +51 -3
  22. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  23. package/dist/cli/__tests__/question.test.js +2 -2
  24. package/dist/cli/__tests__/question.test.js.map +1 -1
  25. package/dist/cli/doctor.d.ts.map +1 -1
  26. package/dist/cli/doctor.js +178 -7
  27. package/dist/cli/doctor.js.map +1 -1
  28. package/dist/cli/index.d.ts +7 -1
  29. package/dist/cli/index.d.ts.map +1 -1
  30. package/dist/cli/index.js +143 -43
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/config/__tests__/codex-hooks.test.js +3 -3
  33. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  34. package/dist/config/codex-hooks.d.ts +1 -0
  35. package/dist/config/codex-hooks.d.ts.map +1 -1
  36. package/dist/config/codex-hooks.js +2 -4
  37. package/dist/config/codex-hooks.js.map +1 -1
  38. package/dist/config/generator.d.ts +14 -0
  39. package/dist/config/generator.d.ts.map +1 -1
  40. package/dist/config/generator.js +100 -1
  41. package/dist/config/generator.js.map +1 -1
  42. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +21 -0
  43. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  44. package/dist/goal-workflows/codex-goal-snapshot.d.ts +3 -0
  45. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  46. package/dist/goal-workflows/codex-goal-snapshot.js +45 -2
  47. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  48. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +17 -0
  49. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  50. package/dist/hooks/__tests__/keyword-detector.test.js +170 -15
  51. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  52. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts +2 -0
  53. package/dist/hooks/__tests__/prometheus-strict-contract.test.d.ts.map +1 -0
  54. package/dist/hooks/__tests__/prometheus-strict-contract.test.js +320 -0
  55. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -0
  56. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +12 -0
  57. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  58. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts +2 -0
  59. package/dist/hooks/__tests__/research-workflow-boundaries.test.d.ts.map +1 -0
  60. package/dist/hooks/__tests__/research-workflow-boundaries.test.js +35 -0
  61. package/dist/hooks/__tests__/research-workflow-boundaries.test.js.map +1 -0
  62. package/dist/hooks/keyword-detector.d.ts +1 -1
  63. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  64. package/dist/hooks/keyword-detector.js +28 -6
  65. package/dist/hooks/keyword-detector.js.map +1 -1
  66. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  67. package/dist/hooks/keyword-registry.js +1 -0
  68. package/dist/hooks/keyword-registry.js.map +1 -1
  69. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  70. package/dist/hooks/prompt-guidance-contract.js +11 -0
  71. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  72. package/dist/hud/__tests__/hud-tmux-injection.test.js +22 -0
  73. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  74. package/dist/hud/__tests__/reconcile.test.js +121 -10
  75. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  76. package/dist/hud/__tests__/render.test.js +84 -0
  77. package/dist/hud/__tests__/render.test.js.map +1 -1
  78. package/dist/hud/__tests__/state.test.js +51 -1
  79. package/dist/hud/__tests__/state.test.js.map +1 -1
  80. package/dist/hud/__tests__/tmux.test.js +69 -23
  81. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  82. package/dist/hud/index.d.ts +1 -1
  83. package/dist/hud/index.d.ts.map +1 -1
  84. package/dist/hud/index.js +8 -3
  85. package/dist/hud/index.js.map +1 -1
  86. package/dist/hud/reconcile.d.ts.map +1 -1
  87. package/dist/hud/reconcile.js +6 -3
  88. package/dist/hud/reconcile.js.map +1 -1
  89. package/dist/hud/render.d.ts.map +1 -1
  90. package/dist/hud/render.js +26 -0
  91. package/dist/hud/render.js.map +1 -1
  92. package/dist/hud/state.d.ts +2 -1
  93. package/dist/hud/state.d.ts.map +1 -1
  94. package/dist/hud/state.js +62 -1
  95. package/dist/hud/state.js.map +1 -1
  96. package/dist/hud/tmux.d.ts +10 -3
  97. package/dist/hud/tmux.d.ts.map +1 -1
  98. package/dist/hud/tmux.js +59 -10
  99. package/dist/hud/tmux.js.map +1 -1
  100. package/dist/hud/types.d.ts +22 -0
  101. package/dist/hud/types.d.ts.map +1 -1
  102. package/dist/hud/types.js.map +1 -1
  103. package/dist/pipeline/__tests__/orchestrator.test.js +63 -1
  104. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  105. package/dist/pipeline/__tests__/stages.test.js +410 -4
  106. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  107. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  108. package/dist/pipeline/orchestrator.js +29 -2
  109. package/dist/pipeline/orchestrator.js.map +1 -1
  110. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  111. package/dist/pipeline/stages/ralplan.js +41 -6
  112. package/dist/pipeline/stages/ralplan.js.map +1 -1
  113. package/dist/question/__tests__/ui.test.js +43 -10
  114. package/dist/question/__tests__/ui.test.js.map +1 -1
  115. package/dist/question/ui.d.ts +12 -0
  116. package/dist/question/ui.d.ts.map +1 -1
  117. package/dist/question/ui.js +83 -46
  118. package/dist/question/ui.js.map +1 -1
  119. package/dist/ralplan/__tests__/runtime.test.js +200 -10
  120. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  121. package/dist/ralplan/consensus-gate.d.ts +23 -0
  122. package/dist/ralplan/consensus-gate.d.ts.map +1 -0
  123. package/dist/ralplan/consensus-gate.js +212 -0
  124. package/dist/ralplan/consensus-gate.js.map +1 -0
  125. package/dist/ralplan/runtime.d.ts +25 -0
  126. package/dist/ralplan/runtime.d.ts.map +1 -1
  127. package/dist/ralplan/runtime.js +144 -8
  128. package/dist/ralplan/runtime.js.map +1 -1
  129. package/dist/scripts/__tests__/codex-native-hook.test.js +626 -7
  130. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  131. package/dist/scripts/__tests__/docs-site-contract.test.d.ts +2 -0
  132. package/dist/scripts/__tests__/docs-site-contract.test.d.ts.map +1 -0
  133. package/dist/scripts/__tests__/docs-site-contract.test.js +42 -0
  134. package/dist/scripts/__tests__/docs-site-contract.test.js.map +1 -0
  135. package/dist/scripts/__tests__/notify-dispatcher.test.js +115 -2
  136. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  137. package/dist/scripts/__tests__/run-test-files.test.js +57 -0
  138. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  139. package/dist/scripts/__tests__/verify-native-agents.test.js +2 -2
  140. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  141. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  142. package/dist/scripts/codex-native-hook.js +214 -34
  143. package/dist/scripts/codex-native-hook.js.map +1 -1
  144. package/dist/scripts/notify-dispatcher.js +188 -4
  145. package/dist/scripts/notify-dispatcher.js.map +1 -1
  146. package/dist/scripts/run-test-files.js +13 -0
  147. package/dist/scripts/run-test-files.js.map +1 -1
  148. package/dist/state/__tests__/workflow-transition.test.js +6 -0
  149. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  150. package/dist/state/workflow-transition.d.ts +1 -1
  151. package/dist/state/workflow-transition.d.ts.map +1 -1
  152. package/dist/state/workflow-transition.js +7 -0
  153. package/dist/state/workflow-transition.js.map +1 -1
  154. package/dist/subagents/tracker.d.ts.map +1 -1
  155. package/dist/subagents/tracker.js +4 -3
  156. package/dist/subagents/tracker.js.map +1 -1
  157. package/dist/team/__tests__/runtime.test.js +36 -44
  158. package/dist/team/__tests__/runtime.test.js.map +1 -1
  159. package/dist/team/__tests__/tmux-session.test.js +58 -18
  160. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  161. package/dist/team/runtime.d.ts.map +1 -1
  162. package/dist/team/runtime.js +10 -20
  163. package/dist/team/runtime.js.map +1 -1
  164. package/dist/team/tmux-session.d.ts.map +1 -1
  165. package/dist/team/tmux-session.js +15 -6
  166. package/dist/team/tmux-session.js.map +1 -1
  167. package/dist/ultragoal/__tests__/artifacts.test.js +50 -0
  168. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  169. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  170. package/dist/ultragoal/artifacts.js +28 -2
  171. package/dist/ultragoal/artifacts.js.map +1 -1
  172. package/package.json +1 -1
  173. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  174. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +16 -4
  175. package/plugins/oh-my-codex/skills/autoresearch/SKILL.md +4 -0
  176. package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +1 -1
  177. package/plugins/oh-my-codex/skills/best-practice-research/SKILL.md +1 -1
  178. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +1 -1
  179. package/plugins/oh-my-codex/skills/plan/SKILL.md +1 -1
  180. package/plugins/oh-my-codex/skills/prometheus-strict/README.md +35 -0
  181. package/plugins/oh-my-codex/skills/prometheus-strict/SKILL.md +219 -0
  182. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +18 -3
  183. package/prompts/prometheus-strict-metis.md +274 -0
  184. package/prompts/prometheus-strict-momus.md +82 -0
  185. package/prompts/prometheus-strict-oracle.md +107 -0
  186. package/prompts/researcher.md +22 -3
  187. package/skills/autopilot/SKILL.md +16 -4
  188. package/skills/autoresearch/SKILL.md +4 -0
  189. package/skills/autoresearch-goal/SKILL.md +1 -1
  190. package/skills/best-practice-research/SKILL.md +1 -1
  191. package/skills/pipeline/SKILL.md +1 -1
  192. package/skills/plan/SKILL.md +1 -1
  193. package/skills/prometheus-strict/README.md +35 -0
  194. package/skills/prometheus-strict/SKILL.md +219 -0
  195. package/skills/ralplan/SKILL.md +18 -3
  196. package/src/scripts/__tests__/codex-native-hook.test.ts +769 -8
  197. package/src/scripts/__tests__/docs-site-contract.test.ts +47 -0
  198. package/src/scripts/__tests__/notify-dispatcher.test.ts +132 -3
  199. package/src/scripts/__tests__/run-test-files.test.ts +67 -0
  200. package/src/scripts/__tests__/verify-native-agents.test.ts +2 -2
  201. package/src/scripts/codex-native-hook.ts +237 -30
  202. package/src/scripts/notify-dispatcher.ts +202 -4
  203. package/src/scripts/run-test-files.ts +13 -0
  204. package/templates/catalog-manifest.json +22 -0
@@ -3,9 +3,9 @@ import { closeSync, existsSync, openSync, readFileSync, readSync } from "fs";
3
3
  import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
4
4
  import { extname, join, relative, resolve } from "path";
5
5
  import { pathToFileURL } from "url";
6
- import { readModeState, readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
7
- import { extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
8
- import { readSubagentSessionSummary, recordSubagentTurnForSession, } from "../subagents/tracker.js";
6
+ import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
7
+ import { SKILL_ACTIVE_STATE_FILE, extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
8
+ import { readSubagentSessionSummary, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
9
9
  import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
10
10
  import { appendToLog, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
11
11
  import { appendTeamEvent, readTeamLeaderAttention, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
@@ -175,18 +175,29 @@ async function nativeSubagentSessionStartBelongsToCanonicalSession(cwd, canonica
175
175
  return summary.allThreadIds.includes(parentThreadId);
176
176
  }
177
177
  async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, threadId) {
178
- const sessionId = canonicalSessionId.trim();
179
- if (!sessionId)
180
- return false;
181
- const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
182
- if (!summary)
183
- return false;
184
178
  const candidateIds = [nativeSessionId, threadId]
185
179
  .map((value) => value.trim())
186
180
  .filter(Boolean);
187
181
  if (candidateIds.length === 0)
188
182
  return false;
189
- return candidateIds.some((id) => summary.allSubagentThreadIds.includes(id));
183
+ const sessionId = canonicalSessionId.trim();
184
+ if (sessionId) {
185
+ const summary = await readSubagentSessionSummary(cwd, sessionId).catch(() => null);
186
+ if (summary && candidateIds.some((id) => summary.allSubagentThreadIds.includes(id))) {
187
+ return true;
188
+ }
189
+ }
190
+ // Native Codex resume can report the child native session as the canonical
191
+ // session id before OMX reconciles it back to the owning session. In that
192
+ // window the per-session summary lookup above misses the child and a
193
+ // subagent UserPromptSubmit can accidentally activate workflow keywords from
194
+ // quoted review context. Fall back to the global tracking index so any known
195
+ // subagent thread is treated as subagent-scoped, regardless of the current
196
+ // hook payload's session-id mapping.
197
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
198
+ if (!trackingState)
199
+ return false;
200
+ return Object.values(trackingState.sessions).some((session) => (candidateIds.some((id) => session.threads[id]?.kind === "subagent")));
190
201
  }
191
202
  function shouldSuppressSubagentLifecycleHookDispatch() {
192
203
  const config = getNotificationConfig();
@@ -1361,6 +1372,17 @@ function buildNativeOutsideTmuxTeamPromptBlockState(prompt, cwd, payload, sessio
1361
1372
  function buildSkillStateCliInstruction(mode, statePath) {
1362
1373
  return `skill: ${mode} activated and initial state initialized at ${statePath}; use CLI-first state updates via \`omx state write/read/clear --input '<json>' --json\`; use omx_state MCP only when explicit MCP compatibility is enabled.`;
1363
1374
  }
1375
+ function buildAutopilotPromptActivationNote(skillState) {
1376
+ if (skillState?.initialized_mode !== "autopilot")
1377
+ return null;
1378
+ return [
1379
+ "Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
1380
+ "Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
1381
+ "The ralplan phase is not complete until Planner output has been reviewed sequentially by Architect and then Critic; do not hand off to Ultragoal or implementation until the ralplan state/artifact records both ralplan_architect_review and ralplan_critic_review with approval or an explicit blocker.",
1382
+ "Do not silently fall back to ordinary $plan/ralplan-only handling; keep autopilot-state.json, skill-active-state.json, HUD/statusline, and Codex goal-mode handoff guidance visible while the workflow is active.",
1383
+ "When Codex goal tools are available, call get_goal/create_goal only from the active thread handoff and treat the active goal as the completion contract until code-review and ultraqa are clean.",
1384
+ ].join(" ");
1385
+ }
1364
1386
  function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(), payload) {
1365
1387
  if (!prompt)
1366
1388
  return null;
@@ -1391,6 +1413,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1391
1413
  const ultragoalPromptActivationNote = match.skill === "ultragoal"
1392
1414
  ? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
1393
1415
  : null;
1416
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
1394
1417
  const combinedTransitionMessage = (() => {
1395
1418
  if (!skillState?.transition_message)
1396
1419
  return null;
@@ -1419,6 +1442,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1419
1442
  : null,
1420
1443
  promptPriorityMessage,
1421
1444
  ultragoalPromptActivationNote,
1445
+ autopilotPromptActivationNote,
1422
1446
  skillState.initialized_mode && skillState.initialized_state_path
1423
1447
  ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
1424
1448
  : null,
@@ -1444,6 +1468,7 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1444
1468
  deepInterviewPromptActivationNote,
1445
1469
  ultraworkPromptActivationNote,
1446
1470
  ultragoalPromptActivationNote,
1471
+ autopilotPromptActivationNote,
1447
1472
  buildTeamRuntimeInstruction(cwd, payload),
1448
1473
  buildTeamHelpInstruction(cwd, payload),
1449
1474
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
@@ -1461,11 +1486,12 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1461
1486
  deepInterviewPromptActivationNote,
1462
1487
  ultraworkPromptActivationNote,
1463
1488
  ultragoalPromptActivationNote,
1489
+ autopilotPromptActivationNote,
1464
1490
  ralphPromptActivationNote,
1465
1491
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1466
1492
  ].join(" ");
1467
1493
  }
1468
- return [detectedKeywordMessage, promptPriorityMessage, ultragoalPromptActivationNote, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
1494
+ return [detectedKeywordMessage, promptPriorityMessage, ultragoalPromptActivationNote, autopilotPromptActivationNote, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
1469
1495
  }
1470
1496
  function parseTeamWorkerEnv(rawValue) {
1471
1497
  const match = /^([a-z0-9][a-z0-9-]{0,29})\/(worker-\d+)$/.exec(rawValue.trim());
@@ -1664,6 +1690,7 @@ async function findActiveGoalWorkflowReconciliationRequirement(cwd) {
1664
1690
  `If get_goal returns a completed task-scoped objective for the same aggregate ultragoal plan, checkpoint ${goalId} with evidence naming ${goalId} plus .omx/ultragoal/goals.json or ledger.jsonl and pass final quality-gate JSON; OMX will reconcile the completed planned scope without mutating Codex goal state.`,
1665
1691
  `If get_goal instead returns a different completed legacy objective and complete checkpointing fails, do not repeat --status complete in this thread.`,
1666
1692
  `Record the non-terminal blocker with: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<different completed get_goal JSON or path>' --evidence '<completed legacy Codex goal blocks create_goal in this thread>'.`,
1693
+ `If get_goal itself is unavailable with a Codex DB/schema/context error such as "no such table: thread_goals", record an auditable safe-recovery blocker instead: omx ultragoal checkpoint --goal-id ${goalId} --status blocked --codex-goal-json '<unavailable get_goal error JSON or path>' --evidence '<get_goal unavailable due to Codex DB/schema/context error; safe recovery requires a working Codex goal context>'.`,
1667
1694
  "Then continue only from a Codex goal context with no active/completed conflicting goal in the same repo/worktree and create the intended goal there.",
1668
1695
  ].join(" "),
1669
1696
  };
@@ -1744,30 +1771,46 @@ async function buildGoalWorkflowReconciliationStopOutput(payload, cwd) {
1744
1771
  systemMessage,
1745
1772
  };
1746
1773
  }
1747
- async function readTeamModeStateForStop(cwd, stateDir, sessionId) {
1774
+ function teamStateMatchesThreadForStop(state, threadId, options = {}) {
1775
+ const normalizedThreadId = safeString(threadId).trim();
1776
+ if (!normalizedThreadId)
1777
+ return true;
1778
+ const ownerThreadId = safeString(state.owner_codex_thread_id ?? state.thread_id).trim();
1779
+ if (!ownerThreadId)
1780
+ return options.requireOwnerThread !== true;
1781
+ return ownerThreadId === normalizedThreadId;
1782
+ }
1783
+ async function readTeamModeStateForStop(cwd, stateDir, sessionId, threadId) {
1748
1784
  const normalizedSessionId = safeString(sessionId).trim();
1749
- if (!normalizedSessionId) {
1750
- return await readModeState("team", cwd);
1751
- }
1785
+ if (!normalizedSessionId)
1786
+ return null;
1752
1787
  const scopedState = await readStopSessionPinnedState("team-state.json", cwd, normalizedSessionId, stateDir);
1753
- if (scopedState)
1754
- return scopedState;
1788
+ if (scopedState) {
1789
+ return teamStateMatchesThreadForStop(scopedState, threadId)
1790
+ ? { state: scopedState, scope: "session" }
1791
+ : null;
1792
+ }
1755
1793
  const rootState = await readJsonIfExists(join(stateDir, "team-state.json"));
1756
1794
  if (rootState?.active !== true)
1757
1795
  return null;
1796
+ const teamName = safeString(rootState.team_name).trim();
1797
+ if (!teamName)
1798
+ return null;
1758
1799
  const ownerSessionId = safeString(rootState.session_id).trim();
1759
- if (ownerSessionId && ownerSessionId !== normalizedSessionId) {
1800
+ if (!ownerSessionId || ownerSessionId !== normalizedSessionId)
1760
1801
  return null;
1761
- }
1762
- return rootState;
1802
+ if (!teamStateMatchesThreadForStop(rootState, threadId, { requireOwnerThread: true }))
1803
+ return null;
1804
+ return { state: rootState, scope: "root" };
1763
1805
  }
1764
- async function buildTeamStopOutput(cwd, sessionId) {
1806
+ async function buildTeamStopOutput(cwd, sessionId, threadId) {
1765
1807
  if (await readCanonicalTerminalRunStateForStop(cwd, sessionId, "team")) {
1766
1808
  return null;
1767
1809
  }
1768
- const teamState = await readTeamModeStateForStop(cwd, getBaseStateDir(cwd), sessionId);
1769
- if (teamState?.active !== true)
1810
+ const teamStateForStop = await readTeamModeStateForStop(cwd, getBaseStateDir(cwd), sessionId, threadId);
1811
+ if (!teamStateForStop || teamStateForStop.state.active !== true)
1770
1812
  return null;
1813
+ const teamState = teamStateForStop.state;
1771
1814
  const teamName = safeString(teamState.team_name).trim();
1772
1815
  if (teamName) {
1773
1816
  const canonicalTeamDir = join(resolveCanonicalTeamStateRoot(cwd), "team", teamName);
@@ -1776,7 +1819,10 @@ async function buildTeamStopOutput(cwd, sessionId) {
1776
1819
  }
1777
1820
  }
1778
1821
  const coarsePhase = teamState.current_phase;
1779
- const canonicalPhase = teamName ? (await readTeamPhase(teamName, cwd))?.current_phase ?? coarsePhase : coarsePhase;
1822
+ const canonicalPhaseState = teamName ? await readTeamPhase(teamName, cwd) : null;
1823
+ if (teamStateForStop.scope === "root" && !canonicalPhaseState)
1824
+ return null;
1825
+ const canonicalPhase = canonicalPhaseState?.current_phase ?? coarsePhase;
1780
1826
  if (!isNonTerminalPhase(canonicalPhase))
1781
1827
  return null;
1782
1828
  return buildTeamStopOutputForPhase(teamName, formatPhase(canonicalPhase));
@@ -1859,6 +1905,137 @@ async function readStopSessionPinnedState(fileName, cwd, sessionId, stateDir) {
1859
1905
  : getStateFilePath(fileName, cwd, sessionId || undefined);
1860
1906
  return readJsonIfExists(statePath);
1861
1907
  }
1908
+ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
1909
+ ".omx/context",
1910
+ ".omx/interviews",
1911
+ ".omx/specs",
1912
+ ".omx/state",
1913
+ ];
1914
+ const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = new Set([
1915
+ "Write",
1916
+ "Edit",
1917
+ "MultiEdit",
1918
+ "apply_patch",
1919
+ "ApplyPatch",
1920
+ ]);
1921
+ function isActiveDeepInterviewPhase(state) {
1922
+ if (!state || state.active !== true)
1923
+ return false;
1924
+ const mode = safeString(state.mode).trim();
1925
+ if (mode && mode !== "deep-interview")
1926
+ return false;
1927
+ const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
1928
+ if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing"))
1929
+ return false;
1930
+ return true;
1931
+ }
1932
+ function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
1933
+ const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
1934
+ if (!trimmed || trimmed.includes("\0"))
1935
+ return false;
1936
+ let relativePath;
1937
+ try {
1938
+ const absolute = resolve(cwd, trimmed);
1939
+ relativePath = relative(cwd, absolute).replace(/\\/g, "/");
1940
+ }
1941
+ catch {
1942
+ return false;
1943
+ }
1944
+ if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/"))
1945
+ return false;
1946
+ return DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES.some((prefix) => (relativePath === prefix || relativePath.startsWith(`${prefix}/`)));
1947
+ }
1948
+ function readPreToolUseCommand(payload) {
1949
+ const toolInput = safeObject(payload.tool_input);
1950
+ return safeString(toolInput.command).trim();
1951
+ }
1952
+ function readPreToolUsePathCandidates(payload) {
1953
+ const input = safeObject(payload.tool_input);
1954
+ const candidates = [
1955
+ input.file_path,
1956
+ input.filePath,
1957
+ input.path,
1958
+ input.target_path,
1959
+ input.targetPath,
1960
+ ];
1961
+ return candidates.map((candidate) => safeString(candidate).trim()).filter(Boolean);
1962
+ }
1963
+ function commandHasDeepInterviewWriteIntent(command) {
1964
+ return /\bapply_patch\b/.test(command)
1965
+ || /(?:^|[;&|]\s*)(?:cat|printf|echo)\b[\s\S]{0,240}>\s*[^\s&|;]+/.test(command)
1966
+ || /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
1967
+ || /\bsed\s+(?:[^\n;&|]*\s)?-i(?:\b|['"])/.test(command)
1968
+ || /\b(?:python3?|node|perl|ruby)\b[\s\S]{0,260}\b(?:writeFileSync|writeFile|write_text|open\([^)]*["']w|File\.write|Path\()/.test(command)
1969
+ || /\b(?:git\s+(?:checkout|switch|restore|reset|apply|am|merge|rebase)|npm\s+(?:install|i|ci)|pnpm\s+(?:install|i)|yarn\s+(?:install|add))\b/.test(command);
1970
+ }
1971
+ function extractDeepInterviewCommandWriteTargets(command) {
1972
+ const targets = [];
1973
+ for (const match of command.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
1974
+ const candidate = safeString(match[2]).trim();
1975
+ if (candidate)
1976
+ targets.push(candidate);
1977
+ }
1978
+ for (const match of command.matchAll(/\btee\s+(?:-a\s+)?(["']?)([^\s&|;<>]+)\1/g)) {
1979
+ const candidate = safeString(match[2]).trim();
1980
+ if (candidate)
1981
+ targets.push(candidate);
1982
+ }
1983
+ return targets;
1984
+ }
1985
+ function isAllowedDeepInterviewBashWrite(cwd, command) {
1986
+ if (!commandHasDeepInterviewWriteIntent(command))
1987
+ return true;
1988
+ if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command))
1989
+ return true;
1990
+ const targets = extractDeepInterviewCommandWriteTargets(command);
1991
+ return targets.length > 0 && targets.every((target) => isAllowedDeepInterviewArtifactPath(cwd, target));
1992
+ }
1993
+ async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId) {
1994
+ const modeState = sessionId
1995
+ ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
1996
+ : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
1997
+ if (!isActiveDeepInterviewPhase(modeState) || !modeState)
1998
+ return null;
1999
+ if (!modeStateMatchesSkillStopContext(modeState, cwd, sessionId))
2000
+ return null;
2001
+ const canonicalState = sessionId
2002
+ ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
2003
+ : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2004
+ if (!canonicalState)
2005
+ return modeState;
2006
+ const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
2007
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
2008
+ return hasActiveDeepInterviewSkill ? modeState : null;
2009
+ }
2010
+ async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir) {
2011
+ const sessionId = readPayloadSessionId(payload);
2012
+ const threadId = readPayloadThreadId(payload);
2013
+ const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
2014
+ if (!activeState)
2015
+ return null;
2016
+ const toolName = safeString(payload.tool_name).trim();
2017
+ const command = readPreToolUseCommand(payload);
2018
+ const pathCandidates = readPreToolUsePathCandidates(payload);
2019
+ let blocked = false;
2020
+ if (toolName === "Bash") {
2021
+ blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
2022
+ }
2023
+ else if (DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
2024
+ blocked = pathCandidates.length === 0
2025
+ || !pathCandidates.every((candidate) => isAllowedDeepInterviewArtifactPath(cwd, candidate));
2026
+ }
2027
+ if (!blocked)
2028
+ return null;
2029
+ const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
2030
+ return {
2031
+ decision: "block",
2032
+ reason: `Deep-interview is active (phase: ${phase}); implementation/write tools are blocked until an explicit handoff workflow is activated.`,
2033
+ hookSpecificOutput: {
2034
+ hookEventName: "PreToolUse",
2035
+ additionalContext: "Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under `.omx/context/`, `.omx/interviews/`, `.omx/specs/`, or required `.omx/state/` files. To implement, first ask for or process an explicit transition such as `$ralplan`, `$autopilot`, `$ralph`, `$team`, or `$ultragoal`.",
2036
+ },
2037
+ };
2038
+ }
1862
2039
  function matchesSkillStopContext(entry, state, sessionId, threadId) {
1863
2040
  const entrySessionId = safeString(entry.session_id ?? state.session_id).trim();
1864
2041
  const entryThreadId = safeString(entry.thread_id ?? state.thread_id).trim();
@@ -2310,7 +2487,7 @@ async function maybeReturnRepeatableStopOutput(payload, stateDir, signature, out
2310
2487
  async function returnPersistentStopBlock(payload, stateDir, signatureKind, signatureValue, output, canonicalSessionId, options = { allowRepeatDuringStopHook: true }) {
2311
2488
  return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, signatureKind, signatureValue, canonicalSessionId), output, canonicalSessionId, options);
2312
2489
  }
2313
- async function findCanonicalActiveTeamForSession(cwd, sessionId) {
2490
+ async function findCanonicalActiveTeamForSession(cwd, sessionId, threadId) {
2314
2491
  if (!sessionId.trim())
2315
2492
  return null;
2316
2493
  const teamsRoot = join(resolveCanonicalTeamStateRoot(cwd), "team");
@@ -2332,6 +2509,8 @@ async function findCanonicalActiveTeamForSession(cwd, sessionId) {
2332
2509
  const ownerSessionId = (manifest.leader?.session_id ?? "").trim();
2333
2510
  if (ownerSessionId && ownerSessionId !== sessionId.trim())
2334
2511
  continue;
2512
+ if (!teamStateMatchesThreadForStop(manifest.leader, threadId))
2513
+ continue;
2335
2514
  if (!isNonTerminalPhase(phaseState.current_phase))
2336
2515
  continue;
2337
2516
  return {
@@ -2341,18 +2520,18 @@ async function findCanonicalActiveTeamForSession(cwd, sessionId) {
2341
2520
  }
2342
2521
  return null;
2343
2522
  }
2344
- async function resolveActiveTeamNameForStop(cwd, stateDir, sessionId) {
2345
- const directState = await readTeamModeStateForStop(cwd, stateDir, sessionId);
2346
- const directTeamName = safeString(directState?.team_name).trim();
2347
- if (directState?.active === true && directTeamName)
2523
+ async function resolveActiveTeamNameForStop(cwd, stateDir, sessionId, threadId) {
2524
+ const directState = await readTeamModeStateForStop(cwd, stateDir, sessionId, threadId);
2525
+ const directTeamName = safeString(directState?.state.team_name).trim();
2526
+ if (directState?.state.active === true && directTeamName)
2348
2527
  return directTeamName;
2349
- const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId);
2528
+ const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, sessionId, threadId);
2350
2529
  return canonicalTeam?.teamName ?? "";
2351
2530
  }
2352
2531
  async function maybeBuildReleaseReadinessFinalizeStopOutput(payload, cwd, stateDir, sessionId) {
2353
2532
  if (!sessionId)
2354
2533
  return { matched: false, output: null };
2355
- const teamName = await resolveActiveTeamNameForStop(cwd, stateDir, sessionId);
2534
+ const teamName = await resolveActiveTeamNameForStop(cwd, stateDir, sessionId, readPayloadThreadId(payload));
2356
2535
  if (!teamName)
2357
2536
  return { matched: false, output: null };
2358
2537
  const explicitReleaseReadinessContext = hasReleaseReadinessMode(payload)
@@ -2569,7 +2748,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
2569
2748
  const releaseReadinessFinalizeResult = await maybeBuildReleaseReadinessFinalizeStopOutput(payload, cwd, stateDir, canonicalSessionId);
2570
2749
  if (releaseReadinessFinalizeResult.matched)
2571
2750
  return releaseReadinessFinalizeResult.output;
2572
- const teamOutput = await buildTeamStopOutput(cwd, canonicalSessionId);
2751
+ const teamOutput = await buildTeamStopOutput(cwd, canonicalSessionId, threadId);
2573
2752
  if (teamOutput) {
2574
2753
  return await returnPersistentStopBlock(payload, stateDir, "team-stop", safeString(teamOutput.stopReason), teamOutput, canonicalSessionId);
2575
2754
  }
@@ -2580,7 +2759,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
2580
2759
  }
2581
2760
  const canonicalTeam = await readCanonicalTerminalRunStateForStop(cwd, canonicalSessionId, "team")
2582
2761
  ? null
2583
- : await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
2762
+ : await findCanonicalActiveTeamForSession(cwd, canonicalSessionId, threadId);
2584
2763
  if (canonicalTeam) {
2585
2764
  const canonicalTeamOutput = buildTeamStopOutputForPhase(canonicalTeam.teamName, canonicalTeam.phase);
2586
2765
  const repeatedCanonicalTeamOutput = await returnPersistentStopBlock(payload, stateDir, "team-stop", `${canonicalTeam.teamName}|${canonicalTeam.phase}`, canonicalTeamOutput, canonicalSessionId);
@@ -2857,7 +3036,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
2857
3036
  }
2858
3037
  }
2859
3038
  else if (hookEventName === "PreToolUse") {
2860
- outputJson = buildNativePreToolUseOutput(payload);
3039
+ outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
3040
+ ?? buildNativePreToolUseOutput(payload);
2861
3041
  }
2862
3042
  else if (hookEventName === "PostToolUse") {
2863
3043
  if (detectMcpTransportFailure(payload)) {