Files
waggle-os/packages/server/tests/local/route-proposals.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

430 lines
14 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import Fastify, { type FastifyInstance } from 'fastify';
import { buildTaskFit, type ExecutorCandidate } from '@waggle/agent';
import type { ExecutorBrief } from '../../src/local/executor-brief.js';
import { routeProposalRoutes } from '../../src/local/routes/route-proposals.js';
const NOW_MS = Date.UTC(2026, 6, 15, 10, 0, 0);
const WORKSPACE_ID = 'workspace-alpha';
const PROMPT = 'Implement the router tests';
const servers: FastifyInstance[] = [];
afterEach(async () => {
vi.useRealTimers();
await Promise.all(servers.splice(0).map((server) => server.close()));
});
function externalCandidate(overrides: Partial<ExecutorCandidate> = {}): ExecutorCandidate {
const id = overrides.id ?? 'external:codex';
return {
id,
kind: 'external',
displayName: 'Codex CLI',
taskFit: buildTaskFit(id),
authClass: 'subscription-cli',
installed: true,
healthy: true,
rateLimit: { state: 'available' },
supportsHeadless: true,
egressDestination: 'OpenAI',
...overrides,
};
}
function personaCandidate(overrides: Partial<ExecutorCandidate> = {}): ExecutorCandidate {
const id = overrides.id ?? 'persona:coder';
return {
id,
kind: 'persona',
displayName: 'Coder',
taskFit: buildTaskFit(id),
authClass: 'api-key',
installed: true,
healthy: true,
rateLimit: { state: 'available' },
supportsHeadless: false,
egressDestination: null,
...overrides,
};
}
function briefFixture(
briefHash = 'brief-full-hash',
text = '## Waggle task context\nMemory evidence:\n- Full retained context',
): ExecutorBrief {
return {
text,
items: [{
frameId: 'frame-1',
date: '2026-07-14',
source: 'user_stated',
preview: 'Full retained context',
content: 'Full retained context',
}],
briefHash,
chars: text.length,
blocked: false,
};
}
interface HarnessOptions {
snapshots: ExecutorCandidate[][];
briefProvider?: (opts: {
workspaceId: string;
prompt: string;
excludeFrameIds?: string[];
}) => Promise<ExecutorBrief>;
personaDispatcher?: (...args: unknown[]) => Promise<{
content: string;
approvalRequired: boolean;
error?: string;
}>;
}
async function createHarness(options: HarnessOptions) {
const server = Fastify({ logger: false });
servers.push(server);
let snapshotIndex = 0;
const snapshot = vi.fn(async () => {
const candidates = options.snapshots[Math.min(snapshotIndex, options.snapshots.length - 1)] ?? [];
snapshotIndex += 1;
return candidates;
});
const briefProvider = options.briefProvider ?? vi.fn(async () => briefFixture());
const personaDispatcher = options.personaDispatcher ?? vi.fn(async () => ({
content: 'Dispatched',
approvalRequired: false,
}));
const toolRunPayloads: unknown[] = [];
server.decorate('localConfig', {
dataDir: '',
port: 4567,
host: '127.0.0.1',
litellmUrl: '',
} as never);
server.decorate('agentState', { wsSessionToken: 'ws-session-token' } as never);
server.decorate('executorRegistry', {
snapshot,
noteRateLimit: vi.fn(),
noteHealthy: vi.fn(),
} as never);
server.decorate('routeProposalBriefProvider', briefProvider as never);
server.decorate('routeProposalPersonaDispatcher', personaDispatcher as never);
server.post('/api/tools/run', async (request, reply) => {
toolRunPayloads.push(request.body);
return reply.code(202).send({
roomId: 'room-1',
runs: [{ runId: 'run-1' }],
});
});
await server.register(routeProposalRoutes);
return { server, snapshot, briefProvider, personaDispatcher, toolRunPayloads };
}
async function propose(
server: FastifyInstance,
overrides: Record<string, unknown> = {},
) {
return server.inject({
method: 'POST',
url: '/api/route-proposals',
payload: {
workspaceId: WORKSPACE_ID,
prompt: PROMPT,
category: 'coding',
...overrides,
},
});
}
describe('route proposal routes', () => {
it('confirms an external proposal with the reviewed brief, read-only access, and attribution', async () => {
const fullBrief = briefFixture('brief-full-hash');
const briefProvider = vi.fn(async () => fullBrief);
const { server, toolRunPayloads } = await createHarness({
snapshots: [[externalCandidate()], [externalCandidate()]],
briefProvider,
});
const proposed = await propose(server);
expect(proposed.statusCode).toBe(200);
const proposal = proposed.json() as { routeDecisionId: string; selected: { id: string } };
expect(proposal.selected.id).toBe('external:codex');
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${proposal.routeDecisionId}/confirm`,
payload: {},
});
expect(confirmed.statusCode).toBe(200);
expect(confirmed.json()).toEqual({
status: 'dispatched',
mode: 'external',
roomId: 'room-1',
runId: 'run-1',
});
// Retrieval runs exactly once, at propose time — confirm must never
// re-query memory (removed frames could be backfilled by new results).
expect(briefProvider).toHaveBeenCalledTimes(1);
expect(briefProvider).toHaveBeenNthCalledWith(1, {
workspaceId: WORKSPACE_ID,
prompt: PROMPT,
});
expect(toolRunPayloads).toEqual([{
toolId: 'codex',
workspaceIds: [WORKSPACE_ID],
prompt: `${fullBrief.text}\n\n${PROMPT}`,
access: 'read-only',
attribution: {
routeDecisionId: proposal.routeDecisionId,
briefHash: fullBrief.briefHash,
},
}]);
});
it('refuses an executor override whose egress destination differs from the disclosure', async () => {
const codex = externalCandidate();
const claude = externalCandidate({
id: 'external:claude-code',
displayName: 'Claude Code',
egressDestination: 'Anthropic',
});
const { server, toolRunPayloads } = await createHarness({
snapshots: [[codex, claude], [codex, claude]],
});
const proposed = await propose(server);
const proposal = proposed.json() as {
routeDecisionId: string;
selected: { id: string };
alternatives: Array<{ id: string }>;
};
const alternative = proposal.alternatives.find((item) => item.id !== proposal.selected.id);
expect(alternative).toBeDefined();
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${proposal.routeDecisionId}/confirm`,
payload: { executorId: alternative!.id },
});
expect(confirmed.statusCode).toBe(409);
expect(confirmed.json()).toMatchObject({ error: 'revalidation_failed' });
expect((confirmed.json() as { reason: string }).reason).toContain('different destination');
expect(toolRunPayloads).toHaveLength(0);
// Proposal stays claimable: a same-destination confirm still succeeds.
const retry = await server.inject({
method: 'POST',
url: `/api/route-proposals/${proposal.routeDecisionId}/confirm`,
payload: {},
});
expect(retry.statusCode).toBe(200);
});
it('dispatches without any memory when the user removes every disclosed frame', async () => {
const fullBrief = briefFixture('brief-full-hash');
const briefProvider = vi.fn(async () => fullBrief);
const { server, toolRunPayloads } = await createHarness({
snapshots: [[externalCandidate()], [externalCandidate()]],
briefProvider,
});
const proposed = await propose(server);
const proposal = proposed.json() as { routeDecisionId: string };
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${proposal.routeDecisionId}/confirm`,
payload: { removeFrameIds: ['frame-1'] },
});
expect(confirmed.statusCode).toBe(200);
expect(briefProvider).toHaveBeenCalledTimes(1);
expect(toolRunPayloads).toHaveLength(1);
const payload = toolRunPayloads[0] as {
prompt: string;
attribution: { routeDecisionId: string; briefHash?: string };
};
// No brief text, no undisclosed frames, no briefHash.
expect(payload.prompt).toBe(PROMPT);
expect(payload.attribution).toEqual({ routeDecisionId: proposal.routeDecisionId });
});
it('dispatches a persona with the bare persona id, router origin, and websocket token', async () => {
const personaDispatcher = vi.fn(async () => ({ content: 'Done', approvalRequired: false }));
const { server, briefProvider, toolRunPayloads } = await createHarness({
snapshots: [[personaCandidate()], [personaCandidate()]],
personaDispatcher,
});
const proposed = await propose(server);
const { routeDecisionId } = proposed.json() as { routeDecisionId: string };
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
expect(confirmed.statusCode).toBe(200);
expect(confirmed.json()).toEqual({ status: 'dispatched', mode: 'internal', resultText: 'Done' });
expect(personaDispatcher).toHaveBeenCalledWith({
port: 4567,
sessionToken: 'ws-session-token',
message: PROMPT,
workspace: WORKSPACE_ID,
session: WORKSPACE_ID,
persona: 'coder',
proposeHeld: true,
origin: 'router',
});
expect(briefProvider).not.toHaveBeenCalled();
expect(toolRunPayloads).toEqual([]);
});
it('fails revalidation when the selected external tool is uninstalled', async () => {
const { server, snapshot, briefProvider, toolRunPayloads } = await createHarness({
snapshots: [
[externalCandidate()],
[externalCandidate({ installed: false, healthy: false })],
],
});
const proposed = await propose(server);
const { routeDecisionId } = proposed.json() as { routeDecisionId: string };
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
expect(confirmed.statusCode).toBe(409);
expect(confirmed.json()).toMatchObject({
error: 'revalidation_failed',
reason: 'not installed',
});
expect(snapshot).toHaveBeenCalledTimes(2);
expect(briefProvider).toHaveBeenCalledTimes(1);
expect(toolRunPayloads).toEqual([]);
});
it('expires proposals after ten minutes', async () => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(NOW_MS);
const { server, snapshot } = await createHarness({ snapshots: [[personaCandidate()]] });
const proposed = await propose(server);
const { routeDecisionId } = proposed.json() as { routeDecisionId: string };
vi.setSystemTime(NOW_MS + 10 * 60_000);
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
expect(confirmed.statusCode).toBe(404);
expect(confirmed.json()).toEqual({ error: 'route_proposal_not_found' });
expect(snapshot).toHaveBeenCalledTimes(1);
});
it('allows a proposal to be confirmed only once', async () => {
const { server, personaDispatcher, snapshot } = await createHarness({
snapshots: [[personaCandidate()], [personaCandidate()]],
});
const proposed = await propose(server);
const { routeDecisionId } = proposed.json() as { routeDecisionId: string };
const first = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
const second = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
expect(first.statusCode).toBe(200);
expect(second.statusCode).toBe(409);
expect(second.json()).toEqual({
error: 'route_proposal_already_claimed',
status: 'dispatched',
});
expect(snapshot).toHaveBeenCalledTimes(2);
expect(personaDispatcher).toHaveBeenCalledTimes(1);
});
it('never selects an external executor for a private task', async () => {
const { server, briefProvider } = await createHarness({
snapshots: [[externalCandidate(), personaCandidate()]],
});
const proposed = await propose(server, { privacy: 'private' });
expect(proposed.statusCode).toBe(200);
expect(proposed.json()).toMatchObject({
selected: { id: 'persona:coder' },
rejected: [{
id: 'external:codex',
reason: 'blocked by private-task policy (egress to OpenAI)',
}],
egress: null,
costLine: 'runs on your configured API key',
});
expect(briefProvider).not.toHaveBeenCalled();
});
it('rejects a proposed route and prevents it from being claimed later', async () => {
const { server, personaDispatcher } = await createHarness({
snapshots: [[personaCandidate()]],
});
const proposed = await propose(server);
const { routeDecisionId } = proposed.json() as { routeDecisionId: string };
const rejected = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/reject`,
});
const confirmed = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: {},
});
expect(rejected.statusCode).toBe(200);
expect(rejected.json()).toEqual({ status: 'rejected' });
expect(confirmed.statusCode).toBe(409);
expect(confirmed.json()).toEqual({
error: 'route_proposal_already_claimed',
status: 'rejected',
});
expect(personaDispatcher).not.toHaveBeenCalled();
});
it('strictly validates proposal and confirmation input', async () => {
const { server, snapshot } = await createHarness({
snapshots: [[personaCandidate()]],
});
const extraProposalField = await propose(server, { unexpected: true });
const emptyPrompt = await propose(server, { prompt: '' });
const validProposal = await propose(server);
const { routeDecisionId } = validProposal.json() as { routeDecisionId: string };
const extraConfirmField = await server.inject({
method: 'POST',
url: `/api/route-proposals/${routeDecisionId}/confirm`,
payload: { unexpected: true },
});
expect(extraProposalField.statusCode).toBe(400);
expect(emptyPrompt.statusCode).toBe(400);
expect(extraConfirmField.statusCode).toBe(400);
expect(snapshot).toHaveBeenCalledTimes(1);
});
});