mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
107 lines
8.4 KiB
TypeScript
107 lines
8.4 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, realpathSync, writeFileSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join, resolve } from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
import { terminal } from './local-terminal.ts';
|
|
import { installationSlots } from '../src/slots.ts';
|
|
import { host, bindingFingerprint } from '../src/host.ts';
|
|
import { relay } from '../src/relay.ts';
|
|
import { connect } from '../src/client.ts';
|
|
import { newKey, publicKey, message, type Message } from '../src/protocol.ts';
|
|
import { writePrivate, readPrivate } from '../src/storage.ts';
|
|
import { profileRevision } from '../src/profiles.ts';
|
|
import { gooseModels, prepareAgent } from '../src/acp.ts';
|
|
|
|
test('Goose native model evidence rejects absent, ambiguous, unadvertised and mismatched acknowledgements', () => {
|
|
const value = { configOptions: [{ configId: 'model', category: 'model', currentValue: 'a', options: [{ value: 'a' }] }] };
|
|
assert.deepEqual(gooseModels(value, 'a'), ['a']);
|
|
for (const bad of [{}, { models: { currentModelId: 'a' } }, { configOptions: [...value.configOptions, ...value.configOptions] }, { configOptions: [{ ...value.configOptions[0], options: [] }] }]) assert.throws(() => gooseModels(bad, 'a'));
|
|
assert.throws(() => gooseModels(value, 'b'));
|
|
});
|
|
|
|
test('actual setup entry Goose/new/reuse/hidden standby and selected binding ACP Restart preserve identity/profile/sibling', { timeout: 35000 }, async () => {
|
|
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'bh-goose-'))), source = join(dir, 'source');
|
|
const secret = newKey(), identity = join(dir, 'owner.json'); writePrivate(identity, { secret });
|
|
const runner = join(dir, 'goose-fixture.ts');
|
|
writeFileSync(runner, `#!${realpathSync(process.execPath)}\nif (process.argv[2] !== 'acp') throw Error('Expected goose acp');\nawait import(${JSON.stringify(pathToFileURL(resolve('test/conversation-harness-fixture.ts')).href)});\n`, { mode: 0o700 });
|
|
writeFileSync(join(dir, 'mode'), 'ok');
|
|
const steps = (name: string) => [
|
|
{ prompt: 'Host name: ', answer: name }, { prompt: 'Setup [', answer: '3' },
|
|
{ prompt: 'Absolute installed Goose executable (runs acp): ', answer: runner },
|
|
{ prompt: 'Allowed workspace (absolute directory): ', answer: dir },
|
|
{ prompt: 'Additional allowed workspace (blank for none): ', answer: '' },
|
|
{ prompt: 'Locally configured Goose provider ID: ', answer: 'fixture-provider' },
|
|
{ prompt: 'Operator-approved compatible exact model IDs (comma-separated): ', answer: 'goose-a,goose-b' },
|
|
{ prompt: 'Purpose [', answer: 'diagnostic' },
|
|
];
|
|
let output = await terminal(['setup', source, identity], [...steps('source'),
|
|
{ prompt: 'Create a NEW agent identity', answer: 'yes' },
|
|
{ prompt: 'Add another independent NEW agent', answer: 'yes' },
|
|
{ prompt: 'Add another independent NEW agent', answer: 'no' },
|
|
]);
|
|
assert.match(output, /Goose owns provider credentials/); assert.ok(!output.includes('auth databricks'));
|
|
assert.match(output, /Start uses an ACP greeting probe, not yet a Buzz relay conversation agent/);
|
|
assert.match(output, /explicit local-setup .*action normal/);
|
|
assert.match(output, /no provider login or community admission has been verified/);
|
|
const slots = installationSlots(source), X = slots[0]!, Y = slots[1]!;
|
|
const state = () => readPrivate(X.path) as any;
|
|
const originalY = readFileSync(Y.path), originalManifest = readFileSync(join(source, 'setup.json'));
|
|
output += await terminal(['setup', source], [{ prompt: 'Local action [', answer: 'reuse' }, { prompt: 'Existing agent public key: ', answer: X.agent }]);
|
|
assert.deepEqual(readFileSync(join(source, 'setup.json')), originalManifest);
|
|
const root = join(dir, 'genesis.json'); writePrivate(root, state().assignment.genesis);
|
|
const target = join(dir, 'target');
|
|
output += await terminal(['setup', target, identity], [...steps('target'),
|
|
{ prompt: 'Create a NEW agent identity', answer: 'import-standby' },
|
|
{ prompt: 'Local public genesis file (no key): ', answer: root },
|
|
{ prompt: `Import exact ${X.agent}`, answer: 'yes' },
|
|
{ prompt: 'Agent private key (64 hex; hidden, never passed as an argument): ', answer: X.setup.agentSecret! },
|
|
{ prompt: 'Add another independent NEW agent', answer: 'no' },
|
|
], process.platform === 'darwin');
|
|
for (const key of [secret, X.setup.agentSecret!, Y.setup.agentSecret!]) assert.ok(!output.includes(key));
|
|
for (const path of [join(source, 'setup.json'), join(target, 'setup.json'), X.path]) assert.equal(statSync(path).mode & 0o777, 0o600);
|
|
assert.equal(statSync(join(source, 'service-home')).mode & 0o777, 0o700);
|
|
const plan = prepareAgent({ executable: runner, args: ['acp'], workspace: dir, home: X.setup.serviceHome!, configDirectory: X.setup.serviceHome!, databricksHost: '', harness: 'goose', provider: 'fixture-provider', model: 'goose-a' });
|
|
assert.equal(plan.env.GOOSE_MODEL, 'goose-a'); assert.equal(plan.env.BUZZ_AGENT_CONFIG_DIR, undefined); assert.equal(plan.env.BUZZ_PRIVATE_KEY, undefined);
|
|
const server = await relay(0, publicKey(secret), join(dir, 'relay.json'));
|
|
const address = server.address(); assert.ok(address && typeof address !== 'string'); const url = `ws://127.0.0.1:${address.port}`;
|
|
const h = await host(source, url), standby = await host(target, url), seen: Message[] = [];
|
|
const client = connect(url, secret, m => seen.push(m)); await client.ready;
|
|
async function request(type: 'start' | 'restart' | 'stop' | 'save', body = {}, hostname = 'source') {
|
|
const revision = hostname === 'source' ? state().revision : 0;
|
|
const m = message(type, hostname, X.agent, revision, body); client.send(m);
|
|
for (let n = 0; n < 400; n++) { const receipt = seen.find(e => e.type === 'receipt' && e.body.operation === m.id); if (receipt) return String(receipt.body.result); await delay(20); }
|
|
throw Error(`Missing ${type} receipt`);
|
|
}
|
|
try {
|
|
assert.notEqual(await request('start', {}, 'target'), 'accepted');
|
|
const instructions = 'GOOSE-PROFILE: keep behavior independent of provider.';
|
|
const revision = profileRevision('Goose behavior', null, instructions);
|
|
const selection = { model: 'goose-a', workspace: dir, profile: revision, behavior: { name: 'Goose behavior', parent: null, revision, instructions }, harnessSetup: { id: 'default', fingerprint: bindingFingerprint(X.setup) } };
|
|
client.send(message('profile', 'profiles', 'profiles', 0, selection.behavior));
|
|
assert.match(await request('save', selection), /saved/);
|
|
assert.equal(await request('start'), 'accepted'); const actual = state().actual;
|
|
assert.match(actual.evidence.session, /^goose-/); assert.equal(actual.selection.model, 'goose-a');
|
|
assert.equal(readFileSync(join(dir, 'received-system-instructions'), 'utf8'), instructions);
|
|
assert.match(await request('save', { ...selection, model: 'goose-b' }), /saved/);
|
|
assert.deepEqual(state().actual, actual);
|
|
assert.equal(await request('restart'), 'accepted');
|
|
assert.notEqual(state().actual.evidence.session, actual.evidence.session); assert.equal(state().actual.evidence.model, 'goose-b');
|
|
assert.equal(state().actual.selection.behavior.revision, revision); assert.equal(state().binding.agent, X.agent);
|
|
assert.deepEqual(state().runs[actual.run], actual);
|
|
assert.notEqual(await request('save', { ...selection, model: 'unknown' }), 'saved; running configuration unchanged');
|
|
assert.notEqual(await request('save', { ...selection, harnessSetup: { id: 'unknown', fingerprint: selection.harnessSetup.fingerprint } }), 'saved; running configuration unchanged');
|
|
assert.notEqual(await request('save', { ...selection, harnessSetup: { id: 'default', fingerprint: '0'.repeat(64) } }), 'saved; running configuration unchanged');
|
|
const running = state().actual; writeFileSync(join(dir, 'mode'), 'wrong-model');
|
|
assert.notEqual(await request('restart'), 'accepted'); assert.deepEqual(state().actual, running);
|
|
rmSync(X.setup.serviceHome!, { recursive: true });
|
|
assert.equal(await request('stop'), 'accepted');
|
|
assert.deepEqual(readFileSync(Y.path), originalY); assert.deepEqual(readFileSync(join(source, 'setup.json')), originalManifest);
|
|
} finally {
|
|
await h.close(); await standby.close(); client.close(); for (const c of server.clients) c.terminate();
|
|
await new Promise<void>(resolve => server.close(() => resolve())); rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|