oh-my-codex 0.13.2 → 0.14.0

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 (296) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/dist/autoresearch/__tests__/skill-validation.test.d.ts +2 -0
  4. package/dist/autoresearch/__tests__/skill-validation.test.d.ts.map +1 -0
  5. package/dist/autoresearch/__tests__/skill-validation.test.js +91 -0
  6. package/dist/autoresearch/__tests__/skill-validation.test.js.map +1 -0
  7. package/dist/autoresearch/skill-validation.d.ts +13 -0
  8. package/dist/autoresearch/skill-validation.d.ts.map +1 -0
  9. package/dist/autoresearch/skill-validation.js +165 -0
  10. package/dist/autoresearch/skill-validation.js.map +1 -0
  11. package/dist/catalog/__tests__/schema.test.js +6 -0
  12. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  13. package/dist/cli/__tests__/autoresearch-guided.test.js +236 -273
  14. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  15. package/dist/cli/__tests__/autoresearch.test.js +64 -653
  16. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  17. package/dist/cli/__tests__/index.test.js +7 -0
  18. package/dist/cli/__tests__/index.test.js.map +1 -1
  19. package/dist/cli/__tests__/nested-help-routing.test.js +2 -1
  20. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  21. package/dist/cli/__tests__/question.test.d.ts +2 -0
  22. package/dist/cli/__tests__/question.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/question.test.js +113 -0
  24. package/dist/cli/__tests__/question.test.js.map +1 -0
  25. package/dist/cli/__tests__/session-search-help.test.js +1 -1
  26. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -0
  28. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  29. package/dist/cli/autoresearch-guided.d.ts +24 -7
  30. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  31. package/dist/cli/autoresearch-guided.js +189 -130
  32. package/dist/cli/autoresearch-guided.js.map +1 -1
  33. package/dist/cli/autoresearch.d.ts +3 -2
  34. package/dist/cli/autoresearch.d.ts.map +1 -1
  35. package/dist/cli/autoresearch.js +29 -305
  36. package/dist/cli/autoresearch.js.map +1 -1
  37. package/dist/cli/doctor.d.ts.map +1 -1
  38. package/dist/cli/doctor.js +43 -0
  39. package/dist/cli/doctor.js.map +1 -1
  40. package/dist/cli/index.d.ts +1 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +8 -1
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/question.d.ts +3 -0
  45. package/dist/cli/question.d.ts.map +1 -0
  46. package/dist/cli/question.js +182 -0
  47. package/dist/cli/question.js.map +1 -0
  48. package/dist/hooks/__tests__/analyze-routing-contract.test.js +22 -13
  49. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  50. package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
  51. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  52. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +2 -2
  53. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -1
  54. package/dist/hooks/__tests__/deep-interview-contract.test.js +22 -5
  55. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  56. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +2 -2
  57. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
  58. package/dist/hooks/__tests__/keyword-detector.test.js +308 -17
  59. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  60. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +570 -2
  61. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  62. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +717 -16
  63. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  64. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +25 -0
  65. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  66. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +894 -1
  67. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  68. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +34 -0
  69. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  70. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +132 -0
  71. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  72. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +22 -4
  73. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  74. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js +4 -2
  75. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  76. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts +1 -0
  77. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
  78. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +4 -1
  79. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  80. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +28 -0
  81. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  82. package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js +5 -4
  83. package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js.map +1 -1
  84. package/dist/hooks/__tests__/prompt-team-routing.test.js +2 -2
  85. package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
  86. package/dist/hooks/__tests__/triage-config.test.d.ts +2 -0
  87. package/dist/hooks/__tests__/triage-config.test.d.ts.map +1 -0
  88. package/dist/hooks/__tests__/triage-config.test.js +211 -0
  89. package/dist/hooks/__tests__/triage-config.test.js.map +1 -0
  90. package/dist/hooks/__tests__/triage-heuristic.test.d.ts +2 -0
  91. package/dist/hooks/__tests__/triage-heuristic.test.d.ts.map +1 -0
  92. package/dist/hooks/__tests__/triage-heuristic.test.js +230 -0
  93. package/dist/hooks/__tests__/triage-heuristic.test.js.map +1 -0
  94. package/dist/hooks/__tests__/triage-state.test.d.ts +2 -0
  95. package/dist/hooks/__tests__/triage-state.test.d.ts.map +1 -0
  96. package/dist/hooks/__tests__/triage-state.test.js +426 -0
  97. package/dist/hooks/__tests__/triage-state.test.js.map +1 -0
  98. package/dist/hooks/keyword-detector.d.ts +26 -7
  99. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  100. package/dist/hooks/keyword-detector.js +97 -26
  101. package/dist/hooks/keyword-detector.js.map +1 -1
  102. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  103. package/dist/hooks/keyword-registry.js +16 -9
  104. package/dist/hooks/keyword-registry.js.map +1 -1
  105. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  106. package/dist/hooks/prompt-guidance-contract.js +28 -1
  107. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  108. package/dist/hooks/triage-config.d.ts +33 -0
  109. package/dist/hooks/triage-config.d.ts.map +1 -0
  110. package/dist/hooks/triage-config.js +87 -0
  111. package/dist/hooks/triage-config.js.map +1 -0
  112. package/dist/hooks/triage-heuristic.d.ts +20 -0
  113. package/dist/hooks/triage-heuristic.d.ts.map +1 -0
  114. package/dist/hooks/triage-heuristic.js +210 -0
  115. package/dist/hooks/triage-heuristic.js.map +1 -0
  116. package/dist/hooks/triage-state.d.ts +63 -0
  117. package/dist/hooks/triage-state.d.ts.map +1 -0
  118. package/dist/hooks/triage-state.js +138 -0
  119. package/dist/hooks/triage-state.js.map +1 -0
  120. package/dist/hud/__tests__/reconcile.test.js +20 -0
  121. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  122. package/dist/hud/reconcile.d.ts +1 -0
  123. package/dist/hud/reconcile.d.ts.map +1 -1
  124. package/dist/hud/reconcile.js +2 -1
  125. package/dist/hud/reconcile.js.map +1 -1
  126. package/dist/mcp/__tests__/state-server.test.js +1 -0
  127. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  128. package/dist/mcp/state-server.d.ts +8 -0
  129. package/dist/mcp/state-server.d.ts.map +1 -1
  130. package/dist/mcp/state-server.js +4 -0
  131. package/dist/mcp/state-server.js.map +1 -1
  132. package/dist/modes/__tests__/base-ralph-contract.test.js +15 -0
  133. package/dist/modes/__tests__/base-ralph-contract.test.js.map +1 -1
  134. package/dist/modes/base.d.ts +1 -0
  135. package/dist/modes/base.d.ts.map +1 -1
  136. package/dist/modes/base.js +22 -6
  137. package/dist/modes/base.js.map +1 -1
  138. package/dist/notifications/__tests__/index.test.js +78 -0
  139. package/dist/notifications/__tests__/index.test.js.map +1 -1
  140. package/dist/notifications/index.d.ts.map +1 -1
  141. package/dist/notifications/index.js +39 -22
  142. package/dist/notifications/index.js.map +1 -1
  143. package/dist/openclaw/index.d.ts +5 -3
  144. package/dist/openclaw/index.d.ts.map +1 -1
  145. package/dist/openclaw/index.js +5 -3
  146. package/dist/openclaw/index.js.map +1 -1
  147. package/dist/question/__tests__/client.test.d.ts +2 -0
  148. package/dist/question/__tests__/client.test.d.ts.map +1 -0
  149. package/dist/question/__tests__/client.test.js +70 -0
  150. package/dist/question/__tests__/client.test.js.map +1 -0
  151. package/dist/question/__tests__/deep-interview.test.d.ts +2 -0
  152. package/dist/question/__tests__/deep-interview.test.d.ts.map +1 -0
  153. package/dist/question/__tests__/deep-interview.test.js +108 -0
  154. package/dist/question/__tests__/deep-interview.test.js.map +1 -0
  155. package/dist/question/__tests__/policy.test.d.ts +2 -0
  156. package/dist/question/__tests__/policy.test.d.ts.map +1 -0
  157. package/dist/question/__tests__/policy.test.js +107 -0
  158. package/dist/question/__tests__/policy.test.js.map +1 -0
  159. package/dist/question/__tests__/renderer.test.d.ts +2 -0
  160. package/dist/question/__tests__/renderer.test.d.ts.map +1 -0
  161. package/dist/question/__tests__/renderer.test.js +88 -0
  162. package/dist/question/__tests__/renderer.test.js.map +1 -0
  163. package/dist/question/__tests__/state.test.d.ts +2 -0
  164. package/dist/question/__tests__/state.test.d.ts.map +1 -0
  165. package/dist/question/__tests__/state.test.js +55 -0
  166. package/dist/question/__tests__/state.test.js.map +1 -0
  167. package/dist/question/__tests__/types.test.d.ts +2 -0
  168. package/dist/question/__tests__/types.test.d.ts.map +1 -0
  169. package/dist/question/__tests__/types.test.js +44 -0
  170. package/dist/question/__tests__/types.test.js.map +1 -0
  171. package/dist/question/__tests__/ui.test.d.ts +2 -0
  172. package/dist/question/__tests__/ui.test.d.ts.map +1 -0
  173. package/dist/question/__tests__/ui.test.js +169 -0
  174. package/dist/question/__tests__/ui.test.js.map +1 -0
  175. package/dist/question/client.d.ts +54 -0
  176. package/dist/question/client.d.ts.map +1 -0
  177. package/dist/question/client.js +77 -0
  178. package/dist/question/client.js.map +1 -0
  179. package/dist/question/deep-interview.d.ts +27 -0
  180. package/dist/question/deep-interview.d.ts.map +1 -0
  181. package/dist/question/deep-interview.js +101 -0
  182. package/dist/question/deep-interview.js.map +1 -0
  183. package/dist/question/policy.d.ts +18 -0
  184. package/dist/question/policy.d.ts.map +1 -0
  185. package/dist/question/policy.js +77 -0
  186. package/dist/question/policy.js.map +1 -0
  187. package/dist/question/renderer.d.ts +18 -0
  188. package/dist/question/renderer.d.ts.map +1 -0
  189. package/dist/question/renderer.js +128 -0
  190. package/dist/question/renderer.js.map +1 -0
  191. package/dist/question/state.d.ts +19 -0
  192. package/dist/question/state.d.ts.map +1 -0
  193. package/dist/question/state.js +108 -0
  194. package/dist/question/state.js.map +1 -0
  195. package/dist/question/types.d.ts +66 -0
  196. package/dist/question/types.d.ts.map +1 -0
  197. package/dist/question/types.js +82 -0
  198. package/dist/question/types.js.map +1 -0
  199. package/dist/question/ui.d.ts +38 -0
  200. package/dist/question/ui.d.ts.map +1 -0
  201. package/dist/question/ui.js +321 -0
  202. package/dist/question/ui.js.map +1 -0
  203. package/dist/ralph/contract.d.ts +1 -1
  204. package/dist/ralph/contract.d.ts.map +1 -1
  205. package/dist/ralph/contract.js +4 -1
  206. package/dist/ralph/contract.js.map +1 -1
  207. package/dist/ralplan/runtime.js +1 -1
  208. package/dist/ralplan/runtime.js.map +1 -1
  209. package/dist/runtime/__tests__/run-loop.test.d.ts +2 -0
  210. package/dist/runtime/__tests__/run-loop.test.d.ts.map +1 -0
  211. package/dist/runtime/__tests__/run-loop.test.js +35 -0
  212. package/dist/runtime/__tests__/run-loop.test.js.map +1 -0
  213. package/dist/runtime/__tests__/run-outcome.test.d.ts +2 -0
  214. package/dist/runtime/__tests__/run-outcome.test.d.ts.map +1 -0
  215. package/dist/runtime/__tests__/run-outcome.test.js +64 -0
  216. package/dist/runtime/__tests__/run-outcome.test.js.map +1 -0
  217. package/dist/runtime/run-loop.d.ts +41 -0
  218. package/dist/runtime/run-loop.d.ts.map +1 -0
  219. package/dist/runtime/run-loop.js +46 -0
  220. package/dist/runtime/run-loop.js.map +1 -0
  221. package/dist/runtime/run-outcome.d.ts +28 -0
  222. package/dist/runtime/run-outcome.d.ts.map +1 -0
  223. package/dist/runtime/run-outcome.js +136 -0
  224. package/dist/runtime/run-outcome.js.map +1 -0
  225. package/dist/runtime/run-state.d.ts +36 -0
  226. package/dist/runtime/run-state.d.ts.map +1 -0
  227. package/dist/runtime/run-state.js +110 -0
  228. package/dist/runtime/run-state.js.map +1 -0
  229. package/dist/scripts/__tests__/codex-native-hook.test.js +1128 -85
  230. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  231. package/dist/scripts/codex-native-hook.d.ts +2 -0
  232. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  233. package/dist/scripts/codex-native-hook.js +199 -11
  234. package/dist/scripts/codex-native-hook.js.map +1 -1
  235. package/dist/scripts/notify-fallback-watcher.js +81 -2
  236. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  237. package/dist/scripts/notify-hook/auto-nudge.d.ts +27 -0
  238. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  239. package/dist/scripts/notify-hook/auto-nudge.js +83 -20
  240. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  241. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  242. package/dist/scripts/notify-hook/managed-tmux.js +64 -38
  243. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  244. package/dist/scripts/notify-hook/ralph-session-resume.js +1 -1
  245. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  246. package/dist/scripts/notify-hook.js +15 -5
  247. package/dist/scripts/notify-hook.js.map +1 -1
  248. package/dist/scripts/sync-prompt-guidance-fragments.js +5 -0
  249. package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
  250. package/dist/state/__tests__/operations-ralph-phase.test.js +21 -0
  251. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
  252. package/dist/state/__tests__/workflow-transition.test.js +11 -0
  253. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  254. package/dist/state/operations.d.ts.map +1 -1
  255. package/dist/state/operations.js +15 -0
  256. package/dist/state/operations.js.map +1 -1
  257. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  258. package/dist/state/workflow-transition-reconcile.js +14 -1
  259. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  260. package/dist/state/workflow-transition.d.ts.map +1 -1
  261. package/dist/state/workflow-transition.js +3 -1
  262. package/dist/state/workflow-transition.js.map +1 -1
  263. package/dist/team/__tests__/followup-planner.test.js +15 -0
  264. package/dist/team/__tests__/followup-planner.test.js.map +1 -1
  265. package/dist/team/__tests__/role-router.test.js +41 -0
  266. package/dist/team/__tests__/role-router.test.js.map +1 -1
  267. package/dist/team/followup-planner.d.ts.map +1 -1
  268. package/dist/team/followup-planner.js +31 -9
  269. package/dist/team/followup-planner.js.map +1 -1
  270. package/dist/team/role-router.d.ts.map +1 -1
  271. package/dist/team/role-router.js +73 -0
  272. package/dist/team/role-router.js.map +1 -1
  273. package/package.json +3 -2
  274. package/prompts/dependency-expert.md +3 -0
  275. package/prompts/executor.md +5 -0
  276. package/prompts/explore.md +2 -0
  277. package/prompts/planner.md +5 -0
  278. package/prompts/product-analyst.md +8 -8
  279. package/prompts/researcher.md +78 -30
  280. package/prompts/verifier.md +4 -0
  281. package/skills/autoresearch/SKILL.md +68 -0
  282. package/skills/deep-interview/SKILL.md +10 -9
  283. package/skills/help/SKILL.md +3 -1
  284. package/skills/ralplan/SKILL.md +1 -0
  285. package/skills/team/SKILL.md +1 -0
  286. package/skills/ultrawork/SKILL.md +1 -0
  287. package/src/scripts/__tests__/codex-native-hook.test.ts +1495 -188
  288. package/src/scripts/codex-native-hook.ts +235 -19
  289. package/src/scripts/notify-fallback-watcher.ts +92 -2
  290. package/src/scripts/notify-hook/auto-nudge.ts +89 -20
  291. package/src/scripts/notify-hook/managed-tmux.ts +70 -31
  292. package/src/scripts/notify-hook/ralph-session-resume.ts +1 -1
  293. package/src/scripts/notify-hook.ts +23 -5
  294. package/src/scripts/sync-prompt-guidance-fragments.ts +4 -0
  295. package/templates/AGENTS.md +48 -37
  296. package/templates/catalog-manifest.json +7 -0
