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
@@ -44,6 +44,18 @@ import type { HookEventEnvelope } from "../hooks/extensibility/types.js";
44
44
  import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
45
45
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
46
46
  import { onSessionStart as buildWikiSessionStartContext } from "../wiki/lifecycle.js";
47
+ import { readAutoresearchCompletionStatus, readAutoresearchModeState } from "../autoresearch/skill-validation.js";
48
+ import { shouldContinueRun } from "../runtime/run-loop.js";
49
+ import { triagePrompt } from "../hooks/triage-heuristic.js";
50
+ import { readTriageConfig } from "../hooks/triage-config.js";
51
+ import {
52
+ readTriageState,
53
+ writeTriageState,
54
+ shouldSuppressFollowup,
55
+ promptSignature,
56
+ type TriageStateFile,
57
+ } from "../hooks/triage-state.js";
58
+ import { isPendingDeepInterviewQuestionEnforcement } from "../question/deep-interview.js";
47
59
 
48
60
  type CodexHookEventName =
49
61
  | "SessionStart"
@@ -57,6 +69,7 @@ type CodexHookPayload = Record<string, unknown>;
57
69
  interface NativeHookDispatchOptions {
58
70
  cwd?: string;
59
71
  sessionOwnerPid?: number;
72
+ reconcileHudForPromptSubmitFn?: typeof reconcileHudForPromptSubmit;
60
73
  }
61
74
 
62
75
  export interface NativeHookDispatchResult {
@@ -66,7 +79,6 @@ export interface NativeHookDispatchResult {
66
79
  outputJson: Record<string, unknown> | null;
67
80
  }
68
81
 
69
- const TERMINAL_RALPH_PHASES = new Set(["complete", "failed", "cancelled"]);
70
82
  const TERMINAL_MODE_PHASES = new Set(["complete", "failed", "cancelled"]);
71
83
  const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
72
84
  const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
@@ -79,6 +91,18 @@ const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
79
91
  ] as const;
80
92
  const RELEASE_READINESS_FINALIZE_SYSTEM_MESSAGE =
81
93
  "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.";
94
+ const EXECUTION_HANDOFF_PATTERNS = [
95
+ /^(?:好|好的|行|可以|那就|那现在)?[,,\s]*(?:开始|继续|直接)\s*(?:执行|优化|实现|修改|修复)(?=$|\s|[,,。.!!??])/u,
96
+ /(?:按照|按|基于)(?:这个|上述|当前)?\s*(?:plan|计划|方案).{0,16}(?:开始|继续|直接)?\s*(?:执行|优化|实现|修改|修复)/u,
97
+ /(?:不用|别|不要).{0,6}讨论/u,
98
+ /\b(?:start|begin|go ahead(?: and)?|proceed(?: now)?)\s+(?:to\s+)?(?:implement|execute|apply|fix)\b/i,
99
+ /\b(?:according to|based on)\s+(?:the|this|that)\s+plan\b.{0,20}\b(?:start|begin|proceed(?: now)?|go ahead(?: and)?)\b/i,
100
+ ] as const;
101
+ const SHORT_FOLLOWUP_PRIORITY_PATTERNS = [
102
+ /^(?:继续|接着|然后|那就|那现在|还有(?:一个)?问题|这些优化都做了么|这些都做了么|现在呢|本轮|当前轮|这一轮)/u,
103
+ /(?:按照|按|基于)(?:这个|上述|当前)?(?:plan|计划|方案)/u,
104
+ /\b(?:follow up|latest request|this turn|current turn|newest request)\b/i,
105
+ ] as const;
82
106
 
