- AgentTree.restart() revives a stopped run in the same session: resets the root, archives old branches, and restores spawnability (stop no longer cancels the root). - ResearchLoop.start() restarts from stopped/completed and resets iteration counters; setGoal is allowed in terminal states; supervisor 'start' revives the tree when the root is not spawnable. - Cancelled/failed/archived children no longer count toward the per-agent child limit and archived agents are ignored by the duplicate-task guard. - cancel() no longer overwrites an already recorded agent result. - Add agent_timeout_seconds (default 1800) so hung agent sessions fail instead of blocking wait() forever. - FTS search now prefix-matches tokens (tolerates inflections) and safely handles punctuation/FTS5 metacharacters instead of returning false negatives or throwing. - Add tests for restart semantics, child limits, result preservation, agent timeouts, and FTS morphology/special characters (38/38 passing).
16 lines
2.4 KiB
TypeScript
16 lines
2.4 KiB
TypeScript
import { mkdtempSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { DEFAULT_CONFIG } from "../src/config.js";
|
|
import { ResearchLoop } from "../src/research-loop.js";
|
|
|
|
const report = (newFindings = 0) => ({ goal: "goal", tasks: ["task"], activeAgents: [], expectedOutput: "finding", state: "synthesized", newFindings, closedQuestions: 0, contradictions: 0, reason: "evaluation" });
|
|
describe("ResearchLoop", () => {
|
|
it("stops after configured iterations without information gain", () => { const loop = new ResearchLoop(mkdtempSync(resolve(tmpdir(), "hm-loop-")), "run", "goal", { ...DEFAULT_CONFIG, max_iterations_without_progress: 2 }); loop.start(); loop.record(report()); expect(loop.record(report()).status).toBe("completed"); expect(loop.snapshot().stopReason).toMatch(/without information gain/); });
|
|
it("resets no-progress counter and handles pause/resume", () => { const loop = new ResearchLoop(mkdtempSync(resolve(tmpdir(), "hm-loop-")), "run", "goal", DEFAULT_CONFIG); loop.start(); loop.record(report()); loop.record(report(1)); expect(loop.snapshot().noProgressIterations).toBe(0); loop.pause(); expect(loop.snapshot().status).toBe("paused"); loop.resume(); expect(loop.snapshot().status).toBe("running"); });
|
|
it("restarts from stopped and resets counters and reports", () => { const loop = new ResearchLoop(mkdtempSync(resolve(tmpdir(), "hm-loop-")), "run", "goal", DEFAULT_CONFIG); loop.start(); loop.record(report(1)); expect(loop.snapshot().iteration).toBe(1); loop.stop(); expect(loop.snapshot().status).toBe("stopped"); loop.start(); expect(loop.snapshot().status).toBe("running"); expect(loop.snapshot().iteration).toBe(0); expect(loop.snapshot().reports).toHaveLength(0); expect(loop.snapshot().stopReason).toBeUndefined(); });
|
|
it("allows changing the goal after stop, then restarting", () => { const loop = new ResearchLoop(mkdtempSync(resolve(tmpdir(), "hm-loop-")), "run", "old goal", DEFAULT_CONFIG); loop.start(); loop.record(report()); loop.stop(); loop.setGoal("new goal"); expect(loop.snapshot().goal).toBe("new goal"); loop.start(); expect(loop.snapshot().status).toBe("running"); });
|
|
it("rejects changing the goal mid-run after iterations", () => { const loop = new ResearchLoop(mkdtempSync(resolve(tmpdir(), "hm-loop-")), "run", "goal", DEFAULT_CONFIG); loop.start(); loop.record(report()); expect(() => loop.setGoal("different")).toThrow(/new research run/); });
|
|
});
|