@@ -21,6 +21,8 @@ import { logTmuxHookEvent } from './log.js';
21
21
  import { evaluatePaneInjectionReadiness, mapPaneInjectionReadinessReason, sendPaneInput } from './team-tmux-guard.js';
22
22
  import { stripOrchestrationIntentTags } from './orchestration-intent.js';
23
23
  import { buildCapturePaneArgv, DEFAULT_MARKER, tmuxHookExplicitlyDisablesInjection } from '../tmux-hook-engine.js';
24
+ import { readAutoresearchCompletionStatus } from '../../autoresearch/skill-validation.js';
25
+ import { persistDeepInterviewModeState } from '../../hooks/keyword-detector.js';
24
26
  import {
25
27
  isManagedOmxSession,
26
28
  resolveManagedCurrentPane,
@@ -36,6 +38,7 @@ export const DEEP_INTERVIEW_INPUT_LOCK_MESSAGE = 'Deep interview is active; auto
36
38
  export const DEFAULT_AUTO_NUDGE_RESPONSE = 'continue with the current task only if it is already authorized';
37
39
  const DEEP_INTERVIEW_ERROR_PATTERNS = [' error', ' failed', ' failure', ' exception', 'unable to continue', 'cannot continue', 'could not continue'];
38
40
  const DEEP_INTERVIEW_ABORT_PATTERNS = ['aborted', 'cancelled', 'canceled'];
41
+ const DEEP_INTERVIEW_SUCCESS_PATTERNS = ['interview completed', 'interview complete', 'interview finished', 'final summary ready'];
39
42
  const DEEP_INTERVIEW_ABORT_INPUTS = new Set(['abort', 'cancel', 'stop']);
40
43
  const DEEP_INTERVIEW_BLOCKED_APPROVAL_PREFIXES = new Set(['next i should']);
41
44
  const SKILL_PHASES = new Set(['planning', 'executing', 'reviewing', 'completing']);
@@ -99,6 +102,10 @@ function hasAnySubstring(text, patterns) {
99
102
  return patterns.some((pattern) => lower.includes(pattern));
100
103
  }
101
104
 
105
+ function looksLikeDeepInterviewSuccess(text) {
106
+ return hasAnySubstring(text, DEEP_INTERVIEW_SUCCESS_PATTERNS);
107
+ }
108
+
102
109
  export function isDeepInterviewAutoApprovalLocked(skillState) {
103
110
  return Boolean(
104
111
  skillState
@@ -188,6 +195,78 @@ async function persistSkillActiveState(stateDir, sessionId, state) {
188
195
  await writeScopedJson(stateDir, SKILL_ACTIVE_STATE_FILE, sessionId, state).catch(() => {});
189
196
  }
190
197
 
198
+ function cloneSkillActiveState(state) {
199
+ if (!state || typeof state !== 'object') return null;
200
+ return {
201
+ ...state,
202
+ input_lock: state.input_lock
203
+ ? {
204
+ ...state.input_lock,
205
+ blocked_inputs: Array.isArray(state.input_lock.blocked_inputs)
206
+ ? [...state.input_lock.blocked_inputs]
207
+ : state.input_lock.blocked_inputs,
208
+ }
209
+ : state.input_lock,
210
+ };
211
+ }
212
+
213
+ export async function syncSkillStateFromTurn(stateDir, payload) {
214
+ const lastMessage = safeString(payload['last-assistant-message'] || payload.last_assistant_message || '');
215
+ const latestUserInput = latestUserInputFromPayload(payload);
216
+ const invocationSessionId = resolveInvocationSessionId(payload);
217
+ let skillState = await loadSkillActiveState(stateDir, invocationSessionId);
218
+ let releaseReason = null;
219
+
220
+ if (!skillState) {
221
+ return { invocationSessionId, skillState: null, releaseReason };
222
+ }
223
+
224
+ const previousSkillState = cloneSkillActiveState(skillState);
225
+ const previousPhase = normalizeSkillPhase(skillState.phase);
226
+ const inferredPhase = inferSkillPhaseFromText(lastMessage, previousPhase);
227
+ const explicitDeepInterviewSuccess = skillState.skill === 'deep-interview' && looksLikeDeepInterviewSuccess(lastMessage);
228
+ const nextPhase = skillState.skill === 'deep-interview'
229
+ && inferredPhase === 'completing'
230
+ && previousPhase !== 'completing'
231
+ && !explicitDeepInterviewSuccess
232
+ ? previousPhase
233
+ : inferredPhase;
234
+ skillState.phase = nextPhase;
235
+ skillState.active = nextPhase !== 'completing';
236
+
237
+ if (skillState.skill === 'autoresearch') {
238
+ const completion = await readAutoresearchCompletionStatus(payload.cwd || process.cwd(), invocationSessionId);
239
+ skillState.validation_mode = completion.validationMode;
240
+ skillState.autoresearch_completion_reason = completion.reason;
241
+ skillState.completion_artifact_path = completion.artifactPath;
242
+ if (completion.complete) {
243
+ skillState.phase = 'completing';
244
+ skillState.active = false;
245
+ } else if (inferredPhase === 'completing') {
246
+ skillState.phase = previousPhase === 'completing' ? 'reviewing' : previousPhase;
247
+ skillState.active = true;
248
+ }
249
+ }
250
+
251
+ const nowIso = new Date().toISOString();
252
+ skillState.updated_at = nowIso;
253
+ releaseReason = inferDeepInterviewReleaseReason({ skillState, latestUserInput, lastMessage });
254
+ if (releaseReason && isDeepInterviewAutoApprovalLocked(skillState)) {
255
+ releaseDeepInterviewInputLock(skillState, releaseReason, nowIso);
256
+ }
257
+ await persistSkillActiveState(stateDir, invocationSessionId, skillState);
258
+
259
+ if (skillState.skill === 'deep-interview' || previousSkillState?.skill === 'deep-interview') {
260
+ await persistDeepInterviewModeState(stateDir, skillState, nowIso, previousSkillState, {
261
+ sessionId: invocationSessionId,
262
+ threadId: safeString(payload?.['thread-id'] || payload?.thread_id || ''),
263
+ turnId: safeString(payload?.['turn-id'] || payload?.turn_id || ''),
264
+ });
265
+ }
266
+
267
+ return { invocationSessionId, skillState, releaseReason };
268
+ }
269
+
191
270
 
192
271
  export async function isDeepInterviewStateActive(stateDir, sessionId) {
193
272
  const modeState = await readScopedJsonIfExists(stateDir, 'deep-interview-state.json', sessionId, null);
@@ -479,8 +558,10 @@ export async function resolveNudgePaneTarget(stateDir: any, cwd = '', payload: a
479
558
  if (!anchoredPane) continue;
480
559
  const managedPane = await resolveManagedPaneFromAnchor(anchoredPane, cwd, payload, { allowTeamWorker });
481
560
  if (managedPane) return managedPane;
482
- const verdict = await verifyManagedPaneTarget(anchoredPane, cwd, payload, { allowTeamWorker });
483
- if (verdict.ok) return anchoredPane;
561
+ if (allowTeamWorker) {
562
+ const verdict = await verifyManagedPaneTarget(anchoredPane, cwd, payload, { allowTeamWorker });
563
+ if (verdict.ok) return anchoredPane;
564
+ }
484
565
  } catch {
485
566
  // skip malformed state
486
567
  }
@@ -516,21 +597,9 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
516
597
  }
517
598
 
518
599
  const lastMessage = safeString(payload['last-assistant-message'] || payload.last_assistant_message || '');
519
- const latestUserInput = latestUserInputFromPayload(payload);
520
- const invocationSessionId = resolveInvocationSessionId(payload);
521
- let skillState = await loadSkillActiveState(stateDir, invocationSessionId);
522
- let releaseReason = null;
600
+ const { invocationSessionId, skillState, releaseReason } = await syncSkillStateFromTurn(stateDir, payload);
523
601
 
524
602
  try {
525
- if (skillState) {
526
- const inferredPhase = inferSkillPhaseFromText(lastMessage, skillState.phase);
527
- skillState.phase = inferredPhase;
528
- skillState.active = inferredPhase !== 'completing';
529
- skillState.updated_at = new Date().toISOString();
530
- releaseReason = inferDeepInterviewReleaseReason({ skillState, latestUserInput, lastMessage });
531
- await persistSkillActiveState(stateDir, invocationSessionId, skillState);
532
- }
533
-
534
603
  const nudgeStatePath = await getScopedStatePath(stateDir, 'auto-nudge-state.json', invocationSessionId);
535
604
  let nudgeState = await readScopedJsonIfExists(stateDir, 'auto-nudge-state.json', invocationSessionId, null);
536
605
  if (!nudgeState || typeof nudgeState !== 'object') {
@@ -618,8 +687,10 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
618
687
  return;
619
688
  }
620
689
 
621
- const deepInterviewLockActive = isDeepInterviewAutoApprovalLocked(skillState) && !releaseReason;
622
- if (deepInterviewLockActive) {
690
+ const blockedAutoApproval = isDeepInterviewAutoApprovalLocked(skillState)
691
+ && !releaseReason
692
+ && isBlockedAutoApprovalInput(effectiveResponse, skillState.input_lock?.blocked_inputs);
693
+ if (blockedAutoApproval) {
623
694
  const blockedMessage = skillState.input_lock?.message || DEEP_INTERVIEW_INPUT_LOCK_MESSAGE;
624
695
  await logTmuxHookEvent(logsDir, {
625
696
  timestamp: new Date().toISOString(),
@@ -628,9 +699,7 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
628
699
  response: effectiveResponse,
629
700
  source,
630
701
  blocked_by: 'deep-interview-lock',
631
- block_kind: isBlockedAutoApprovalInput(effectiveResponse, skillState.input_lock?.blocked_inputs)
632
- ? 'blocked-auto-approval'
633
- : 'input-lock-active',
702
+ block_kind: 'blocked-auto-approval',
634
703
  message: blockedMessage,
635
704
  suppressed: true,
636
705
  }).catch(() => {});
@@ -248,7 +248,7 @@ export async function verifyManagedPaneTarget(paneId: string, cwd: string, paylo
248
248
  }
249
249
 
250
250
 
251
- async function readManagedPaneCommandState(paneTarget: string): Promise<{ currentCommand: string; startCommand: string }> {
251
+ async function readManagedPaneCommandState(paneTarget: string): Promise<{ currentCommand: string; startCommand: string; lookupFailed: boolean }> {
252
252
  try {
253
253
  const [currentResult, startResult] = await Promise.all([
254
254
  runProcess('tmux', ['display-message', '-p', '-t', paneTarget, '#{pane_current_command}'], 2000),
@@ -257,9 +257,10 @@ async function readManagedPaneCommandState(paneTarget: string): Promise<{ curren
257
257
  return {
258
258
  currentCommand: safeString(currentResult.stdout).trim().toLowerCase(),
259
259
  startCommand: safeString(startResult.stdout).trim().toLowerCase(),
260
+ lookupFailed: false,
260
261
  };
261
262
  } catch {
262
- return { currentCommand: '', startCommand: '' };
263
+ return { currentCommand: '', startCommand: '', lookupFailed: true };
263
264
  }
264
265
  }
265
266
 
@@ -268,6 +269,59 @@ function paneLooksLikeManagedAgent({ currentCommand, startCommand }: { currentCo
268
269
  if (startCommand.includes('codex')) return true;
269
270
  return currentCommand === 'codex' || currentCommand === 'node' || currentCommand === 'npx';
270
271
  }
272
+
273
+ function paneLooksLikeRetainableManagedAnchor({ currentCommand, startCommand }: { currentCommand: string; startCommand: string }): boolean {
274
+ if (/\bomx\b.*\bhud\b.*--watch/i.test(startCommand)) return false;
275
+ if (currentCommand === 'codex') return true;
276
+ if ((currentCommand === 'node' || currentCommand === 'npx') && startCommand.includes('codex')) return true;
277
+ return false;
278
+ }
279
+
280
+ function paneLooksLikeDetachedManagedWrapperFallback({ currentCommand, startCommand }: { currentCommand: string; startCommand: string }): boolean {
281
+ if (/\bomx\b.*\bhud\b.*--watch/i.test(startCommand)) return false;
282
+ return currentCommand === 'node' || currentCommand === 'npx';
283
+ }
284
+
285
+ interface ManagedSessionPaneRow {
286
+ paneId: string;
287
+ active: boolean;
288
+ currentCommand: string;
289
+ startCommand: string;
290
+ }
291
+
292
+ function parseManagedSessionPaneRows(stdout: string): ManagedSessionPaneRow[] {
293
+ return safeString(stdout)
294
+ .trim()
295
+ .split('\n')
296
+ .filter(Boolean)
297
+ .map((line) => {
298
+ const [paneId = '', activeRaw = '0', rawCurrentCommand = '', rawStartCommand = ''] = line.split('\t');
299
+ return {
300
+ paneId: safeString(paneId).trim(),
301
+ active: safeString(activeRaw).trim() === '1',
302
+ currentCommand: safeString(rawCurrentCommand).trim().toLowerCase(),
303
+ startCommand: safeString(rawStartCommand).trim().toLowerCase(),
304
+ };
305
+ })
306
+ .filter((row) => row.paneId !== '');
307
+ }
308
+
309
+ function selectManagedSessionPane(
310
+ rows: ManagedSessionPaneRow[],
311
+ { allowWrapperFallback = false }: { allowWrapperFallback?: boolean } = {},
312
+ ): string {
313
+ const nonHudRows = rows.filter((row) => !/\bomx\b.*\bhud\b.*--watch/i.test(row.startCommand));
314
+ const canonicalRows = nonHudRows.filter((row) => paneLooksLikeRetainableManagedAnchor(row));
315
+ const activeCanonical = canonicalRows.find((row) => row.active);
316
+ if (activeCanonical) return activeCanonical.paneId;
317
+ if (canonicalRows[0]?.paneId) return canonicalRows[0].paneId;
318
+ if (!allowWrapperFallback) return '';
319
+
320
+ const wrapperFallbackRows = nonHudRows.filter((row) => paneLooksLikeDetachedManagedWrapperFallback(row));
321
+ const activeWrapperFallback = wrapperFallbackRows.find((row) => row.active);
322
+ if (activeWrapperFallback) return activeWrapperFallback.paneId;
323
+ return wrapperFallbackRows[0]?.paneId || '';
324
+ }
271
325
  export async function resolveManagedCurrentPane(cwd: string, payload: any, { allowTeamWorker = false } = {}): Promise<string> {
272
326
  const paneTarget = safeString(process.env.TMUX_PANE || '').trim();
273
327
  if (!paneTarget) return '';
@@ -286,22 +340,10 @@ export async function resolveManagedSessionPane(cwd: string, payload: any): Prom
286
340
  try {
287
341
  const panesResult = await runProcess(
288
342
  'tmux',
289
- ['list-panes', '-s', '-t', expectedSession, '-F', '#{pane_id}\t#{pane_current_command}\t#{pane_start_command}'],
343
+ ['list-panes', '-s', '-t', expectedSession, '-F', '#{pane_id}\t#{pane_active}\t#{pane_current_command}\t#{pane_start_command}'],
290
344
  2000,
291
345
  );
292
- const panes = safeString(panesResult.stdout)
293
- .trim()
294
- .split('\n')
295
- .filter(Boolean);
296
- for (const line of panes) {
297
- const [candidatePaneId, rawCurrentCommand = '', rawStartCommand = ''] = line.split('\t');
298
- const startCommand = safeString(rawStartCommand).toLowerCase();
299
- const currentCommand = safeString(rawCurrentCommand).trim().toLowerCase();
300
- if (!candidatePaneId) continue;
301
- if (/\bomx\b.*\bhud\b.*--watch/i.test(startCommand)) continue;
302
- if (startCommand.includes('codex')) return candidatePaneId;
303
- if (currentCommand === 'codex') return candidatePaneId;
304
- }
346
+ return selectManagedSessionPane(parseManagedSessionPaneRows(panesResult.stdout));
305
347
  } catch {
306
348
  // best effort only
307
349
  }
@@ -315,29 +357,26 @@ export async function resolveManagedPaneFromAnchor(anchorPane: string, cwd: stri
315
357
  const verdict = await verifyManagedPaneTarget(paneTarget, cwd, payload, { allowTeamWorker });
316
358
  if (!verdict.ok) return '';
317
359
 
360
+ const commandState = await readManagedPaneCommandState(paneTarget);
361
+ if (commandState.lookupFailed) return paneTarget;
362
+ if (paneLooksLikeRetainableManagedAnchor(commandState)) return paneTarget;
363
+
318
364
  try {
319
- const sessionResult = await runProcess('tmux', ['display-message', '-p', '-t', paneTarget, '#S'], 2000);
320
- const sessionName = safeString(sessionResult.stdout).trim();
321
- if (!sessionName) return paneTarget;
365
+ const sessionName = safeString(verdict.paneSessionName || verdict.managedContext?.expectedTmuxSessionName).trim();
366
+ if (!sessionName) return '';
322
367
 
323
368
  const panesResult = await runProcess(
324
369
  'tmux',
325
- ['list-panes', '-s', '-t', sessionName, '-F', '#{pane_id}\t#{pane_current_command}\t#{pane_start_command}'],
370
+ ['list-panes', '-s', '-t', sessionName, '-F', '#{pane_id}\t#{pane_active}\t#{pane_current_command}\t#{pane_start_command}'],
326
371
  2000,
327
372
  );
328
- const panes = safeString(panesResult.stdout).trim().split('\n').filter(Boolean);
329
- for (const line of panes) {
330
- const [candidatePaneId, rawCurrentCommand = '', rawStartCommand = ''] = line.split('\t');
331
- const startCommand = safeString(rawStartCommand).toLowerCase();
332
- const currentCommand = safeString(rawCurrentCommand).trim().toLowerCase();
333
- if (!candidatePaneId) continue;
334
- if (/\bomx\b.*\bhud\b.*--watch/i.test(startCommand)) continue;
335
- if (startCommand.includes('codex')) return candidatePaneId;
336
- if (currentCommand === 'codex') return candidatePaneId;
337
- }
373
+ const selectedPane = selectManagedSessionPane(parseManagedSessionPaneRows(panesResult.stdout), {
374
+ allowWrapperFallback: paneLooksLikeDetachedManagedWrapperFallback(commandState),
375
+ });
376
+ if (selectedPane) return selectedPane;
338
377
  } catch {
339
378
  // best effort only
340
379
  }
341
380
 
342
- return paneTarget;
381
+ return '';
343
382
  }
@@ -7,7 +7,7 @@ import { resolveCodexPane } from '../tmux-hook-engine.js';
7
7
  import { safeString } from './utils.js';
8
8
 
9
9
  const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
10
- const RALPH_TERMINAL_PHASES = new Set(['complete', 'failed', 'cancelled']);
10
+ const RALPH_TERMINAL_PHASES = new Set(['blocked_on_user', 'complete', 'failed', 'cancelled']);
11
11
  const RALPH_RESUME_LOCK_STALE_MS = 10_000;
12
12
  const RALPH_RESUME_LOCK_TIMEOUT_MS = 5_000;
13
13
  const RALPH_RESUME_LOCK_RETRY_MS = 25;
@@ -40,7 +40,13 @@ import {
40
40
  import { isLeaderStale, resolveLeaderStalenessThresholdMs, maybeNudgeTeamLeader } from './notify-hook/team-leader-nudge.js';
41
41
  import { drainPendingTeamDispatch } from './notify-hook/team-dispatch.js';
42
42
  import { handleTmuxInjection } from './notify-hook/tmux-injection.js';
43
- import { maybeAutoNudge, resolveNudgePaneTarget, isDeepInterviewStateActive } from './notify-hook/auto-nudge.js';
43
+ import {
44
+ maybeAutoNudge,
45
+ resolveNudgePaneTarget,
46
+ isDeepInterviewStateActive,
47
+ isDeepInterviewInputLockActive,
48
+ syncSkillStateFromTurn,
49
+ } from './notify-hook/auto-nudge.js';
44
50
  import { isManagedOmxSession } from './notify-hook/managed-tmux.js';
45
51
  import { logNotifyHookEvent } from './notify-hook/log.js';
46
52
  import { reconcileRalphSessionResume } from './notify-hook/ralph-session-resume.js';
@@ -238,14 +244,19 @@ async function main() {
238
244
  }
239
245
 
240
246
  // 1. Log the turn
247
+ const normalizedInputMessages = normalizeInputMessages(payload);
248
+ const latestInputPreview = safeString(
249
+ normalizedInputMessages.length > 0
250
+ ? normalizedInputMessages[normalizedInputMessages.length - 1]
251
+ : '',
252
+ ).slice(0, 200);
241
253
  const logEntry = {
242
254
  timestamp: new Date().toISOString(),
243
255
  type: payload.type || 'agent-turn-complete',
244
256
  thread_id: payload['thread-id'] || payload.thread_id,
245
257
  turn_id: payload['turn-id'] || payload.turn_id,
246
- input_preview: (payload['input-messages'] || payload.input_messages || [])
247
- .map((m: any) => m.slice(0, 100))
248
- .join('; '),
258
+ input_preview: latestInputPreview,
259
+ input_message_count: normalizedInputMessages.length,
249
260
  output_preview: (payload['last-assistant-message'] || payload.last_assistant_message || '')
250
261
  .slice(0, 200),
251
262
  };
@@ -473,7 +484,14 @@ async function main() {
473
484
  // Non-fatal: keyword detector module may not be built yet
474
485
  }
475
486
 
487
+ try {
488
+ await syncSkillStateFromTurn(stateDir, payload);
489
+ } catch {
490
+ // Non-fatal: lifecycle sync should not block the hook
491
+ }
492
+
476
493
  const deepInterviewStateActive = await isDeepInterviewStateActive(stateDir, getEffectiveSessionId());
494
+ const deepInterviewInputLockActive = await isDeepInterviewInputLockActive(stateDir, getEffectiveSessionId());
477
495
 
478
496
  // 4.55. Notify leader when individual worker transitions to idle (worker session only)
479
497
  if (isTeamWorker && parsedTeamWorker && !deepInterviewStateActive) {
@@ -642,7 +660,7 @@ async function main() {
642
660
 
643
661
  // 9. Auto-nudge: detect Codex stall patterns and automatically send a continuation prompt.
644
662
  // Works for both leader and worker contexts.
645
- if (!deepInterviewStateActive) {
663
+ if (!deepInterviewStateActive || deepInterviewInputLockActive) {
646
664
  try {
647
665
  await maybeAutoNudge({ cwd, stateDir, logsDir, payload });
648
666
  } catch {
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { existsSync } from 'node:fs';
2
3
  import { readFile, writeFile } from 'node:fs/promises';
3
4
 
4
5
  async function read(path: string): Promise<string> { return await readFile(path, 'utf-8'); }
@@ -12,6 +13,7 @@ function replaceBetween(text: string, startMarker: string, endMarker: string, re
12
13
 
13
14
  async function main(): Promise<void> {
14
15
  const op = (await read('docs/prompt-guidance-fragments/core-operating-principles.md')).trim();
16
+ const sr = (await read('docs/prompt-guidance-fragments/leader-specialist-routing.md')).trim();
15
17
  const vs = (await read('docs/prompt-guidance-fragments/core-verification-and-sequencing.md')).trim();
16
18
  const exC = (await read('docs/prompt-guidance-fragments/executor-constraints.md')).trim();
17
19
  const exO = (await read('docs/prompt-guidance-fragments/executor-output.md')).trim();
@@ -22,8 +24,10 @@ async function main(): Promise<void> {
22
24
  const vfI = (await read('docs/prompt-guidance-fragments/verifier-investigation.md')).trim();
23
25
 
24
26
  for (const file of ['AGENTS.md', 'templates/AGENTS.md']) {
27
+ if (!existsSync(file)) continue;
25
28
  let text = await read(file);
26
29
  text = replaceBetween(text, '<!-- OMX:GUIDANCE:OPERATING:START -->', '<!-- OMX:GUIDANCE:OPERATING:END -->', op);
30
+ text = replaceBetween(text, '<!-- OMX:GUIDANCE:SPECIALIST-ROUTING:START -->', '<!-- OMX:GUIDANCE:SPECIALIST-ROUTING:END -->', sr);
27
31
  text = replaceBetween(text, '<!-- OMX:GUIDANCE:VERIFYSEQ:START -->', '<!-- OMX:GUIDANCE:VERIFYSEQ:END -->', vs);
28
32
  await writeFile(file, text);
29
33
  }
@@ -10,6 +10,7 @@ USE CODEX NATIVE SUBAGENTS FOR INDEPENDENT PARALLEL SUBTASKS WHEN THAT IMPROVES
10
10
  You are running with oh-my-codex (OMX), a coordination layer for Codex CLI.
11
11
  This AGENTS.md is the top-level operating contract for the workspace.
12
12
  Role prompts under `prompts/*.md` are narrower execution surfaces. They must follow this file, not override it.
13
+ When OMX is installed, load the installed prompt/skill/agent surfaces from `~/.codex/prompts`, `~/.codex/skills`, and `~/.codex/agents` (or the project-local `./.codex/...` equivalents when project scope is active).
13
14
 
14
15
  <guidance_schema_contract>
15
16
  Canonical guidance schema for this template is defined in `docs/guidance-schema.md`.
@@ -38,6 +39,11 @@ Keep runtime marker contracts stable and non-destructive when overlays are appli
38
39
  <!-- OMX:GUIDANCE:OPERATING:START -->
39
40
  - Default to quality-first, intent-deepening responses; think one more step before replying or asking for clarification, and use as much detail as needed for a strong result without empty verbosity.
40
41
  - Proceed automatically on clear, low-risk, reversible next steps; ask only for irreversible, side-effectful, or materially branching actions.
42
+ - AUTO-CONTINUE for clear, already-requested, low-risk, reversible, local edit-test-verify work; keep inspecting, editing, testing, and verifying without permission handoff.
43
+ - ASK only for destructive, irreversible, credential-gated, external-production, or materially scope-changing actions, or when missing authority blocks progress.
44
+ - On AUTO-CONTINUE branches, do not use permission-handoff phrasing; state the next action or evidence-backed result.
45
+ - Keep going unless blocked; finish the current safe branch before asking for confirmation or handoff.
46
+ - Ask only when blocked by missing information, missing authority, or an irreversible/destructive branch.
41
47
  - Do not ask or instruct humans to perform ordinary non-destructive, reversible actions; execute those safe reversible OMX/runtime operations and ordinary commands yourself.
42
48
  - Treat OMX runtime manipulation, state transitions, and ordinary command execution as agent responsibilities when they are safe and reversible.
43
49
  - Treat newer user task updates as local overrides for the active task while preserving earlier non-conflicting instructions.
@@ -176,12 +182,25 @@ Rules:
176
182
  <model_routing>
177
183
  Match role to task shape:
178
184
  - Low complexity: `explore`, `style-reviewer`, `writer`
185
+ - Research/discovery: `explore` for repo lookup, `researcher` for official docs/reference gathering, `dependency-expert` for SDK/API/package evaluation
179
186
  - Standard: `executor`, `debugger`, `test-engineer`
180
187
  - High complexity: `architect`, `executor`, `critic`
181
188
 
182
189
  For Codex native child agents, model routing defaults to inheritance/current repo defaults unless the caller has a concrete reason to override it.
183
190
  </model_routing>
184
191
 
192
+ <specialist_routing>
193
+ Leader/workflow routing contract:
194
+ <!-- OMX:GUIDANCE:SPECIALIST-ROUTING:START -->
195
+ - Route to `explore` for repo-local file / symbol / pattern / relationship lookup, current implementation discovery, or mapping how this repo currently uses a dependency. `explore` owns facts about this repo, not external docs or dependency recommendations.
196
+ - Route to `researcher` when the main need is official docs, external API behavior, version-aware framework guidance, release-note history, or citation-backed reference gathering. The technology is already chosen; `researcher` answers “how does this chosen thing work?” and is not the default dependency-comparison role.
197
+ - Route to `dependency-expert` when the main need is package / SDK selection or a comparative dependency decision: whether / which package, SDK, or framework to adopt, upgrade, replace, or migrate; candidate comparison; maintenance, license, security, or risk evaluation across options.
198
+ - Use mixed routing deliberately: `explore` -> `researcher` for current local usage plus official-doc confirmation; `explore` -> `dependency-expert` for current dependency usage plus upgrade / replacement / migration evaluation; `researcher` -> `explore` when docs are clear but repo usage or impact still needs confirmation; `dependency-expert` -> `explore` when a dependency decision is clear but the local migration surface still needs mapping.
199
+ - Specialists should report boundary crossings upward instead of silently absorbing adjacent work.
200
+ - When external evidence materially affects the answer, do not keep the leader in the main lane on recall alone; route to the relevant specialist first, then return to planning or execution.
201
+ <!-- OMX:GUIDANCE:SPECIALIST-ROUTING:END -->
202
+ </specialist_routing>
203
+
185
204
  ---
186
205
 
187
206
  <agent_catalog>
@@ -193,50 +212,43 @@ Key roles:
193
212
  - `executor` — implementation and refactoring
194
213
  - `verifier` — completion evidence and validation
195
214
 
215
+ Research/discovery specialists:
216
+ - `explore` — first-stop repository lookup and symbol/file mapping
217
+ - `researcher` — official docs, references, and external fact gathering
218
+ - `dependency-expert` — SDK/API/package evaluation before adopting or changing dependencies
219
+
196
220
  Specialists remain available through the role catalog and native child-agent surfaces when the task clearly benefits from them.
197
221
  </agent_catalog>
198
222
 
199
223
  ---
200
224
 
201
225
  <keyword_detection>
202
- When the user message contains a mapped keyword, activate the corresponding skill immediately.
203
- Do not ask for confirmation.
226
+ Keyword routing is implemented primarily by native `UserPromptSubmit` hooks and the generated keyword registry. Treat hook-injected routing context as authoritative for the current turn, then load the named `SKILL.md` or prompt file as instructed.
204
227
 
205
- Supported workflow triggers include: `ralph`, `autopilot`, `ultrawork`, `ultraqa`, `cleanup`/`refactor`/`deslop`, `analyze`, `plan this`, `deep interview`, `ouroboros`, `ralplan`, `team`/`swarm`, `ecomode`, `cancel`, `tdd`, `fix build`, `code review`, `security review`, and `web-clone`.
206
- The `deep-interview` skill is the Socratic deep interview workflow and includes the ouroboros trigger family.
228
+ Fallback behavior when hook context is unavailable:
229
+ - Explicit `$name` invocations run left-to-right and override implicit keywords.
230
+ - Bare skill names do not activate skills by themselves; skill-name activation requires explicit `$skill` invocation. Natural-language routing phrases may still map to a workflow when they are not just the bare skill name. Examples: `analyze` / `investigate` → `$analyze` for read-only deep analysis with ranked synthesis, explicit confidence, and concrete file references; `deep interview`, `interview`, `don't assume`, or `ouroboros` → `$deep-interview`; `ralplan` / `consensus plan` → `$ralplan`; `cancel`, `stop`, or `abort` → `$cancel`.
231
+ - Keep the detailed keyword list in `src/hooks/keyword-registry.ts`; do not duplicate that table here.
207
232
 
208
- | Keyword(s) | Skill | Action |
209
- |-------------|-------|--------|
210
233
  Runtime availability gate:
211
234
  - Treat `autopilot`, `ralph`, `ultrawork`, `ultraqa`, `team`/`swarm`, and `ecomode` as **OMX runtime workflows**, not generic prompt aliases.
212
- - Auto-activate those runtime workflows only when the current session is actually running under OMX CLI/runtime (for example, launched via `omx`, with OMX session overlay/runtime state available, or when the user explicitly asks to run `omx ...` in the shell).
235
+ - Auto-activate runtime workflows only when the current session is actually running under OMX CLI/runtime (for example, launched via `omx`, with OMX session overlay/runtime state available, or when the user explicitly asks to run `omx ...` in the shell).
213
236
  - In Codex App or plain Codex sessions without OMX runtime, do **not** treat those keywords alone as activation. Explain that they require OMX CLI runtime support, and continue with the nearest App-safe surface (`deep-interview`, `ralplan`, `plan`, or native subagents) unless the user explicitly wants you to launch OMX from the shell.
237
+ - When deep-interview is active in OMX CLI/runtime, ask interview rounds via `omx question`; do not substitute `request_user_input` or ad hoc plain-text questioning, and respect Stop-hook blocking while a deep-interview question obligation is pending.
238
+
239
+ <triage_routing>
240
+ ## Triage: advisory prompt-routing context
241
+
242
+ The keyword detector is the first and deterministic routing surface. Triage runs only when no keyword matches.
214
243
 
215
- | Keyword(s) | Skill | Action |
216
- |-------------|-------|--------|
217
- | "ralph", "don't stop", "must complete", "keep going" | `$ralph` | Runtime-only: read `~/.codex/skills/ralph/SKILL.md`, execute persistence loop only inside OMX CLI/runtime |
218
- | "autopilot", "build me", "I want a" | `$autopilot` | Runtime-only: read `~/.codex/skills/autopilot/SKILL.md`, execute autonomous pipeline only inside OMX CLI/runtime |
219
- | "ultrawork", "ulw", "parallel" | `$ultrawork` | Runtime-only: read `~/.codex/skills/ultrawork/SKILL.md`, execute parallel agents only inside OMX CLI/runtime |
220
- | "ultraqa" | `$ultraqa` | Runtime-only: read `~/.codex/skills/ralph/SKILL.md`, run persistent completion and verification loop only inside OMX CLI/runtime (UltraQA compatibility alias) |
221
- | "analyze", "investigate" | `$analyze` | Read `~/.codex/skills/analyze/SKILL.md`, run read-only deep analysis with ranked synthesis, explicit confidence, and concrete file references |
222
- | "plan this", "plan the", "let's plan" | `$plan` | Read `~/.codex/skills/plan/SKILL.md`, start planning workflow |
223
- | "interview", "deep interview", "gather requirements", "interview me", "don't assume", "ouroboros" | `$deep-interview` | Read `~/.codex/skills/deep-interview/SKILL.md`, run Ouroboros-inspired Socratic ambiguity-gated interview workflow |
224
- | "ralplan", "consensus plan" | `$ralplan` | Read `~/.codex/skills/ralplan/SKILL.md`, start consensus planning with RALPLAN-DR structured deliberation (short by default, `--deliberate` for high-risk) |
225
- | "team", "swarm", "coordinated team", "coordinated swarm" | `$team` | Runtime-only: read `~/.codex/skills/team/SKILL.md`, start tmux-based team orchestration only inside OMX CLI/runtime (swarm compatibility alias) |
226
- | "ecomode", "eco", "budget" | `$ecomode` | Runtime-only: read `~/.codex/skills/ultrawork/SKILL.md`, execute cost-aware parallel workflow only inside OMX CLI/runtime (ecomode compatibility alias) |
227
- | "cancel", "stop", "abort" | `$cancel` | Read `~/.codex/skills/cancel/SKILL.md`, cancel active modes |
228
- | "tdd", "test first" | `$tdd` | Read `~/.codex/prompts/test-engineer.md`, run test-first workflow (tdd compatibility alias) |
229
- | "fix build", "type errors" | `$build-fix` | Read `~/.codex/prompts/build-fixer.md`, fix build errors with minimal diff (build-fix compatibility alias) |
230
- | "review code", "code review", "code-review" | `$code-review` | Read `~/.codex/skills/code-review/SKILL.md`, run code review |
231
- | "security review" | `$security-review` | Read `~/.codex/skills/security-review/SKILL.md`, run security audit |
232
- | "web-clone", "clone site", "clone website", "copy webpage" | `$web-clone` | Read `~/.codex/skills/web-clone/SKILL.md`, start website cloning pipeline |
233
-
234
- Detection rules:
235
- - Keywords are case-insensitive and match anywhere in the user message.
236
- - Explicit `$name` invocations run left-to-right and override non-explicit keyword resolution.
237
- - If multiple non-explicit keywords match, use the most specific match.
238
- - Runtime-only keywords must pass the runtime availability gate before activation.
239
- - The rest of the user message becomes the task description.
244
+ When active, triage emits **advisory prompt-routing context** — a developer-context string that the model may follow. It does not activate a skill or workflow by itself. It is a best-effort hint, not a guarantee.
245
+
246
+ Note: `explore`, `executor`, and `designer` are agent role-prompt files under `prompts/`, not workflow skills.
247
+
248
+ Explicit keywords remain the deterministic control surface when you want explicit, guaranteed routing use them whenever exact behavior matters.
249
+
250
+ To opt out per prompt with phrases such as `no workflow`, `just chat`, or `plain answer` the triage layer will suppress context injection for that prompt.
251
+ </triage_routing>
240
252
 
241
253
  Ralph / Ralplan execution gate:
242
254
  - Enforce **ralplan-first** when ralph is active and planning is not complete.
@@ -374,6 +386,8 @@ Do not cancel while recoverable work remains.
374
386
  ---
375
387
 
376
388
  <state_management>
389
+ Hooks own normal skill-active and workflow-state persistence under `.omx/state/`.
390
+
377
391
  OMX persists runtime state under `.omx/`:
378
392
  - `.omx/state/` — mode state
379
393
  - `.omx/notepad.md` — session notes
@@ -383,11 +397,8 @@ OMX persists runtime state under `.omx/`:
383
397
 
384
398
  Available MCP groups include state/memory tools, code-intel tools, and trace tools.
385
399
 
386
- Mode lifecycle requirements:
387
- - Write state on start.
388
- - Update state on phase or iteration change.
389
- - Mark inactive with `completed_at` on completion.
390
- - Clear state on cancel/abort cleanup.
400
+ Agents may use OMX state/MCP tools for explicit lifecycle transitions, recovery, checkpointing, cancellation cleanup, or compaction resilience.
401
+ Do not manually duplicate hook-owned activation state unless recovering from missing or stale state.
391
402
  </state_management>
392
403
 
393
404
  ---
@@ -45,6 +45,13 @@
45
45
  "core": false,
46
46
  "internalRequired": false
47
47
  },
48
+ {
49
+ "name": "autoresearch",
50
+ "category": "execution",
51
+ "status": "active",
52
+ "core": false,
53
+ "internalRequired": false
54
+ },
48
55
  {
49
56
  "name": "swarm",
50
57
  "category": "execution",