83
107
  function safeString(value: unknown): string {
84
108
  return typeof value === "string" ? value : "";
@@ -97,6 +121,34 @@ function safePositiveInteger(value: unknown): number | null {
97
121
  return null;
98
122
  }
99
123
 
124
+ function normalizePromptSignalText(text: string): string {
125
+ return text.trim().replace(/\s+/g, " ");
126
+ }
127
+
128
+ function looksLikeExecutionHandoffPrompt(prompt: string): boolean {
129
+ const normalized = normalizePromptSignalText(prompt);
130
+ if (!normalized) return false;
131
+ return EXECUTION_HANDOFF_PATTERNS.some((pattern) => pattern.test(normalized));
132
+ }
133
+
134
+ function looksLikeShortFollowupPrompt(prompt: string): boolean {
135
+ const normalized = normalizePromptSignalText(prompt);
136
+ if (!normalized) return false;
137
+ if (looksLikeExecutionHandoffPrompt(normalized)) return true;
138
+ if (normalized.length > 240) return false;
139
+ return SHORT_FOLLOWUP_PRIORITY_PATTERNS.some((pattern) => pattern.test(normalized));
140
+ }
141
+
142
+ function buildPromptPriorityMessage(prompt: string): string | null {
143
+ if (looksLikeExecutionHandoffPrompt(prompt)) {
144
+ return "Newest user input is an execution handoff for the current task. Treat it as authorization to act now against the latest approved plan/request. Do not restate the prior plan unless the user explicitly asks for a recap or status update.";
145
+ }
146
+ if (looksLikeShortFollowupPrompt(prompt)) {
147
+ return "Newest user input is a same-thread follow-up. Answer that latest follow-up directly and prefer it over older unresolved prompts when choosing what to do next.";
148
+ }
149
+ return null;
150
+ }
151
+
100
152
  function readHookEventName(payload: CodexHookPayload): CodexHookEventName | null {
101
153
  const raw = safeString(
102
154
  payload.hook_event_name
@@ -196,6 +248,18 @@ function formatPhase(value: unknown, fallback = "active"): string {
196
248
  return phase || fallback;
197
249
  }
198
250
 
251
+ async function readActiveAutoresearchState(
252
+ cwd: string,
253
+ sessionId?: string,
254
+ ): Promise<Record<string, unknown> | null> {
255
+ const normalizedSessionId = sessionId?.trim() || undefined;
256
+ if (!normalizedSessionId) return null;
257
+ const state = await readAutoresearchModeState(cwd, normalizedSessionId);
258
+ if (state?.active !== true) return null;
259
+ if (!isNonTerminalPhase(state.current_phase ?? state.currentPhase ?? 'executing')) return null;
260
+ return state;
261
+ }
262
+
199
263
  async function readActiveRalphState(
200
264
  stateDir: string,
201
265
  preferredSessionId?: string,
@@ -211,12 +275,7 @@ async function readActiveRalphState(
211
275
  const sessionScoped = await readJsonIfExists(
212
276
  join(stateDir, "sessions", sessionId, "ralph-state.json"),
213
277
  );
214
- if (
215
- sessionScoped?.active === true
216
- && !TERMINAL_RALPH_PHASES.has(
217
- safeString(sessionScoped.current_phase).trim().toLowerCase(),
218
- )
219
- ) {
278
+ if (sessionScoped?.active === true && shouldContinueRun(sessionScoped)) {
220
279
  return sessionScoped;
221
280
  }
222
281
  }
@@ -224,7 +283,7 @@ async function readActiveRalphState(
224
283
  if (sessionCandidates.length > 0) return null;
225
284
 
226
285
  const direct = await readJsonIfExists(join(stateDir, "ralph-state.json"));
227
- if (direct?.active === true && !TERMINAL_RALPH_PHASES.has(safeString(direct.current_phase).trim().toLowerCase())) {
286
+ if (direct?.active === true && shouldContinueRun(direct)) {
228
287
  return direct;
229
288
  }
230
289
 
@@ -234,12 +293,7 @@ async function readActiveRalphState(
234
293
  for (const entry of entries) {
235
294
  if (!entry.isDirectory()) continue;
236
295
  const candidate = await readJsonIfExists(join(sessionsRoot, entry.name, "ralph-state.json"));
237
- if (
238
- candidate?.active === true
239
- && !TERMINAL_RALPH_PHASES.has(
240
- safeString(candidate.current_phase).trim().toLowerCase(),
241
- )
242
- ) {
296
+ if (candidate?.active === true && shouldContinueRun(candidate)) {
243
297
  return candidate;
244
298
  }
245
299
  }
@@ -471,9 +525,10 @@ async function buildSessionStartContext(
471
525
 
472
526
  function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveState | null): string | null {
473
527
  if (!prompt) return null;
528
+ const promptPriorityMessage = buildPromptPriorityMessage(prompt);
474
529
  const matches = detectKeywords(prompt);
475
530
  const match = detectPrimaryKeyword(prompt);
476
- if (!match) return null;
531
+ if (!match) return promptPriorityMessage;
477
532
  const detectedKeywordMessage = matches.length > 1
478
533
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
479
534
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
@@ -487,6 +542,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
487
542
  const ralphPromptActivationNote = skillState?.initialized_mode === "ralph"
488
543
  ? "Prompt-side `$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`. Use `omx ralph --prd ...` only when you explicitly want the PRD-gated CLI startup path."
489
544
  : null;
545
+ const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
546
+ ? "Deep-interview must ask each interview round via `omx question`; do not fall back to `request_user_input` or plain-text questioning. Stop remains blocked while a deep-interview question obligation is pending."
547
+ : null;
490
548
  const combinedTransitionMessage = (() => {
491
549
  if (!skillState?.transition_message) return null;
492
550
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -499,6 +557,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
499
557
  return [
500
558
  `OMX native UserPromptSubmit denied workflow keyword "${match.keyword}" -> ${match.skill}.`,
501
559
  skillState.transition_error,
560
+ promptPriorityMessage,
502
561
  'Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.',
503
562
  ].join(' ');
504
563
  }
@@ -511,6 +570,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
511
570
  deferredSkills.length > 0
512
571
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
513
572
  : null,
573
+ promptPriorityMessage,
514
574
  skillState.initialized_mode && skillState.initialized_state_path
515
575
  ? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
516
576
  : null,
@@ -532,7 +592,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
532
592
  deferredSkills.length > 0
533
593
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
534
594
  : null,
595
+ promptPriorityMessage,
535
596
  initializedStateMessage,
597
+ deepInterviewPromptActivationNote,
536
598
  "Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout.",
537
599
  "If you need runtime syntax, run `omx team --help` yourself.",
538
600
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
@@ -546,13 +608,15 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
546
608
  deferredSkills.length > 0
547
609
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
548
610
  : null,
611
+ promptPriorityMessage,
549
612
  `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
613
+ deepInterviewPromptActivationNote,
550
614
  ralphPromptActivationNote,
551
615
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
552
616
  ].join(" ");
553
617
  }
554
618
 
555
- return `${detectedKeywordMessage} Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.`;
619
+ return [detectedKeywordMessage, promptPriorityMessage, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
556
620
  }
557
621
 
558
622
  function parseTeamWorkerEnv(rawValue: string): { teamName: string; workerName: string } | null {
@@ -678,7 +742,7 @@ async function buildModeBasedStopOutput(
678
742
  const state = sessionId
679
743
  ? await readModeStateForSession(mode, sessionId, cwd)
680
744
  : await readModeState(mode, cwd);
681
- if (state?.active !== true || !isNonTerminalPhase(state.current_phase)) return null;
745
+ if (!state || !shouldContinueRun(state)) return null;
682
746
  const phase = formatPhase(state.current_phase);
683
747
  return {
684
748
  decision: "block",
@@ -927,6 +991,51 @@ async function readStopAutoNudgePhase(
927
991
  return modePhase === "intent-first" ? "planning" : "";
928
992
  }
929
993
 
994
+ async function buildDeepInterviewQuestionStopOutput(
995
+ cwd: string,
996
+ sessionId: string,
997
+ threadId: string,
998
+ ): Promise<{ output: Record<string, unknown>; obligationId: string } | null> {
999
+ const modeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
1000
+ if (!modeState || modeState.active !== true) return null;
1001
+
1002
+ const phase = formatPhase(modeState.current_phase, "planning");
1003
+ if (TERMINAL_MODE_PHASES.has(phase.toLowerCase()) || phase === "completing") {
1004
+ return null;
1005
+ }
1006
+
1007
+ const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
1008
+ if (canonicalState) {
1009
+ const blocker = listActiveSkills(canonicalState).find((entry) => (
1010
+ entry.skill === "deep-interview"
1011
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
1012
+ ));
1013
+ if (!blocker) return null;
1014
+ }
1015
+
1016
+ const questionEnforcement = safeObject(modeState.question_enforcement);
1017
+ if (!isPendingDeepInterviewQuestionEnforcement(questionEnforcement)) {
1018
+ return null;
1019
+ }
1020
+
1021
+ const obligationId = safeString(questionEnforcement.obligation_id).trim();
1022
+ if (!obligationId) return null;
1023
+
1024
+ const systemMessage =
1025
+ `OMX deep-interview is still active (phase: ${phase}) and requires a structured question via omx question before stopping.`;
1026
+
1027
+ return {
1028
+ obligationId,
1029
+ output: {
1030
+ decision: "block",
1031
+ reason:
1032
+ `Deep interview is still active (phase: ${phase}) and has a pending structured question obligation; use \`omx question\` before stopping.`,
1033
+ stopReason: "deep_interview_question_required",
1034
+ systemMessage,
1035
+ },
1036
+ };
1037
+ }
1038
+
930
1039
  function resolveRepeatableStopSessionId(
931
1040
  payload: CodexHookPayload,
932
1041
  canonicalSessionId?: string,
@@ -1283,6 +1392,28 @@ async function buildStopHookOutput(
1283
1392
  const threadId = readPayloadThreadId(payload);
1284
1393
  const ralphState = await readActiveRalphState(stateDir, canonicalSessionId);
1285
1394
  if (!ralphState) {
1395
+ const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
1396
+ if (autoresearchState) {
1397
+ const completion = await readAutoresearchCompletionStatus(cwd, canonicalSessionId!.trim());
1398
+ if (!completion.complete) {
1399
+ const currentPhase = safeString(autoresearchState.current_phase ?? autoresearchState.currentPhase).trim() || 'executing';
1400
+ const systemMessage = `OMX autoresearch is still active (phase: ${currentPhase}); continue until validator evidence is complete before stopping.`;
1401
+ return await maybeReturnRepeatableStopOutput(
1402
+ payload,
1403
+ stateDir,
1404
+ buildRepeatableStopSignature(payload, 'autoresearch-stop', `${currentPhase}|${completion.reason}`, canonicalSessionId),
1405
+ {
1406
+ decision: 'block',
1407
+ reason: systemMessage,
1408
+ stopReason: `autoresearch_${currentPhase}`,
1409
+ systemMessage,
1410
+ },
1411
+ canonicalSessionId,
1412
+ { allowRepeatDuringStopHook: true },
1413
+ );
1414
+ }
1415
+ }
1416
+
1286
1417
  const teamWorkerOutput = await buildTeamWorkerStopOutput(cwd);
1287
1418
  if (hasTeamWorkerContext() && teamWorkerOutput) return teamWorkerOutput;
1288
1419
 
@@ -1343,6 +1474,22 @@ async function buildStopHookOutput(
1343
1474
  }
1344
1475
 
1345
1476
  if (canonicalSessionId) {
1477
+ const deepInterviewQuestionOutput = await buildDeepInterviewQuestionStopOutput(
1478
+ cwd,
1479
+ canonicalSessionId,
1480
+ threadId,
1481
+ );
1482
+ if (deepInterviewQuestionOutput) {
1483
+ return await returnPersistentStopBlock(
1484
+ payload,
1485
+ stateDir,
1486
+ "deep-interview-question-stop",
1487
+ deepInterviewQuestionOutput.obligationId,
1488
+ deepInterviewQuestionOutput.output,
1489
+ canonicalSessionId,
1490
+ );
1491
+ }
1492
+
1346
1493
  const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
1347
1494
  if (canonicalTeam) {
1348
1495
  const canonicalTeamOutput = buildTeamStopOutputForPhase(
@@ -1435,6 +1582,7 @@ export async function dispatchCodexNativeHook(
1435
1582
 
1436
1583
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
1437
1584
  let skillState: SkillActiveState | null = null;
1585
+ let triageAdditionalContext: string | null = null;
1438
1586
 
1439
1587
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
1440
1588
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
@@ -1464,7 +1612,75 @@ export async function dispatchCodexNativeHook(
1464
1612
  turnId,
1465
1613
  });
1466
1614
  }
1467
- await reconcileHudForPromptSubmit(cwd).catch(() => {});
1615
+ // --- Triage classifier (advisory-only, non-keyword prompts) ---
1616
+ if (prompt && skillState === null) {
1617
+ try {
1618
+ if (readTriageConfig().enabled) {
1619
+ const normalized = prompt.trim().toLowerCase();
1620
+ const previous = readTriageState({ cwd, sessionId: sessionIdForState || null });
1621
+ const suppress = shouldSuppressFollowup({
1622
+ previous,
1623
+ currentPrompt: normalized,
1624
+ currentHasKeyword: false,
1625
+ });
1626
+ if (!suppress) {
1627
+ const decision = triagePrompt(prompt);
1628
+ const nowIso = new Date().toISOString();
1629
+ const effectiveTurnId = turnId || nowIso;
1630
+ if (decision.lane === "HEAVY") {
1631
+ triageAdditionalContext =
1632
+ "OMX native UserPromptSubmit triage detected a multi-step goal with no workflow keyword. This is advisory prompt-routing context only; it did not activate autopilot or initialize workflow state. Prefer the existing autopilot-style workflow if AGENTS.md/runtime conditions allow it, unless newer user context narrows or opts out.";
1633
+ const newState: TriageStateFile = {
1634
+ version: 1,
1635
+ last_triage: {
1636
+ lane: "HEAVY",
1637
+ destination: "autopilot",
1638
+ reason: decision.reason,
1639
+ prompt_signature: promptSignature(normalized),
1640
+ turn_id: effectiveTurnId,
1641
+ created_at: nowIso,
1642
+ },
1643
+ suppress_followup: true,
1644
+ };
1645
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
1646
+ } else if (decision.lane === "LIGHT") {
1647
+ if (decision.destination === "explore") {
1648
+ triageAdditionalContext =
1649
+ "OMX native UserPromptSubmit triage detected a read-only/question-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the explore role surface rather than escalating to autopilot.";
1650
+ } else if (decision.destination === "executor") {
1651
+ triageAdditionalContext =
1652
+ "OMX native UserPromptSubmit triage detected a narrow edit-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the executor role surface rather than autopilot.";
1653
+ } else if (decision.destination === "designer") {
1654
+ triageAdditionalContext =
1655
+ "OMX native UserPromptSubmit triage detected a visual/style request with no workflow keyword. This is advisory prompt-routing context only. Prefer the designer role surface.";
1656
+ }
1657
+ if (triageAdditionalContext !== null) {
1658
+ const dest = decision.destination as "explore" | "executor" | "designer";
1659
+ const newState: TriageStateFile = {
1660
+ version: 1,
1661
+ last_triage: {
1662
+ lane: "LIGHT",
1663
+ destination: dest,
1664
+ reason: decision.reason,
1665
+ prompt_signature: promptSignature(normalized),
1666
+ turn_id: effectiveTurnId,
1667
+ created_at: nowIso,
1668
+ },
1669
+ suppress_followup: true,
1670
+ };
1671
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
1672
+ }
1673
+ }
1674
+ // lane === "PASS": no context, no state write
1675
+ }
1676
+ }
1677
+ } catch {
1678
+ // Swallow all triage errors; never break the hook
1679
+ triageAdditionalContext = null;
1680
+ }
1681
+ }
1682
+ const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
1683
+ await reconcileHudForPromptSubmitFn(cwd, { sessionId: canonicalSessionId || sessionIdForState || undefined }).catch(() => {});
1468
1684
  }
1469
1685
 
1470
1686
  if (omxEventName) {
@@ -1493,7 +1709,7 @@ export async function dispatchCodexNativeHook(
1493
1709
  if (hookEventName === "SessionStart" || hookEventName === "UserPromptSubmit") {
1494
1710
  const additionalContext = hookEventName === "SessionStart"
1495
1711
  ? await buildSessionStartContext(cwd, canonicalSessionId || nativeSessionId)
1496
- : buildAdditionalContextMessage(readPromptText(payload), skillState);
1712
+ : (buildAdditionalContextMessage(readPromptText(payload), skillState) ?? triageAdditionalContext);
1497
1713
  if (additionalContext) {
1498
1714
  outputJson = {
1499
1715
  hookSpecificOutput: {
@@ -25,6 +25,7 @@ import {
25
25
  maybeNudgeTeamLeader,
26
26
  resolveLeaderStalenessThresholdMs,
27
27
  } from './notify-hook/team-leader-nudge.js';
28
+ import { resolveManagedPaneFromAnchor, resolveManagedSessionPane } from './notify-hook/managed-tmux.js';
28
29
  import { DEFAULT_MARKER } from './tmux-hook-engine.js';
29
30
  import { isTerminalPhase } from './notify-hook/utils.js';
30
31
  import { isSessionStale, isSessionStateAuthoritativeForCwd, readSessionState } from '../hooks/session.js';
@@ -36,6 +37,7 @@ import { listNotifyCanonicalActiveTeams } from './notify-hook/active-team.js';
36
37
  import { sameFilePath } from '../utils/paths.js';
37
38
  import { validateSessionId } from '../mcp/state-paths.js';
38
39
  import { TEAM_NAME_SAFE_PATTERN } from '../team/contracts.js';
40
+ import { shouldContinueRun } from '../runtime/run-loop.js';
39
41
 
40
42
  function argValue(name: string, fallback = ''): string {
41
43
  const idx = process.argv.indexOf(name);
@@ -145,7 +147,7 @@ const watcherOwnerToken = `${process.pid}-${startedAt}-${Math.random().toString(
145
147
  const RALPH_CONTINUE_TEXT = 'Ralph loop active continue';
146
148
  const RALPH_CONTINUE_CADENCE_MS = 60_000;
147
149
  const RALPH_STEER_LOCK_STALE_MS = 30_000;
148
- const RALPH_TERMINAL_PHASES = new Set(['complete', 'failed', 'cancelled']);
150
+ const RALPH_TERMINAL_PHASES = new Set(['blocked_on_user', 'complete', 'failed', 'cancelled']);
149
151
  const RALPH_STARTING_PHASE_TIMEOUT_MS = RALPH_CONTINUE_CADENCE_MS * 2;
150
152
  const QUIET_ONCE_EVENT_TYPES = new Set(['watcher_start', 'watcher_once_complete']);
151
153
 
@@ -474,6 +476,7 @@ function normalizeRalphContinueSteerState(raw: Record<string, unknown> | null |
474
476
  function hasRalphTerminalState(raw: Record<string, unknown> | null | undefined): boolean {
475
477
  if (!raw || typeof raw !== 'object') return true;
476
478
  if (raw.active !== true) return true;
479
+ if (!shouldContinueRun(raw)) return true;
477
480
  const phase = safeString(raw.current_phase).trim().toLowerCase();
478
481
  if (phase && RALPH_TERMINAL_PHASES.has(phase)) return true;
479
482
  if (isStaleRalphStartingPhase(raw)) return true;
@@ -1029,6 +1032,90 @@ async function writePidFileRecord(): Promise<void> {
1029
1032
  await writeFile(pidFilePath, JSON.stringify(nextRecord, null, 2)).catch(() => {});
1030
1033
  }
1031
1034
 
1035
+ async function buildWatcherManagedPayload(): Promise<Record<string, string> | null> {
1036
+ const session = await readSessionState(cwd).catch(() => null);
1037
+ const sessionId = safeString(session?.session_id).trim();
1038
+ if (!sessionId || !session || isSessionStale(session)) return null;
1039
+ return { session_id: sessionId };
1040
+ }
1041
+
1042
+ async function persistReboundRalphPaneState(
1043
+ statePath: string,
1044
+ state: Record<string, unknown> | null,
1045
+ paneId: string,
1046
+ nowIso: string,
1047
+ ): Promise<Record<string, unknown>> {
1048
+ const latestState = await readFile(statePath, 'utf-8')
1049
+ .then((content) => JSON.parse(content) as Record<string, unknown>)
1050
+ .catch(() => null);
1051
+ const nextState = {
1052
+ ...((latestState && typeof latestState === 'object') ? latestState : (state || {})),
1053
+ tmux_pane_id: paneId,
1054
+ tmux_pane_set_at: nowIso,
1055
+ };
1056
+ const tmpPath = `${statePath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
1057
+ await writeFile(tmpPath, JSON.stringify(nextState, null, 2));
1058
+ try {
1059
+ await rename(tmpPath, statePath);
1060
+ } catch (error) {
1061
+ await unlink(tmpPath).catch(() => {});
1062
+ throw error;
1063
+ }
1064
+ return nextState;
1065
+ }
1066
+
1067
+ async function resolveRalphContinuePaneTarget(
1068
+ activeRalph: ActiveModeResult,
1069
+ nowIso: string,
1070
+ ): Promise<{ paneId: string; state: Record<string, unknown> | null; reboundFrom: string }> {
1071
+ const currentState = activeRalph.state && typeof activeRalph.state === 'object'
1072
+ ? activeRalph.state as Record<string, unknown>
1073
+ : null;
1074
+ const anchorPaneId = safeString(currentState?.tmux_pane_id).trim();
1075
+ if (!anchorPaneId) {
1076
+ return {
1077
+ paneId: '',
1078
+ state: currentState,
1079
+ reboundFrom: '',
1080
+ };
1081
+ }
1082
+
1083
+ const managedPayload = await buildWatcherManagedPayload();
1084
+ if (!managedPayload) {
1085
+ return {
1086
+ paneId: anchorPaneId,
1087
+ state: currentState,
1088
+ reboundFrom: '',
1089
+ };
1090
+ }
1091
+
1092
+ let resolvedPaneId = await resolveManagedPaneFromAnchor(anchorPaneId, cwd, managedPayload, { allowTeamWorker: false });
1093
+ if (!resolvedPaneId) {
1094
+ resolvedPaneId = await resolveManagedSessionPane(cwd, managedPayload);
1095
+ }
1096
+ if (!resolvedPaneId) {
1097
+ return {
1098
+ paneId: '',
1099
+ state: currentState,
1100
+ reboundFrom: '',
1101
+ };
1102
+ }
1103
+ if (resolvedPaneId === anchorPaneId) {
1104
+ return {
1105
+ paneId: resolvedPaneId,
1106
+ state: currentState,
1107
+ reboundFrom: '',
1108
+ };
1109
+ }
1110
+
1111
+ const updatedState = await persistReboundRalphPaneState(activeRalph.path, currentState, resolvedPaneId, nowIso);
1112
+ return {
1113
+ paneId: resolvedPaneId,
1114
+ state: updatedState,
1115
+ reboundFrom: anchorPaneId,
1116
+ };
1117
+ }
1118
+
1032
1119
  async function runRalphContinueSteerTick(): Promise<void> {
1033
1120
  const now = Date.now();
1034
1121
  const nowIso = new Date(now).toISOString();
@@ -1088,7 +1175,9 @@ async function runRalphContinueSteerTick(): Promise<void> {
1088
1175
  return { sent: false, skipped: true };
1089
1176
  }
1090
1177
 
1091
- const paneId = safeString(activeRalph.state?.tmux_pane_id).trim();
1178
+ const resolvedPane = await resolveRalphContinuePaneTarget(activeRalph, nowIso);
1179
+ activeRalph.state = resolvedPane.state;
1180
+ const paneId = resolvedPane.paneId;
1092
1181
  if (!paneId) {
1093
1182
  lastRalphContinueSteer.last_reason = 'pane_missing';
1094
1183
  lastRalphContinueSteer.pane_id = '';
@@ -1113,6 +1202,7 @@ async function runRalphContinueSteerTick(): Promise<void> {
1113
1202
  type: 'ralph_continue_steer',
1114
1203
  reason: 'sent',
1115
1204
  pane_id: paneId,
1205
+ rebound_from: resolvedPane.reboundFrom || null,
1116
1206
  state_path: activeRalph.path,
1117
1207
  current_phase: safeString(activeRalph.state?.current_phase) || null,
1118
1208
  cadence_ms: RALPH_CONTINUE_CADENCE_MS,