oh-my-codex 0.15.2 → 0.15.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.
- package/Cargo.lock +5 -5
- package/Cargo.toml +1 -1
- package/dist/agents/__tests__/native-config.test.js +33 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -1
- package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js +122 -0
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -2
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +1 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +40 -17
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +141 -8
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-serve.test.js +27 -1
- package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +59 -1
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +2 -1
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +55 -10
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +46 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +16 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +126 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.d.ts +1 -0
- package/dist/cli/mcp-serve.d.ts.map +1 -1
- package/dist/cli/mcp-serve.js +8 -0
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/ralph.d.ts +2 -0
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +17 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/team.d.ts +4 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +47 -22
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +27 -5
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/generator.d.ts +11 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +114 -58
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +59 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js +109 -18
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +21 -0
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +30 -14
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js +1 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +398 -14
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/team-exec.d.ts +8 -4
- package/dist/pipeline/stages/team-exec.d.ts.map +1 -1
- package/dist/pipeline/stages/team-exec.js +198 -13
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.js +246 -1
- package/dist/planning/__tests__/artifacts.test.js.map +1 -1
- package/dist/planning/artifact-names.d.ts +13 -0
- package/dist/planning/artifact-names.d.ts.map +1 -0
- package/dist/planning/artifact-names.js +108 -0
- package/dist/planning/artifact-names.js.map +1 -0
- package/dist/planning/artifacts.d.ts +22 -1
- package/dist/planning/artifacts.d.ts.map +1 -1
- package/dist/planning/artifacts.js +165 -50
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/ralph/__tests__/persistence.test.js +21 -1
- package/dist/ralph/__tests__/persistence.test.js.map +1 -1
- package/dist/ralph/persistence.d.ts.map +1 -1
- package/dist/ralph/persistence.js +6 -4
- package/dist/ralph/persistence.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +352 -2
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +85 -6
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +123 -0
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js +1 -1
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook.js +1 -1
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts +1 -0
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +8 -2
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +41 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +220 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +40 -9
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/repo-aware-decomposition.test.js +41 -0
- package/dist/team/__tests__/repo-aware-decomposition.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +24 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +446 -67
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +13 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/team-identity.test.d.ts +2 -0
- package/dist/team/__tests__/team-identity.test.d.ts.map +1 -0
- package/dist/team/__tests__/team-identity.test.js +166 -0
- package/dist/team/__tests__/team-identity.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.test.js +55 -1
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +12 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +1 -0
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +159 -129
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +3 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js +2 -0
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +32 -2
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +78 -26
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +338 -35
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/state.d.ts +9 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +21 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts +26 -0
- package/dist/team/team-identity.d.ts.map +1 -0
- package/dist/team/team-identity.js +169 -0
- package/dist/team/team-identity.js.map +1 -0
- package/dist/team/tmux-session.d.ts +18 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +61 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +2 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +10 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/skills/ai-slop-cleaner/SKILL.md +30 -5
- package/src/scripts/__tests__/codex-native-hook.test.ts +398 -2
- package/src/scripts/codex-native-hook.ts +115 -5
- package/src/scripts/codex-native-pre-post.ts +121 -0
- package/src/scripts/notify-hook/team-worker-posttooluse.ts +1 -1
- package/src/scripts/notify-hook.ts +1 -1
- package/src/scripts/sync-plugin-mirror.ts +11 -2
|
@@ -2420,6 +2420,147 @@ esac
|
|
|
2420
2420
|
}
|
|
2421
2421
|
});
|
|
2422
2422
|
|
|
2423
|
+
it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => {
|
|
2424
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
|
|
2425
|
+
try {
|
|
2426
|
+
const result = await dispatchCodexNativeHook(
|
|
2427
|
+
{
|
|
2428
|
+
hook_event_name: "PreToolUse",
|
|
2429
|
+
cwd,
|
|
2430
|
+
tool_name: "Bash",
|
|
2431
|
+
tool_use_id: "tool-slop-warn",
|
|
2432
|
+
tool_input: {
|
|
2433
|
+
command: [
|
|
2434
|
+
"cat > src/runtime.ts <<'EOF'",
|
|
2435
|
+
"export function loadRuntime() {",
|
|
2436
|
+
" // implement a quick hack fallback if it fails",
|
|
2437
|
+
" return process.env.RUNTIME || 'local';",
|
|
2438
|
+
"}",
|
|
2439
|
+
"EOF",
|
|
2440
|
+
].join("\n"),
|
|
2441
|
+
},
|
|
2442
|
+
},
|
|
2443
|
+
{ cwd },
|
|
2444
|
+
);
|
|
2445
|
+
|
|
2446
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2447
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined);
|
|
2448
|
+
assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse");
|
|
2449
|
+
assert.match(JSON.stringify(result.outputJson), /don't make potential slop/);
|
|
2450
|
+
assert.match(JSON.stringify(result.outputJson), /architect/);
|
|
2451
|
+
assert.match(JSON.stringify(result.outputJson), /environment issue/);
|
|
2452
|
+
} finally {
|
|
2453
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
it("does not warn on PreToolUse for read-only fallback text inspection", async () => {
|
|
2458
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-readonly-"));
|
|
2459
|
+
try {
|
|
2460
|
+
const result = await dispatchCodexNativeHook(
|
|
2461
|
+
{
|
|
2462
|
+
hook_event_name: "PreToolUse",
|
|
2463
|
+
cwd,
|
|
2464
|
+
tool_name: "Bash",
|
|
2465
|
+
tool_use_id: "tool-slop-readonly",
|
|
2466
|
+
tool_input: { command: "rg \"quick hack fallback if it fails\" src docs" },
|
|
2467
|
+
},
|
|
2468
|
+
{ cwd },
|
|
2469
|
+
);
|
|
2470
|
+
|
|
2471
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2472
|
+
assert.equal(result.outputJson, null);
|
|
2473
|
+
} finally {
|
|
2474
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2475
|
+
}
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
it("warns when a read-only command is chained before sloppy fallback writes", async () => {
|
|
2479
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-chained-write-"));
|
|
2480
|
+
try {
|
|
2481
|
+
const result = await dispatchCodexNativeHook(
|
|
2482
|
+
{
|
|
2483
|
+
hook_event_name: "PreToolUse",
|
|
2484
|
+
cwd,
|
|
2485
|
+
tool_name: "Bash",
|
|
2486
|
+
tool_use_id: "tool-slop-chained-write",
|
|
2487
|
+
tool_input: {
|
|
2488
|
+
command: [
|
|
2489
|
+
"rg foo src && cat > src/runtime.ts <<EOF",
|
|
2490
|
+
"export function loadRuntime() {",
|
|
2491
|
+
" // implement quick hack fallback if it fails",
|
|
2492
|
+
" return 'local';",
|
|
2493
|
+
"}",
|
|
2494
|
+
"EOF",
|
|
2495
|
+
].join("\n"),
|
|
2496
|
+
},
|
|
2497
|
+
},
|
|
2498
|
+
{ cwd },
|
|
2499
|
+
);
|
|
2500
|
+
|
|
2501
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2502
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, undefined);
|
|
2503
|
+
assert.equal((result.outputJson as { hookSpecificOutput?: { hookEventName?: string } } | null)?.hookSpecificOutput?.hookEventName, "PreToolUse");
|
|
2504
|
+
assert.match(JSON.stringify(result.outputJson), /don't make potential slop/);
|
|
2505
|
+
} finally {
|
|
2506
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2507
|
+
}
|
|
2508
|
+
});
|
|
2509
|
+
|
|
2510
|
+
it("does not warn on PreToolUse for grounded compatibility fallback code", async () => {
|
|
2511
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-grounded-"));
|
|
2512
|
+
try {
|
|
2513
|
+
const result = await dispatchCodexNativeHook(
|
|
2514
|
+
{
|
|
2515
|
+
hook_event_name: "PreToolUse",
|
|
2516
|
+
cwd,
|
|
2517
|
+
tool_name: "Bash",
|
|
2518
|
+
tool_use_id: "tool-slop-grounded",
|
|
2519
|
+
tool_input: {
|
|
2520
|
+
command: [
|
|
2521
|
+
"cat > src/compat.ts <<'EOF'",
|
|
2522
|
+
"export function resolveCompatMode() {",
|
|
2523
|
+
" // temporary fallback because legacy compatibility needs fail-safe startup behavior",
|
|
2524
|
+
" return 'legacy';",
|
|
2525
|
+
"}",
|
|
2526
|
+
"// Tested: npm test",
|
|
2527
|
+
"EOF",
|
|
2528
|
+
].join("\n"),
|
|
2529
|
+
},
|
|
2530
|
+
},
|
|
2531
|
+
{ cwd },
|
|
2532
|
+
);
|
|
2533
|
+
|
|
2534
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2535
|
+
assert.equal(result.outputJson, null);
|
|
2536
|
+
} finally {
|
|
2537
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
|
|
2541
|
+
it("keeps git commit Lore enforcement ahead of sloppy fallback advisory", async () => {
|
|
2542
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-git-priority-"));
|
|
2543
|
+
try {
|
|
2544
|
+
const result = await dispatchCodexNativeHook(
|
|
2545
|
+
{
|
|
2546
|
+
hook_event_name: "PreToolUse",
|
|
2547
|
+
cwd,
|
|
2548
|
+
tool_name: "Bash",
|
|
2549
|
+
tool_use_id: "tool-slop-git-priority",
|
|
2550
|
+
tool_input: { command: 'git commit -m "quick hack fallback if it fails"' },
|
|
2551
|
+
},
|
|
2552
|
+
{ cwd },
|
|
2553
|
+
);
|
|
2554
|
+
|
|
2555
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
2556
|
+
assert.equal((result.outputJson as { decision?: string } | null)?.decision, "block");
|
|
2557
|
+
assert.match(JSON.stringify(result.outputJson), /Lore protocol/);
|
|
2558
|
+
assert.doesNotMatch(JSON.stringify(result.outputJson), /don't make potential slop/);
|
|
2559
|
+
} finally {
|
|
2560
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2561
|
+
}
|
|
2562
|
+
});
|
|
2563
|
+
|
|
2423
2564
|
it("blocks PreToolUse git commit with supported response shape when the inline message is not Lore-compliant", async () => {
|
|
2424
2565
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-git-commit-invalid-"));
|
|
2425
2566
|
try {
|
|
@@ -3946,7 +4087,7 @@ esac
|
|
|
3946
4087
|
team_state_root: join(cwd, ".omx", "state"),
|
|
3947
4088
|
});
|
|
3948
4089
|
await writeJson(join(workerDir, "status.json"), {
|
|
3949
|
-
state: "
|
|
4090
|
+
state: "working",
|
|
3950
4091
|
current_task_id: "1",
|
|
3951
4092
|
updated_at: new Date().toISOString(),
|
|
3952
4093
|
});
|
|
@@ -3990,6 +4131,72 @@ esac
|
|
|
3990
4131
|
}
|
|
3991
4132
|
});
|
|
3992
4133
|
|
|
4134
|
+
it("does not block Stop as a team-worker task failure when worker status is already terminal", async () => {
|
|
4135
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-terminal-stale-"));
|
|
4136
|
+
const prevTeamWorker = process.env.OMX_TEAM_WORKER;
|
|
4137
|
+
const prevTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
|
|
4138
|
+
const prevLeaderCwd = process.env.OMX_TEAM_LEADER_CWD;
|
|
4139
|
+
try {
|
|
4140
|
+
await initTeamState(
|
|
4141
|
+
"worker-stale-team",
|
|
4142
|
+
"worker stale stop fallback",
|
|
4143
|
+
"executor",
|
|
4144
|
+
1,
|
|
4145
|
+
cwd,
|
|
4146
|
+
undefined,
|
|
4147
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-team-worker-stale" },
|
|
4148
|
+
);
|
|
4149
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4150
|
+
const workerCwd = join(cwd, ".omx", "team", "worker-stale-team", "worktrees", "worker-1");
|
|
4151
|
+
const workerDir = join(stateDir, "team", "worker-stale-team", "workers", "worker-1");
|
|
4152
|
+
await mkdir(workerCwd, { recursive: true });
|
|
4153
|
+
await writeJson(join(workerDir, "identity.json"), {
|
|
4154
|
+
name: "worker-1",
|
|
4155
|
+
index: 1,
|
|
4156
|
+
role: "executor",
|
|
4157
|
+
assigned_tasks: ["1"],
|
|
4158
|
+
worktree_path: workerCwd,
|
|
4159
|
+
team_state_root: stateDir,
|
|
4160
|
+
});
|
|
4161
|
+
await writeJson(join(workerDir, "status.json"), {
|
|
4162
|
+
state: "done",
|
|
4163
|
+
current_task_id: "1",
|
|
4164
|
+
updated_at: new Date().toISOString(),
|
|
4165
|
+
});
|
|
4166
|
+
await writeJson(join(stateDir, "team", "worker-stale-team", "tasks", "task-1.json"), {
|
|
4167
|
+
id: "1",
|
|
4168
|
+
subject: "stale hook task",
|
|
4169
|
+
description: "stale task should not trap terminal worker Stop",
|
|
4170
|
+
status: "in_progress",
|
|
4171
|
+
owner: "worker-1",
|
|
4172
|
+
created_at: new Date().toISOString(),
|
|
4173
|
+
});
|
|
4174
|
+
|
|
4175
|
+
process.env.OMX_TEAM_WORKER = "worker-stale-team/worker-1";
|
|
4176
|
+
process.env.OMX_TEAM_STATE_ROOT = stateDir;
|
|
4177
|
+
process.env.OMX_TEAM_LEADER_CWD = cwd;
|
|
4178
|
+
|
|
4179
|
+
const result = await dispatchCodexNativeHook(
|
|
4180
|
+
{
|
|
4181
|
+
hook_event_name: "Stop",
|
|
4182
|
+
cwd: workerCwd,
|
|
4183
|
+
session_id: "sess-stop-team-worker-stale",
|
|
4184
|
+
},
|
|
4185
|
+
{ cwd: workerCwd },
|
|
4186
|
+
);
|
|
4187
|
+
|
|
4188
|
+
assert.equal((result.outputJson as { stopReason?: string } | null)?.stopReason, "team_team-exec");
|
|
4189
|
+
} finally {
|
|
4190
|
+
if (typeof prevTeamWorker === "string") process.env.OMX_TEAM_WORKER = prevTeamWorker;
|
|
4191
|
+
else delete process.env.OMX_TEAM_WORKER;
|
|
4192
|
+
if (typeof prevTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = prevTeamStateRoot;
|
|
4193
|
+
else delete process.env.OMX_TEAM_STATE_ROOT;
|
|
4194
|
+
if (typeof prevLeaderCwd === "string") process.env.OMX_TEAM_LEADER_CWD = prevLeaderCwd;
|
|
4195
|
+
else delete process.env.OMX_TEAM_LEADER_CWD;
|
|
4196
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4197
|
+
}
|
|
4198
|
+
});
|
|
4199
|
+
|
|
3993
4200
|
it("suppresses identical team worker Stop replays but re-blocks fresh turns and task state changes", async () => {
|
|
3994
4201
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-worker-repeat-"));
|
|
3995
4202
|
try {
|
|
@@ -4016,7 +4223,7 @@ esac
|
|
|
4016
4223
|
team_state_root: stateDir,
|
|
4017
4224
|
});
|
|
4018
4225
|
await writeJson(join(workerDir, "status.json"), {
|
|
4019
|
-
state: "
|
|
4226
|
+
state: "working",
|
|
4020
4227
|
current_task_id: "1",
|
|
4021
4228
|
updated_at: new Date().toISOString(),
|
|
4022
4229
|
});
|
|
@@ -4648,6 +4855,100 @@ esac
|
|
|
4648
4855
|
}
|
|
4649
4856
|
});
|
|
4650
4857
|
|
|
4858
|
+
it("does not block on stale ralplan skill-active when canonical run-state is terminal", async () => {
|
|
4859
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-terminal-ralplan-run-"));
|
|
4860
|
+
try {
|
|
4861
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4862
|
+
const sessionId = "sess-stop-terminal-ralplan";
|
|
4863
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
4864
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
4865
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
4866
|
+
active: true,
|
|
4867
|
+
skill: "ralplan",
|
|
4868
|
+
phase: "planning",
|
|
4869
|
+
session_id: sessionId,
|
|
4870
|
+
active_skills: [{
|
|
4871
|
+
skill: "ralplan",
|
|
4872
|
+
phase: "planning",
|
|
4873
|
+
active: true,
|
|
4874
|
+
session_id: sessionId,
|
|
4875
|
+
}],
|
|
4876
|
+
});
|
|
4877
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
4878
|
+
active: true,
|
|
4879
|
+
mode: "ralplan",
|
|
4880
|
+
current_phase: "planning",
|
|
4881
|
+
session_id: sessionId,
|
|
4882
|
+
});
|
|
4883
|
+
await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
|
|
4884
|
+
version: 1,
|
|
4885
|
+
mode: "ralplan",
|
|
4886
|
+
active: false,
|
|
4887
|
+
outcome: "finish",
|
|
4888
|
+
lifecycle_outcome: "finished",
|
|
4889
|
+
current_phase: "complete",
|
|
4890
|
+
completed_at: "2026-05-01T00:00:00.000Z",
|
|
4891
|
+
updated_at: "2026-05-01T00:00:00.000Z",
|
|
4892
|
+
});
|
|
4893
|
+
|
|
4894
|
+
const result = await dispatchCodexNativeHook(
|
|
4895
|
+
{
|
|
4896
|
+
hook_event_name: "Stop",
|
|
4897
|
+
cwd,
|
|
4898
|
+
session_id: sessionId,
|
|
4899
|
+
},
|
|
4900
|
+
{ cwd },
|
|
4901
|
+
);
|
|
4902
|
+
|
|
4903
|
+
assert.equal(result.omxEventName, "stop");
|
|
4904
|
+
assert.equal(result.outputJson, null);
|
|
4905
|
+
} finally {
|
|
4906
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4907
|
+
}
|
|
4908
|
+
});
|
|
4909
|
+
|
|
4910
|
+
it("does not block on stale ralplan skill-active when pinned mode state belongs to another session", async () => {
|
|
4911
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-foreign-ralplan-"));
|
|
4912
|
+
try {
|
|
4913
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
4914
|
+
const sessionId = "sess-stop-current-ralplan";
|
|
4915
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
4916
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
4917
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
4918
|
+
active: true,
|
|
4919
|
+
skill: "ralplan",
|
|
4920
|
+
phase: "planning",
|
|
4921
|
+
session_id: sessionId,
|
|
4922
|
+
active_skills: [{
|
|
4923
|
+
skill: "ralplan",
|
|
4924
|
+
phase: "planning",
|
|
4925
|
+
active: true,
|
|
4926
|
+
session_id: sessionId,
|
|
4927
|
+
}],
|
|
4928
|
+
});
|
|
4929
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
4930
|
+
active: true,
|
|
4931
|
+
mode: "ralplan",
|
|
4932
|
+
current_phase: "planning",
|
|
4933
|
+
session_id: "sess-other-ralplan",
|
|
4934
|
+
});
|
|
4935
|
+
|
|
4936
|
+
const result = await dispatchCodexNativeHook(
|
|
4937
|
+
{
|
|
4938
|
+
hook_event_name: "Stop",
|
|
4939
|
+
cwd,
|
|
4940
|
+
session_id: sessionId,
|
|
4941
|
+
},
|
|
4942
|
+
{ cwd },
|
|
4943
|
+
);
|
|
4944
|
+
|
|
4945
|
+
assert.equal(result.omxEventName, "stop");
|
|
4946
|
+
assert.equal(result.outputJson, null);
|
|
4947
|
+
} finally {
|
|
4948
|
+
await rm(cwd, { recursive: true, force: true });
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4651
4952
|
it("does not block on active ralplan skill when subagents are still active", async () => {
|
|
4652
4953
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-subagent-"));
|
|
4653
4954
|
try {
|
|
@@ -5654,6 +5955,101 @@ esac
|
|
|
5654
5955
|
}
|
|
5655
5956
|
});
|
|
5656
5957
|
|
|
5958
|
+
it("does not block a question-only pane from Ralph state owned by another Codex session", async () => {
|
|
5959
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-question-pane-"));
|
|
5960
|
+
const previousTmuxPane = process.env.TMUX_PANE;
|
|
5961
|
+
try {
|
|
5962
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5963
|
+
const questionSessionId = "sess-question-pane";
|
|
5964
|
+
const questionNativeSessionId = "codex-question-pane";
|
|
5965
|
+
await mkdir(join(stateDir, "sessions", questionSessionId), { recursive: true });
|
|
5966
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
5967
|
+
session_id: questionSessionId,
|
|
5968
|
+
native_session_id: questionNativeSessionId,
|
|
5969
|
+
cwd,
|
|
5970
|
+
});
|
|
5971
|
+
await writeJson(join(stateDir, "sessions", questionSessionId, "ralph-state.json"), {
|
|
5972
|
+
active: true,
|
|
5973
|
+
mode: "ralph",
|
|
5974
|
+
current_phase: "executing",
|
|
5975
|
+
session_id: questionSessionId,
|
|
5976
|
+
owner_omx_session_id: "sess-ralph-owner",
|
|
5977
|
+
owner_codex_session_id: "codex-ralph-owner",
|
|
5978
|
+
thread_id: "thread-ralph-owner",
|
|
5979
|
+
tmux_pane_id: "%41",
|
|
5980
|
+
});
|
|
5981
|
+
|
|
5982
|
+
process.env.TMUX_PANE = "%99";
|
|
5983
|
+
const result = await dispatchCodexNativeHook(
|
|
5984
|
+
{
|
|
5985
|
+
hook_event_name: "Stop",
|
|
5986
|
+
cwd,
|
|
5987
|
+
session_id: questionNativeSessionId,
|
|
5988
|
+
thread_id: "thread-question-pane",
|
|
5989
|
+
},
|
|
5990
|
+
{ cwd },
|
|
5991
|
+
);
|
|
5992
|
+
|
|
5993
|
+
assert.equal(result.omxEventName, "stop");
|
|
5994
|
+
assert.equal(result.outputJson, null);
|
|
5995
|
+
} finally {
|
|
5996
|
+
if (typeof previousTmuxPane === "string") process.env.TMUX_PANE = previousTmuxPane;
|
|
5997
|
+
else delete process.env.TMUX_PANE;
|
|
5998
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5999
|
+
}
|
|
6000
|
+
});
|
|
6001
|
+
|
|
6002
|
+
it("blocks same-session Ralph Stop continuation when ownership identifiers match", async () => {
|
|
6003
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-owned-session-"));
|
|
6004
|
+
const previousTmuxPane = process.env.TMUX_PANE;
|
|
6005
|
+
try {
|
|
6006
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6007
|
+
const omxSessionId = "sess-ralph-owned";
|
|
6008
|
+
const nativeSessionId = "codex-ralph-owned";
|
|
6009
|
+
await mkdir(join(stateDir, "sessions", omxSessionId), { recursive: true });
|
|
6010
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
6011
|
+
session_id: omxSessionId,
|
|
6012
|
+
native_session_id: nativeSessionId,
|
|
6013
|
+
cwd,
|
|
6014
|
+
});
|
|
6015
|
+
await writeJson(join(stateDir, "sessions", omxSessionId, "ralph-state.json"), {
|
|
6016
|
+
active: true,
|
|
6017
|
+
mode: "ralph",
|
|
6018
|
+
current_phase: "executing",
|
|
6019
|
+
session_id: omxSessionId,
|
|
6020
|
+
owner_omx_session_id: omxSessionId,
|
|
6021
|
+
owner_codex_session_id: nativeSessionId,
|
|
6022
|
+
thread_id: "thread-ralph-owned",
|
|
6023
|
+
tmux_pane_id: "%42",
|
|
6024
|
+
});
|
|
6025
|
+
|
|
6026
|
+
process.env.TMUX_PANE = "%42";
|
|
6027
|
+
const result = await dispatchCodexNativeHook(
|
|
6028
|
+
{
|
|
6029
|
+
hook_event_name: "Stop",
|
|
6030
|
+
cwd,
|
|
6031
|
+
session_id: nativeSessionId,
|
|
6032
|
+
thread_id: "thread-ralph-owned",
|
|
6033
|
+
},
|
|
6034
|
+
{ cwd },
|
|
6035
|
+
);
|
|
6036
|
+
|
|
6037
|
+
assert.equal(result.omxEventName, "stop");
|
|
6038
|
+
assert.deepEqual(result.outputJson, {
|
|
6039
|
+
decision: "block",
|
|
6040
|
+
reason:
|
|
6041
|
+
"OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-ralph-owned/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
|
|
6042
|
+
stopReason: "ralph_executing",
|
|
6043
|
+
systemMessage:
|
|
6044
|
+
"OMX Ralph is still active (phase: executing; state: .omx/state/sessions/sess-ralph-owned/ralph-state.json); continue the task and gather fresh verification evidence before stopping.",
|
|
6045
|
+
});
|
|
6046
|
+
} finally {
|
|
6047
|
+
if (typeof previousTmuxPane === "string") process.env.TMUX_PANE = previousTmuxPane;
|
|
6048
|
+
else delete process.env.TMUX_PANE;
|
|
6049
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6050
|
+
}
|
|
6051
|
+
});
|
|
6052
|
+
|
|
5657
6053
|
it("prefers canonical run-state terminal lifecycle before stale session Ralph state during Stop", async () => {
|
|
5658
6054
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-canonical-run-state-ralph-"));
|
|
5659
6055
|
try {
|
|
@@ -107,9 +107,10 @@ export interface NativeHookDispatchResult {
|
|
|
107
107
|
outputJson: Record<string, unknown> | null;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
const TERMINAL_MODE_PHASES = new Set(["complete", "failed", "cancelled"]);
|
|
110
|
+
const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancelled"]);
|
|
111
111
|
const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
|
|
112
112
|
const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
|
|
113
|
+
const TEAM_WORKER_STOP_ACTIVE_STATES = new Set(["working", "blocked"]);
|
|
113
114
|
const NATIVE_STOP_STATE_FILE = "native-stop-state.json";
|
|
114
115
|
const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
115
116
|
/^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
@@ -444,10 +445,59 @@ interface ActiveRalphStopState {
|
|
|
444
445
|
path: string;
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
interface RalphStopOwnershipContext {
|
|
449
|
+
sessionId: string;
|
|
450
|
+
payloadSessionId: string;
|
|
451
|
+
threadId: string;
|
|
452
|
+
currentNativeSessionId: string;
|
|
453
|
+
tmuxPaneId: string;
|
|
454
|
+
}
|
|
455
|
+
|
|
447
456
|
function isRalphStartingPhase(state: Record<string, unknown>): boolean {
|
|
448
457
|
return safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase() === "starting";
|
|
449
458
|
}
|
|
450
459
|
|
|
460
|
+
function hasValue(values: string[], value: string): boolean {
|
|
461
|
+
return value !== "" && values.some((candidate) => candidate === value);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function activeRalphStateMatchesStopOwner(
|
|
465
|
+
state: Record<string, unknown>,
|
|
466
|
+
context: RalphStopOwnershipContext,
|
|
467
|
+
): boolean {
|
|
468
|
+
const ownerOmxSessionId = safeString(state.owner_omx_session_id).trim();
|
|
469
|
+
if (ownerOmxSessionId && ownerOmxSessionId !== context.sessionId) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const stateSessionId = safeString(state.session_id).trim();
|
|
474
|
+
if (!ownerOmxSessionId && stateSessionId && stateSessionId !== context.sessionId) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const codexOwnerSessionId = safeString(state.owner_codex_session_id).trim();
|
|
479
|
+
if (codexOwnerSessionId) {
|
|
480
|
+
const stopCodexSessionIds = [
|
|
481
|
+
context.payloadSessionId,
|
|
482
|
+
context.currentNativeSessionId,
|
|
483
|
+
context.sessionId,
|
|
484
|
+
].filter(Boolean);
|
|
485
|
+
if (!hasValue(stopCodexSessionIds, codexOwnerSessionId)) return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const stateThreadId = safeString(state.owner_codex_thread_id ?? state.thread_id).trim();
|
|
489
|
+
if (stateThreadId && context.threadId && stateThreadId !== context.threadId) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const statePaneId = safeString(state.tmux_pane_id).trim();
|
|
494
|
+
if (statePaneId && context.tmuxPaneId && statePaneId !== context.tmuxPaneId) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
|
|
451
501
|
function shouldHonorCanonicalTerminalRunState(
|
|
452
502
|
runState: Record<string, unknown> | null,
|
|
453
503
|
mode: string,
|
|
@@ -481,6 +531,11 @@ async function isVisibleRalphActiveForSession(cwd: string, sessionId: string): P
|
|
|
481
531
|
async function readActiveRalphState(
|
|
482
532
|
stateDir: string,
|
|
483
533
|
preferredSessionId?: string,
|
|
534
|
+
ownerContext?: {
|
|
535
|
+
payloadSessionId?: string;
|
|
536
|
+
threadId?: string;
|
|
537
|
+
tmuxPaneId?: string;
|
|
538
|
+
},
|
|
484
539
|
): Promise<ActiveRalphStopState | null> {
|
|
485
540
|
const cwd = resolve(stateDir, "..", "..");
|
|
486
541
|
const [rawSessionInfo, usableSessionInfo] = await Promise.all([
|
|
@@ -488,6 +543,7 @@ async function readActiveRalphState(
|
|
|
488
543
|
readUsableSessionState(cwd),
|
|
489
544
|
]);
|
|
490
545
|
const currentOmxSessionId = safeString(usableSessionInfo?.session_id).trim();
|
|
546
|
+
const currentNativeSessionId = safeString(usableSessionInfo?.native_session_id).trim();
|
|
491
547
|
const staleCurrentSessionId = rawSessionInfo && !isSessionStateUsable(rawSessionInfo, cwd)
|
|
492
548
|
? safeString(rawSessionInfo.session_id).trim()
|
|
493
549
|
: "";
|
|
@@ -515,7 +571,17 @@ async function readActiveRalphState(
|
|
|
515
571
|
) {
|
|
516
572
|
continue;
|
|
517
573
|
}
|
|
518
|
-
if (
|
|
574
|
+
if (
|
|
575
|
+
sessionScoped?.active === true
|
|
576
|
+
&& shouldContinueRun(sessionScoped)
|
|
577
|
+
&& activeRalphStateMatchesStopOwner(sessionScoped, {
|
|
578
|
+
sessionId,
|
|
579
|
+
payloadSessionId: safeString(ownerContext?.payloadSessionId).trim(),
|
|
580
|
+
threadId: safeString(ownerContext?.threadId).trim(),
|
|
581
|
+
currentNativeSessionId,
|
|
582
|
+
tmuxPaneId: safeString(ownerContext?.tmuxPaneId).trim(),
|
|
583
|
+
})
|
|
584
|
+
) {
|
|
519
585
|
return { state: sessionScoped, path: sessionScopedPath };
|
|
520
586
|
}
|
|
521
587
|
}
|
|
@@ -1131,7 +1197,7 @@ async function resolveTeamStateDirForWorkerContext(
|
|
|
1131
1197
|
async function buildTeamWorkerStopOutput(
|
|
1132
1198
|
cwd: string,
|
|
1133
1199
|
): Promise<Record<string, unknown> | null> {
|
|
1134
|
-
const workerContext = parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_WORKER));
|
|
1200
|
+
const workerContext = parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER));
|
|
1135
1201
|
if (!workerContext) return null;
|
|
1136
1202
|
|
|
1137
1203
|
const stateDir = await resolveTeamStateDirForWorkerContext(cwd, workerContext);
|
|
@@ -1142,6 +1208,9 @@ async function buildTeamWorkerStopOutput(
|
|
|
1142
1208
|
readJsonIfExists(join(workerRoot, "status.json")),
|
|
1143
1209
|
]);
|
|
1144
1210
|
|
|
1211
|
+
const workerState = safeString(status?.state).trim().toLowerCase();
|
|
1212
|
+
if (!TEAM_WORKER_STOP_ACTIVE_STATES.has(workerState)) return null;
|
|
1213
|
+
|
|
1145
1214
|
const candidateTaskIds = new Set<string>();
|
|
1146
1215
|
const currentTaskId = safeString(status?.current_task_id).trim();
|
|
1147
1216
|
if (currentTaskId) candidateTaskIds.add(currentTaskId);
|
|
@@ -1171,7 +1240,7 @@ async function buildTeamWorkerStopOutput(
|
|
|
1171
1240
|
}
|
|
1172
1241
|
|
|
1173
1242
|
function hasTeamWorkerContext(): boolean {
|
|
1174
|
-
return parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_WORKER)) !== null;
|
|
1243
|
+
return parseTeamWorkerEnv(safeString(process.env.OMX_TEAM_INTERNAL_WORKER || process.env.OMX_TEAM_WORKER)) !== null;
|
|
1175
1244
|
}
|
|
1176
1245
|
|
|
1177
1246
|
function isStopExempt(payload: CodexHookPayload): boolean {
|
|
@@ -1366,6 +1435,36 @@ function matchesSkillStopContext(
|
|
|
1366
1435
|
return true;
|
|
1367
1436
|
}
|
|
1368
1437
|
|
|
1438
|
+
function modeStateMatchesSkillStopContext(
|
|
1439
|
+
state: Record<string, unknown>,
|
|
1440
|
+
cwd: string,
|
|
1441
|
+
sessionId: string,
|
|
1442
|
+
): boolean {
|
|
1443
|
+
const stateSessionId = safeString(
|
|
1444
|
+
state.owner_omx_session_id
|
|
1445
|
+
?? state.session_id
|
|
1446
|
+
?? state.codex_session_id
|
|
1447
|
+
?? state.owner_codex_session_id,
|
|
1448
|
+
).trim();
|
|
1449
|
+
if (sessionId && stateSessionId && stateSessionId !== sessionId) return false;
|
|
1450
|
+
|
|
1451
|
+
const stateCwd = safeString(
|
|
1452
|
+
state.cwd
|
|
1453
|
+
?? state.workingDirectory
|
|
1454
|
+
?? state.working_directory
|
|
1455
|
+
?? state.project_path,
|
|
1456
|
+
).trim();
|
|
1457
|
+
if (stateCwd) {
|
|
1458
|
+
try {
|
|
1459
|
+
if (resolve(stateCwd) !== resolve(cwd)) return false;
|
|
1460
|
+
} catch {
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1369
1468
|
async function readBlockingSkillForStop(
|
|
1370
1469
|
cwd: string,
|
|
1371
1470
|
sessionId: string,
|
|
@@ -1379,8 +1478,15 @@ async function readBlockingSkillForStop(
|
|
|
1379
1478
|
: [...SKILL_STOP_BLOCKERS];
|
|
1380
1479
|
|
|
1381
1480
|
for (const skill of candidateSkills) {
|
|
1481
|
+
const terminalRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, skill);
|
|
1482
|
+
if (terminalRunState) continue;
|
|
1483
|
+
|
|
1382
1484
|
const modeState = await readStopSessionPinnedState(`${skill}-state.json`, cwd, sessionId);
|
|
1383
1485
|
if (!modeState || modeState.active !== true) continue;
|
|
1486
|
+
if (!modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) continue;
|
|
1487
|
+
|
|
1488
|
+
const modeSnapshot = getRunContinuationSnapshot(modeState);
|
|
1489
|
+
if (modeSnapshot?.terminal === true) continue;
|
|
1384
1490
|
|
|
1385
1491
|
const phase = formatPhase(
|
|
1386
1492
|
modeState.current_phase,
|
|
@@ -1865,7 +1971,11 @@ async function buildStopHookOutput(
|
|
|
1865
1971
|
const threadId = readPayloadThreadId(payload);
|
|
1866
1972
|
const execFollowupOutput = await buildExecFollowupStopOutput(cwd, canonicalSessionId);
|
|
1867
1973
|
if (execFollowupOutput) return execFollowupOutput;
|
|
1868
|
-
const ralphState = await readActiveRalphState(stateDir, canonicalSessionId
|
|
1974
|
+
const ralphState = await readActiveRalphState(stateDir, canonicalSessionId, {
|
|
1975
|
+
payloadSessionId: sessionId,
|
|
1976
|
+
threadId,
|
|
1977
|
+
tmuxPaneId: safeString(process.env.TMUX_PANE).trim(),
|
|
1978
|
+
});
|
|
1869
1979
|
if (!ralphState) {
|
|
1870
1980
|
const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
|
|
1871
1981
|
if (autoresearchState) {
|