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.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/skill-validation.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js +91 -0
- package/dist/autoresearch/__tests__/skill-validation.test.js.map +1 -0
- package/dist/autoresearch/skill-validation.d.ts +13 -0
- package/dist/autoresearch/skill-validation.d.ts.map +1 -0
- package/dist/autoresearch/skill-validation.js +165 -0
- package/dist/autoresearch/skill-validation.js.map +1 -0
- package/dist/catalog/__tests__/schema.test.js +6 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js +236 -273
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +64 -653
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +7 -0
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +2 -1
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.d.ts +2 -0
- package/dist/cli/__tests__/question.test.d.ts.map +1 -0
- package/dist/cli/__tests__/question.test.js +113 -0
- package/dist/cli/__tests__/question.test.js.map +1 -0
- package/dist/cli/__tests__/session-search-help.test.js +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -0
- package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts +24 -7
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +189 -130
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +3 -2
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +29 -305
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +43 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +8 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/question.d.ts +3 -0
- package/dist/cli/question.d.ts.map +1 -0
- package/dist/cli/question.js +182 -0
- package/dist/cli/question.js.map +1 -0
- package/dist/hooks/__tests__/analyze-routing-contract.test.js +22 -13
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +2 -2
- package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +22 -5
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +2 -2
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +308 -17
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +570 -2
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +717 -16
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +25 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +894 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +34 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +132 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js +22 -4
- package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js +4 -2
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts +1 -0
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +4 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +28 -0
- package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js +5 -4
- package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-team-routing.test.js +2 -2
- package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/triage-config.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-config.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-config.test.js +211 -0
- package/dist/hooks/__tests__/triage-config.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-heuristic.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js +230 -0
- package/dist/hooks/__tests__/triage-heuristic.test.js.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts +2 -0
- package/dist/hooks/__tests__/triage-state.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/triage-state.test.js +426 -0
- package/dist/hooks/__tests__/triage-state.test.js.map +1 -0
- package/dist/hooks/keyword-detector.d.ts +26 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +97 -26
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +16 -9
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +28 -1
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/triage-config.d.ts +33 -0
- package/dist/hooks/triage-config.d.ts.map +1 -0
- package/dist/hooks/triage-config.js +87 -0
- package/dist/hooks/triage-config.js.map +1 -0
- package/dist/hooks/triage-heuristic.d.ts +20 -0
- package/dist/hooks/triage-heuristic.d.ts.map +1 -0
- package/dist/hooks/triage-heuristic.js +210 -0
- package/dist/hooks/triage-heuristic.js.map +1 -0
- package/dist/hooks/triage-state.d.ts +63 -0
- package/dist/hooks/triage-state.d.ts.map +1 -0
- package/dist/hooks/triage-state.js +138 -0
- package/dist/hooks/triage-state.js.map +1 -0
- package/dist/hud/__tests__/reconcile.test.js +20 -0
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts +1 -0
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +2 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +1 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/state-server.d.ts +8 -0
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +4 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-ralph-contract.test.js +15 -0
- package/dist/modes/__tests__/base-ralph-contract.test.js.map +1 -1
- package/dist/modes/base.d.ts +1 -0
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +22 -6
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/index.test.js +78 -0
- package/dist/notifications/__tests__/index.test.js.map +1 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +39 -22
- package/dist/notifications/index.js.map +1 -1
- package/dist/openclaw/index.d.ts +5 -3
- package/dist/openclaw/index.d.ts.map +1 -1
- package/dist/openclaw/index.js +5 -3
- package/dist/openclaw/index.js.map +1 -1
- package/dist/question/__tests__/client.test.d.ts +2 -0
- package/dist/question/__tests__/client.test.d.ts.map +1 -0
- package/dist/question/__tests__/client.test.js +70 -0
- package/dist/question/__tests__/client.test.js.map +1 -0
- package/dist/question/__tests__/deep-interview.test.d.ts +2 -0
- package/dist/question/__tests__/deep-interview.test.d.ts.map +1 -0
- package/dist/question/__tests__/deep-interview.test.js +108 -0
- package/dist/question/__tests__/deep-interview.test.js.map +1 -0
- package/dist/question/__tests__/policy.test.d.ts +2 -0
- package/dist/question/__tests__/policy.test.d.ts.map +1 -0
- package/dist/question/__tests__/policy.test.js +107 -0
- package/dist/question/__tests__/policy.test.js.map +1 -0
- package/dist/question/__tests__/renderer.test.d.ts +2 -0
- package/dist/question/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/question/__tests__/renderer.test.js +88 -0
- package/dist/question/__tests__/renderer.test.js.map +1 -0
- package/dist/question/__tests__/state.test.d.ts +2 -0
- package/dist/question/__tests__/state.test.d.ts.map +1 -0
- package/dist/question/__tests__/state.test.js +55 -0
- package/dist/question/__tests__/state.test.js.map +1 -0
- package/dist/question/__tests__/types.test.d.ts +2 -0
- package/dist/question/__tests__/types.test.d.ts.map +1 -0
- package/dist/question/__tests__/types.test.js +44 -0
- package/dist/question/__tests__/types.test.js.map +1 -0
- package/dist/question/__tests__/ui.test.d.ts +2 -0
- package/dist/question/__tests__/ui.test.d.ts.map +1 -0
- package/dist/question/__tests__/ui.test.js +169 -0
- package/dist/question/__tests__/ui.test.js.map +1 -0
- package/dist/question/client.d.ts +54 -0
- package/dist/question/client.d.ts.map +1 -0
- package/dist/question/client.js +77 -0
- package/dist/question/client.js.map +1 -0
- package/dist/question/deep-interview.d.ts +27 -0
- package/dist/question/deep-interview.d.ts.map +1 -0
- package/dist/question/deep-interview.js +101 -0
- package/dist/question/deep-interview.js.map +1 -0
- package/dist/question/policy.d.ts +18 -0
- package/dist/question/policy.d.ts.map +1 -0
- package/dist/question/policy.js +77 -0
- package/dist/question/policy.js.map +1 -0
- package/dist/question/renderer.d.ts +18 -0
- package/dist/question/renderer.d.ts.map +1 -0
- package/dist/question/renderer.js +128 -0
- package/dist/question/renderer.js.map +1 -0
- package/dist/question/state.d.ts +19 -0
- package/dist/question/state.d.ts.map +1 -0
- package/dist/question/state.js +108 -0
- package/dist/question/state.js.map +1 -0
- package/dist/question/types.d.ts +66 -0
- package/dist/question/types.d.ts.map +1 -0
- package/dist/question/types.js +82 -0
- package/dist/question/types.js.map +1 -0
- package/dist/question/ui.d.ts +38 -0
- package/dist/question/ui.d.ts.map +1 -0
- package/dist/question/ui.js +321 -0
- package/dist/question/ui.js.map +1 -0
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralph/contract.js +4 -1
- package/dist/ralph/contract.js.map +1 -1
- package/dist/ralplan/runtime.js +1 -1
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/__tests__/run-loop.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-loop.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-loop.test.js +35 -0
- package/dist/runtime/__tests__/run-loop.test.js.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts +2 -0
- package/dist/runtime/__tests__/run-outcome.test.d.ts.map +1 -0
- package/dist/runtime/__tests__/run-outcome.test.js +64 -0
- package/dist/runtime/__tests__/run-outcome.test.js.map +1 -0
- package/dist/runtime/run-loop.d.ts +41 -0
- package/dist/runtime/run-loop.d.ts.map +1 -0
- package/dist/runtime/run-loop.js +46 -0
- package/dist/runtime/run-loop.js.map +1 -0
- package/dist/runtime/run-outcome.d.ts +28 -0
- package/dist/runtime/run-outcome.d.ts.map +1 -0
- package/dist/runtime/run-outcome.js +136 -0
- package/dist/runtime/run-outcome.js.map +1 -0
- package/dist/runtime/run-state.d.ts +36 -0
- package/dist/runtime/run-state.d.ts.map +1 -0
- package/dist/runtime/run-state.js +110 -0
- package/dist/runtime/run-state.js.map +1 -0
- package/dist/scripts/__tests__/codex-native-hook.test.js +1128 -85
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +2 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +199 -11
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +81 -2
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +27 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +83 -20
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +64 -38
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook.js +15 -5
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/sync-prompt-guidance-fragments.js +5 -0
- package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
- package/dist/state/__tests__/operations-ralph-phase.test.js +21 -0
- package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +11 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +15 -0
- package/dist/state/operations.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +14 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +3 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/team/__tests__/followup-planner.test.js +15 -0
- package/dist/team/__tests__/followup-planner.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js +41 -0
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/followup-planner.d.ts.map +1 -1
- package/dist/team/followup-planner.js +31 -9
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/role-router.d.ts.map +1 -1
- package/dist/team/role-router.js +73 -0
- package/dist/team/role-router.js.map +1 -1
- package/package.json +3 -2
- package/prompts/dependency-expert.md +3 -0
- package/prompts/executor.md +5 -0
- package/prompts/explore.md +2 -0
- package/prompts/planner.md +5 -0
- package/prompts/product-analyst.md +8 -8
- package/prompts/researcher.md +78 -30
- package/prompts/verifier.md +4 -0
- package/skills/autoresearch/SKILL.md +68 -0
- package/skills/deep-interview/SKILL.md +10 -9
- package/skills/help/SKILL.md +3 -1
- package/skills/ralplan/SKILL.md +1 -0
- package/skills/team/SKILL.md +1 -0
- package/skills/ultrawork/SKILL.md +1 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +1495 -188
- package/src/scripts/codex-native-hook.ts +235 -19
- package/src/scripts/notify-fallback-watcher.ts +92 -2
- package/src/scripts/notify-hook/auto-nudge.ts +89 -20
- package/src/scripts/notify-hook/managed-tmux.ts +70 -31
- package/src/scripts/notify-hook/ralph-session-resume.ts +1 -1
- package/src/scripts/notify-hook.ts +23 -5
- package/src/scripts/sync-prompt-guidance-fragments.ts +4 -0
- package/templates/AGENTS.md +48 -37
- package/templates/catalog-manifest.json +7 -0
|
@@ -7,7 +7,7 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { initTeamState, enqueueDispatchRequest, readDispatchRequest } from '../../team/state.js';
|
|
10
|
-
import { buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
|
|
10
|
+
import { buildTmuxSessionName, buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
|
|
11
11
|
import { writeSessionStart } from '../session.js';
|
|
12
12
|
const DEFAULT_AUTO_NUDGE_RESPONSE = 'continue with the current task only if it is already authorized';
|
|
13
13
|
async function appendLine(path, line) {
|
|
@@ -236,6 +236,101 @@ fi
|
|
|
236
236
|
exit 0
|
|
237
237
|
`;
|
|
238
238
|
}
|
|
239
|
+
function buildManagedRalphTmux(tmuxLogPath, options) {
|
|
240
|
+
const { cwd, managedSessionName, anchorPane, livePane, codexPanes, missingAnchor = false } = options;
|
|
241
|
+
const panes = (codexPanes && codexPanes.length > 0)
|
|
242
|
+
? codexPanes
|
|
243
|
+
: [{ paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' }];
|
|
244
|
+
const listPaneOutput = panes
|
|
245
|
+
.map((pane) => {
|
|
246
|
+
const paneId = pane.paneId;
|
|
247
|
+
const active = pane.active ? '1' : '0';
|
|
248
|
+
const currentCommand = pane.currentCommand || 'codex';
|
|
249
|
+
const startCommand = pane.startCommand || 'codex';
|
|
250
|
+
return `${paneId}\t${active}\t${currentCommand}\t${startCommand}`;
|
|
251
|
+
})
|
|
252
|
+
.join('\n');
|
|
253
|
+
const paneCommandBranches = panes
|
|
254
|
+
.map((pane) => {
|
|
255
|
+
const currentCommand = (pane.currentCommand || 'codex').replace(/"/g, '\\"');
|
|
256
|
+
const startCommand = (pane.startCommand || 'codex').replace(/"/g, '\\"');
|
|
257
|
+
return ` if [[ "$format" == "#{pane_current_command}" && "$target" == "${pane.paneId}" ]]; then
|
|
258
|
+
echo "${currentCommand}"
|
|
259
|
+
exit 0
|
|
260
|
+
fi
|
|
261
|
+
if [[ "$format" == "#{pane_start_command}" && "$target" == "${pane.paneId}" ]]; then
|
|
262
|
+
echo "${startCommand}"
|
|
263
|
+
exit 0
|
|
264
|
+
fi`;
|
|
265
|
+
})
|
|
266
|
+
.join('\n');
|
|
267
|
+
return `#!/usr/bin/env bash
|
|
268
|
+
set -eu
|
|
269
|
+
echo "$@" >> "${tmuxLogPath}"
|
|
270
|
+
cmd="$1"
|
|
271
|
+
shift || true
|
|
272
|
+
if [[ "$cmd" == "display-message" ]]; then
|
|
273
|
+
target=""
|
|
274
|
+
format=""
|
|
275
|
+
while [[ "$#" -gt 0 ]]; do
|
|
276
|
+
case "$1" in
|
|
277
|
+
-p) shift ;;
|
|
278
|
+
-t) target="$2"; shift 2 ;;
|
|
279
|
+
*) format="$1"; shift ;;
|
|
280
|
+
esac
|
|
281
|
+
done
|
|
282
|
+
if [[ "$target" == "${anchorPane}" && "${missingAnchor ? '1' : '0'}" == "1" ]]; then
|
|
283
|
+
echo "pane missing" >&2
|
|
284
|
+
exit 1
|
|
285
|
+
fi
|
|
286
|
+
if [[ "$format" == "#{pane_in_mode}" ]]; then
|
|
287
|
+
echo "0"
|
|
288
|
+
exit 0
|
|
289
|
+
fi
|
|
290
|
+
if [[ "$format" == "#{pane_id}" ]]; then
|
|
291
|
+
echo "$target"
|
|
292
|
+
exit 0
|
|
293
|
+
fi
|
|
294
|
+
if [[ "$format" == "#{pane_current_path}" ]]; then
|
|
295
|
+
echo "${cwd}"
|
|
296
|
+
exit 0
|
|
297
|
+
fi
|
|
298
|
+
${paneCommandBranches}
|
|
299
|
+
if [[ "$format" == "#S" ]]; then
|
|
300
|
+
if [[ "$target" == "${anchorPane}" || "$target" == "${livePane}" ]]; then
|
|
301
|
+
echo "${managedSessionName}"
|
|
302
|
+
exit 0
|
|
303
|
+
fi
|
|
304
|
+
echo "unknown target" >&2
|
|
305
|
+
exit 1
|
|
306
|
+
fi
|
|
307
|
+
exit 0
|
|
308
|
+
fi
|
|
309
|
+
if [[ "$cmd" == "list-panes" ]]; then
|
|
310
|
+
target=""
|
|
311
|
+
while [[ "$#" -gt 0 ]]; do
|
|
312
|
+
case "$1" in
|
|
313
|
+
-F) shift 2 ;;
|
|
314
|
+
-t) shift; target="$1" ;;
|
|
315
|
+
esac
|
|
316
|
+
shift || true
|
|
317
|
+
done
|
|
318
|
+
if [[ "$target" == "${managedSessionName}" ]]; then
|
|
319
|
+
printf '%s\n' "${listPaneOutput}"
|
|
320
|
+
exit 0
|
|
321
|
+
fi
|
|
322
|
+
echo "can't find session" >&2
|
|
323
|
+
exit 1
|
|
324
|
+
fi
|
|
325
|
+
if [[ "$cmd" == "capture-pane" ]]; then
|
|
326
|
+
exit 0
|
|
327
|
+
fi
|
|
328
|
+
if [[ "$cmd" == "send-keys" ]]; then
|
|
329
|
+
exit 0
|
|
330
|
+
fi
|
|
331
|
+
exit 0
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
239
334
|
function buildCleanNotifyEnv(overrides = {}) {
|
|
240
335
|
return {
|
|
241
336
|
...process.env,
|
|
@@ -1941,8 +2036,391 @@ exit 0
|
|
|
1941
2036
|
await rm(wd, { recursive: true, force: true });
|
|
1942
2037
|
}
|
|
1943
2038
|
});
|
|
2039
|
+
it('rebinds a stale-but-present session-scoped Ralph shell pane to the live pane before continue steer', async () => {
|
|
2040
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-stale-anchor-'));
|
|
2041
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2042
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2043
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2044
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2045
|
+
const sessionId = 'sess-ralph-rebind';
|
|
2046
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
2047
|
+
const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
|
|
2048
|
+
const anchorPane = '%99';
|
|
2049
|
+
const livePane = '%42';
|
|
2050
|
+
try {
|
|
2051
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
2052
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2053
|
+
await writeSessionStart(wd, sessionId);
|
|
2054
|
+
const managedSessionName = buildTmuxSessionName(wd, sessionId);
|
|
2055
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
|
|
2056
|
+
cwd: wd,
|
|
2057
|
+
managedSessionName,
|
|
2058
|
+
anchorPane,
|
|
2059
|
+
livePane,
|
|
2060
|
+
codexPanes: [
|
|
2061
|
+
{ paneId: anchorPane, active: false, currentCommand: 'sh', startCommand: 'bash' },
|
|
2062
|
+
{ paneId: '%41', active: false, currentCommand: 'codex', startCommand: 'codex' },
|
|
2063
|
+
{ paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
|
|
2064
|
+
],
|
|
2065
|
+
}));
|
|
2066
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2067
|
+
await writeFile(ralphStatePath, JSON.stringify({
|
|
2068
|
+
active: true,
|
|
2069
|
+
current_phase: 'executing',
|
|
2070
|
+
tmux_pane_id: anchorPane,
|
|
2071
|
+
}, null, 2));
|
|
2072
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2073
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2074
|
+
}, null, 2));
|
|
2075
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2076
|
+
ralph_continue_steer: {
|
|
2077
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2078
|
+
},
|
|
2079
|
+
}, null, 2));
|
|
2080
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2081
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2082
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2083
|
+
encoding: 'utf-8',
|
|
2084
|
+
env: buildCleanNotifyEnv({
|
|
2085
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2086
|
+
}),
|
|
2087
|
+
});
|
|
2088
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2089
|
+
const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
|
|
2090
|
+
assert.equal(persistedRalph.tmux_pane_id, livePane);
|
|
2091
|
+
assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
|
2092
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2093
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
|
|
2094
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
|
|
2095
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
2096
|
+
assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2097
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2098
|
+
}
|
|
2099
|
+
finally {
|
|
2100
|
+
await rm(wd, { recursive: true, force: true });
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
it('preserves newer Ralph state fields when a pane rebound happens after the state file advances', async () => {
|
|
2104
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-state-merge-'));
|
|
2105
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2106
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2107
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2108
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2109
|
+
const sessionId = 'sess-ralph-rebind-merge';
|
|
2110
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
2111
|
+
const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
|
|
2112
|
+
const anchorPane = '%99';
|
|
2113
|
+
const livePane = '%42';
|
|
2114
|
+
try {
|
|
2115
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
2116
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2117
|
+
await writeSessionStart(wd, sessionId);
|
|
2118
|
+
const managedSessionName = buildTmuxSessionName(wd, sessionId);
|
|
2119
|
+
const fakeTmux = `#!/usr/bin/env bash
|
|
2120
|
+
set -eu
|
|
2121
|
+
echo "$@" >> "${tmuxLogPath}"
|
|
2122
|
+
cmd="$1"
|
|
2123
|
+
shift || true
|
|
2124
|
+
if [[ "$cmd" == "display-message" ]]; then
|
|
2125
|
+
target=""
|
|
2126
|
+
format=""
|
|
2127
|
+
while [[ "$#" -gt 0 ]]; do
|
|
2128
|
+
case "$1" in
|
|
2129
|
+
-p) shift ;;
|
|
2130
|
+
-t) target="$2"; shift 2 ;;
|
|
2131
|
+
*) format="$1"; shift ;;
|
|
2132
|
+
esac
|
|
2133
|
+
done
|
|
2134
|
+
if [[ "$format" == "#{pane_in_mode}" ]]; then
|
|
2135
|
+
echo "0"
|
|
2136
|
+
exit 0
|
|
2137
|
+
fi
|
|
2138
|
+
if [[ "$format" == "#{pane_id}" ]]; then
|
|
2139
|
+
echo "$target"
|
|
2140
|
+
exit 0
|
|
2141
|
+
fi
|
|
2142
|
+
if [[ "$format" == "#{pane_current_path}" ]]; then
|
|
2143
|
+
echo "${wd}"
|
|
2144
|
+
exit 0
|
|
2145
|
+
fi
|
|
2146
|
+
if [[ "$format" == "#{pane_current_command}" && "$target" == "${anchorPane}" ]]; then
|
|
2147
|
+
echo "sh"
|
|
2148
|
+
exit 0
|
|
2149
|
+
fi
|
|
2150
|
+
if [[ "$format" == "#{pane_start_command}" && "$target" == "${anchorPane}" ]]; then
|
|
2151
|
+
echo "bash"
|
|
2152
|
+
exit 0
|
|
2153
|
+
fi
|
|
2154
|
+
if [[ "$format" == "#S" && "$target" == "${anchorPane}" ]]; then
|
|
2155
|
+
echo "${managedSessionName}"
|
|
2156
|
+
exit 0
|
|
2157
|
+
fi
|
|
2158
|
+
exit 0
|
|
2159
|
+
fi
|
|
2160
|
+
if [[ "$cmd" == "list-panes" ]]; then
|
|
2161
|
+
target=""
|
|
2162
|
+
while [[ "$#" -gt 0 ]]; do
|
|
2163
|
+
case "$1" in
|
|
2164
|
+
-F) shift 2 ;;
|
|
2165
|
+
-t) shift; target="$1" ;;
|
|
2166
|
+
esac
|
|
2167
|
+
shift || true
|
|
2168
|
+
done
|
|
2169
|
+
if [[ "$target" == "${managedSessionName}" ]]; then
|
|
2170
|
+
cat > "${ralphStatePath}" <<'JSON'
|
|
2171
|
+
{
|
|
2172
|
+
"active": true,
|
|
2173
|
+
"current_phase": "reviewing",
|
|
2174
|
+
"iteration": 11,
|
|
2175
|
+
"owner_codex_session_id": "codex-updated-owner",
|
|
2176
|
+
"tmux_pane_id": "%99"
|
|
2177
|
+
}
|
|
2178
|
+
JSON
|
|
2179
|
+
printf "%%99\t0\tsh\tbash\n%%42\t1\tcodex\tcodex\n"
|
|
2180
|
+
exit 0
|
|
2181
|
+
fi
|
|
2182
|
+
echo "can't find session" >&2
|
|
2183
|
+
exit 1
|
|
2184
|
+
fi
|
|
2185
|
+
if [[ "$cmd" == "capture-pane" ]]; then
|
|
2186
|
+
exit 0
|
|
2187
|
+
fi
|
|
2188
|
+
if [[ "$cmd" == "send-keys" ]]; then
|
|
2189
|
+
exit 0
|
|
2190
|
+
fi
|
|
2191
|
+
exit 0
|
|
2192
|
+
`;
|
|
2193
|
+
await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
|
|
2194
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2195
|
+
await writeFile(ralphStatePath, JSON.stringify({
|
|
2196
|
+
active: true,
|
|
2197
|
+
current_phase: 'executing',
|
|
2198
|
+
iteration: 1,
|
|
2199
|
+
owner_codex_session_id: 'codex-stale-owner',
|
|
2200
|
+
tmux_pane_id: anchorPane,
|
|
2201
|
+
}, null, 2));
|
|
2202
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2203
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2204
|
+
}, null, 2));
|
|
2205
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2206
|
+
ralph_continue_steer: {
|
|
2207
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2208
|
+
},
|
|
2209
|
+
}, null, 2));
|
|
2210
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2211
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2212
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2213
|
+
encoding: 'utf-8',
|
|
2214
|
+
env: buildCleanNotifyEnv({
|
|
2215
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2216
|
+
}),
|
|
2217
|
+
});
|
|
2218
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2219
|
+
const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
|
|
2220
|
+
assert.equal(persistedRalph.tmux_pane_id, livePane);
|
|
2221
|
+
assert.equal(persistedRalph.current_phase, 'reviewing');
|
|
2222
|
+
assert.equal(persistedRalph.iteration, 11);
|
|
2223
|
+
assert.equal(persistedRalph.owner_codex_session_id, 'codex-updated-owner');
|
|
2224
|
+
assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
|
2225
|
+
}
|
|
2226
|
+
finally {
|
|
2227
|
+
await rm(wd, { recursive: true, force: true });
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
it('keeps the verified Ralph anchor pane when another codex pane is focused in the same managed session', async () => {
|
|
2231
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-keep-anchor-pane-'));
|
|
2232
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2233
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2234
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2235
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2236
|
+
const sessionId = 'sess-ralph-keep-anchor';
|
|
2237
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
2238
|
+
const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
|
|
2239
|
+
const anchorPane = '%99';
|
|
2240
|
+
const livePane = '%42';
|
|
2241
|
+
try {
|
|
2242
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
2243
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2244
|
+
await writeSessionStart(wd, sessionId);
|
|
2245
|
+
const managedSessionName = buildTmuxSessionName(wd, sessionId);
|
|
2246
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
|
|
2247
|
+
cwd: wd,
|
|
2248
|
+
managedSessionName,
|
|
2249
|
+
anchorPane,
|
|
2250
|
+
livePane,
|
|
2251
|
+
codexPanes: [
|
|
2252
|
+
{ paneId: anchorPane, active: false, currentCommand: 'codex', startCommand: 'codex' },
|
|
2253
|
+
{ paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
|
|
2254
|
+
],
|
|
2255
|
+
}));
|
|
2256
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2257
|
+
await writeFile(ralphStatePath, JSON.stringify({
|
|
2258
|
+
active: true,
|
|
2259
|
+
current_phase: 'executing',
|
|
2260
|
+
tmux_pane_id: anchorPane,
|
|
2261
|
+
}, null, 2));
|
|
2262
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2263
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2264
|
+
}, null, 2));
|
|
2265
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2266
|
+
ralph_continue_steer: {
|
|
2267
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2268
|
+
},
|
|
2269
|
+
}, null, 2));
|
|
2270
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2271
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2272
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2273
|
+
encoding: 'utf-8',
|
|
2274
|
+
env: buildCleanNotifyEnv({
|
|
2275
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2276
|
+
}),
|
|
2277
|
+
});
|
|
2278
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2279
|
+
const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
|
|
2280
|
+
assert.equal(persistedRalph.tmux_pane_id, anchorPane);
|
|
2281
|
+
assert.equal(typeof persistedRalph.tmux_pane_set_at, 'undefined');
|
|
2282
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2283
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
|
|
2284
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, anchorPane);
|
|
2285
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
2286
|
+
assert.match(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2287
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2288
|
+
}
|
|
2289
|
+
finally {
|
|
2290
|
+
await rm(wd, { recursive: true, force: true });
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
it('rebinds a shell-degraded codex anchor to the live pane before continue steer', async () => {
|
|
2294
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-degraded-codex-anchor-'));
|
|
2295
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2296
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2297
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2298
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2299
|
+
const sessionId = 'sess-ralph-degraded-codex-anchor';
|
|
2300
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
2301
|
+
const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
|
|
2302
|
+
const anchorPane = '%99';
|
|
2303
|
+
const livePane = '%42';
|
|
2304
|
+
try {
|
|
2305
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
2306
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2307
|
+
await writeSessionStart(wd, sessionId);
|
|
2308
|
+
const managedSessionName = buildTmuxSessionName(wd, sessionId);
|
|
2309
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
|
|
2310
|
+
cwd: wd,
|
|
2311
|
+
managedSessionName,
|
|
2312
|
+
anchorPane,
|
|
2313
|
+
livePane,
|
|
2314
|
+
codexPanes: [
|
|
2315
|
+
{ paneId: anchorPane, active: true, currentCommand: 'bash', startCommand: 'codex --model gpt-5' },
|
|
2316
|
+
{ paneId: livePane, active: false, currentCommand: 'codex', startCommand: 'codex' },
|
|
2317
|
+
],
|
|
2318
|
+
}));
|
|
2319
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2320
|
+
await writeFile(ralphStatePath, JSON.stringify({
|
|
2321
|
+
active: true,
|
|
2322
|
+
current_phase: 'executing',
|
|
2323
|
+
tmux_pane_id: anchorPane,
|
|
2324
|
+
}, null, 2));
|
|
2325
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2326
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2327
|
+
}, null, 2));
|
|
2328
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2329
|
+
ralph_continue_steer: {
|
|
2330
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2331
|
+
},
|
|
2332
|
+
}, null, 2));
|
|
2333
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2334
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2335
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2336
|
+
encoding: 'utf-8',
|
|
2337
|
+
env: buildCleanNotifyEnv({
|
|
2338
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2339
|
+
}),
|
|
2340
|
+
});
|
|
2341
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2342
|
+
const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
|
|
2343
|
+
assert.equal(persistedRalph.tmux_pane_id, livePane);
|
|
2344
|
+
assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
|
2345
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2346
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
|
|
2347
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
|
|
2348
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
2349
|
+
assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2350
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2351
|
+
}
|
|
2352
|
+
finally {
|
|
2353
|
+
await rm(wd, { recursive: true, force: true });
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
it('falls back to the current managed session pane when the stored Ralph pane anchor is dead', async () => {
|
|
2357
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-dead-anchor-'));
|
|
2358
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2359
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2360
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2361
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2362
|
+
const sessionId = 'sess-ralph-dead-anchor';
|
|
2363
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
2364
|
+
const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
|
|
2365
|
+
const anchorPane = '%99';
|
|
2366
|
+
const livePane = '%42';
|
|
2367
|
+
try {
|
|
2368
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
2369
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2370
|
+
await writeSessionStart(wd, sessionId);
|
|
2371
|
+
const managedSessionName = buildTmuxSessionName(wd, sessionId);
|
|
2372
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
|
|
2373
|
+
cwd: wd,
|
|
2374
|
+
managedSessionName,
|
|
2375
|
+
anchorPane,
|
|
2376
|
+
livePane,
|
|
2377
|
+
codexPanes: [
|
|
2378
|
+
{ paneId: '%41', active: false, currentCommand: 'codex', startCommand: 'codex' },
|
|
2379
|
+
{ paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
|
|
2380
|
+
],
|
|
2381
|
+
missingAnchor: true,
|
|
2382
|
+
}));
|
|
2383
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2384
|
+
await writeFile(ralphStatePath, JSON.stringify({
|
|
2385
|
+
active: true,
|
|
2386
|
+
current_phase: 'executing',
|
|
2387
|
+
tmux_pane_id: anchorPane,
|
|
2388
|
+
}, null, 2));
|
|
2389
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2390
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2391
|
+
}, null, 2));
|
|
2392
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2393
|
+
ralph_continue_steer: {
|
|
2394
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2395
|
+
},
|
|
2396
|
+
}, null, 2));
|
|
2397
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2398
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2399
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2400
|
+
encoding: 'utf-8',
|
|
2401
|
+
env: buildCleanNotifyEnv({
|
|
2402
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2403
|
+
}),
|
|
2404
|
+
});
|
|
2405
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2406
|
+
const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
|
|
2407
|
+
assert.equal(persistedRalph.tmux_pane_id, livePane);
|
|
2408
|
+
assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
|
|
2409
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2410
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
|
|
2411
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
|
|
2412
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
2413
|
+
assert.match(tmuxLog, /display-message -p -t %99 #S/);
|
|
2414
|
+
assert.match(tmuxLog, /list-panes -s -t .*sess-ralph-dead-anchor/);
|
|
2415
|
+
assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2416
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
|
|
2417
|
+
}
|
|
2418
|
+
finally {
|
|
2419
|
+
await rm(wd, { recursive: true, force: true });
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
1944
2422
|
it('sends the first Ralph continue steer immediately when persisted steer state is empty', async () => {
|
|
1945
|
-
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-
|
|
2423
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-startup-cooldown-'));
|
|
1946
2424
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
1947
2425
|
const stateDir = join(wd, '.omx', 'state');
|
|
1948
2426
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
@@ -2029,6 +2507,51 @@ exit 0
|
|
|
2029
2507
|
await rm(wd, { recursive: true, force: true });
|
|
2030
2508
|
}
|
|
2031
2509
|
});
|
|
2510
|
+
it('treats blocked_on_user as terminal so Ralph continue steer stays off', async () => {
|
|
2511
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-blocked-on-user-'));
|
|
2512
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2513
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2514
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2515
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2516
|
+
try {
|
|
2517
|
+
await mkdir(stateDir, { recursive: true });
|
|
2518
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2519
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
2520
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2521
|
+
await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
|
|
2522
|
+
active: false,
|
|
2523
|
+
current_phase: 'blocked_on_user',
|
|
2524
|
+
completed_at: new Date().toISOString(),
|
|
2525
|
+
tmux_pane_id: '%42',
|
|
2526
|
+
}, null, 2));
|
|
2527
|
+
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
2528
|
+
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2529
|
+
}, null, 2));
|
|
2530
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2531
|
+
ralph_continue_steer: {
|
|
2532
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2533
|
+
},
|
|
2534
|
+
}, null, 2));
|
|
2535
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2536
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2537
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2538
|
+
encoding: 'utf-8',
|
|
2539
|
+
env: buildCleanNotifyEnv({
|
|
2540
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2541
|
+
}),
|
|
2542
|
+
});
|
|
2543
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2544
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
2545
|
+
const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
|
|
2546
|
+
assert.equal(sends.length, 0, 'blocked_on_user should suppress Ralph continue steer');
|
|
2547
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2548
|
+
assert.equal(watcherState.ralph_continue_steer?.active, false);
|
|
2549
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'terminal');
|
|
2550
|
+
}
|
|
2551
|
+
finally {
|
|
2552
|
+
await rm(wd, { recursive: true, force: true });
|
|
2553
|
+
}
|
|
2554
|
+
});
|
|
2032
2555
|
it('stops Ralph continue steer immediately once Ralph state is terminal or cleared', async () => {
|
|
2033
2556
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-terminal-'));
|
|
2034
2557
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -2133,6 +2656,51 @@ exit 0
|
|
|
2133
2656
|
await rm(wd, { recursive: true, force: true });
|
|
2134
2657
|
}
|
|
2135
2658
|
});
|
|
2659
|
+
it('treats an explicit blocked_on_user run_outcome as terminal for Ralph continue steer', async () => {
|
|
2660
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-blocked-on-user-'));
|
|
2661
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2662
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2663
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2664
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2665
|
+
try {
|
|
2666
|
+
await mkdir(stateDir, { recursive: true });
|
|
2667
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2668
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
2669
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2670
|
+
await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
|
|
2671
|
+
active: true,
|
|
2672
|
+
current_phase: 'executing',
|
|
2673
|
+
run_outcome: 'blocked_on_user',
|
|
2674
|
+
tmux_pane_id: '%42',
|
|
2675
|
+
}, null, 2));
|
|
2676
|
+
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
2677
|
+
last_progress_at: new Date(Date.now() - 5 * 60_000).toISOString(),
|
|
2678
|
+
}, null, 2));
|
|
2679
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2680
|
+
ralph_continue_steer: {
|
|
2681
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2682
|
+
},
|
|
2683
|
+
}, null, 2));
|
|
2684
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2685
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2686
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2687
|
+
encoding: 'utf-8',
|
|
2688
|
+
env: buildCleanNotifyEnv({
|
|
2689
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2690
|
+
}),
|
|
2691
|
+
});
|
|
2692
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2693
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
2694
|
+
const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
|
|
2695
|
+
assert.equal(sends.length, 0, 'blocked_on_user should suppress Ralph continue steer');
|
|
2696
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2697
|
+
assert.equal(watcherState.ralph_continue_steer?.active, false);
|
|
2698
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'terminal');
|
|
2699
|
+
}
|
|
2700
|
+
finally {
|
|
2701
|
+
await rm(wd, { recursive: true, force: true });
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2136
2704
|
it('globally debounces Ralph continue steer across concurrent watcher instances', async () => {
|
|
2137
2705
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-global-debounce-'));
|
|
2138
2706
|
const fakeBinDir = join(wd, 'fake-bin');
|