60 lines
3.3 KiB
TypeScript
60 lines
3.3 KiB
TypeScript
// P2 rehearsal: real bash tools pause for two passkey approvals and resume durably.
|
|
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
|
|
import { createModels } from '@earendil-works/pi-ai/models';
|
|
import { fauxProvider, fauxAssistantMessage, fauxToolCall, fauxText } from '@earendil-works/pi-ai/providers/faux';
|
|
import { Harness, createRegistry } from '@earendil-works/pi-durable';
|
|
import { CodingTools } from '@earendil-works/pi-durable/tools';
|
|
import { environment } from './environment';
|
|
import { openStorage } from './sqlite';
|
|
import { CredentialRequests, startRequests } from './credentials';
|
|
const models = createModels();
|
|
const faux = fauxProvider({ tokensPerSecond: 100000 });
|
|
models.setProvider(faux.provider);
|
|
const bash = (command: string, id: string) => fauxAssistantMessage([fauxToolCall('bash', { command }, { id })], { stopReason: 'toolUse' });
|
|
const curl = `curl -i http://127.0.0.1:${globalThis.p2Http}/whoami`;
|
|
const push = 'cd /repo && git push origin main';
|
|
faux.setResponses([
|
|
bash(curl, 'curl-denied'), fauxAssistantMessage([fauxText('Wait for HTTP approval.')]),
|
|
bash(curl, 'curl-approved'), bash(push, 'ssh-denied'), fauxAssistantMessage([fauxText('Wait for SSH approval.')]),
|
|
bash(push, 'ssh-approved'), fauxAssistantMessage([fauxText('P2 complete.')]),
|
|
]);
|
|
const registry = createRegistry();
|
|
registry.install(CodingTools); registry.install(CredentialRequests);
|
|
const harness = await Harness.open(await openStorage(globalThis.storagePath), {
|
|
models, registry, env: (target: any) => environment('/', target.conversationId),
|
|
}, context);
|
|
const root = await harness.root(context, { agent: {
|
|
model: { provider: 'faux', modelId: 'faux-1' }, extensions: [CodingTools, CredentialRequests], cwd: '/',
|
|
} });
|
|
const stop = await startRequests(harness, context);
|
|
harness.resume();
|
|
await (await root.submit({ type: 'input', content: 'Rehearse P2', requestId: 'p2' }, context)).wait(context);
|
|
await Deno.core.ops.op_file('write', '/p2-http-idle', []);
|
|
const watch = await root.watch(context);
|
|
let sshIdle = false;
|
|
await new Promise<void>((resolve, reject) => {
|
|
const check = async (event: any) => {
|
|
if (event.kind !== 'value') return;
|
|
const transcript = JSON.stringify(event.value.entries);
|
|
if (!sshIdle && transcript.includes('Wait for SSH approval.')) {
|
|
sshIdle = true;
|
|
await harness.waitForIdle(context);
|
|
await Deno.core.ops.op_file('write', '/p2-ssh-idle', []);
|
|
}
|
|
const approvals = event.value.entries.filter((entry: any) => JSON.stringify(entry).includes('Your request for'));
|
|
if (approvals.length > 2) reject(new Error('Duplicate approval wake-up'));
|
|
if (transcript.includes('P2 complete.')) resolve();
|
|
};
|
|
watch.start(check); check({ kind: 'value', value: watch.value });
|
|
});
|
|
await harness.waitForIdle(context);
|
|
const transcript = JSON.stringify(watch.value.entries);
|
|
for (const expected of ['403', 'demo-api', 'demo-ssh', 'Bearer accepted', 'P2 complete.']) {
|
|
if (!transcript.includes(expected)) throw new Error(`Missing rehearsal result: ${expected}`);
|
|
}
|
|
if (watch.value.entries.filter((entry: any) => JSON.stringify(entry).includes('Your request for')).length !== 2) {
|
|
throw new Error('Expected exactly two approval inputs');
|
|
}
|
|
await Deno.core.ops.op_file('write', '/p2-transcript', [...new TextEncoder().encode(transcript)]);
|
|
await watch.stop(); await stop(); await harness.close(context);
|