mirror of
https://github.com/Significant-Gravitas/AgentProbe.git
synced 2026-11-10 17:47:15 -10:00
Summary: - Add authenticated server write routes for starting and cancelling runs, preset CRUD, preset launch history, and frozen preset snapshots. - Extend SQLite run history to schema v4 with preset tables, server-run metadata, cancellation timestamps, and WAL-enabled server connections. - Teach runSuite to accept prepared file/id scenario selections and a cooperative AbortSignal while preserving existing CLI and dashboard mode. - Add dashboard start/preset/cancel views plus Docker and Compose packaging with a token-protected SQLite volume deployment path. Rationale: - Phase 2 needs an operator workflow that can configure a run, save it as a reusable preset, launch it from the server UI, and observe/cancel progress. - Scenario references now use file plus id so duplicate ids across scenario files remain deterministic, and write paths validate boundaries at data-root and bearer-auth edges. Tests: - bun run docs:validate - bun run test tests/unit/server - bun run test tests/integration/server - bun run test:e2e - bun run dashboard:build - bun run typecheck - bun run fast-feedback - COMPOSE_PROGRESS=plain AGENTPROBE_SERVER_TOKEN=... OPEN_ROUTER_API_KEY=... docker compose -p agentprobe-sym22 up --build --detach - curl -fsS http://127.0.0.1:7878/healthz - curl -fsS -X POST http://127.0.0.1:7878/api/runs ... dry-run payload - OPEN_ROUTER_API_KEY=... docker compose -p agentprobe-sym22-missing config exited 1 for missing AGENTPROBE_SERVER_TOKEN Co-authored-by: Codex <[email protected]>
90 lines
2.7 KiB
TypeScript
90 lines
2.7 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
import { SuiteController } from "../../../src/runtime/server/controllers/suite-controller.ts";
|
|
import { makeTempDir } from "../support.ts";
|
|
|
|
function writeSharedFiles(root: string): void {
|
|
writeFileSync(
|
|
join(root, "personas.yaml"),
|
|
[
|
|
"personas:",
|
|
" - id: analyst",
|
|
" name: Analyst",
|
|
" demographics:",
|
|
" role: operator",
|
|
" tech_literacy: high",
|
|
" domain_expertise: intermediate",
|
|
" language_style: terse",
|
|
" personality:",
|
|
" patience: 3",
|
|
" assertiveness: 3",
|
|
" detail_orientation: 4",
|
|
" cooperativeness: 4",
|
|
" emotional_intensity: 1",
|
|
" behavior:",
|
|
" opening_style: Direct.",
|
|
" follow_up_style: Concise.",
|
|
" escalation_triggers: []",
|
|
" topic_drift: none",
|
|
" clarification_compliance: high",
|
|
" system_prompt: You are direct.",
|
|
"",
|
|
].join("\n"),
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
function scenarioDoc(name: string): string {
|
|
return [
|
|
"scenarios:",
|
|
" - id: duplicate",
|
|
` name: ${name}`,
|
|
" tags: [smoke]",
|
|
" persona: analyst",
|
|
" rubric: support",
|
|
" turns:",
|
|
" - role: user",
|
|
" content: Say hello.",
|
|
" use_exact_message: true",
|
|
" expectations:",
|
|
" expected_behavior: Greets the user.",
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
describe("server scenario selection", () => {
|
|
test("resolves duplicate scenario ids by file and id", () => {
|
|
const root = makeTempDir("server-selection");
|
|
mkdirSync(root, { recursive: true });
|
|
writeSharedFiles(root);
|
|
writeFileSync(join(root, "a.yaml"), scenarioDoc("Alpha"), "utf8");
|
|
writeFileSync(join(root, "b.yaml"), scenarioDoc("Beta"), "utf8");
|
|
|
|
const controller = new SuiteController({ dataPath: root });
|
|
const resolved = controller.resolveSelection([
|
|
{ file: "a.yaml", id: "duplicate" },
|
|
{ file: "b.yaml", id: "duplicate" },
|
|
]);
|
|
|
|
expect(resolved.refs).toEqual([
|
|
{ file: "a.yaml", id: "duplicate" },
|
|
{ file: "b.yaml", id: "duplicate" },
|
|
]);
|
|
expect(resolved.selectedScenarios.map((scenario) => scenario.name)).toEqual(
|
|
["Alpha", "Beta"],
|
|
);
|
|
});
|
|
|
|
test("rejects scenario selection paths outside the data root", () => {
|
|
const root = makeTempDir("server-selection-outside");
|
|
mkdirSync(root, { recursive: true });
|
|
const controller = new SuiteController({ dataPath: root });
|
|
|
|
expect(() =>
|
|
controller.resolveSelection([{ file: "../outside.yaml", id: "x" }]),
|
|
).toThrow(/data root/);
|
|
});
|
|
});
|