feat(beehive): align saved providers and model choices with Desktop

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-16 10:22:16 -04:00
parent 86921025b1
commit 446bbbca4d
23 changed files with 284 additions and 73 deletions

View File

@@ -131,3 +131,93 @@ Independent UX acceptance is arranged by Larry after this functional handback.
Current-Desktop provider/harness parity is a separately supplied bounded read-only
delta, not a parity claim by this implementation. No packaged harness-contract pin,
Pi/Codex policy, Desktop/native source or installed artifact changed here.
## Current Desktop parity continuation — b5cd2aa0
Local source continuation of `86921025`, compared with Desktop
`6d1f488d2273dd314184fc2ec805f8982327dc51` at `refs/parity/a8c4d530-main`.
The parity paragraph above is historical; this bounded delta is now implemented.
The enrollment/Move evidence above remains reusable and unchanged in its original
artifact directory. Installed `176497874` exports, native code, relay protocols,
harness discovery tiers and Databricks v2 auth are unchanged.
- Providers independently adds OpenAI, Anthropic, OpenAI-compatible and OpenRouter,
alongside existing Databricks v2. Compatible endpoints require HTTPS without
URL credentials/query/fragment and select auto/chat/responses. Anthropic and
OpenRouter use their canonical endpoints and key contracts. Each API-key save
validates the entire candidate before retaining an exact recovery reference,
writes/read-backs the OS entry, then commits public settings by CAS. Secrets
stay in hidden input/private helper IPC/closed runtime environment, not argv or
public settings. No plaintext fallback. Failed persistence retains references.
- Buzz Agent accepts all five providers. Pi remains OpenAI/Databricks v2 only;
Codex remains OpenAI only with its own CLI/effort restrictions. Catalog validation
repeats these restrictions, independent of picker filtering. The stale Pi
diagnostic row now uses buzz-pi-acp and salman1993/buzz-pi-acp#86b201e.
- OpenAI models use Desktop text prefixes/exclusions, creation-descending ordering,
ID tie-break, dated alias suppression and display names. Compatible lists are
unfiltered. Anthropic uses x-api-key/anthropic-version, display_name and after_id;
OpenRouter selects tools-capable entries. Fetches forbid redirects, bound each
response to 1 MiB, total rows to 10,000, returned rows to 1,000 and pagination to
20 pages with repeated/missing-cursor refusal. UI labels never replace exact IDs.
Custom model entry remains available after listing failure.
- Unknown/custom models and UC FQNs retain authored provider fallback efforts.
Known model constraints and Anthropic omit-fields remain; Pi projects only its
supported effort vocabulary. Save and actual launch both validate. Saving a
runtime/selected-next still does not alter an active run.
### New evidence
`/Users/loganj/.buzz/artifacts/beehive-parity-b5cd2aa0/`
- `DESKTOP_PARITY_DELTA.md`: durable reconciliation of public source report
`62399bb5…89a24`, with canonical consumer pointers and resolved scope.
- `focused-candidate.log`: **63 passed / 0 failed**. Provider-specific model APIs,
bounded pagination, labels, compatibility and effort validation; actual host
Save preserves the prior run, then Stop/Start produces real owned synthetic
requests for Anthropic/compatible/OpenRouter. Unknown compatible/router models
deliver high effort; Anthropic preserves omitted effort. Existing Pi/Codex,
registration-first continuation, controller and discovery checks remain green.
These external wrappers assert the production provider environment then call a
loopback fixture; they are not actual vendor adapters or live API attestations.
- `typecheck-final.log`: strict Node 24.15.0 TypeScript passed.
- `renderer-candidate.log`: Bun 1.4.2 renderer **4/4**.
- `full-candidate.log`: one final default-concurrent package run, **244 passed /
2 failed / 19 skipped**, 265 tests, natural 45.719s. **Not green.** Broker `ok`
conversation timeout recurred. Admission wrong-authority timing was 3210ms >
2500ms; retained records show wrong-authority rejection plus accepted Start and
subsequent authorized Stop, with storage-phase and receive-order trace. Those
records establish a timing assertion failure, not a wrong-authority execution
failure; underlying latency cause is not established. No timeout/assertion
relaxation, serial masking or retry-to-green. Native 700-test history reused.
- `pty-candidate.log`, `walkthrough.json`, numbered `.ansi` and `.txt`: actual
isolated 100×30 PTY adds all three providers with hidden synthetic keys, opens
selected-provider Models, saves OpenRouter/custom/high/environment runtime,
registers the initially unassigned agent with that runtime, observes actual
unknown-custom-model running, awaits Stop and exits 0. Synthetic API key absent
from terminal bytes. Frames reconstruct PTY output; not screenshots.
- Development failures retained: first provider fixture incorrectly assigned high
effort to an Anthropic omit-fields model and reused OpenAI-only ACP validation;
corrected fixture now omits effort and checks the generic saved-provider seam.
First unit constraint assumed gpt-4.1 lacked fallback high (manifest says otherwise);
replaced with explicit known gpt-5 xhigh rejection. `provider-first.log` and
`anthropic-first.log` reached outer execution timeouts after failures; they are
not successful runs. First typechecks retain headers-union and duplicate-test-
ternary diagnostics. `renderer.log` is an invocation/filter error, superseded by
the explicit-path renderer run. First PTY retained a noncanonical /tmp executable
alias; production correctly refused Start. Loader now resolves the synthetic
executable canonically. `pty-failed/` retains those frames; no production path
check was weakened.
### Reviewer delta
`sh beehive/tools/ux-walkthrough.sh --help` documents the new provider/runtime steps.
Run without arguments for a fresh explicit installed-loader and synthetic-credential
HOME. Use only `synthetic-provider-key` and the printed synthetic nsec. Host service
buttons remain fenced. Provider API tests exercise bounded fetches separately; the
PTY uses synthetic model responses. `pty-walkthrough.py` in the new artifact folder
reproduces the final provider/custom-runtime Start/Stop delta. Prior independent-
root Move PTY is reused, not relabeled as a new provider Move test.
No production OS-store/provider/browser/relay/harness/service operations, native
rebuild, repository-wide CI, push, export or install were performed. Independent UX
review remains Larry's acceptance step.

View File

