Files
sausage/host/js/p3-test.ts
T

141 lines
12 KiB
TypeScript

/** P3 exit contract: one branch, two child answers, and one landing across three crash boundaries. */
import { BACKGROUND_CONTEXT as context, awaitWithContext } from '@earendil-works/chord/context';
import { createModels } from '@earendil-works/pi-ai/models';
import { Harness, createRegistry, defineExtension, defineTool } from '@earendil-works/pi-durable';
import { obj } from '@earendil-works/pi-schema';
import { loadExtensions, fs, exec } from './extensions';
import { openStorage } from './sqlite';
import { conversationChains } from './environment';
import { scriptedModel, testReplay, type Rule } from './scripted-model';
const assert = (ok: any, message: string) => { if (!ok) throw new Error(message); };
testReplay();
if (globalThis.p3Mode !== 'unit') {
const workerId = (transcript: any) => Number(/Started subagent (\d+)/.exec(JSON.stringify(transcript.messages))?.[1]);
const rules: Rule[] = [
{ id: 'standing-branch', role: 'standing', match: { kind: 'user', text: 'launch scenario' }, answer: { tools: [{ name: 'branch', args: { repo: '/src/demo', isolated: true, prompt: 'begin' } }] } },
{ id: 'standing-probe', role: 'standing', match: { kind: 'user', text: 'launch branch' }, answer: { tools: [{ name: 'branch', args: { repo: '/src/demo', prompt: 'branch probe' } }] } },
{ id: 'coordinator-probe', role: 'coordinator', match: { kind: 'user', text: 'branch probe' }, answer: { text: 'branch complete' } },
{ id: 'standing-receipt', role: 'standing', match: { text: '' }, answer: { text: 'Receipt noted.' } },
{ id: 'coordinator-echo', role: 'coordinator', match: { kind: 'user', text: 'Receipt noted.' }, answer: { text: 'Acknowledged.' } },
{ id: 'start-worker', role: 'coordinator', match: { kind: 'user', text: 'begin' }, answer: { tools: [{ name: 'subagent', args: { action: 'start', agent: 'worker', isolate: 'worktree', task: 'build feature', background: true } }] } },
{ id: 'start-reviewer', role: 'coordinator', match: { kind: 'user', text: 'worker complete' }, answer: { tools: [{ name: 'subagent', args: { action: 'start', agent: 'reviewer', isolate: 'none', task: 'review feature', background: true } }] } },
{ id: 'land-worker', role: 'coordinator', match: { kind: 'user', text: 'approve' }, answer: transcript => ({ tools: [{ name: 'subagent', args: { action: 'land', id: workerId(transcript) } }] }) },
{ id: 'submit', role: 'coordinator', match: { text: 'Landed commits' }, answer: { tools: [{ name: 'workspace', args: { action: 'submit', divergence: 'None' } }] } },
{ id: 'coordinator-wait', role: 'coordinator', match: { text: '' }, answer: { text: 'Wait for child report.' } },
{ id: 'worker-write', role: 'worker', match: { kind: 'user', text: 'build feature' }, answer: { tools: [{ name: 'write-feature', args: {} }] } },
{ id: 'worker-commit', role: 'worker', match: { text: 'feature written' }, answer: { tools: [{ name: 'commit-feature', args: {} }] } },
{ id: 'worker-done', role: 'worker', match: { text: 'feature committed' }, answer: { text: 'worker complete' } },
{ id: 'reviewer-approve', role: 'reviewer', match: { kind: 'user', text: 'review feature' }, answer: { tools: [{ name: 'review-feature', args: {} }] } },
{ id: 'reviewer-done', role: 'reviewer', match: { text: 'feature verified' }, answer: { text: 'approve' } },
];
const models = createModels();
models.setProvider(scriptedModel(rules, rule => Deno.core.ops.op_output(`scripted rule: ${rule}\n`)));
const storage = await openStorage(globalThis.storagePath), registry = createRegistry();
const harness = await Harness.open(storage, { models, registry, settings: { retry: { enabled: false }, get extensions() { return registry.snapshot().installed(); } } }, context);
const root = await harness.root(context, { agent: { model: { provider: 'faux', modelId: 'faux-1' }, instructions: 'P3 role:standing', cwd: '/src/demo' } });
// Stop after the durable lander commit records landed.sha, before its event/report commit.
if (globalThis.p3Mode === 'kill-forward') {
const install = registry.install.bind(registry);
registry.install = (extension: any) => install(extension.name !== 'workspaces' ? extension : { ...extension, tasks: extension.tasks.map((task: any) => task.definition.name !== 'workspaces.lander' ? task : { definition: { ...task.definition, phases: { ...task.definition.phases, land: async (record: any, runtime: any, ctx: any) => {
const commit = runtime.commit.bind(runtime);
return task.definition.phases.land(record, { ...runtime, commit: async (...args: any[]) => {
const result = await commit(...args);
const index = await harness.snapshot(globalThis.p3Workspaces.doc, root.id, context);
if (index.queue.some((entry: any) => entry.landed)) {
await fs.write('/forward-recorded.tmp', JSON.stringify(index.queue));
await fs.rename('/forward-recorded.tmp', '/forward-recorded');
await awaitWithContext(new Promise(() => {}), ctx);
}
return result;
} }, ctx);
} } } }) });
}
const git = async (cwd: string, ...args: string[]) => {
const result = await exec(['/bin/git.wasm', ...args], { cwd, env: [['GIT_AUTHOR_NAME', 'Test'], ['GIT_AUTHOR_EMAIL', 'test@example.com'], ['GIT_COMMITTER_NAME', 'Test'], ['GIT_COMMITTER_EMAIL', 'test@example.com']] });
assert(result.code === 0, `World git ${args}: ${JSON.stringify(result)}`);
return result;
};
const tools = defineExtension({ name: 'p3-tools', tools: [
defineTool({ name: 'write-feature', description: 'Write the isolated feature', parameters: obj({}), replay: 'safe', execute: async (_args, api, ctx) => {
const cwd = (await api.agent(ctx)).cwd;
assert(cwd.startsWith('/worktrees/') && await fs.read(`${cwd}/README.md`) === 'P3 demo\n', 'Worker must start in a prepared checkout');
if (globalThis.p3Mode === 'kill-worker') {
await fs.write('/worker-running', 'yes');
await awaitWithContext(new Promise(() => {}), ctx);
}
await fs.write(`${cwd}/feature.txt`, 'P3 feature\n');
return { output: [{ type: 'text', text: 'feature written' }] };
} }),
defineTool({ name: 'commit-feature', description: 'Commit the isolated feature', parameters: obj({}), replay: 'safe', execute: async (_args, api, ctx) => {
const cwd = (await api.agent(ctx)).cwd;
await git(cwd, 'add', 'feature.txt');
if ((await git(cwd, 'status', '--porcelain')).stdout.trim()) await git(cwd, 'commit', '--no-gpg-sign', '-m', 'P3 feature');
return { output: [{ type: 'text', text: 'feature committed' }] };
} }),
defineTool({ name: 'review-feature', description: 'Verify the worker commit before landing', parameters: obj({}), replay: 'safe', execute: async (_args, _api, ctx) => {
const index = await harness.snapshot(globalThis.p3Workspaces.doc, root.id, ctx);
const worker = Object.values(index.items).find((item: any) => item.source.startsWith('/worktrees/')) as any;
assert(worker && (await git(worker.path, 'show', 'HEAD:feature.txt')).stdout === 'P3 feature\n', 'Reviewer must approve the worker commit');
return { output: [{ type: 'text', text: 'feature verified' }] };
} }),
] });
const loader = await loadExtensions(harness, registry, storage, models, context, [tools]);
assert(!loader.state().error, `Extension load: ${loader.state().error}`);
try {
if (globalThis.p3Mode === 'load-all') {
assert((await root.agent(context)).tools.some((tool: any) => tool.name === 'branch'), 'Every extension must load');
} else {
const standing = (await harness.conversation(await globalThis.p3Subagents.standing('standing', context), context))!;
harness.resume();
const probe = globalThis.p3Mode.startsWith('branch');
if (globalThis.p3Mode === 'branch' || globalThis.p3Mode.startsWith('kill-')) {
await (await standing.submit({ type: 'input', content: probe ? 'launch branch' : 'launch scenario', requestId: 'p3-launch' }, context)).wait(context);
const entries = (await standing.context(context)).entries;
assert(!JSON.stringify(entries).includes('"isError":true'), `Branch tool failed: ${JSON.stringify(entries)}`);
}
if (globalThis.p3Mode.startsWith('kill-')) await new Promise(() => {});
const deadline = Date.now() + 60000;
const wait = async (predicate: () => Promise<boolean>, message: string) => {
while (!await predicate()) {
assert(Date.now() < deadline, `${message}: ${JSON.stringify(Deno.core.ops.op_sql('all', "SELECT id, kind, status, record FROM tasks WHERE status != 'terminal'", []))}`);
await new Promise(resolve => setTimeout(resolve, 0));
}
};
const replies = async () => (await standing.context(context)).entries.filter((entry: any) => entry.kind === 'board.reply');
await wait(async () => (await replies()).length > 0, 'No branch reply');
const received = await replies();
const coordinator = (await harness.conversation(received[0].data.from, context))!;
assert((await coordinator.agent(context)).instructions.includes('P3 role:coordinator'), 'Branch must use coordinator definition');
assert((await conversationChains(harness, context)(coordinator.id)).includes(String(standing.id)), 'D63 must include standing');
if (!probe) {
await wait(async () => Object.keys((await harness.snapshot(globalThis.p3Workspaces.doc, root.id, context)).results).length === 1, 'No landing');
await wait(async () => (await harness.snapshot(globalThis.p3Workspaces.doc, root.id, context)).queue.length === 0, 'Lander did not report');
}
await harness.waitForIdle(context);
await wait(async () => Deno.core.ops.op_sql('all', "SELECT id FROM tasks WHERE status NOT IN ('terminal', 'faulted')", []).length === 0, 'Background tasks did not settle');
const entries = (await coordinator.context(context)).entries;
if (probe) assert(received.length === 1 && received[0].data.text === 'branch complete' && !received[0].data.failed, 'Expected one branch answer');
else {
const reports = entries.filter((entry: any) => entry.kind === 'subagents.report');
assert(reports.length === 2 && new Set(reports.map((entry: any) => entry.data.sender)).size === 2, `Expected one report per child: ${JSON.stringify(reports)}`);
for (const answer of ['worker complete', 'approve']) assert(reports.filter((entry: any) => JSON.stringify(entry).includes(answer)).length === 1, `Duplicate child answer: ${answer}`);
const index = await harness.snapshot(globalThis.p3Workspaces.doc, root.id, context);
const worker = Object.values(index.items).find((item: any) => item.source.startsWith('/worktrees/')) as any;
assert(worker?.state === 'removed', 'Worker checkout must be released');
assert(!await fs.stat(worker.path).then(() => true, () => false), 'Released checkout must not exist');
assert(Object.values(index.results).every((result: any) => result.outcome === 'landed'), `Landing failed: ${JSON.stringify(index)}`);
assert((await globalThis.p3Host.eventLog.recent({ events: [{ kind: 'event.workspaces.landed' }] })).length === 1, 'Expected one landed event');
const standingEntries = (await standing.context(context)).entries;
const landed = Object.values(index.results)[0] as any;
const cards = standingEntries.filter((entry: any) => entry.kind === 'board.message' && entry.data.text.startsWith('[lander] Submission') && entry.data.text.includes(' landed as '));
assert(cards.length === 1 && cards[0].data.from === coordinator.id && cards[0].data.text.includes(landed.sha), `Expected one landing card through messages: ${JSON.stringify(cards)}`);
const repliesNow = await replies();
assert(repliesNow.length === 1 && !repliesNow[0].data.failed && new Set(repliesNow.map((entry: any) => entry.data.id)).size === repliesNow.length, `Duplicate board.reply: ${JSON.stringify(repliesNow)}`);
assert(entries.filter((entry: any) => entry.kind === 'workspaces.lander-report').length === 1, 'Expected one lander report');
}
const faults = Deno.core.ops.op_sql('all', "SELECT id, record FROM tasks WHERE status = 'faulted'", []);
assert(faults.length === 0, `Faulted tasks: ${JSON.stringify(faults)}`);
}
} finally { await loader.close(); await harness.close(context); }
}