oh-my-codex 0.10.2 → 0.10.3

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 (260) hide show
  1. package/README.de.md +4 -4
  2. package/README.es.md +4 -4
  3. package/README.fr.md +4 -4
  4. package/README.it.md +4 -4
  5. package/README.ja.md +4 -4
  6. package/README.ko.md +4 -4
  7. package/README.md +13 -7
  8. package/README.pt.md +4 -4
  9. package/README.ru.md +4 -4
  10. package/README.tr.md +4 -4
  11. package/README.vi.md +4 -4
  12. package/README.zh-TW.md +4 -4
  13. package/README.zh.md +4 -4
  14. package/dist/agents/__tests__/native-config.test.js +37 -33
  15. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  16. package/dist/agents/__tests__/skill-bridge.test.d.ts +2 -0
  17. package/dist/agents/__tests__/skill-bridge.test.d.ts.map +1 -0
  18. package/dist/agents/__tests__/skill-bridge.test.js +71 -0
  19. package/dist/agents/__tests__/skill-bridge.test.js.map +1 -0
  20. package/dist/agents/native-config.d.ts +18 -6
  21. package/dist/agents/native-config.d.ts.map +1 -1
  22. package/dist/agents/native-config.js +109 -92
  23. package/dist/agents/native-config.js.map +1 -1
  24. package/dist/agents/skill-bridge.d.ts +20 -0
  25. package/dist/agents/skill-bridge.d.ts.map +1 -0
  26. package/dist/agents/skill-bridge.js +150 -0
  27. package/dist/agents/skill-bridge.js.map +1 -0
  28. package/dist/autoresearch/__tests__/contracts.test.js +37 -1
  29. package/dist/autoresearch/__tests__/contracts.test.js.map +1 -1
  30. package/dist/autoresearch/__tests__/runtime-parity-extra.test.js +10 -10
  31. package/dist/autoresearch/__tests__/runtime-parity-extra.test.js.map +1 -1
  32. package/dist/autoresearch/__tests__/runtime.test.js +2 -2
  33. package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
  34. package/dist/autoresearch/contracts.d.ts.map +1 -1
  35. package/dist/autoresearch/contracts.js +17 -10
  36. package/dist/autoresearch/contracts.js.map +1 -1
  37. package/dist/autoresearch/runtime.d.ts.map +1 -1
  38. package/dist/autoresearch/runtime.js +71 -96
  39. package/dist/autoresearch/runtime.js.map +1 -1
  40. package/dist/cli/__tests__/agents-init.test.js +2 -0
  41. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  42. package/dist/cli/__tests__/agents.test.d.ts +2 -0
  43. package/dist/cli/__tests__/agents.test.d.ts.map +1 -0
  44. package/dist/cli/__tests__/agents.test.js +114 -0
  45. package/dist/cli/__tests__/agents.test.js.map +1 -0
  46. package/dist/cli/__tests__/autoresearch-guided.test.js +156 -1
  47. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  48. package/dist/cli/__tests__/autoresearch.test.js +195 -24
  49. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  50. package/dist/cli/__tests__/cleanup.test.d.ts +2 -0
  51. package/dist/cli/__tests__/cleanup.test.d.ts.map +1 -0
  52. package/dist/cli/__tests__/cleanup.test.js +213 -0
  53. package/dist/cli/__tests__/cleanup.test.js.map +1 -0
  54. package/dist/cli/__tests__/error-handling-warnings.test.js +1 -1
  55. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  56. package/dist/cli/__tests__/explore.test.js +3 -3
  57. package/dist/cli/__tests__/explore.test.js.map +1 -1
  58. package/dist/cli/__tests__/index.test.js +521 -401
  59. package/dist/cli/__tests__/index.test.js.map +1 -1
  60. package/dist/cli/__tests__/native-assets.test.js +72 -9
  61. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  62. package/dist/cli/__tests__/ralphthon.test.d.ts +2 -0
  63. package/dist/cli/__tests__/ralphthon.test.d.ts.map +1 -0
  64. package/dist/cli/__tests__/ralphthon.test.js +28 -0
  65. package/dist/cli/__tests__/ralphthon.test.js.map +1 -0
  66. package/dist/cli/__tests__/setup-agents-overwrite.test.js +36 -1
  67. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  68. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +35 -5
  69. package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
  70. package/dist/cli/__tests__/setup-refresh.test.js +2 -2
  71. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  72. package/dist/cli/__tests__/setup-scope.test.js +131 -161
  73. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  74. package/dist/cli/__tests__/setup-skills-overwrite.test.js +10 -10
  75. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  76. package/dist/cli/__tests__/sparkshell-cli.test.js +28 -2
  77. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  78. package/dist/cli/__tests__/team.test.js +1 -112
  79. package/dist/cli/__tests__/team.test.js.map +1 -1
  80. package/dist/cli/__tests__/uninstall.test.js +7 -20
  81. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  82. package/dist/cli/agents-init.d.ts.map +1 -1
  83. package/dist/cli/agents-init.js +99 -95
  84. package/dist/cli/agents-init.js.map +1 -1
  85. package/dist/cli/agents.d.ts +14 -0
  86. package/dist/cli/agents.d.ts.map +1 -0
  87. package/dist/cli/agents.js +261 -0
  88. package/dist/cli/agents.js.map +1 -0
  89. package/dist/cli/autoresearch-guided.d.ts +8 -0
  90. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  91. package/dist/cli/autoresearch-guided.js +104 -37
  92. package/dist/cli/autoresearch-guided.js.map +1 -1
  93. package/dist/cli/autoresearch-intake.d.ts +60 -0
  94. package/dist/cli/autoresearch-intake.d.ts.map +1 -0
  95. package/dist/cli/autoresearch-intake.js +318 -0
  96. package/dist/cli/autoresearch-intake.js.map +1 -0
  97. package/dist/cli/autoresearch.d.ts +3 -1
  98. package/dist/cli/autoresearch.d.ts.map +1 -1
  99. package/dist/cli/autoresearch.js +64 -10
  100. package/dist/cli/autoresearch.js.map +1 -1
  101. package/dist/cli/cleanup.d.ts +52 -0
  102. package/dist/cli/cleanup.d.ts.map +1 -0
  103. package/dist/cli/cleanup.js +302 -0
  104. package/dist/cli/cleanup.js.map +1 -0
  105. package/dist/cli/doctor.d.ts.map +1 -1
  106. package/dist/cli/doctor.js +9 -37
  107. package/dist/cli/doctor.js.map +1 -1
  108. package/dist/cli/explore.d.ts.map +1 -1
  109. package/dist/cli/explore.js +5 -4
  110. package/dist/cli/explore.js.map +1 -1
  111. package/dist/cli/index.d.ts +5 -7
  112. package/dist/cli/index.d.ts.map +1 -1
  113. package/dist/cli/index.js +610 -427
  114. package/dist/cli/index.js.map +1 -1
  115. package/dist/cli/native-assets.d.ts +15 -1
  116. package/dist/cli/native-assets.d.ts.map +1 -1
  117. package/dist/cli/native-assets.js +134 -32
  118. package/dist/cli/native-assets.js.map +1 -1
  119. package/dist/cli/ralph.d.ts.map +1 -1
  120. package/dist/cli/ralph.js +38 -1
  121. package/dist/cli/ralph.js.map +1 -1
  122. package/dist/cli/ralphthon.d.ts +14 -0
  123. package/dist/cli/ralphthon.d.ts.map +1 -0
  124. package/dist/cli/ralphthon.js +234 -0
  125. package/dist/cli/ralphthon.js.map +1 -0
  126. package/dist/cli/setup.d.ts +1 -4
  127. package/dist/cli/setup.d.ts.map +1 -1
  128. package/dist/cli/setup.js +111 -76
  129. package/dist/cli/setup.js.map +1 -1
  130. package/dist/cli/sparkshell.d.ts +3 -1
  131. package/dist/cli/sparkshell.d.ts.map +1 -1
  132. package/dist/cli/sparkshell.js +35 -16
  133. package/dist/cli/sparkshell.js.map +1 -1
  134. package/dist/cli/team.d.ts.map +1 -1
  135. package/dist/cli/team.js +1 -0
  136. package/dist/cli/team.js.map +1 -1
  137. package/dist/cli/uninstall.d.ts +1 -1
  138. package/dist/cli/uninstall.d.ts.map +1 -1
  139. package/dist/cli/uninstall.js +82 -64
  140. package/dist/cli/uninstall.js.map +1 -1
  141. package/dist/config/__tests__/generator-idempotent.test.js +10 -10
  142. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  143. package/dist/config/__tests__/generator-notify.test.js +15 -0
  144. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  145. package/dist/config/generator.d.ts +0 -1
  146. package/dist/config/generator.d.ts.map +1 -1
  147. package/dist/config/generator.js +53 -42
  148. package/dist/config/generator.js.map +1 -1
  149. package/dist/hooks/__tests__/agents-overlay.test.js +295 -230
  150. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  151. package/dist/hooks/__tests__/deep-interview-contract.test.js +49 -24
  152. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  153. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts +2 -0
  154. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts.map +1 -0
  155. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js +193 -0
  156. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js.map +1 -0
  157. package/dist/hooks/agents-overlay.d.ts +1 -1
  158. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  159. package/dist/hooks/agents-overlay.js +109 -106
  160. package/dist/hooks/agents-overlay.js.map +1 -1
  161. package/dist/modes/base.d.ts +1 -1
  162. package/dist/modes/base.d.ts.map +1 -1
  163. package/dist/modes/base.js +1 -1
  164. package/dist/modes/base.js.map +1 -1
  165. package/dist/ralphthon/__tests__/bootstrap.test.d.ts +2 -0
  166. package/dist/ralphthon/__tests__/bootstrap.test.d.ts.map +1 -0
  167. package/dist/ralphthon/__tests__/bootstrap.test.js +23 -0
  168. package/dist/ralphthon/__tests__/bootstrap.test.js.map +1 -0
  169. package/dist/ralphthon/__tests__/orchestrator.test.d.ts +2 -0
  170. package/dist/ralphthon/__tests__/orchestrator.test.d.ts.map +1 -0
  171. package/dist/ralphthon/__tests__/orchestrator.test.js +309 -0
  172. package/dist/ralphthon/__tests__/orchestrator.test.js.map +1 -0
  173. package/dist/ralphthon/__tests__/prd.test.d.ts +2 -0
  174. package/dist/ralphthon/__tests__/prd.test.d.ts.map +1 -0
  175. package/dist/ralphthon/__tests__/prd.test.js +133 -0
  176. package/dist/ralphthon/__tests__/prd.test.js.map +1 -0
  177. package/dist/ralphthon/bootstrap.d.ts +3 -0
  178. package/dist/ralphthon/bootstrap.d.ts.map +1 -0
  179. package/dist/ralphthon/bootstrap.js +84 -0
  180. package/dist/ralphthon/bootstrap.js.map +1 -0
  181. package/dist/ralphthon/orchestrator.d.ts +50 -0
  182. package/dist/ralphthon/orchestrator.d.ts.map +1 -0
  183. package/dist/ralphthon/orchestrator.js +362 -0
  184. package/dist/ralphthon/orchestrator.js.map +1 -0
  185. package/dist/ralphthon/prd.d.ts +191 -0
  186. package/dist/ralphthon/prd.d.ts.map +1 -0
  187. package/dist/ralphthon/prd.js +355 -0
  188. package/dist/ralphthon/prd.js.map +1 -0
  189. package/dist/ralphthon/runtime.d.ts +31 -0
  190. package/dist/ralphthon/runtime.d.ts.map +1 -0
  191. package/dist/ralphthon/runtime.js +104 -0
  192. package/dist/ralphthon/runtime.js.map +1 -0
  193. package/dist/ralphthon/tmux.d.ts +3 -0
  194. package/dist/ralphthon/tmux.d.ts.map +1 -0
  195. package/dist/ralphthon/tmux.js +39 -0
  196. package/dist/ralphthon/tmux.js.map +1 -0
  197. package/dist/subagents/__tests__/tracker.test.d.ts +2 -0
  198. package/dist/subagents/__tests__/tracker.test.d.ts.map +1 -0
  199. package/dist/subagents/__tests__/tracker.test.js +47 -0
  200. package/dist/subagents/__tests__/tracker.test.js.map +1 -0
  201. package/dist/subagents/tracker.d.ts +52 -0
  202. package/dist/subagents/tracker.d.ts.map +1 -0
  203. package/dist/subagents/tracker.js +175 -0
  204. package/dist/subagents/tracker.js.map +1 -0
  205. package/dist/team/__tests__/worker-bootstrap.test.js +189 -163
  206. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  207. package/dist/team/__tests__/worktree.test.js +1 -1
  208. package/dist/team/__tests__/worktree.test.js.map +1 -1
  209. package/dist/team/worker-bootstrap.d.ts +1 -1
  210. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  211. package/dist/team/worker-bootstrap.js +58 -63
  212. package/dist/team/worker-bootstrap.js.map +1 -1
  213. package/dist/team/worktree.js +1 -1
  214. package/dist/team/worktree.js.map +1 -1
  215. package/dist/utils/__tests__/agents-md.test.d.ts +2 -0
  216. package/dist/utils/__tests__/agents-md.test.d.ts.map +1 -0
  217. package/dist/utils/__tests__/agents-md.test.js +32 -0
  218. package/dist/utils/__tests__/agents-md.test.js.map +1 -0
  219. package/dist/utils/__tests__/agents-model-table.test.d.ts +2 -0
  220. package/dist/utils/__tests__/agents-model-table.test.d.ts.map +1 -0
  221. package/dist/utils/__tests__/agents-model-table.test.js +84 -0
  222. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -0
  223. package/dist/utils/__tests__/paths.test.js +78 -83
  224. package/dist/utils/__tests__/paths.test.js.map +1 -1
  225. package/dist/utils/agents-md.d.ts.map +1 -1
  226. package/dist/utils/agents-md.js +10 -0
  227. package/dist/utils/agents-md.js.map +1 -1
  228. package/dist/utils/agents-model-table.d.ts +16 -0
  229. package/dist/utils/agents-model-table.d.ts.map +1 -0
  230. package/dist/utils/agents-model-table.js +83 -0
  231. package/dist/utils/agents-model-table.js.map +1 -0
  232. package/dist/utils/paths.d.ts +6 -6
  233. package/dist/utils/paths.d.ts.map +1 -1
  234. package/dist/utils/paths.js +31 -31
  235. package/dist/utils/paths.js.map +1 -1
  236. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +21 -3
  237. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  238. package/dist/verification/__tests__/native-release-manifest.test.d.ts +2 -0
  239. package/dist/verification/__tests__/native-release-manifest.test.d.ts.map +1 -0
  240. package/dist/verification/__tests__/native-release-manifest.test.js +80 -0
  241. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -0
  242. package/package.json +1 -1
  243. package/prompts/executor.md +15 -0
  244. package/scripts/__tests__/smoke-packed-install.test.mjs +137 -8
  245. package/scripts/eval-adaptive-sort-optimization.py +24 -0
  246. package/scripts/eval-in-action-cat-shellout-demo.js +31 -0
  247. package/scripts/eval-ml-kaggle-model-optimization.py +29 -0
  248. package/scripts/eval-noisy-bayesopt-highdim.py +44 -0
  249. package/scripts/eval-noisy-latent-subspace-discovery.py +44 -0
  250. package/scripts/generate-native-release-manifest.mjs +14 -3
  251. package/scripts/notify-fallback-watcher.js +308 -6
  252. package/scripts/notify-hook.js +20 -0
  253. package/scripts/run-autoresearch-showcase.sh +75 -0
  254. package/scripts/smoke-packed-install.mjs +142 -10
  255. package/skills/deep-interview/SKILL.md +30 -1
  256. package/skills/omx-setup/SKILL.md +2 -2
  257. package/skills/skill/SKILL.md +32 -32
  258. package/skills/team/SKILL.md +6 -0
  259. package/skills/worker/SKILL.md +2 -2
  260. package/templates/AGENTS.md +97 -16
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { existsSync } from 'fs';
4
4
  import { appendFile, mkdir, readFile, readdir, stat, unlink, writeFile } from 'fs/promises';