@@ -17,7 +17,7 @@ export function validateBuzzProvider(value: BuzzProvider): void {
for (const model of value.models) validateRuntimeEffort(value.provider, model, value.effort);
if (value.provider === 'databricks_v2' ? !['token', 'external-oauth'].includes(value.auth ?? '') : value.auth !== undefined) throw Error('Unsupported provider authentication contract');
if (value.credential) {
if (!['openai-compat','databricks_v2'].includes(value.provider) || value.apiKeyFile !== undefined || (value.provider === 'databricks_v2' ? value.auth !== 'token' : value.auth !== undefined) || value.credential.service !== 'beehive' || !/^provider:[0-9a-f-]{36}$/.test(value.credential.account)) throw Error('Invalid OS provider binding');
if (value.apiKeyFile !== undefined || (value.provider === 'databricks_v2' ? value.auth !== 'token' : value.auth !== undefined) || value.credential.service !== 'beehive' || !/^provider:[0-9a-f-]{36}$/.test(value.credential.account)) throw Error('Invalid OS provider binding');
} else if (value.auth === 'external-oauth' ? value.apiKeyFile !== undefined : typeof value.apiKeyFile !== 'string' || !isAbsolute(value.apiKeyFile)) throw Error('Provider requires owner-only key file; external OAuth forbids token fallback');
let url: URL;
try { url = new URL(value.baseUrl); } catch { throw Error('Invalid local provider endpoint'); }

View File

@@ -78,7 +78,7 @@ export async function discoverHarnesses(signal: AbortSignal, options: { home?: s
let state: DetectedHarness['state'] = !binary ? 'not-installed' : row.underlyingCli && !cli ? 'cli-missing' : 'available';
if (state === 'available' && row.id === 'codex' && !compatibleCodex(await probe(binary!, ['--version'], [...managed, path].join(delimiter), home, signal) ?? '')) state = 'incompatible';
signal.throwIfAborted();
const providers = state === 'available' && ['buzz-agent','pi'].includes(row.id) ? ['openai', 'databricks_v2'] : state === 'available' && row.id === 'codex' ? ['openai'] : [];
const providers = state === 'available' && ['buzz-agent','pi'].includes(row.id) ? row.id === 'buzz-agent' ? ['openai', 'anthropic', 'openai-compat', 'openrouter', 'databricks_v2'] : ['openai', 'databricks_v2'] : state === 'available' && row.id === 'codex' ? ['openai'] : [];
const reason = state === 'not-installed' ? 'Not installed' : state === 'cli-missing' ? `Underlying ${row.underlyingCli} CLI not installed` : state === 'incompatible' ? 'Codex ACP 1.10.0 or newer required; version unknown/outdated' : providers.length ? 'Detected; provider/model access unverified' : 'Detected; this catalog’s OS-backed providers are not supported by this adapter. Use existing local setup for native authentication.';
result.push({ id: row.id, label: row.label!, executable: binary, cli, state, providers, reason });
}

View File

@@ -33,7 +33,7 @@ export type ManagerResult =
export type ManagerRequest = { id: number; action: string; values?: Record<string, string>; target?: string; revision?: number };
export type ManagerItem = { startTarget?: string; startRevision?: number; target?: string; id: string; label: string; detail: string; evidence?: string; disabled?: Record<string, string> };
export type MoveDestination = { target: string; revision: number; label: string; reason?: string };
export type ManagerSnapshot = { managementRelay?: 'connected' | 'disconnected'; diagnostics?: ManagerItem[]; local: ManagerItem[]; agents: (ManagerItem & { revision: number; configurations: string[]; destinations?: MoveDestination[] })[]; routing?: { owner: string; relay: string }; owner?: string; status: string; settings?: Settings; service?: ServiceStatus; hostRelay?: string; relayName?: string; profilePreview?: RegisteredAgent; models?: string[]; runtimeExecutable?: string; harnesses?: DetectedHarness[]; databricksHost?: string };
export type ManagerSnapshot = { managementRelay?: 'connected' | 'disconnected'; diagnostics?: ManagerItem[]; local: ManagerItem[]; agents: (ManagerItem & { revision: number; configurations: string[]; destinations?: MoveDestination[] })[]; routing?: { owner: string; relay: string }; owner?: string; status: string; settings?: Settings; service?: ServiceStatus; hostRelay?: string; relayName?: string; profilePreview?: RegisteredAgent; models?: string[]; modelLabels?: Record<string,string>; runtimeExecutable?: string; harnesses?: DetectedHarness[]; databricksHost?: string };
const short = (s: string) => s.length > 22 ? `${s.slice(0,8)}…${s.slice(-6)}` : s;
/** Final plain display text thrown by this boundary; never rewrapped or retranslated. */
@@ -154,6 +154,7 @@ export class ManagerController {
private probing = false;
private profilePreview?: RegisteredAgent;
private models?: string[];
private modelLabels?: Record<string,string>;
private runtimeExecutable?: string;
private harnesses?: DetectedHarness[];
private databricksHost?: string;
@@ -236,7 +237,7 @@ A Stop result is not a recent host report that confirms the agent is stopped.` }
try { settings = readSettings(this.hostDirectory); if (existsSync(join(this.hostDirectory,'host-identity.json'))) hostRelay = readHostIdentityPublic(this.hostDirectory).pairing.relay; } catch { /* Existing error row remains actionable; never reset settings. */ }
const footerRelay = hostRelay ?? routing?.relay;
this.observeRelayName(footerRelay);
return { managementRelay:this.owner ? this.client?.connected ? 'connected' : 'disconnected' : undefined, settings, service: this.service, hostRelay, relayName: this.relayName && this.relayName.relay === footerRelay ? this.relayName.name : undefined, profilePreview: this.profilePreview, models: this.models, runtimeExecutable: this.runtimeExecutable, harnesses: this.harnesses, databricksHost: this.databricksHost, local, agents, diagnostics, routing: routing ? { owner: routing.owner, relay: routing.relay } : undefined, owner: this.owner, status: this.status + (this.owner ? `\n${this.directoryState}` : '') };
return { managementRelay:this.owner ? this.client?.connected ? 'connected' : 'disconnected' : undefined, settings, service: this.service, hostRelay, relayName: this.relayName && this.relayName.relay === footerRelay ? this.relayName.name : undefined, profilePreview: this.profilePreview, models: this.models, modelLabels:this.modelLabels, runtimeExecutable: this.runtimeExecutable, harnesses: this.harnesses, databricksHost: this.databricksHost, local, agents, diagnostics, routing: routing ? { owner: routing.owner, relay: routing.relay } : undefined, owner: this.owner, status: this.status + (this.owner ? `\n${this.directoryState}` : '') };
}
private moveDestinations(source: Message): MoveDestination[] {
@@ -403,17 +404,17 @@ A Stop result is not a recent host report that confirms the agent is stopped.` }
this.databricksHost = process.env.DATABRICKS_HOST ?? ''; this.status = 'Provider credentials stay in Beehive’s OS store.';
} else if (request.action === 'add-databricks') {
await addDatabricks(this.hostDirectory,v.name,v.endpoint,abort.signal); check(); this.status = 'Databricks signed in and saved. No agent was started.';
} else if (request.action === 'add-openai') {
await this.credential({ action: 'add-openai', directory: this.hostDirectory, name: v.name, secret: v.secret },abort.signal); check(); this.status = 'Provider saved. No agent was started.';
} else if (request.action === 'add-openai' || request.action === 'add-provider') {
await this.credential({ action: request.action, directory: this.hostDirectory, name: v.name, secret: v.secret, type:v.type, endpoint:v.endpoint, wire:v.wire },abort.signal); check(); this.status = 'Provider saved. No agent was started.';
} else if (request.action === 'runtime-form') {
this.runtimeExecutable = undefined; this.harnesses = undefined; this.models = undefined;
this.runtimeExecutable = undefined; this.harnesses = undefined; this.models = undefined; this.modelLabels = undefined;
const harnesses = await discoverHarnesses(abort.signal); check(); this.harnesses = harnesses;
this.runtimeExecutable = this.harnesses.find(h => h.id === 'buzz-agent' && h.providers.length)?.executable; this.models = undefined;
this.runtimeExecutable = this.harnesses.find(h => h.id === 'buzz-agent' && h.providers.length)?.executable; this.models = undefined; this.modelLabels = undefined;
this.status = this.harnesses.map(h => `${h.label}: ${h.reason}`).join(' · ');
} else if (request.action === 'models') {
this.models = undefined;
this.models = undefined; this.modelLabels = undefined;
const provider = readSettings(this.hostDirectory).providers.find(p => p.id === v.provider);
const result = provider?.type === 'databricks_v2' ? await databricksNative({ action: 'models',host:provider.endpoint,key:provider.key },abort.signal) : await this.credential({ action: 'models', directory: this.hostDirectory, provider: v.provider },abort.signal); check(); this.models = result.models; this.status = 'Model list loaded. Custom model is also available.';
const result = provider?.type === 'databricks_v2' ? await databricksNative({ action: 'models',host:provider.endpoint,key:provider.key },abort.signal) : await this.credential({ action: 'models', directory: this.hostDirectory, provider: v.provider },abort.signal); check(); this.models = result.models; this.modelLabels = result.modelLabels; this.status = 'Model list loaded. Custom model is also available.';
} else if (request.action === 'add-runtime') {
const selected = this.harnesses?.find(h => h.id === (v.harness ?? 'buzz-agent'));
const previous = readSettings(this.hostDirectory);

View File

@@ -6,9 +6,9 @@ import { object } from './protocol.ts';
import { readHostIdentity, bootstrapHostIdentity } from './host-identity.ts';
import { createCredential, credentialReference, readCredential, systemCredentials } from './credential-store.ts';
import { publicKey } from './protocol.ts';
import { registerAgent, agentNsec, addOpenAI, providerCredentials } from './settings-credentials.ts';
import { registerAgent, agentNsec, addOpenAI, addProvider, providerCredentials } from './settings-credentials.ts';
import { readSettings } from './settings.ts';
import { openAIModels } from './settings-models.ts';
import { providerModelOptions } from './settings-models.ts';
// Node-only, bounded by the controller. Secrets use private IPC, never argv/output.
process.once('message', async (input: any) => {
@@ -30,6 +30,9 @@ process.once('message', async (input: any) => {
} else if (input.action === 'add-openai') {
addOpenAI(input.directory,input.name,input.secret,providerCredentials());
process.send?.({ ok: true });
} else if (input.action === 'add-provider') {
addProvider(input.directory,input.name,input.secret,providerCredentials(),input.type,input.endpoint,input.wire);
process.send?.({ok:true});
} else if (input.action === 'provider-read') {
const secret = providerCredentials().read(input.key);
if (!secret) throw Error('Missing provider credential');
@@ -39,8 +42,8 @@ process.once('message', async (input: any) => {
if (!provider) throw Error('Missing provider');
const secret = providerCredentials().read(provider.key);
if (!secret) throw Error('Missing provider credential');
const models = await openAIModels(secret, AbortSignal.timeout(7000));
process.send?.({ ok: true, models });
const options = await providerModelOptions(provider, secret, AbortSignal.timeout(7000));
process.send?.({ ok: true, models:options.map(v=>v.id), modelLabels:Object.fromEntries(options.map(v=>[v.id,v.name])) });
} else if (input.action === 'signin') {
const ref = credentialReference('owner', input.owner);
if (input.secret !== undefined) {

View File

@@ -83,16 +83,19 @@ function render() {
let actions: ManagerAction[] = scope !== 1 ? [
{ label: 'Add provider', run: async () => {
await request('provider-form');
const type = await screen.choose('Add provider',['OpenAI','Databricks v2']); if (!type) return;
const type = await screen.choose('Add provider',['OpenAI','Anthropic','OpenAI-compatible','OpenRouter','Databricks v2']); if (!type) return;
if (type === 'Databricks v2') {
const endpoint = await screen.input('Databricks workspace URL',snapshot.databricksHost ?? ''); if (!endpoint) return;
const name = await screen.input('Provider name','Databricks'); if (!name) return;
if (await screen.confirm(`Sign in to ${name} in your browser? Tokens stay in Beehive’s OS store. No agent will start.`)) await request('add-databricks',{ name,endpoint });
return;
}
const name = await screen.input('Provider name','OpenAI'); if (!name) return;
let secret = await screen.input('OpenAI API key. Hidden. Saved in Beehive’s OS credential store.','',true); if (!secret) return;
if (await screen.confirm(`Save provider ${name}? No agent will start.`)) await request('add-openai',{ name, secret });
const providerType = type === 'Anthropic' ? 'anthropic' : type === 'OpenRouter' ? 'openrouter' : type === 'OpenAI-compatible' ? 'openai-compat' : 'openai';
const endpoint = providerType === 'openai-compat' ? await screen.input('HTTPS endpoint') : providerType === 'anthropic' ? 'https://api.anthropic.com' : providerType === 'openrouter' ? 'https://openrouter.ai/api/v1' : 'https://api.openai.com/v1'; if (!endpoint) return;
const wire = providerType === 'openai-compat' ? await screen.choose('API',['auto','chat','responses']) : undefined; if (providerType === 'openai-compat' && !wire) return;
const name = await screen.input('Provider name',type); if (!name) return;
let secret = await screen.input(`${type} API key · Hidden · OS credential store`,'',true); if (!secret) return;
if (await screen.confirm(`Save provider ${name}? No agent will start.`)) await request('add-provider',{ name, secret, type:providerType, endpoint, ...(wire ? {wire} : {}) });
secret = '';
} },
{ label: 'Add runtime', run: () => runtimeForm(screen, () => snapshot, request) },
@@ -157,7 +160,7 @@ The source stops before destination launch. Workspace, session and credentials s
{ label: 'Models', disabled: rows.some(r => r.id === selected) ? undefined : 'Select provider', run: async () => {
const provider = selected;
await request('models', { provider });
if (snapshot.models) await screen.choose('Models', snapshot.models);
if (snapshot.models) await screen.choose('Models', snapshot.models.map(id=>snapshot.modelLabels?.[id] && snapshot.modelLabels[id] !== id ? `${snapshot.modelLabels[id]} · ${id}` : id));
} },
];
if (scope === 1 && !snapshot.owner) actions.push({ label: 'New private instructions draft…', run: async () => { const name = await screen.input('Private instructions · 1 of 2\nDraft name', '', false, false, v => v.trim() ? '' : 'Enter a draft name.'); if (name === undefined) return; const instructions = await screen.input(`Private instructions · 2 of 2\nDraft: ${name}\nSaved only on this computer. Not published or used by an agent. Do not enter passwords or keys.`, '', false, true, v => v.trim() ? '' : 'Enter instructions.'); if (instructions !== undefined) await request('draft', { name, instructions }); } });

View File

@@ -7,11 +7,11 @@ export const presets = [
{
"id": "pi",
"label": "Pi",
"command": "pi-acp",
"command": "buzz-pi-acp",
"args": [],
"env": {},
"installInstructionsUrl": "https://github.com/svkozak/pi-acp",
"installHint": "Install Pi with npm install -g --ignore-scripts @earendil-works/pi-coding-agent. Install the Pi ACP adapter with npm install -g pi-acp.",
"installInstructionsUrl": "https://github.com/salman1993/buzz-pi-acp",
"installHint": "Install Pi with npm install -g --ignore-scripts @earendil-works/pi-coding-agent. Requires Node.js 22 or newer. Install the Pi ACP adapter with npm install -g --install-links=true git+https://github.com/salman1993/buzz-pi-acp.git#86b201e. Make sure buzz-pi-acp is on PATH, then restart Buzz.",
"underlyingCli": "pi",
"underlyingCliInstallInstructionsUrl": "https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent"
},

View File

@@ -1,8 +1,7 @@
import manifest from './model-capabilities.json' with { type: 'json' };
/** Packaged verbatim from scripts/model-capabilities.json. Interpretation mirrors
* Desktop modelCapabilities.ts exact/family precedence. No fallback effort picker:
* unknown custom names and UC FQNs deliberately omit tuning rather than guessing. */
* Desktop modelCapabilities.ts exact/family precedence. Provider fallbacks are authored capabilities, including custom names and UC FQNs. */
export function runtimeCapabilities(provider: string, model: string) {
const canon = provider === 'openai-compat' ? 'openai' : provider;
if (canon === 'databricks_v2' && model.split('.').length === 3 && model.split('.').every(v => v.length > 0 && !/[\s/]/.test(v))) {
@@ -17,7 +16,7 @@ export function runtimeCapabilities(provider: string, model: string) {
});
const stripped = service.slice(tokens.length ? Math.min(...tokens) : 0);
const version = /^gpt-(\d+)(?=[^a-z0-9]|$)/.exec(stripped);
return { ...manifest.provider_fallbacks.databricks_v2.concrete_unknown, supported_efforts: [] as string[], databricks_v2_wire_route: version && Number(version[1]) >= 5 ? 'openai-responses' : 'mlflow-chat' };
return { ...manifest.provider_fallbacks.databricks_v2.concrete_unknown, databricks_v2_wire_route: version && Number(version[1]) >= 5 ? 'openai-responses' : 'mlflow-chat' };
}
const lower = model.toLowerCase();
const exact = manifest.exact_records.find(r => r.provider === canon && r.raw_model_id.toLowerCase() === lower);
@@ -40,8 +39,8 @@ export function runtimeCapabilities(provider: string, model: string) {
}).sort((a, b) => b.length - a.length || (a.rule.id < b.rule.id ? -1 : 1));
const rule = matches[0]?.rule;
if (rule) return rule;
const fallback = manifest.provider_fallbacks[canon as keyof typeof manifest.provider_fallbacks]?.concrete_unknown;
return fallback ? { ...fallback, supported_efforts: [] as string[] } : undefined;
const pair = new Map(Object.entries(manifest.provider_fallbacks)).get(canon) ?? manifest.provider_fallbacks._default;
return model.trim() ? pair.concrete_unknown : pair.blank;
}
export function runtimeEfforts(provider: string, model: string): readonly string[] {

View File

@@ -24,8 +24,10 @@ export async function runtimeForm(screen: {
const modelResult = await request('models', { provider: provider.id });
if (modelResult.state === 'cancelled' || modelResult.state === 'ignored') return;
const models = snapshot().models;
const choiceModel = await screen.choose(models ? 'Model' : 'Model listing unavailable · Custom model allowed', [...(models ?? []), 'Custom model']); if (!choiceModel) return;
const model = choiceModel === 'Custom model' ? await screen.input('Exact model ID. Custom does not verify provider access.') : choiceModel; if (!model) return;
const labelsById = snapshot().modelLabels ?? {};
const modelLabels = (models ?? []).map(id=>labelsById[id] && labelsById[id] !== id ? `${labelsById[id]} · ${id}` : id);
const choiceModel = await screen.choose(models ? 'Model' : 'Model listing unavailable · Custom model allowed', [...modelLabels, 'Custom model']); if (!choiceModel) return;
const model = choiceModel === 'Custom model' ? await screen.input('Exact model ID. Custom does not verify provider access.') : models?.[modelLabels.indexOf(choiceModel)]; if (!model) return;
const efforts = selected.id === 'buzz-agent' ? runtimeEfforts(provider.type, model) : selected.id === 'pi' ? piEfforts(provider.type, model) : [];
const effort = efforts.length ? await screen.choose('Effort · new runs only', ['Inherit', ...efforts]) : 'Inherit'; if (!effort) return;
const entry = await screen.input('Environment · JSON name/value map · no secrets', '{}'); if (entry === undefined) return;

View File

@@ -6,7 +6,7 @@ import { loadNativeEntry, type NativeEntry } from './native-credentials.ts';
import { type CredentialBackend, credentialReference, createCredential, readCredential } from './credential-store.ts';
import { nip19 } from 'nostr-tools';
import { publicKey } from './protocol.ts';
import { readSettings, saveSettings, settingsId, settingsLock, type ProviderReference, type RegisteredAgent } from './settings.ts';
import { readSettings, saveSettings, settingsId, settingsLock, type ProviderReference, type RegisteredAgent, type SavedProvider, validateSettings } from './settings.ts';
/** Decode hidden nsec input on Node, never in the renderer's display state. */
export function agentNsec(input: string): string {
@@ -46,13 +46,18 @@ export function providerCredentials(load = loadNativeEntry): ProviderCredentials
}
/** Read-back precedes public persistence. Failed commit retains the exact key entry. */
export function addOpenAI(directory: string, name: string, secret: string, backend: ProviderCredentials) {
return addProvider(directory,name,secret,backend,'openai','https://api.openai.com/v1');
}
/** Verified immutable OS entry for each supported API-key provider. */
export function addProvider(directory: string, name: string, secret: string, backend: ProviderCredentials, type: SavedProvider['type'], endpoint: string, wire?: SavedProvider['wire']) {
if (!name || name.length > 128 || /[\x00-\x1f\x7f]/.test(name)) throw Error('Enter a provider name');
if (!secret || secret.length > 16384 || /\s/.test(secret)) throw Error('Enter an API key');
const previous = readSettings(directory), id = settingsId();
const key: ProviderReference = { service: 'beehive', account: `provider:${id}` };
const candidate = validateSettings({ ...previous, providers: [...previous.providers, { id, name, type, endpoint, key, ...(wire ? {wire} : {}) }] });
settingsLock(directory, () => { retainCredentialAttempt(directory,key); backend.create(key, secret); });
if (backend.read(key) !== secret) throw Error('Provider credential verification failed');
return saveSettings(directory, { ...previous, providers: [...previous.providers, { id, name, type: 'openai', endpoint: 'https://api.openai.com/v1', key }] }, previous.revision);
return saveSettings(directory, candidate, previous.revision);
}
/** Public recovery references precede OS writes. Cancellation may leave a stored

View File

@@ -9,18 +9,51 @@ export function detectBuzzAgent(path = process.env.PATH ?? ''): string | undefin
}
return undefined;
}
/** Authenticated OpenAI model list. Empty and error stay distinct; custom is independent. */
export async function openAIModels(secret: string, signal: AbortSignal, fetcher: typeof fetch = fetch): Promise<string[]> {
const response = await fetcher('https://api.openai.com/v1/models',{ headers: { Authorization: `Bearer ${secret}` }, signal, redirect: 'error' });
if (!response.ok || !response.body) throw Error('OpenAI model list unavailable');
const reader = response.body.getReader(); let bytes = 0; const chunks: Uint8Array[] = [];
try { while (true) { const next = await reader.read(); if (next.done) break; bytes += next.value.length; if (bytes > 1048576) throw Error('OpenAI model list exceeds limit'); chunks.push(next.value); } }
finally { await reader.cancel(); reader.releaseLock(); }
try {
const json = JSON.parse(Buffer.concat(chunks).toString());
if (!Array.isArray(json.data) || json.data.length > 10000) throw Error();
const ids: string[] = json.data.map((v: any) => v.id).filter((id: unknown): id is string => typeof id === 'string' && /^[a-zA-Z0-9_.:/-]{1,200}$/.test(id) && !/(embedding|whisper|tts|dall-e|image|audio|realtime|moderation)/i.test(id));
const all = new Set(ids);
return [...all].filter(id => { const base = id.replace(/-\d{4}-\d{2}-\d{2}$/,''); return base === id || !all.has(base); }).sort().slice(0,1000);
} catch { throw Error('OpenAI model list is invalid'); }
/** A provider label never substitutes for the exact launch ID. */
export type ProviderModel = { id: string; name: string };
const validId = (id: unknown): id is string => typeof id === 'string' && /^[a-zA-Z0-9_.:/-]{1,200}$/.test(id);
function openAILabel(id: string) {
const canonical = id.replace(/-\d{4}-\d{2}-\d{2}$/,'');
const suffix = (s: string) => s.split('-').map(v => /^pro$/i.test(v) ? 'Pro' : /^(mini|nano)$/i.test(v) ? v.toLowerCase() : v).join(' ');
return canonical.startsWith('chatgpt-') ? `ChatGPT ${suffix(canonical.slice(8))}` : canonical.startsWith('gpt-') ? `GPT-${suffix(canonical.slice(4))}` : canonical;
}
async function page(url: string, headers: Record<string,string>, signal: AbortSignal, fetcher: typeof fetch): Promise<any> {
const response = await fetcher(url,{headers,signal,redirect:'error'});
if (!response.ok || !response.body) throw Error('Provider model list unavailable');
const reader = response.body.getReader(); let bytes = 0; const chunks: Uint8Array[] = [];
try { while (true) { const next = await reader.read(); if (next.done) break; bytes += next.value.length; if (bytes > 1048576) throw Error('Provider model list exceeds limit'); chunks.push(next.value); } }
finally { await reader.cancel(); reader.releaseLock(); }
try { const json = JSON.parse(Buffer.concat(chunks).toString()); if (!Array.isArray(json.data) || json.data.length > 10000) throw Error(); return json; }
catch { throw Error('Provider model list is invalid'); }
}
/** Desktop provider-specific discovery, bounded by response bytes, rows and pages.
* Errors never include response bodies, endpoints or credentials. */
export async function providerModelOptions(provider: {type:string;endpoint:string}, secret: string, signal: AbortSignal, fetcher: typeof fetch = fetch): Promise<ProviderModel[]> {
try {
if (!['openai','openai-compat','anthropic','openrouter'].includes(provider.type)) throw Error();
const base = provider.endpoint.replace(/\/$/,'');
const url = `${base}${provider.type === 'anthropic' && !base.endsWith('/v1') ? '/v1' : ''}/models`;
const headers: Record<string,string> = provider.type === 'anthropic' ? {'x-api-key':secret,'anthropic-version':'2023-06-01'} : {Authorization:`Bearer ${secret}`};
const rows: any[] = []; const cursors = new Set<string>(); let cursor: string | undefined;
for (let i=0;i<20;i++) {
const json = await page(url + (cursor ? `?after_id=${encodeURIComponent(cursor)}` : ''),headers,signal,fetcher);
rows.push(...json.data); if (rows.length > 10000) throw Error();
if (provider.type !== 'anthropic' || !json.has_more) break;
if (!validId(json.last_id) || cursors.has(json.last_id) || i === 19) throw Error();
cursor=json.last_id; cursors.add(cursor!);
}
const all = new Set(rows.map(v=>v.id)); const seen = new Set<string>();
if (['openai','openai-compat'].includes(provider.type)) rows.sort((a,b)=>(Number.isFinite(b.created) ? b.created : -Infinity)-(Number.isFinite(a.created) ? a.created : -Infinity) || (String(a.id) < String(b.id) ? -1 : String(a.id) > String(b.id) ? 1 : 0));
return rows.filter(v=>validId(v.id)).filter(v=>provider.type !== 'openai' || /^(gpt-|o|chatgpt-)/i.test(v.id) && !/(audio|dall-e|embedding|image|moderation|realtime|speech|transcribe|tts|whisper)/i.test(v.id) && (v.id.replace(/-\d{4}-\d{2}-\d{2}$/,'') === v.id || !all.has(v.id.replace(/-\d{4}-\d{2}-\d{2}$/,''))))
.filter(v=>provider.type !== 'openrouter' || Array.isArray(v.supported_parameters) && v.supported_parameters.includes('tools'))
.filter(v=>!seen.has(v.id) && !!seen.add(v.id)).slice(0,1000)
.map(v=>({id:v.id,name:provider.type === 'anthropic' && typeof v.display_name === 'string' && v.display_name.length <= 200 && !/[\x00-\x1f\x7f]/.test(v.display_name) ? v.display_name : ['openai','openai-compat'].includes(provider.type) ? openAILabel(v.id) : v.id}));
} catch { throw Error('Provider model list unavailable or invalid'); }
}
/** Compatibility ID-only API; UI receives labels separately. */
export async function providerModels(provider: {type:string;endpoint:string}, secret: string, signal: AbortSignal, fetcher: typeof fetch = fetch) {
return (await providerModelOptions(provider,secret,signal,fetcher)).map(v=>v.id);
}
export async function openAIModels(secret: string, signal: AbortSignal, fetcher: typeof fetch = fetch) {
return providerModels({type:'openai',endpoint:'https://api.openai.com/v1'},secret,signal,fetcher);
}

View File

@@ -22,7 +22,7 @@ export function runtimeBindings(base: Setup, settings: Settings, agent = base.ag
runner: runtime.executable, args: [], workspace: base.workspace, allowedWorkspaces: base.allowedWorkspaces,
serviceHome: paths?.home ?? base.serviceHome, configDirectory: paths?.config ?? base.configDirectory,
...(base.conversation ? { conversation: base.conversation } : {}), ...(runtime.harness === 'codex' ? { mode: 'codex', codex: { ...(managedHome ? { managedHome } : {}), cli: runtime.cli!, credential: provider.key, models: [runtime.model] } } : { mode: runtime.harness === 'pi' ? 'pi' : 'buzz-agent-api-key', ...(runtime.harness === 'pi' ? { piCli: runtime.cli } : {}),
buzzProvider: provider.type === 'databricks_v2' ? { provider:'databricks_v2',auth:'token',baseUrl:provider.endpoint,credential:provider.key,models:[runtime.model],...(runtime.effort ? { effort:runtime.effort } : {}) } : { provider: 'openai-compat', baseUrl: provider.endpoint, credential: provider.key, wire: 'auto', models: [runtime.model], ...(runtime.effort ? { effort:runtime.effort } : {}) } }),
buzzProvider: provider.type === 'databricks_v2' ? { provider:'databricks_v2',auth:'token',baseUrl:provider.endpoint,credential:provider.key,models:[runtime.model],...(runtime.effort ? { effort:runtime.effort } : {}) } : { provider: provider.type === 'openai' ? 'openai-compat' : provider.type, baseUrl: provider.endpoint, credential: provider.key, ...(['openai','openai-compat'].includes(provider.type) ? {wire: provider.wire ?? 'auto'} : {}), models: [runtime.model], ...(runtime.effort ? { effort:runtime.effort } : {}) } }),
})];
}));
}

View File

@@ -1,3 +1,4 @@
import { validateBuzzProvider } from './buzz-provider.ts';
import { runtimeEnvironment } from './runtime-environment.ts';
import { piEfforts } from './pi.ts';
import { validateRuntimeEffort } from './runtime-effort.ts';
@@ -14,7 +15,7 @@ import { credentialReference, type CredentialReference } from './credential-stor
export type RegisteredAgent = { runtimeId?: string; publicKey: string; key: CredentialReference; profile?: { relay: string; name?: string; about?: string; picture?: string }; profileState: 'found' | 'none' | 'unavailable' };
/** Each provider version owns an exact immutable OS credential entry. */
export type ProviderReference = { service: 'beehive'; account: string };
export type SavedProvider = { id: string; name: string; type: 'openai' | 'databricks_v2'; endpoint: string; key: ProviderReference };
export type SavedProvider = { id: string; name: string; type: 'openai' | 'anthropic' | 'openai-compat' | 'openrouter' | 'databricks_v2'; endpoint: string; wire?: 'auto' | 'chat' | 'responses'; key: ProviderReference };
export type SavedRuntime = { environment?: Record<string, string>; id: string; name: string; harness: 'buzz-agent' | 'codex' | 'pi'; cli?: string; executable: string; providerId: string; model: string; effort?: string };
export type Settings = { version: 1; revision: number; agents: RegisteredAgent[]; providers: SavedProvider[]; runtimes: SavedRuntime[] };
const path = (directory: string) => join(directory, 'settings.json');
@@ -30,18 +31,21 @@ export function validateSettings(value: Settings): Settings {
if (a.runtimeId !== undefined && !value.runtimes.some(r => r.id === a.runtimeId)) throw Error('Registered runtime missing');
if (a.profile && (typeof a.profile.relay !== 'string' || Object.values(a.profile).some(v => typeof v !== 'string' || v.length > 2048 || /[\x00-\x1f\x7f]/.test(v)))) throw Error('Invalid public profile');
}
for (const p of value.providers) if (!label(p.name) || !label(p.id) || !['openai','databricks_v2'].includes(p.type) || (p.type === 'openai' ? p.endpoint !== 'https://api.openai.com/v1' : databricksHost(p.endpoint) !== p.endpoint) || p.key?.service !== 'beehive' || !/^provider:[0-9a-f-]{36}$/.test(p.key.account)) throw Error('Invalid saved provider');
for (const p of value.providers) {
if (!label(p.name) || !label(p.id) || !['openai','anthropic','openai-compat','openrouter','databricks_v2'].includes(p.type) || (p.type === 'openai' && p.endpoint !== 'https://api.openai.com/v1') || (p.type === 'databricks_v2' && databricksHost(p.endpoint) !== p.endpoint) || (p.type !== 'openai-compat' && p.wire !== undefined)) throw Error('Invalid saved provider');
validateBuzzProvider({provider:p.type === 'openai' ? 'openai-compat' : p.type, baseUrl:p.endpoint, credential:p.key, models:['validation-custom'], ...(p.type === 'databricks_v2' ? {auth:'token'} : {}), ...(['openai','openai-compat'].includes(p.type) ? {wire:p.wire ?? 'auto'} : {})});
}
for (const r of value.runtimes) if (!label(r.id) || !label(r.name) || !['buzz-agent','codex','pi'].includes(r.harness) || !isAbsolute(r.executable) || !/^[a-zA-Z0-9_.:/-]{1,200}$/.test(r.model) || !value.providers.some(p => p.id === r.providerId)) throw Error('Invalid saved runtime');
for (const r of value.runtimes) {
if (r.environment !== undefined) runtimeEnvironment(r.environment);
if (r.harness === 'codex' ? !r.cli || !isAbsolute(r.cli) || r.effort !== undefined || value.providers.find(p => p.id === r.providerId)?.type !== 'openai' : r.harness === 'pi' ? !r.cli || !isAbsolute(r.cli) || r.effort !== undefined && !piEfforts(value.providers.find(p => p.id === r.providerId)!.type, r.model).includes(r.effort) : r.cli !== undefined) throw Error('Unsupported runtime provider/CLI/effort combination');
if (r.harness === 'codex' ? !r.cli || !isAbsolute(r.cli) || r.effort !== undefined || value.providers.find(p => p.id === r.providerId)?.type !== 'openai' : r.harness === 'pi' ? !['openai','databricks_v2'].includes(value.providers.find(p => p.id === r.providerId)!.type) || !r.cli || !isAbsolute(r.cli) || r.effort !== undefined && !piEfforts(value.providers.find(p => p.id === r.providerId)!.type, r.model).includes(r.effort) : r.cli !== undefined) throw Error('Unsupported runtime provider/CLI/effort combination');
}
for (const r of value.runtimes) validateRuntimeEffort(value.providers.find(p => p.id === r.providerId)!.type, r.model, r.effort);
// Reject unknown fields, including accidental secret-bearing input.
const fields = (o: object, allowed: string[]) => { if (Object.keys(o).some(k => !allowed.includes(k))) throw Error('Unexpected settings field'); };
fields(value, ['version','revision','agents','providers','runtimes']);
for (const a of value.agents) { fields(a, ['publicKey','key','profile','profileState','runtimeId']); if (a.profile) fields(a.profile, ['relay','name','picture','about']); }
for (const p of value.providers) { fields(p, ['id','name','type','endpoint','key']); fields(p.key, ['service','account']); }
for (const p of value.providers) { fields(p, ['id','name','type','endpoint','key','wire']); fields(p.key, ['service','account']); }
for (const r of value.runtimes) fields(r, ['id','name','harness','executable','providerId','model','effort','cli','environment']);
return structuredClone(value);
}

View File

@@ -14,13 +14,13 @@ for await (const line of createInterface({ input: process.stdin })) {
if (mode === 'flood') { console.log('x'.repeat(1024 * 1024 + 1)); continue; }
let result: unknown;
if (m.method === 'initialize') result = { protocolVersion: m.params.protocolVersion, agentInfo: { name: 'buzz-agent' } };
else if (m.method === 'session/new') result = { sessionId, models: { currentModelId: 'ignored-default', availableModels: mode === 'empty' ? [] : [{ modelId: mode === 'filtered' ? 'other-model' : ['openai','databricks-os'].includes(mode ?? '') ? process.env.BUZZ_AGENT_MODEL : 'databricks-claude-haiku-4-5', name: 'DO-NOT-RELAY-SECRET' }] } };
else if (m.method === 'session/new') result = { sessionId, models: { currentModelId: 'ignored-default', availableModels: mode === 'empty' ? [] : [{ modelId: mode === 'filtered' ? 'other-model' : ['openai','databricks-os','saved-provider'].includes(mode ?? '') ? process.env.BUZZ_AGENT_MODEL : 'databricks-claude-haiku-4-5', name: 'DO-NOT-RELAY-SECRET' }] } };
else if (m.method === 'session/set_model') {
selected = m.params.modelId;
if (mode === 'reject') { send({ jsonrpc: '2.0', id: m.id, error: { code: -1, message: 'DO-NOT-RELAY-SECRET' } }); continue; }
result = { sessionId, modelId: mode === 'wrong-model' ? 'other-model' : selected };
} else if (m.method === 'session/prompt') {
if (selected !== process.env.BUZZ_AGENT_MODEL || m.params.sessionId !== sessionId || (mode !== 'databricks-os' && process.env.DATABRICKS_TOKEN) || process.env.BUZZ_PRIVATE_KEY || process.env.BUZZ_AGENT_PROVIDER !== (mode === 'openai' ? 'openai-compat' : 'databricks_v2')) process.exit(8);
if (selected !== process.env.BUZZ_AGENT_MODEL || m.params.sessionId !== sessionId || (mode !== 'databricks-os' && process.env.DATABRICKS_TOKEN) || process.env.BUZZ_PRIVATE_KEY || mode !== 'saved-provider' && process.env.BUZZ_AGENT_PROVIDER !== (mode === 'openai' ? 'openai-compat' : 'databricks_v2')) process.exit(8);
if (mode === 'databricks-os' && (!/^http:\/\/127\.0\.0\.1:\d+$/.test(process.env.DATABRICKS_HOST ?? '') || !/^[0-9a-f]{64}$/.test(process.env.DATABRICKS_TOKEN ?? '') || process.env.BEEHIVE_DATABRICKS_RUNTIME !== undefined)) process.exit(8);
if (mode === 'openai' && process.env.OPENAI_COMPAT_API_KEY !== 'synthetic-provider-key') process.exit(8);
if (mode === 'delayed') { writeFileSync('prompt-started', '1'); await delay(4000); }

View File

@@ -22,14 +22,14 @@ test('ten pinned presets distinguish executable/CLI/adapter absence without laun
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'bh-presets-')));
try {
assert.deepEqual(presets.map(p => [p.id, p.command, [...p.args]]), [
['pi','pi-acp',[]], ['devin','devin',['acp']], ['cursor','cursor-agent',['acp']], ['omp','omp',['acp']], ['grok','grok',['agent','--always-approve','stdio']], ['opencode','opencode',['acp']], ['kimi','kimi',['acp']], ['amp','amp-acp',[]], ['hermes','hermes-acp',[]], ['openclaw','openclaw',['acp']],
['pi','buzz-pi-acp',[]], ['devin','devin',['acp']], ['cursor','cursor-agent',['acp']], ['omp','omp',['acp']], ['grok','grok',['agent','--always-approve','stdio']], ['opencode','opencode',['acp']], ['kimi','kimi',['acp']], ['amp','amp-acp',[]], ['hermes','hermes-acp',[]], ['openclaw','openclaw',['acp']],
]);
assert.deepEqual(presets.filter(p => 'underlyingCli' in p).map(p => p.id), ['pi', 'amp']);
const pi = () => discoverPresets(dir).find(p => p.id === 'pi')!;
assert.equal(pi().availability, 'not-installed');
writeFileSync(join(dir, 'pi'), '#!/bin/sh\nexit 99\n', { mode: 0o700 });
assert.equal(pi().availability, 'adapter-missing');
writeFileSync(join(dir, 'pi-acp'), `#!/bin/sh\ntouch '${join(dir, 'unexpected-execution')}'\n`, { mode: 0o700 });
writeFileSync(join(dir, 'buzz-pi-acp'), `#!/bin/sh\ntouch '${join(dir, 'unexpected-execution')}'\n`, { mode: 0o700 });
assert.equal(pi().availability, 'available');
rmSync(join(dir, 'pi')); assert.equal(pi().availability, 'cli-missing');
for (const p of discoverPresets(dir)) { assert.equal(p.authentication, 'unverified'); assert.deepEqual(p.env, {}); assert.match(p.reason, /no exact actual-session model/); }

View File

@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
import { tmpdir } from 'node:os';
import { finalizeEvent } from 'nostr-tools/pure';
import { nsecEncode } from 'nostr-tools/nip19';
import { registerAgent, agentNsec, addOpenAI } from '../src/settings-credentials.ts';
import { registerAgent, agentNsec, addOpenAI, addProvider } from '../src/settings-credentials.ts';
import { readSettings, saveSettings, settingsId } from '../src/settings.ts';
import { profileFromEvents } from '../src/agent-profile.ts';
import { isolatedFileCredentials } from './isolated-file-credentials.ts';
@@ -75,28 +75,49 @@ test('OpenAI verifies store before public commit; listing is bounded and custom
assert.throws(() => saveSettings(root,catalog,catalog.revision),/changed/);
});
for (const providerType of ['openai','databricks_v2','codex-openai'] as const) test(`${providerType} saved runtime reaches host Save/new Start while the prior active run remains immutable`, async t => {
for (const providerType of ['openai','databricks_v2','codex-openai','anthropic','openai-compat','openrouter'] as const) test(`${providerType} saved runtime reaches host Save/new Start while the prior active run remains immutable`, async t => {
const root = fixture(t), owner = newKey(), agent = newKey(), hostKey = publicKey(newKey());
const codex = providerType === 'codex-openai';
const added = ['anthropic','openai-compat','openrouter'].includes(providerType);
const home = root, config = root;
const setup: Setup = { host: hostKey, ownerSecret: owner, agentSecret: agent, runner: realpathSync(process.execPath), args: ['-e','setInterval(()=>{},1000)'], workspace: root, serviceHome: home, configDirectory: config, mode: 'fixture' };
provision(root,setup,createGenesis(publicKey(owner),publicKey(agent),hostKey));
let receive: (m: Message) => void = () => {}; const reports: Message[] = [];
const transport = { binding: { host: hostKey, owner: publicKey(owner) }, validate() {}, connect(_url: string,_secret: string,handle: (m: Message) => void) { receive = handle; return { ready: Promise.resolve(), send(m: Message) { reports.push(m); }, close() {} }; } };
let providerReads = 0;
const running = await host(root,'ws://127.0.0.1',undefined,transport,undefined,async (input,signal) => { signal.throwIfAborted(); assert.equal((input as any).provider,providerType !== 'databricks_v2' ? 'openai-compat' : 'databricks_v2'); if (providerType === 'databricks_v2') assert.equal((input as any).host,'https://fixture.example'); providerReads++; return {ok:true,secret:codex ? 'fixture-codex-private-key' : 'synthetic-provider-key'}; });
const running = await host(root,'ws://127.0.0.1',undefined,transport,undefined,async (input,signal) => { signal.throwIfAborted(); assert.equal((input as any).provider,providerType === 'openai' || codex ? 'openai-compat' : providerType); if (providerType === 'databricks_v2') assert.equal((input as any).host,'https://fixture.example'); providerReads++; return {ok:true,secret:codex ? 'fixture-codex-private-key' : 'synthetic-provider-key'}; });
t.after(() => running.close());
const start = message('start',hostKey,publicKey(agent),0); receive(start);
await wait(() => reports.some(m => m.type === 'inventory' && m.body.phase === 'running'));
const active = reports.filter(m => m.type === 'inventory').at(-1)!.body.actualRun;
let stored: string | null = null;
if (providerType !== 'databricks_v2') addOpenAI(root,'Fixture','synthetic',{ read: () => stored, create(_r,v) { stored = v; } });
if (added) addProvider(root,'Fixture','synthetic',{read:()=>stored,create:(_r,v)=>{stored=v;}},providerType as 'anthropic'|'openai-compat'|'openrouter','https://fixture.example/v1',providerType === 'openai-compat' ? 'responses' : undefined);
else if (providerType !== 'databricks_v2') addOpenAI(root,'Fixture','synthetic',{ read: () => stored, create(_r,v) { stored = v; } });
else await addDatabricks(root,'Fixture','https://fixture.example',new AbortController().signal,async () => ({ok:true}));
const prior = readSettings(root), id = settingsId();
const model = codex ? 'custom-codex-model' : providerType === 'openai' ? 'gpt-5' : 'databricks-gpt-5-4';
const model = codex ? 'custom-codex-model' : added ? (providerType === 'anthropic' ? 'claude-sonnet-4-5' : 'unknown-custom-model') : providerType === 'openai' ? 'gpt-5' : 'databricks-gpt-5-4';
const executable = join(root,'fixture-buzz-agent');
writeFileSync(executable,`#!/bin/sh\n[ \"$REVIEW_MODE\" = synthetic ] || exit 10\n[ \"$BUZZ_AGENT_THINKING_EFFORT\" = high ] || exit 9\nexec '${process.execPath}' '${fileURLToPath(new URL('./acp-fixture.ts',import.meta.url))}' ${providerType === 'openai' ? 'openai' : 'databricks-os'}\n`,{mode:0o700});
let requests = 0;
if (added) {
const keyName = providerType === 'anthropic' ? 'ANTHROPIC_API_KEY' : providerType === 'openrouter' ? 'OPENROUTER_API_KEY' : 'OPENAI_COMPAT_API_KEY';
const server = createServer(async (req,res) => {
assert.equal(req.method,'POST'); assert.equal(req.url, providerType === 'anthropic' ? '/v1/messages' : providerType === 'openrouter' ? '/v1/chat/completions' : '/v1/responses');
assert.equal(req.headers.authorization,'Bearer synthetic-provider-key');
let body=''; for await (const chunk of req) body+=chunk;
assert.deepEqual(JSON.parse(body),{model,...(providerType === 'anthropic' ? {} : {effort:'high'})}); requests++; res.end('{}');
});
server.listen(0,'127.0.0.1'); await once(server,'listening');
t.after(()=>new Promise<void>(resolve=>server.close(()=>resolve())));
const endpoint = `http://127.0.0.1:${(server.address() as any).port}` + (providerType === 'anthropic' ? '/v1/messages' : providerType === 'openrouter' ? '/v1/chat/completions' : '/v1/responses');
writeFileSync(executable,`#!${process.execPath}
if (process.env.BUZZ_AGENT_PROVIDER !== ${JSON.stringify(providerType)} || process.env.REVIEW_MODE !== 'synthetic') throw Error('Wrong binding');
const response = await fetch(${JSON.stringify(endpoint)},{method:'POST',headers:{authorization:'Bearer '+process.env[${JSON.stringify(keyName)}]},body:JSON.stringify({model:process.env.BUZZ_AGENT_MODEL,effort:process.env.BUZZ_AGENT_THINKING_EFFORT})});
if (!response.ok) throw Error('Synthetic provider refused');
process.argv[2]='saved-provider';
await import(${JSON.stringify(new URL('./acp-fixture.ts',import.meta.url).href)});
`,{mode:0o700});
}
if (codex) {
const server = createServer(async (req,res) => {
assert.equal(req.method,'POST'); assert.equal(req.url,'/v1/responses');
@@ -116,7 +137,7 @@ if (!response.ok) throw Error('Synthetic provider rejected');
await import(${JSON.stringify(new URL('./conversation-harness-fixture.ts',import.meta.url).href)});
`,{mode:0o700});
}
saveSettings(root,{ ...prior, runtimes: [{ id, name: 'Future', environment: {REVIEW_MODE:'synthetic'}, harness: codex ? 'codex' : 'buzz-agent', executable, providerId: prior.providers[0]!.id, model, ...(codex ? {cli:realpathSync(process.execPath)} : {effort:'high'}) }] },prior.revision);
saveSettings(root,{ ...prior, runtimes: [{ id, name: 'Future', environment: {REVIEW_MODE:'synthetic'}, harness: codex ? 'codex' : 'buzz-agent', executable, providerId: prior.providers[0]!.id, model, ...(codex ? {cli:realpathSync(process.execPath)} : providerType === 'anthropic' ? {} : {effort:'high'}) }] },prior.revision);
const projected = runtimeBindings(setup,readSettings(root))[`runtime:${id}`]!;
if (codex) { assert.notEqual(projected.serviceHome,root); assert.equal(existsSync(projected.serviceHome!),false); }
await wait(() => running.settingsRevision === 2);
@@ -137,6 +158,7 @@ await import(${JSON.stringify(new URL('./conversation-harness-fixture.ts',import
assert.equal(reports.find(m => m.body.operation === nextStart.id)?.body.result,'accepted');
assert.equal((reports.filter(m => m.type === 'inventory').at(-1)!.body.actualRun as any).selection.model,model);
assert.equal(providerReads,1);
if (added) { assert.equal(requests,1); assert.ok(!readFileSync(join(root,'journal.json'),'utf8').includes('synthetic-provider-key')); }
if (codex) { assert.equal(requests,1); assert.ok(!readFileSync(join(root,'journal.json'),'utf8').includes('fixture-codex-private-key')); }
assert.ok(!JSON.stringify(reports).includes('synthetic-provider-key'));
assert.ok(!readFileSync(join(root,'journal.json'),'utf8').includes('synthetic-provider-key'));

View File

@@ -1,4 +1,4 @@
import { registerAgent, agentNsec, addOpenAI } from '../src/settings-credentials.ts';
import { registerAgent, agentNsec, addOpenAI, addProvider } from '../src/settings-credentials.ts';
import type { CredentialReference } from '../src/credential-store.ts';
import { bootstrapHostIdentity } from '../src/host-identity.ts';
import { isolatedFileCredentials } from './isolated-file-credentials.ts';
@@ -16,6 +16,7 @@ export async function fixtureCredential(input: any, signal: AbortSignal) {
}
if (input.action === 'register-agent') { registerAgent(input.directory,agentNsec(input.secret),backend,input.profile,input.runtimeId,input.expectedRevision); return { ok: true }; }
if (input.action === 'add-openai') { addOpenAI(input.directory,input.name,input.secret,{ read: r => backend.read(r as unknown as CredentialReference), create: (r,s) => backend.create(r as unknown as CredentialReference,s) }); return { ok: true }; }
if (input.action === 'add-provider') { addProvider(input.directory,input.name,input.secret,{read:r=>backend.read(r as unknown as CredentialReference),create:(r,s)=>backend.create(r as unknown as CredentialReference,s)},input.type,input.endpoint,input.wire); return {ok:true}; }
if (input.action === 'models') return { ok: true, models: ['fixture-model'] };
if (input.action !== 'signin') throw Error('Unsupported installed fixture action');
if (input.secret) {

View File

@@ -6,13 +6,13 @@ const fixture = fileURLToPath(new URL('./manager-installed-fixture.ts', import.m
// Explicit test-only seam. No production environment switch or OS-store fallback.
registerHooks({ load(url, context, next) {
const result = next(url, context);
if (url.endsWith('/src/harness-discovery.ts')) return { ...result, source: "export async function discoverHarnesses() { return [{id:'buzz-agent',label:'Buzz Agent',executable:process.execPath,state:'available',providers:['openai','databricks_v2'],reason:'Synthetic only'}]; }" };
if (url.endsWith('/src/harness-discovery.ts')) return { ...result, source: "import {realpathSync} from 'node:fs'; export async function discoverHarnesses() { return [{id:'buzz-agent',label:'Buzz Agent',executable:process.env.BEEHIVE_TEST_REGISTRATION_WALKTHROUGH === '1' ? realpathSync(process.env.HOME + '/synthetic-acp') : process.execPath,state:'available',providers:['openai','anthropic','openai-compat','openrouter','databricks_v2'],reason:'Synthetic only'}]; }" };
if (url.endsWith('/src/databricks-runtime.ts') || url.endsWith('/src/databricks-runtime-child.ts')) return { ...result, source: "throw Error('Runtime auth disabled by installed fixture');" };
if (url.endsWith('/src/databricks.ts')) return { ...result, source: "export function databricksHost(v) { return new URL(v).origin; } export async function databricksNative() { throw Error('Native OAuth disabled by installed fixture'); } export async function addDatabricks() { throw Error('Native OAuth disabled by installed fixture'); }" };
if (!walkthrough && url.endsWith('/src/relay-directory.ts')) return { ...result, source: "export async function fetchRelayDirectory() { throw Error('Directory disabled by installed fixture'); }" };
if (url.endsWith('/src/agent-profile.ts')) return { ...result, source: "export async function fetchAgentProfile() { return {profileState:'none'}; }" };
if (url.endsWith('/src/host-service.ts')) return { ...result, source: "export async function serviceStatus() { return {state:'stopped',relay:'disconnected'}; } export async function startService() { throw Error('Service start disabled by installed fixture'); } export async function stopService() { throw Error('Service stop disabled by installed fixture'); }" };
if (url.endsWith('/src/settings-models.ts')) return { ...result, source: "export function detectBuzzAgent() { return process.execPath; } export async function openAIModels() { throw Error('Direct provider access disabled by installed fixture'); }" };
if (url.endsWith('/src/settings-models.ts')) return { ...result, source: "export function detectBuzzAgent() { return process.execPath; } export async function providerModels() { throw Error('Direct provider access disabled by installed fixture'); } export async function providerModelOptions() { throw Error('Direct provider access disabled by installed fixture'); } export async function openAIModels() { throw Error('Direct provider access disabled by installed fixture'); }" };
if (url.endsWith('/src/relay-name.ts')) return { ...result, source: "export function relayNameFromDocument() { return undefined; } export async function fetchRelayName() { return undefined; }" };
if (!url.endsWith('/src/manager-entry.ts')) return result;
const source = String(result.source);

View File

@@ -32,9 +32,9 @@ test('Pi model routes use the shared capability owner; UC tenant components neve
const p=piModelConfig(db,model).providers.beehive;
assert.equal(p.api,api);assert.equal(p.baseUrl,db.baseUrl+suffix);assert.equal(p.models[0].id,model);
}
assert.deepEqual(piEfforts('databricks_v2','catalog.schema.gpt-6-service'),[]);
assert.deepEqual(piEfforts('openai','unknown-custom'),[]);
assert.equal(runtimeCapabilities('databricks_v2','gpt-5.schema.unknown')!.supported_efforts.length,0);
assert.ok(piEfforts('databricks_v2','catalog.schema.gpt-6-service').includes('high'));
assert.ok(piEfforts('openai','unknown-custom').includes('high'));
assert.ok(runtimeCapabilities('databricks_v2','gpt-5.schema.unknown')!.supported_efforts.includes('high'));
});
test('actual runtime form offers Pi both providers, retains custom names, submits only supported model effort',async()=>{

View File

@@ -0,0 +1,40 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { providerModelOptions } from '../src/settings-models.ts';
import { runtimeEfforts } from '../src/runtime-effort.ts';
import { validateSettings, type Settings } from '../src/settings.ts';
import { presets } from '../src/presets.ts';
const key = {service:'beehive' as const,account:'provider:11111111-1111-1111-1111-111111111111'};
const signal = () => AbortSignal.timeout(1000);
test('Desktop OpenAI prefix/exclusion/creation ordering/alias labels differ from compatible endpoints',async()=>{
const data=[{id:'gpt-5-2026-01-01',created:10},{id:'gpt-5',created:1},{id:'o3',created:9},{id:'other-model',created:7},{id:'gpt-transcribe',created:12},{id:'chatgpt-4o',created:2},{id:'custom',created:20},{id:'gpt-4.1-2025-04-14',created:3}];
const fetcher: typeof fetch = async(url,options)=>{assert.equal(url,'https://fixture.example/v1/models');assert.equal((options?.headers as any).Authorization,'Bearer synthetic');assert.equal(options?.redirect,'error');return new Response(JSON.stringify({data}));};
assert.deepEqual(await providerModelOptions({type:'openai',endpoint:'https://fixture.example/v1'},'synthetic',signal(),fetcher),[{id:'o3',name:'o3'},{id:'other-model',name:'other-model'},{id:'gpt-4.1-2025-04-14',name:'GPT-4.1'},{id:'chatgpt-4o',name:'ChatGPT 4o'},{id:'gpt-5',name:'GPT-5'}]);
const compatible=await providerModelOptions({type:'openai-compat',endpoint:'https://fixture.example/v1'},'synthetic',signal(),fetcher);
assert.equal(compatible[0]?.id,'custom');assert.ok(compatible.some(v=>v.id==='gpt-5-2026-01-01'));assert.ok(compatible.some(v=>v.id==='gpt-transcribe'));
});
test('Anthropic headers, display names, bounded pagination; OpenRouter tools-only',async()=>{
let calls=0;
const rows=await providerModelOptions({type:'anthropic',endpoint:'https://fixture.example'},'synthetic',signal(),async(url,options)=>{
assert.equal((options?.headers as any)['x-api-key'],'synthetic');assert.equal((options?.headers as any)['anthropic-version'],'2023-06-01');
assert.equal(url,'https://fixture.example/v1/models'+(calls?'?after_id=a':''));
return new Response(JSON.stringify(calls++?{data:[{id:'b',display_name:'Beta'}],has_more:false}:{data:[{id:'a',display_name:'Alpha'}],has_more:true,last_id:'a'}));
});assert.deepEqual(rows,[{id:'a',name:'Alpha'},{id:'b',name:'Beta'}]);assert.equal(calls,2);
await assert.rejects(providerModelOptions({type:'anthropic',endpoint:'https://fixture.example'},'synthetic',signal(),async()=>new Response(JSON.stringify({data:[],has_more:true,last_id:'repeat'}))),/unavailable/);
const router=await providerModelOptions({type:'openrouter',endpoint:'https://fixture.example/api/v1'},'synthetic',signal(),async()=>new Response(JSON.stringify({data:[{id:'vendor/tools',supported_parameters:['tools']},{id:'vendor/text',supported_parameters:[]}]})));
assert.deepEqual(router,[{id:'vendor/tools',name:'vendor/tools'}]);
await assert.rejects(providerModelOptions({type:'openai',endpoint:'https://fixture.example'},'synthetic',signal(),async()=>new Response('x'.repeat(1048577))),/unavailable/);
});
test('OS-backed provider validation preserves harness matrix and custom effort rules',()=>{
for (const type of ['anthropic','openai-compat','openrouter'] as const) {
const settings:Settings={version:1,revision:0,agents:[],providers:[{id:'p',name:'P',type,endpoint:'https://fixture.example/v1',key,...(type==='openai-compat'?{wire:'responses' as const}:{})}],runtimes:[{id:'r',name:'R',harness:'buzz-agent',executable:process.execPath,providerId:'p',model:'custom'}]};
validateSettings(settings);
for (const harness of ['codex','pi'] as const) assert.throws(()=>validateSettings({...settings,runtimes:[{...settings.runtimes[0]!,harness,cli:process.execPath}]}),/combination/);
assert.throws(()=>validateSettings({...settings,providers:[{...settings.providers[0]!,endpoint:'https://secret@fixture.example'}]}),/endpoint/);
if(type !== 'anthropic') validateSettings({...settings,runtimes:[{...settings.runtimes[0]!,effort:'high'}]});
}
assert.deepEqual(runtimeEfforts('anthropic','custom'),[]);
assert.ok(runtimeEfforts('openai','custom').includes('high'));
assert.ok(!runtimeEfforts('openai','gpt-5').includes('xhigh'));
const pi=presets.find(p=>p.id==='pi')!;assert.equal(pi.command,'buzz-pi-acp');assert.match(pi.installHint,/salman1993\/buzz-pi-acp.git#86b201e/);
});

View File

@@ -15,7 +15,7 @@ import { createControllerConfig } from '../src/controller-config.ts';
import { provisionCredentialSlot } from '../src/credential-slots.ts';
import { createGenesis } from '../src/assignment.ts';
import { createCredential } from '../src/credential-store.ts';
import { registerAgent, agentNsec, addOpenAI } from '../src/settings-credentials.ts';
import { registerAgent, agentNsec, addOpenAI, addProvider } from '../src/settings-credentials.ts';
import { readSettings, saveSettings } from '../src/settings.ts';
import { host } from '../src/host.ts';
import { isolatedFileCredentials } from './isolated-file-credentials.ts';
@@ -61,7 +61,7 @@ export async function registrationFixture(home: string, changed: (s: ManagerSnap
let providerKey: string | null = null;
addOpenAI(directory,'Review OpenAI','synthetic-provider-key',{read:()=>providerKey,create:(_r,v)=>{providerKey=v;}});
const executable = join(home,'synthetic-acp');
writeFileSync(executable,`#!/bin/sh\n[ "$REVIEW_MODE" = synthetic ] || exit 9\nexec '${process.execPath}' '${fileURLToPath(new URL('./acp-fixture.ts',import.meta.url))}' openai\n`,{mode:0o700});
writeFileSync(executable,`#!/bin/sh\n[ "$REVIEW_MODE" = synthetic ] || exit 9\nexec '${process.execPath}' '${fileURLToPath(new URL('./acp-fixture.ts',import.meta.url))}' saved-provider\n`,{mode:0o700});
const settings = readSettings(directory), runtimeId = 'review-runtime';
saveSettings(directory,{...settings,runtimes:[{id:runtimeId,name:'Review runtime',harness:'buzz-agent',executable,providerId:settings.providers[0]!.id,model:'gpt-5',effort:'high',environment:{REVIEW_MODE:'synthetic'}}]},settings.revision);
let providerFailure = false;
@@ -120,7 +120,8 @@ export async function registrationFixture(home: string, changed: (s: ManagerSnap
const credential = async (input: any, signal: AbortSignal) => {
signal.throwIfAborted();
if (input.action === 'signin') return {ok:true,secret:ownerSecret};
if (input.action === 'models') return {ok:true,models:['gpt-5']};
if (input.action === 'add-provider') { addProvider(directory,input.name,input.secret,{read:()=>providerKey,create:(_r,v)=>{providerKey=v;}},input.type,input.endpoint,input.wire); return {ok:true}; }
if (input.action === 'models') { const p=readSettings(directory).providers.find(p=>p.id===input.provider); const model=p?.type === 'anthropic' ? 'claude-sonnet-4-5' : p?.type === 'openrouter' ? 'vendor/custom-model' : 'gpt-5'; return {ok:true,models:[model],modelLabels:{[model]:p?.type === 'anthropic' ? 'Claude Sonnet 4.5' : model}}; }
if (input.action !== 'register-agent') throw Error('Fixture denies external credential operation');
await beforeCredential?.(); signal.throwIfAborted();
if (credentialFailure) throw Error('Synthetic credential failure');

View File

@@ -115,8 +115,8 @@ test('Desktop contract detects fake adapter/CLI independently, strict Codex vers
test('effort is manifest-backed, validates persisted settings and reaches actual ACP harness environment', async t => {
assert.equal(readFileSync(new URL('../src/model-capabilities.json',import.meta.url),'utf8'),readFileSync(new URL('../../scripts/model-capabilities.json',import.meta.url),'utf8'));
assert.ok(runtimeEfforts('openai','gpt-5').includes('high'));
assert.deepEqual(runtimeEfforts('openai','unknown-custom'),[]);
assert.deepEqual(runtimeEfforts('databricks_v2','catalog.schema.gpt-5'),[]);
assert.ok(runtimeEfforts('openai','unknown-custom').includes('high'));
assert.ok(runtimeEfforts('databricks_v2','catalog.schema.gpt-5').includes('high'));
assert.ok(!runtimeEfforts('databricks_v2','databricks-claude-sonnet-4-5').includes('none'));
const root = fixture(t), runner=join(root,'agent');
writeFileSync(runner, `#!/bin/sh\n[ "$BUZZ_AGENT_THINKING_EFFORT" = high ] || exit 9\nexec '${process.execPath}' '${fileURLToPath(new URL('./acp-fixture.ts',import.meta.url))}' openai\n`, {mode:0o700});
@@ -129,7 +129,7 @@ test('effort is manifest-backed, validates persisted settings and reaches actual
await session.catalog(); await session.verify();
});
test('short runtime form selects supported model-specific effort; unknown custom omits it and unsupported harness cannot save', async () => {
test('short runtime form selects supported model-specific effort; unknown custom inherits provider capabilities and unsupported harness cannot save', async () => {
const snapshot: ManagerSnapshot = { local:[], agents:[], status:'synthetic', models:['gpt-5'], harnesses:[{id:'buzz-agent',label:'Buzz Agent',executable:'/fixture',state:'available',providers:['openai'],reason:'fixture'},{id:'pi',label:'Pi',state:'available',providers:[],reason:'Provider integration unavailable'}], settings:{version:1,revision:1,agents:[],runtimes:[],providers:[{id:'p',name:'OpenAI',type:'openai',endpoint:'https://api.openai.com/v1',key}]} };
const submitted: {action:string;values?:Record<string,string>}[] = [];
const run = async (choices: string[], inputs: string[]) => runtimeForm({choose:async (_label,options) => {const value=choices.shift(); assert.ok(value === undefined || options.includes(value)); return value;},input:async label => label.startsWith('Environment') ? '{}' : inputs.shift(),confirm:async label => {assert.match(label,/Running agents do not change/);return true;},notice:() => {}}, () => snapshot, async (action,values) => {submitted.push({action,values});return {state:"completed"};});
@@ -137,7 +137,7 @@ test('short runtime form selects supported model-specific effort; unknown custom
assert.equal(submitted.at(-1)?.values?.effort,'high');
assert.equal(submitted.at(-1)?.values?.model,'gpt-5');
submitted.length=0;
await run(['Buzz Agent · Available','OpenAI · p','Custom model'],['unrecognized-model','Custom']);
await run(['Buzz Agent · Available','OpenAI · p','Custom model','Inherit'],['unrecognized-model','Custom']);
assert.equal(submitted.at(-1)?.action,'add-runtime'); assert.equal(submitted.at(-1)?.values?.effort,undefined);
submitted.length=0;
await run(['Pi · Provider integration unavailable'],[]);

View File

@@ -18,6 +18,13 @@ Agents: Sign in with saved owner key -> select Review agent -> Start -> yes ->
changing the current run or requiring identity registration again.
Synthetic agent nsec (never use real credentials in this fixture):
nsec1yg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3qxh9tww
Providers: Add provider -> Anthropic / OpenAI-compatible / OpenRouter.
Use synthetic-provider-key (hidden); compatible endpoint https://fixture.example/v1.
Compatible API: auto / chat / responses. Select provider -> Models.
Harnesses: Add runtime -> Buzz Agent -> saved provider -> model or Custom model ->
effort (when offered) -> environment {"REVIEW_MODE":"synthetic"} -> name -> Save.
Agents: Choose runtime then explicit Start/Restart applies the selected runtime.
Synthetic model access is not real provider authentication.
Ctrl-Q: exit even inside a form; this test closes its owned relay/host.
Review host begins WITHOUT an agent slot or genesis. Explicit local Start