/** * @file Unit tests for the codex evidence safety net: nudges a live codex pane * to run `agenthub stage --evidence` when the lane has sat at an inferred ( * evidence-less) stage past the delay, without ever writing stage itself. * @author Nguyễn Ngọc Trí Vĩ */ const os = require("node:os"); const path = require("node:path"); const SUITE_DB_PATH = path.join( os.tmpdir(), `dashboard-codex-nudge-${Date.now()}-${process.pid}.db` ); process.env.DASHBOARD_DB_PATH = SUITE_DB_PATH; const { describe, it, beforeEach, afterEach } = require("node:test"); const assert = require("node:assert/strict"); const { db } = require("../db"); const lanes = require("../lib/lanes"); const tmux = require("../lib/tmux"); const nudge = require("../lib/codex-nudge"); /** Backdate the current stage's enteredAt so it looks `minutes` old. */ function backdateStage(laneId, minutes) { const lane = lanes.getLane(laneId); const stages = { ...lane.stages }; stages[lane.stage] = { ...stages[lane.stage], enteredAt: new Date(Date.now() - minutes * 60_000).toISOString(), }; db.prepare("UPDATE lanes SET stages = ? WHERE id = ?").run(JSON.stringify(stages), laneId); } describe("codex-nudge sweep", () => { let calls; beforeEach(() => { calls = []; tmux.__setExecImpl((args) => { calls.push(args); if (args[0] === "display-message") return "codex"; // pane running the codex CLI return ""; }); nudge.__resetNudgeState(); }); afterEach(() => { tmux.__reset(); }); function makeCodexLane({ cwd, stage = "tests", evidence }) { const lane = lanes.createLane({ title: "cx", cwd, tool: "codex" }); lanes.updateLane(lane.id, { run_id: `agenthub-lane-${lane.id}` }); lanes.setStage(lane.id, { stage, evidence }); return lane; } it("nudges once the lane has held an evidence-less stage past the delay", () => { const cwd = os.tmpdir(); const lane = makeCodexLane({ cwd: `${cwd}/nudge-a` }); backdateStage(lane.id, 3); // older than the 2 min delay assert.equal(nudge.sweepCodexNudges(), 1); const sendKeys = calls.filter((a) => a[0] === "send-keys" && a[3] === "-l"); assert.equal(sendKeys.length, 1); assert.match(sendKeys[0][4], /agenthub stage tests --tool codex --evidence/); // A second pass for the same still-unevidenced stage must not repeat it. assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never nudges before the delay has elapsed", () => { const cwd = os.tmpdir(); const lane = makeCodexLane({ cwd: `${cwd}/nudge-b` }); // enteredAt is "now" — setStage just wrote it. assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never nudges a stage the session already backed with evidence", () => { const cwd = os.tmpdir(); const lane = makeCodexLane({ cwd: `${cwd}/nudge-c`, evidence: "12 tests green" }); backdateStage(lane.id, 10); assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never nudges a lane that has not entered a pipeline node", () => { const cwd = os.tmpdir(); const lane = lanes.createLane({ title: "cx", cwd: `${cwd}/nudge-h`, tool: "codex" }); lanes.updateLane(lane.id, { run_id: `agenthub-lane-${lane.id}` }); assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never nudges a claude lane", () => { const cwd = os.tmpdir(); const lane = lanes.createLane({ title: "cc", cwd: `${cwd}/nudge-d`, tool: "claude" }); lanes.updateLane(lane.id, { run_id: `agenthub-lane-${lane.id}` }); lanes.setStage(lane.id, { stage: "tests" }); backdateStage(lane.id, 3); assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never nudges a lane with no live run", () => { const cwd = os.tmpdir(); const lane = lanes.createLane({ title: "cx", cwd: `${cwd}/nudge-e`, tool: "codex" }); lanes.setStage(lane.id, { stage: "tests" }); backdateStage(lane.id, 3); assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("never types into a pane sitting at a bare shell prompt", () => { const cwd = os.tmpdir(); const lane = makeCodexLane({ cwd: `${cwd}/nudge-f` }); backdateStage(lane.id, 3); tmux.__setExecImpl((args) => { calls.push(args); if (args[0] === "display-message") return "bash"; return ""; }); assert.equal(nudge.sweepCodexNudges(), 0); lanes.deleteLane(lane.id); }); it("nudges again once the lane moves to another evidence-less stage", () => { const cwd = os.tmpdir(); const lane = makeCodexLane({ cwd: `${cwd}/nudge-g` }); backdateStage(lane.id, 3); assert.equal(nudge.sweepCodexNudges(), 1); lanes.setStage(lane.id, { stage: "review" }); backdateStage(lane.id, 3); assert.equal(nudge.sweepCodexNudges(), 1); lanes.deleteLane(lane.id); }); });