5
- import { spawnSync } from 'child_process';
5
+ import { spawn, spawnSync } from 'child_process';
6
6
  import { dirname, join, resolve } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { drainPendingTeamDispatch } from './notify-hook/team-dispatch.js';
@@ -14,6 +14,9 @@ import {
14
14
  resolveLeaderStalenessThresholdMs,
15
15
  } from './notify-hook/team-leader-nudge.js';
16
16
  import { DEFAULT_MARKER } from './tmux-hook-engine.js';
17
+ import { RalphthonOrchestrator } from '../dist/ralphthon/orchestrator.js';
18
+ import { readRalphthonPrd, writeRalphthonPrd } from '../dist/ralphthon/prd.js';
19
+ import { createRalphthonRuntimeState, readRalphthonRuntimeState, writeRalphthonRuntimeState } from '../dist/ralphthon/runtime.js';
17
20
 
18
21
  function argValue(name, fallback = '') {
19
22
  const idx = process.argv.indexOf(name);
@@ -83,6 +86,8 @@ const logPath = join(logsDir, `notify-fallback-${new Date().toISOString().split(
83
86
  const RALPH_CONTINUE_TEXT = 'Ralph loop active continue';
84
87
  const RALPH_CONTINUE_CADENCE_MS = 60_000;
85
88
  const RALPH_TERMINAL_PHASES = new Set(['complete', 'failed', 'cancelled']);
89
+ const RALPHTHON_WATCHDOG_RESTART_LIMIT = 3;
90
+ const RALPHTHON_WATCHDOG_RESTART_WINDOW_MS = 5 * 60 * 1000;
86
91
 
87
92
  const fileState = new Map();
88
93
  const seenTurnKeys = new Set();
@@ -124,6 +129,17 @@ let lastParentGuard = {
124
129
  state_path: '',
125
130
  current_phase: '',
126
131
  };
132
+ let lastRalphthonWatchdog = {
133
+ active: false,
134
+ restart_count: 0,
135
+ restart_window_started_at: '',
136
+ last_tick_at: null,
137
+ last_error: null,
138
+ last_result: null,
139
+ };
140
+ let ralphthonRestartWindowStartedAt = 0;
141
+ let ralphthonRestartCount = 0;
142
+ let ralphthonOrchestrator = null;
127
143
 
128
144
  function eventLog(event) {
129
145
  return appendFile(logPath, `${JSON.stringify({ timestamp: new Date().toISOString(), ...event })}\n`).catch(() => {});
@@ -161,9 +177,23 @@ async function loadPersistedWatcherState() {
161
177
  .then((content) => JSON.parse(content))
162
178
  .catch(() => null);
163
179
  lastRalphContinueSteer = normalizeRalphContinueSteerState(persisted?.ralph_continue_steer);
180
+ if (persisted?.ralphthon_watchdog && typeof persisted.ralphthon_watchdog === 'object') {
181
+ lastRalphthonWatchdog = {
182
+ ...lastRalphthonWatchdog,
183
+ ...persisted.ralphthon_watchdog,
184
+ };
185
+ const persistedRestartCount = Number(persisted.ralphthon_watchdog.restart_count);
186
+ if (Number.isFinite(persistedRestartCount) && persistedRestartCount >= 0) {
187
+ ralphthonRestartCount = Math.floor(persistedRestartCount);
188
+ }
189
+ const persistedWindowStart = Date.parse(safeString(persisted.ralphthon_watchdog.restart_window_started_at));
190
+ if (Number.isFinite(persistedWindowStart) && persistedWindowStart > 0) {
191
+ ralphthonRestartWindowStartedAt = persistedWindowStart;
192
+ }
193
+ }
164
194
  }
165
195
 
166
- async function resolveActiveRalphState() {
196
+ async function resolveActiveModeState(mode) {
167
197
  const candidateDirs = [];
168
198
  const sessionPath = join(stateDir, 'session.json');
169
199
  try {
@@ -178,7 +208,7 @@ async function resolveActiveRalphState() {
178
208
  if (!candidateDirs.includes(stateDir)) candidateDirs.push(stateDir);
179
209
 
180
210
  for (const dir of candidateDirs) {
181
- const path = join(dir, 'ralph-state.json');
211
+ const path = join(dir, `${mode}-state.json`);
182
212
  if (!existsSync(path)) continue;
183
213
  const parsed = await readFile(path, 'utf-8')
184
214
  .then((content) => JSON.parse(content))
@@ -208,6 +238,14 @@ async function resolveActiveRalphState() {
208
238
  };
209
239
  }
210
240
 
241
+ async function resolveActiveRalphState() {
242
+ return resolveActiveModeState('ralph');
243
+ }
244
+
245
+ async function resolveActiveRalphthonState() {
246
+ return resolveActiveModeState('ralphthon');
247
+ }
248
+
211
249
  async function emitRalphContinueSteer(paneId, message) {
212
250
  const markedText = `${message} ${DEFAULT_MARKER}`;
213
251
  await new Promise((resolve) => {
@@ -226,6 +264,34 @@ async function emitRalphContinueSteer(paneId, message) {
226
264
  }
227
265
  }
228
266
 
267
+ function resolveSessionLeaderPaneId(sessionName) {
268
+ const normalized = safeString(sessionName).trim();
269
+ if (!normalized) return '';
270
+ const listed = spawnSync('tmux', ['list-panes', '-t', normalized, '-F', '#{pane_id}\t#{pane_start_command}\t#{pane_current_command}'], { encoding: 'utf-8' });
271
+ if (listed.status !== 0) return '';
272
+ const lines = safeString(listed.stdout).split('\n').map((line) => line.trim()).filter(Boolean);
273
+ for (const line of lines) {
274
+ const [paneId = '', startCommand = '', currentCommand = ''] = line.split('\t');
275
+ if (!paneId.startsWith('%')) continue;
276
+ const combined = `${startCommand} ${currentCommand}`.toLowerCase();
277
+ if (/\bomx(?:\.js)?\b/.test(combined) && /\bhud\b/.test(combined) && /--watch\b/.test(combined)) continue;
278
+ return paneId;
279
+ }
280
+ return '';
281
+ }
282
+
283
+ function resolveRalphthonPaneTarget(activeRalphthon) {
284
+ const state = activeRalphthon?.state && typeof activeRalphthon.state === 'object' ? activeRalphthon.state : {};
285
+ const leaderPaneId = safeString(state.leader_pane_id).trim();
286
+ if (leaderPaneId.startsWith('%')) return leaderPaneId;
287
+ const tmuxPaneId = safeString(state.tmux_pane_id).trim();
288
+ if (tmuxPaneId.startsWith('%')) return tmuxPaneId;
289
+ const sessionName = safeString(state.tmux_session).trim();
290
+ const sessionPane = resolveSessionLeaderPaneId(sessionName);
291
+ if (sessionPane) return sessionPane;
292
+ return '';
293
+ }
294
+
229
295
  async function runRalphContinueSteerTick() {
230
296
  const now = Date.now();
231
297
  const nowIso = new Date(now).toISOString();
@@ -395,6 +461,9 @@ async function writeState(extra = {}) {
395
461
  cadence_ms: RALPH_CONTINUE_CADENCE_MS,
396
462
  message: RALPH_CONTINUE_TEXT,
397
463
  },
464
+ ralphthon_watchdog: {
465
+ ...lastRalphthonWatchdog,
466
+ },
398
467
  ...extra,
399
468
  };
400
469
  await writeFile(statePath, JSON.stringify(state, null, 2)).catch(() => {});
@@ -422,11 +491,13 @@ async function enforceLifecycleGuards() {
422
491
  if (runOnce) return false;
423
492
  if (parentIsGone()) {
424
493
  const activeRalph = await resolveActiveRalphState();
425
- if (activeRalph.active) {
426
- const currentPhase = safeString(activeRalph.state?.current_phase);
494
+ const activeRalphthon = await resolveActiveRalphthonState();
495
+ const activeMode = activeRalph.active ? activeRalph : activeRalphthon;
496
+ if (activeMode.active) {
497
+ const currentPhase = safeString(activeMode.state?.current_phase);
427
498
  const nextParentGuard = {
428
499
  reason: 'parent_gone_deferred_for_active_ralph',
429
- state_path: activeRalph.path,
500
+ state_path: activeMode.path,
430
501
  current_phase: currentPhase,
431
502
  };
432
503
  if (
@@ -716,11 +787,216 @@ async function pumpTeamControlPlaneTick() {
716
787
  await runLeaderNudgeTick();
717
788
  }
718
789
 
790
+
791
+ async function updateRalphthonModePatch(patch) {
792
+ try {
793
+ const sessionPath = join(stateDir, 'session.json');
794
+ let stateFile = join(stateDir, 'ralphthon-state.json');
795
+ try {
796
+ const session = JSON.parse(await readFile(sessionPath, 'utf-8'));
797
+ const sessionId = safeString(session?.session_id).trim();
798
+ if (sessionId) {
799
+ const scoped = join(stateDir, 'sessions', sessionId, 'ralphthon-state.json');
800
+ if (existsSync(scoped)) stateFile = scoped;
801
+ }
802
+ } catch {}
803
+
804
+ if (!existsSync(stateFile)) return;
805
+ const parsed = JSON.parse(await readFile(stateFile, 'utf-8'));
806
+ const next = { ...parsed, ...patch };
807
+ await writeFile(stateFile, `${JSON.stringify(next, null, 2)}\n`);
808
+ } catch {}
809
+ }
810
+
811
+ async function readActiveSessionId() {
812
+ try {
813
+ const session = JSON.parse(await readFile(join(stateDir, 'session.json'), 'utf-8'));
814
+ const sessionId = safeString(session?.session_id).trim();
815
+ return sessionId || 'ralphthon-watchdog';
816
+ } catch {
817
+ return 'ralphthon-watchdog';
818
+ }
819
+ }
820
+
821
+ async function notifyRalphthonWatchdogFailure(message) {
822
+ const sessionId = await readActiveSessionId();
823
+ try {
824
+ const { notifyLifecycle } = await import('../dist/notifications/index.js');
825
+ const result = await notifyLifecycle('session-stop', {
826
+ sessionId,
827
+ projectPath: cwd,
828
+ projectName: cwd.split('/').filter(Boolean).at(-1) || 'unknown',
829
+ activeMode: 'ralphthon',
830
+ reason: 'ralphthon_watchdog_restart_limit_reached',
831
+ tmuxTail: message,
832
+ contextSummary: message,
833
+ });
834
+ await eventLog({
835
+ type: 'ralphthon_alert_notification',
836
+ status: result ? 'delivered' : 'skipped',
837
+ session_id: sessionId,
838
+ message,
839
+ });
840
+ } catch (error) {
841
+ await eventLog({
842
+ type: 'ralphthon_alert_notification',
843
+ status: 'failed',
844
+ session_id: sessionId,
845
+ message,
846
+ error: error instanceof Error ? error.message : safeString(error),
847
+ });
848
+ }
849
+ }
850
+
851
+ function shouldRestartRalphthonWatchdog(nowMs) {
852
+ if (!ralphthonRestartWindowStartedAt || (nowMs - ralphthonRestartWindowStartedAt) > RALPHTHON_WATCHDOG_RESTART_WINDOW_MS) {
853
+ ralphthonRestartWindowStartedAt = nowMs;
854
+ ralphthonRestartCount = 0;
855
+ }
856
+ return ralphthonRestartCount < RALPHTHON_WATCHDOG_RESTART_LIMIT;
857
+ }
858
+
859
+ async function runRalphthonWatchdogTick() {
860
+ const now = Date.now();
861
+ const nowIso = new Date(now).toISOString();
862
+ const activeRalphthon = await resolveActiveRalphthonState();
863
+ const paneTarget = resolveRalphthonPaneTarget(activeRalphthon) || await resolveNudgePaneTarget(stateDir);
864
+ let runtime = await readRalphthonRuntimeState(cwd).catch(() => null);
865
+
866
+ if (!activeRalphthon.active) {
867
+ lastRalphthonWatchdog = {
868
+ ...lastRalphthonWatchdog,
869
+ active: false,
870
+ last_tick_at: nowIso,
871
+ last_result: 'inactive',
872
+ last_error: null,
873
+ state_path: activeRalphthon.path,
874
+ runtime_path: runtime ? join(cwd, '.omx', 'ralphthon', 'runtime.json') : '',
875
+ };
876
+ return;
877
+ }
878
+ if (!runtime) {
879
+ runtime = createRalphthonRuntimeState(paneTarget);
880
+ }
881
+ if (!paneTarget) {
882
+ lastRalphthonWatchdog = {
883
+ ...lastRalphthonWatchdog,
884
+ active: true,
885
+ last_tick_at: nowIso,
886
+ last_result: 'pane_missing',
887
+ last_error: null,
888
+ state_path: activeRalphthon.path,
889
+ runtime_path: join(cwd, '.omx', 'ralphthon', 'runtime.json'),
890
+ pane_id: '',
891
+ };
892
+ await writeRalphthonRuntimeState(cwd, runtime).catch(() => {});
893
+ return;
894
+ }
895
+ if (safeString(runtime.leaderTarget).trim() !== paneTarget) {
896
+ runtime = { ...runtime, leaderTarget: paneTarget };
897
+ await writeRalphthonRuntimeState(cwd, runtime).catch(() => {});
898
+ }
899
+
900
+ if (!ralphthonOrchestrator) {
901
+ ralphthonOrchestrator = new RalphthonOrchestrator({
902
+ readPrd: async () => readRalphthonPrd(cwd),
903
+ writePrd: async (prd) => { await writeRalphthonPrd(cwd, prd); },
904
+ readRuntime: async () => readRalphthonRuntimeState(cwd),
905
+ writeRuntime: async (next) => { await writeRalphthonRuntimeState(cwd, next); },
906
+ capturePane: async (leaderTarget) => {
907
+ const result = spawnSync('tmux', ['capture-pane', '-t', leaderTarget, '-p', '-S', '-120'], { encoding: 'utf-8' });
908
+ return result.status === 0 ? safeString(result.stdout) : '';
909
+ },
910
+ injectPrompt: async (leaderTarget, prompt) => {
911
+ const paneGuard = await checkPaneReadyForTeamSendKeys(leaderTarget);
912
+ if (!paneGuard.ok) {
913
+ await eventLog({
914
+ type: 'ralphthon_injection_skipped',
915
+ pane_id: leaderTarget,
916
+ reason: paneGuard.reason || 'pane_guard_blocked',
917
+ pane_current_command: paneGuard.paneCurrentCommand || null,
918
+ });
919
+ return false;
920
+ }
921
+ const marked = `${prompt} ${DEFAULT_MARKER}`;
922
+ const send = spawnSync('tmux', ['send-keys', '-t', leaderTarget, '-l', marked], { encoding: 'utf-8' });
923
+ if (send.status !== 0) throw new Error((send.stderr || send.stdout || '').trim() || 'tmux send-keys failed');
924
+ spawnSync('tmux', ['send-keys', '-t', leaderTarget, 'C-m'], { encoding: 'utf-8' });
925
+ spawnSync('tmux', ['send-keys', '-t', leaderTarget, 'C-m'], { encoding: 'utf-8' });
926
+ return true;
927
+ },
928
+ updateModeState: updateRalphthonModePatch,
929
+ alert: async (message) => { await eventLog({ type: 'ralphthon_alert', message }); },
930
+ });
931
+ }
932
+
933
+ try {
934
+ const result = await ralphthonOrchestrator.tick();
935
+ lastRalphthonWatchdog = {
936
+ ...lastRalphthonWatchdog,
937
+ active: true,
938
+ last_tick_at: nowIso,
939
+ last_result: result,
940
+ last_error: null,
941
+ state_path: activeRalphthon.path,
942
+ runtime_path: join(cwd, '.omx', 'ralphthon', 'runtime.json'),
943
+ pane_id: paneTarget,
944
+ restart_count: ralphthonRestartCount,
945
+ restart_window_started_at: ralphthonRestartWindowStartedAt ? new Date(ralphthonRestartWindowStartedAt).toISOString() : '',
946
+ };
947
+ } catch (error) {
948
+ const message = error instanceof Error ? error.message : safeString(error);
949
+ if (shouldRestartRalphthonWatchdog(now)) {
950
+ ralphthonRestartCount += 1;
951
+ ralphthonOrchestrator = null;
952
+ lastRalphthonWatchdog = {
953
+ ...lastRalphthonWatchdog,
954
+ active: true,
955
+ last_tick_at: nowIso,
956
+ last_result: 'restart_scheduled',
957
+ last_error: message,
958
+ state_path: activeRalphthon.path,
959
+ runtime_path: join(cwd, '.omx', 'ralphthon', 'runtime.json'),
960
+ pane_id: paneTarget,
961
+ restart_count: ralphthonRestartCount,
962
+ restart_window_started_at: new Date(ralphthonRestartWindowStartedAt).toISOString(),
963
+ };
964
+ await eventLog({ type: 'ralphthon_watchdog_restart', restart_count: ralphthonRestartCount, error: message });
965
+ return;
966
+ }
967
+ lastRalphthonWatchdog = {
968
+ ...lastRalphthonWatchdog,
969
+ active: false,
970
+ last_tick_at: nowIso,
971
+ last_result: 'restart_limit_reached',
972
+ last_error: message,
973
+ state_path: activeRalphthon.path,
974
+ runtime_path: join(cwd, '.omx', 'ralphthon', 'runtime.json'),
975
+ pane_id: paneTarget,
976
+ restart_count: ralphthonRestartCount,
977
+ restart_window_started_at: ralphthonRestartWindowStartedAt ? new Date(ralphthonRestartWindowStartedAt).toISOString() : '',
978
+ };
979
+ await updateRalphthonModePatch({
980
+ active: false,
981
+ current_phase: 'failed',
982
+ completed_at: nowIso,
983
+ error: message || 'ralphthon_watchdog_restart_limit_reached',
984
+ stop_reason: 'ralphthon_watchdog_restart_limit_reached',
985
+ });
986
+ await eventLog({ type: 'ralphthon_watchdog_failed', restart_count: ralphthonRestartCount, error: message });
987
+ const notifyLine = `[ralphthon] watchdog failed permanently after ${ralphthonRestartCount} restarts: ${message || 'unknown error'}`;
988
+ process.stderr.write(`${notifyLine}\n`);
989
+ await eventLog({ type: 'ralphthon_alert', message: notifyLine, user_visible: true });
990
+ await notifyRalphthonWatchdogFailure(notifyLine);
991
+ }
992
+ }
993
+
719
994
  async function runWatcherCycle() {
720
995
  await ensureTrackedFiles();
721
996
  await pollFiles();
722
997
  await pumpTeamControlPlaneTick();
723
998
  await runRalphWatcherBehaviorTick();
999
+ await runRalphthonWatchdogTick();
724
1000
  await writeState();
725
1001
  }
726
1002
 
@@ -775,10 +1051,36 @@ async function main() {
775
1051
 
776
1052
  main().catch(async (err) => {
777
1053
  await mkdir(dirname(logPath), { recursive: true }).catch(() => {});
1054
+ const restartCount = Math.max(0, asNumber(process.env.OMX_RALPHTHON_WATCHDOG_PROC_RESTART_COUNT || '0', 0));
1055
+ const activeRalphthon = await resolveActiveRalphthonState().catch(() => ({ active: false, path: '', state: null }));
778
1056
  await eventLog({
779
1057
  type: 'watcher_error',
780
1058
  reason: 'fatal',
781
1059
  error: err instanceof Error ? err.message : safeString(err),
1060
+ ralphthon_active: activeRalphthon.active === true,
1061
+ restart_count: restartCount,
782
1062
  });
1063
+ if (!runOnce && activeRalphthon.active === true && restartCount < RALPHTHON_WATCHDOG_RESTART_LIMIT) {
1064
+ const child = spawn(
1065
+ process.execPath,
1066
+ [process.argv[1], ...process.argv.slice(2)],
1067
+ {
1068
+ cwd,
1069
+ detached: true,
1070
+ stdio: 'ignore',
1071
+ env: {
1072
+ ...process.env,
1073
+ OMX_RALPHTHON_WATCHDOG_PROC_RESTART_COUNT: String(restartCount + 1),
1074
+ },
1075
+ },
1076
+ );
1077
+ child.unref();
1078
+ await eventLog({
1079
+ type: 'watcher_restart_spawned',
1080
+ reason: 'fatal_recovery',
1081
+ restart_count: restartCount + 1,
1082
+ spawned_pid: child.pid ?? null,
1083
+ });
1084
+ }
783
1085
  process.exit(1);
784
1086
  });
@@ -124,6 +124,26 @@ async function main() {
124
124
  // Non-critical
125
125
  }
126
126
 
127
+ // 0.5. Track leader + native subagent thread activity (lead session only)
128
+ if (!isTeamWorker) {
129
+ try {
130
+ const threadId = safeString(payload['thread-id'] || payload.thread_id || '');
131
+ const turnId = safeString(payload['turn-id'] || payload.turn_id || '');
132
+ if (payloadSessionId && threadId) {
133
+ const { recordSubagentTurnForSession } = await import('../dist/subagents/tracker.js');
134
+ await recordSubagentTurnForSession(cwd, {
135
+ sessionId: payloadSessionId,
136
+ threadId,
137
+ ...(turnId ? { turnId } : {}),
138
+ timestamp: new Date().toISOString(),
139
+ mode: safeString(payload.mode || ''),
140
+ });
141
+ }
142
+ } catch {
143
+ // Non-critical: tracking must never block the hook
144
+ }
145
+ }
146
+
127
147
  // 1. Log the turn
128
148
  const logEntry = {
129
149
  timestamp: new Date().toISOString(),
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ show_usage() {
5
+ cat <<'USAGE'
6
+ Usage:
7
+ scripts/run-autoresearch-showcase.sh --list
8
+ scripts/run-autoresearch-showcase.sh <showcase> [<showcase> ...]
9
+
10
+ Available showcases:
11
+ omx-self -> missions/in-action-cat-shellout-demo
12
+ ml-tabular -> missions/ml-kaggle-model-optimization
13
+ bayesopt -> missions/noisy-bayesopt-highdim
14
+ latent -> missions/noisy-latent-subspace-discovery
15
+ sorting -> missions/adaptive-sort-optimization
16
+ all -> omx-self ml-tabular bayesopt latent sorting
17
+
18
+ Examples:
19
+ scripts/run-autoresearch-showcase.sh --list
20
+ scripts/run-autoresearch-showcase.sh bayesopt
21
+ scripts/run-autoresearch-showcase.sh omx-self ml-tabular
22
+ USAGE
23
+ }
24
+
25
+ mission_for() {
26
+ case "$1" in
27
+ omx-self) printf '%s' 'missions/in-action-cat-shellout-demo' ;;
28
+ ml-tabular) printf '%s' 'missions/ml-kaggle-model-optimization' ;;
29
+ bayesopt) printf '%s' 'missions/noisy-bayesopt-highdim' ;;
30
+ latent) printf '%s' 'missions/noisy-latent-subspace-discovery' ;;
31
+ sorting) printf '%s' 'missions/adaptive-sort-optimization' ;;
32
+ *) return 1 ;;
33
+ esac
34
+ }
35
+
36
+ list_showcases() {
37
+ cat <<'LIST'
38
+ omx-self missions/in-action-cat-shellout-demo
39
+ ml-tabular missions/ml-kaggle-model-optimization
40
+ bayesopt missions/noisy-bayesopt-highdim
41
+ latent missions/noisy-latent-subspace-discovery
42
+ sorting missions/adaptive-sort-optimization
43
+ LIST
44
+ }
45
+
46
+ if [[ $# -eq 0 ]]; then
47
+ show_usage
48
+ exit 1
49
+ fi
50
+
51
+ if [[ "$1" == "--help" || "$1" == "-h" ]]; then
52
+ show_usage
53
+ exit 0
54
+ fi
55
+
56
+ if [[ "$1" == "--list" ]]; then
57
+ list_showcases
58
+ exit 0
59
+ fi
60
+
61
+ args=("$@")
62
+ if [[ " ${args[*]} " == *" all "* ]]; then
63
+ args=(omx-self ml-tabular bayesopt latent sorting)
64
+ fi
65
+
66
+ for showcase in "${args[@]}"; do
67
+ mission="$(mission_for "$showcase")" || {
68
+ printf 'Unknown showcase: %s\n\n' "$showcase" >&2
69
+ show_usage >&2
70
+ exit 1
71
+ }
72
+
73
+ printf '\n==> Running showcase %s (%s)\n' "$showcase" "$mission"
74
+ omx autoresearch "$mission"
75
+ done