mirror of
https://github.com/block/buzz.git
synced 2026-10-28 21:11:48 +00:00
Integrate run-owned Databricks refresh and capability-aware runtime settings
Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Host settings implementation checkpoint
|
||||
|
||||
This is an **incomplete candidate**, not the complete authorized prototype. No install or publication was performed. Capability/discovery and final delivery work remain required before calling the owner request complete. Databricks setup/catalog/preflight is now implemented as described below; continuous in-run OAuth refresh remains a limitation.
|
||||
This is an **incomplete candidate**, not the complete authorized prototype. No install or publication was performed. Runtime integration now includes in-run Databricks refresh, Desktop-backed detection and model-specific Buzz Agent effort. Final combined validation, relay integration and delivery remain required. Earlier chronological limitations below are superseded by the runtime integration section at the end.
|
||||
|
||||
## Implemented
|
||||
|
||||
@@ -87,3 +87,55 @@ This is a bounded continuation handoff, not a genuine external/toolchain blocker
|
||||
- Pinned Node 24.15.0 strict typecheck and 15 focused tests passed. Databricks Save/Start test reaches the actual host launch and ACP consumer with exact provider/host/model/bearer while prior active state remains unchanged, using an explicitly injected credential provider and synthetic ACP executable. Cancellation/late completion/public-secret exclusion covered. No combined Beehive rerun or reassessment of the two previously recorded suite failures.
|
||||
- Installed smoke loader now explicitly blocks the new native path. No real environment Databricks host, tokens, Keychain entries, retained OAuth state, browser login, production provider or installed harness was inspected/executed.
|
||||
- This supersedes only earlier Databricks-unimplemented statements. The larger owner request and delivery are not complete. The helper package must be incorporated into subsequent exports; continuous refresh inside an already-running stock harness remains unsupported.
|
||||
|
||||
|
||||
## Runtime integration 43ed57d2
|
||||
|
||||
- `spawnAgent` is now the common actual ACP-probe/conversation-harness spawn
|
||||
boundary. OS-backed Databricks bindings use a run-owned Node wrapper/proxy;
|
||||
native Rust remains the OAuth/custody owner. Each request re-enters the exact
|
||||
workspace/reference token source, including expiry refresh, without restarting
|
||||
the host/agent. Only an isolated per-run capability reaches the model harness;
|
||||
the relay runtime no longer receives the Databricks bearer. Stop aborts requests
|
||||
and helpers inside the existing supervised process group. Upstream 401/403 is
|
||||
latched, no redirect/workspace fallback, no new auth authority or token files.
|
||||
- Current-main Desktop Rust catalog/preset data is exported as checked public JSON,
|
||||
including Pi `buzz-pi-acp` + underlying `pi`, separate adapter/CLI availability,
|
||||
Codex strict >=1.10.0 metadata probe, bundled/managed/PATH/login-shell/common/nvm
|
||||
lookup. Probes are bounded, output-capped and owned; no auth/status probes.
|
||||
Unix-only Beehive discovery uses login-shell PATH rather than shell aliases or
|
||||
functions; no Desktop Tauri IPC or credential/config reading is implied.
|
||||
- The Add runtime picker shows installed/missing/incompatible adapters explicitly.
|
||||
This catalog's supported OS-backed provider combinations remain **Buzz Agent ×
|
||||
OpenAI/Databricks v2**. Other detected adapters are visibly unavailable for this
|
||||
provider catalog, not falsely offered universal credentials or effort. Existing
|
||||
Goose/Claude/Codex native-auth CLI setup paths remain intact; Pi execution/provider
|
||||
integration is not newly implemented.
|
||||
- Effort comes from the packaged canonical model-capability manifest and exact/
|
||||
boundary-aware family precedence. Unknown custom models and UC FQNs omit the
|
||||
picker (no invented fallback effort). Inherit omits the variable. Explicit
|
||||
supported effort persists in the immutable runtime/provider binding, is checked
|
||||
again at launch, and reaches `BUZZ_AGENT_THINKING_EFFORT` in the actual harness.
|
||||
Save/adoption affects only new authorized runs; active snapshots remain unchanged.
|
||||
- Installed-smoke loader explicitly blocks the new discovery/auth-runtime seams.
|
||||
No real Keychain, browser, provider, relay, profile, installed harness or existing
|
||||
host lifecycle was used. Native source/binary unchanged; reuse a7e21caa evidence
|
||||
and packaged helper. Exports must now include **src JSON files**, not just TS.
|
||||
|
||||
Evidence: `/Users/loganj/.buzz/artifacts/beehive-runtime-integration-43ed57d2/`.
|
||||
Final focused integration: **25/25** (`focused-final.log`), including actual wrapper
|
||||
single-run expiry/refresh/revocation and relay-transport bearer exclusion.
|
||||
Bun 1.4.2 renderer: 2/2. Node 24.15.0 typecheck passes. The first renderer invocation
|
||||
used a filter instead of an explicit path (no tests ran); corrected invocation is
|
||||
saved separately. Initial broker/conversation invocation used repository-root CWD,
|
||||
while these fixtures require package CWD (`resolve('test/...')`); both reported
|
||||
process exit from nonexistent fixture paths. Correct package-CWD run: 3/3, unchanged
|
||||
assertions/deadlines. These invocation errors do **not** explain the historical
|
||||
4eada whole-suite timeout/missing-receipt failures. No combined suite was rerun.
|
||||
|
||||
Both old failing stimuli now pass through the shared `spawnAgent` function, but
|
||||
lack `buzzProvider` and therefore retain identical legacy executable/args/env and
|
||||
never enter the new proxy. Shared launch/ownership remains touched; Larry must
|
||||
assess the final combined suite after relay integration, not waive older failures.
|
||||
No push/install/publication. Capability-specific non-Buzz provider integration and
|
||||
live-account readiness are not claimed by this component.
|
||||
|
||||
@@ -49,12 +49,25 @@ The binary is built for arm64; other platform custody/export is not implemented.
|
||||
after awaited close. Node deadlines: login 180s, models/token 20s; cancellation
|
||||
sends TERM, then KILL after 250ms, and awaits close before settling.
|
||||
* Model listing and Start preflight are headless and can refresh, never opening
|
||||
the browser. Launch passes the refreshed access token through the existing
|
||||
private `DATABRICKS_TOKEN` harness environment and the exact workspace/model.
|
||||
**A running stock buzz-agent receives a static access token**: long-lived
|
||||
in-process refresh after that token expires is not provided by this bridge.
|
||||
An explicit new Start/Restart obtains a refreshed token. This is a known
|
||||
remaining limitation, not continuous token-source integration in the harness.
|
||||
the browser. For catalog OS-backed Databricks runtimes, `spawnAgent` now starts
|
||||
the run-owned Node `databricks-runtime-child.ts` wrapper. The stock harness
|
||||
retains `databricks_v2` and its native wire routing, but receives an ephemeral
|
||||
loopback endpoint and random per-run bearer capability, not the provider token.
|
||||
Each model/catalog request calls this unchanged native helper's `token` action
|
||||
against the immutable real workspace/reference. Native expiry/refresh/verified
|
||||
rotation therefore serves an ongoing run without restarting the agent.
|
||||
* The proxy admits only canonical v2 catalog/model paths and the selected model;
|
||||
no redirects or arbitrary upstream hosts. OAuth/network failures fail closed;
|
||||
upstream 401/403 latches rejection for that run (explicit sign-in/new run may be
|
||||
needed). It does not implement a parallel forced-refresh OAuth engine. Native
|
||||
refresh is expiry-based; a server-side early rejection is not silently retried
|
||||
with another provider/workspace. No streaming extension is claimed: current
|
||||
canonical Buzz Agent requests/responses are non-streaming.
|
||||
* Wrapper, harness and native helpers belong to the existing supervised process
|
||||
group. Stop fences responses, aborts fetch/helpers, awaits close, and retains
|
||||
the supervisor's existing TERM/KILL/absence verification. Legacy external-OAuth
|
||||
and explicit token-file bindings are unchanged; only OS-backed catalog bindings
|
||||
use the adapter. No helper binary change/rebuild is needed for this integration.
|
||||
* Cancellation may follow an OS write. It cannot undo grant rotation or pretend
|
||||
rollback; no cancelled operation commits a new public provider. Recovery refs
|
||||
remain. Browser windows owned by the OS may remain open; helper/listener do not.
|
||||
@@ -62,3 +75,14 @@ The binary is built for arm64; other platform custody/export is not implemented.
|
||||
Tests explicitly substitute native/browser/OS custody with synthetic seams.
|
||||
`manager-installed-loader.ts` blocks this new native path in future install
|
||||
smokes. Fresh HOME alone is not credential or browser isolation.
|
||||
|
||||
## Export additions for runtime integration
|
||||
|
||||
Include every `beehive/src/*.ts` **and `beehive/src/*.json`** in the Node/Bun
|
||||
package, especially both runtime adapter modules, `harness-contract.json` and
|
||||
`model-capabilities.json`. The latter is a byte-identical packaged copy of
|
||||
`scripts/model-capabilities.json`, checked by tests. Regenerate the harness
|
||||
contract explicitly with `python3 beehive/tools/export-harnesses.py <reviewed-SHA>`
|
||||
from the repository root; its current source is Desktop main
|
||||
`78618804ec86a014524ad7d1fb55928e8f5c3edf`. Keep the existing helper binary in
|
||||
`bin/beehive-databricks`; do not build/install anything at runtime.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { buzzProviderEnvironment, type BuzzProvider } from './buzz-provider.ts';
|
||||
import { validateCustom, type CustomAcp } from './custom-acp.ts';
|
||||
import { CODEX_ADAPTER, codexEnvironment, codexModels, type CodexSetup } from './codex.ts';
|
||||
@@ -94,8 +95,7 @@ export class AgentSession {
|
||||
constructor(input: AgentLaunch, timeoutMs = 30_000) {
|
||||
this.timeoutMs = timeoutMs;
|
||||
this.prepared = prepareAgent(input);
|
||||
const { plan, env } = this.prepared;
|
||||
this.owned = spawnOwned(plan.executable, plan.args, plan.workspace, env);
|
||||
this.owned = spawnAgent(this.prepared);
|
||||
this.child = this.owned.child;
|
||||
void this.owned.ready.catch(() => this.fail('Harness executable unavailable'));
|
||||
this.child.on('message', () => { if (this.owned.exited) this.fail('Harness exited'); });
|
||||
@@ -238,3 +238,17 @@ export class AgentSession {
|
||||
} finally { this.prompting = false; this.response = ''; }
|
||||
}
|
||||
}
|
||||
|
||||
/** Both probe and conversation launch use this actual transport boundary. Public
|
||||
* preparation hashes retain the original executable/provider, not ephemeral ports.
|
||||
* The supervisor owns wrapper, harness and native helpers as one process group. */
|
||||
export function spawnAgent(prepared: ReturnType<typeof prepareAgent>, extra: Record<string, string> = {}) {
|
||||
const { plan } = prepared;
|
||||
const env = { ...prepared.env, ...extra };
|
||||
if (plan.buzzProvider?.provider === 'databricks_v2' && plan.buzzProvider.credential) {
|
||||
delete env.DATABRICKS_TOKEN;
|
||||
env.BEEHIVE_DATABRICKS_RUNTIME = JSON.stringify({ host: plan.buzzProvider.baseUrl, key: plan.buzzProvider.credential, model: plan.model });
|
||||
return spawnOwned(process.execPath, [fileURLToPath(new URL('./databricks-runtime-child.ts', import.meta.url)), plan.executable, ...plan.args], plan.workspace, env);
|
||||
}
|
||||
return spawnOwned(plan.executable, plan.args, plan.workspace, env);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { spawnOwned, type OwnedProcess } from './owned.ts';
|
||||
import { prepareAgent, type AgentLaunch, type Evidence } from './acp.ts';
|
||||
import { prepareAgent, spawnAgent, type AgentLaunch, type Evidence } from './acp.ts';
|
||||
import { ReplyTool } from './reply-tool.ts';
|
||||
import { prepareConversation, type ConversationSetup } from './conversation.ts';
|
||||
|
||||
@@ -137,10 +137,9 @@ export class ConversationSession {
|
||||
if (!hello || hello.capability !== this.capability || !Number.isSafeInteger(hello.pid) || hello.pid! <= 1 || this.harnesses.length >= 8) { socket.destroy(); return; }
|
||||
this.shimPids.add(hello.pid!);
|
||||
authenticated = true; clearTimeout(timer);
|
||||
const { plan, env } = this.prepared;
|
||||
// No data supplied by a shim can alter argv, workspace, identity or env.
|
||||
harness = spawnOwned(plan.executable, plan.args, plan.workspace, {
|
||||
...env, BUZZ_PRIVATE_KEY: this.plan.env.BUZZ_PRIVATE_KEY,
|
||||
harness = spawnAgent(this.prepared, {
|
||||
BUZZ_PRIVATE_KEY: this.plan.env.BUZZ_PRIVATE_KEY,
|
||||
BUZZ_RELAY_URL: this.plan.env.BUZZ_RELAY_URL,
|
||||
...(this.plan.env.BUZZ_AUTH_TAG ? { BUZZ_AUTH_TAG: this.plan.env.BUZZ_AUTH_TAG } : {}),
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { validateRuntimeEffort } from './runtime-effort.ts';
|
||||
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
||||
import { isAbsolute } from 'node:path';
|
||||
import type { ProviderReference } from './settings.ts';
|
||||
|
||||
/** Local immutable Buzz Agent provider contracts, pinned config.rs:631–691,922–960. */
|
||||
export type BuzzProvider = { provider: 'anthropic' | 'openai-compat' | 'openrouter' | 'databricks_v2'; apiKeyFile?: string; credential?: ProviderReference; auth?: 'token' | 'external-oauth'; baseUrl: string; models: string[]; wire?: 'auto' | 'chat' | 'responses' };
|
||||
export type BuzzProvider = { provider: 'anthropic' | 'openai-compat' | 'openrouter' | 'databricks_v2'; apiKeyFile?: string; credential?: ProviderReference; auth?: 'token' | 'external-oauth'; baseUrl: string; models: string[]; effort?: string; wire?: 'auto' | 'chat' | 'responses' };
|
||||
const contracts = {
|
||||
databricks_v2: { key: 'DATABRICKS_TOKEN', url: 'DATABRICKS_HOST' },
|
||||
anthropic: { key: 'ANTHROPIC_API_KEY', url: 'ANTHROPIC_BASE_URL' },
|
||||
@@ -12,7 +13,8 @@ const contracts = {
|
||||
} as const;
|
||||
/** Validate without reading credentials, contacting endpoints or initiating login. */
|
||||
export function validateBuzzProvider(value: BuzzProvider): void {
|
||||
if (!value || !Object.hasOwn(contracts, value.provider) || Object.keys(value).some(k => !['provider', 'apiKeyFile', 'credential', 'baseUrl', 'models', 'wire', 'auth'].includes(k)) || !Array.isArray(value.models) || !value.models.length || value.models.length > 100 || value.models.some(m => typeof m !== 'string' || !/^[a-zA-Z0-9_.:/-]{1,200}$/.test(m))) throw Error('Invalid local Buzz Agent provider/key-file/models binding');
|
||||
if (!value || !Object.hasOwn(contracts, value.provider) || Object.keys(value).some(k => !['provider', 'apiKeyFile', 'credential', 'baseUrl', 'models', 'wire', 'auth', 'effort'].includes(k)) || !Array.isArray(value.models) || !value.models.length || value.models.length > 100 || value.models.some(m => typeof m !== 'string' || !/^[a-zA-Z0-9_.:/-]{1,200}$/.test(m))) throw Error('Invalid local Buzz Agent provider/key-file/models binding');
|
||||
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');
|
||||
@@ -27,7 +29,7 @@ export function validateBuzzProvider(value: BuzzProvider): void {
|
||||
export function buzzProviderEnvironment(value: BuzzProvider, home: string, configDirectory: string, model: string, resolvedKey?: string): Record<string, string> {
|
||||
validateBuzzProvider(value);
|
||||
if (!value.models.includes(model)) throw Error('Unsupported Buzz Agent model');
|
||||
const base = { PATH: '/usr/bin:/bin', HOME: home, BUZZ_AGENT_CONFIG_DIR: configDirectory, BUZZ_AGENT_PROVIDER: value.provider, BUZZ_AGENT_MODEL: model };
|
||||
const base = { PATH: '/usr/bin:/bin', HOME: home, BUZZ_AGENT_CONFIG_DIR: configDirectory, BUZZ_AGENT_PROVIDER: value.provider, BUZZ_AGENT_MODEL: model, ...(value.effort === undefined ? {} : { BUZZ_AGENT_THINKING_EFFORT: value.effort }) };
|
||||
if (value.auth === 'external-oauth') return { ...base, DATABRICKS_HOST: new URL(value.baseUrl).origin }; // Native owner alone reads/refreshes OAuth after explicit launch.
|
||||
const apiKeyFile = value.apiKeyFile!;
|
||||
let key: string;
|
||||
|
||||
@@ -35,6 +35,10 @@ export function prepareConversationBinding(input: ConversationSetup, agent: Agen
|
||||
// fixed shim argv uses the installed runtime's historical comma transport.
|
||||
if (prepared.plan.args.some(a => a.includes('\0'))) throw Error('ACP arguments cannot contain NUL');
|
||||
if (input.authTag !== undefined && (typeof input.authTag !== 'string' || input.authTag.length > 16384 || !input.authTag.length)) throw Error('Invalid local owner attestation');
|
||||
const providerEnv = { ...prepared.env };
|
||||
// The relay transport/shim never needs the OS-backed Databricks bearer.
|
||||
// The actual model consumer obtains a run-local capability in spawnAgent.
|
||||
if (agent.buzzProvider?.provider === 'databricks_v2' && agent.buzzProvider.credential) delete providerEnv.DATABRICKS_TOKEN;
|
||||
return Object.freeze({
|
||||
replyTool: input.replyTool ? prepareReplyTool(input.replyTool) : undefined,
|
||||
executable: input.executable,
|
||||
@@ -45,7 +49,7 @@ export function prepareConversationBinding(input: ConversationSetup, agent: Agen
|
||||
relay: url.href,
|
||||
agentExecutableHash: prepared.executableHash,
|
||||
// Authoritative identity/transport applied after provider env; no arbitrary env input.
|
||||
env: Object.freeze<Record<string, string>>({ ...prepared.env,
|
||||
env: Object.freeze<Record<string, string>>({ ...providerEnv,
|
||||
BUZZ_RELAY_URL: url.href,
|
||||
BUZZ_ACP_AGENT_OWNER: owner,
|
||||
...(input.authTag === undefined ? {} : { BUZZ_AUTH_TAG: input.authTag }),
|
||||
|
||||
23
beehive/src/databricks-runtime-child.ts
Normal file
23
beehive/src/databricks-runtime-child.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { databricksRuntime } from './databricks-runtime.ts';
|
||||
|
||||
// Owned by the existing POSIX supervisor group. No independent daemon, restart,
|
||||
// login, token files or PID adoption. Only ACP bytes are written to stdout.
|
||||
let stopping = false;
|
||||
let bridge: Awaited<ReturnType<typeof databricksRuntime>> | undefined;
|
||||
let child: ReturnType<typeof spawn> | undefined;
|
||||
const stop = async () => { stopping = true; child?.kill('SIGTERM'); await bridge?.stop(); };
|
||||
process.on('SIGTERM', () => { void stop(); });
|
||||
process.on('SIGINT', () => { void stop(); });
|
||||
try {
|
||||
const config = JSON.parse(process.env.BEEHIVE_DATABRICKS_RUNTIME ?? '');
|
||||
delete process.env.BEEHIVE_DATABRICKS_RUNTIME;
|
||||
bridge = await databricksRuntime(config);
|
||||
if (stopping) await bridge.stop();
|
||||
else {
|
||||
const executable = process.argv[2]; if (!executable) throw Error();
|
||||
child = spawn(executable, process.argv.slice(3), { stdio: ['inherit', 'inherit', 'ignore'], env: { ...process.env, DATABRICKS_HOST: bridge.endpoint, DATABRICKS_TOKEN: bridge.capability } });
|
||||
await new Promise<void>((resolve, reject) => { child!.once('error', reject); child!.once('close', code => { process.exitCode = stopping ? 0 : code ?? 1; resolve(); }); });
|
||||
await bridge.stop();
|
||||
}
|
||||
} catch { await stop(); process.exitCode = 1; }
|
||||
77
beehive/src/databricks-runtime.ts
Normal file
77
beehive/src/databricks-runtime.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
||||
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
||||
import { databricksHost, databricksNative } from './databricks.ts';
|
||||
import type { ProviderReference } from './settings.ts';
|
||||
|
||||
const posts = new Set(['/ai-gateway/openai/v1/responses', '/ai-gateway/anthropic/v1/messages', '/ai-gateway/mlflow/v1/chat/completions']);
|
||||
const catalogs = new Set(['/api/ai-gateway/v2/endpoints', '/api/2.1/unity-catalog/model-services']);
|
||||
/** Run-local transport adapter. Native Rust still owns OAuth/refresh and OS custody;
|
||||
* the stock harness retains its canonical Databricks v2 routing and wire bodies.
|
||||
* Only the private loopback capability reaches the harness, never a provider token.
|
||||
*/
|
||||
export async function databricksRuntime(input: { host: string; key: ProviderReference; model: string }, native = databricksNative, fetcher: typeof fetch = fetch) {
|
||||
const host = databricksHost(input.host), capability = randomBytes(32).toString('hex');
|
||||
const active = new Set<AbortController>(), jobs = new Set<Promise<void>>();
|
||||
let stopped = false, denied = false, closing: Promise<void> | undefined;
|
||||
const server = createServer((req, res) => {
|
||||
const job = serve(req, res); jobs.add(job); void job.finally(() => jobs.delete(job));
|
||||
});
|
||||
server.headersTimeout = 5000; server.requestTimeout = 20000; server.keepAliveTimeout = 1000;
|
||||
server.maxConnections = 16;
|
||||
const error = (res: ServerResponse, code: number) => { if (!res.destroyed) { res.writeHead(code, { 'content-type': 'application/json' }); res.end(JSON.stringify({ error: code === 401 ? 'Databricks authentication rejected; explicit sign-in may be required' : 'Databricks runtime request unavailable; credential refresh or network access failed; explicit sign-in may be required' })); } };
|
||||
async function serve(req: IncomingMessage, res: ServerResponse) {
|
||||
const supplied = Buffer.from(req.headers.authorization ?? ''), expected = Buffer.from(`Bearer ${capability}`);
|
||||
if (stopped || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) { error(res, 403); return; }
|
||||
if (denied) { error(res, 401); return; }
|
||||
if (active.size >= 8) { error(res, 503); return; }
|
||||
const controller = new AbortController(); active.add(controller);
|
||||
const abort = () => controller.abort();
|
||||
req.once('aborted', abort); res.once('close', abort);
|
||||
const timer = setTimeout(abort, 120000);
|
||||
try {
|
||||
const url = new URL(req.url ?? '', 'http://127.0.0.1');
|
||||
if (url.href.length > 8192 || url.origin !== 'http://127.0.0.1' || url.hash || (req.method === 'POST' ? !posts.has(url.pathname) || !!url.search : req.method !== 'GET' || !catalogs.has(url.pathname) || [...url.searchParams.keys()].some(k => !['page_token', 'page_size', 'view'].includes(k) || k === 'page_size' && url.searchParams.get(k) !== '100' || k === 'view' && url.searchParams.get(k) !== 'FULL'))) { error(res, 403); return; }
|
||||
let body: Buffer | undefined;
|
||||
if (req.method === 'POST') {
|
||||
const chunks: Buffer[] = []; let size = 0;
|
||||
for await (const chunk of req) { size += chunk.length; if (size > 16 * 1024 * 1024) throw Error(); chunks.push(chunk); }
|
||||
body = Buffer.concat(chunks);
|
||||
if (JSON.parse(body.toString()).model !== input.model) { error(res, 403); return; }
|
||||
}
|
||||
controller.signal.throwIfAborted();
|
||||
// Every request re-enters the workspace-bound native token source. Its
|
||||
// cross-process lock serializes rotated grants across concurrent runtimes.
|
||||
const token = await native({ action: 'token', host, key: input.key }, controller.signal);
|
||||
controller.signal.throwIfAborted();
|
||||
if (!token.secret || /\s/.test(token.secret)) throw Error();
|
||||
const headers = new Headers({ authorization: `Bearer ${token.secret}`, 'content-type': 'application/json' });
|
||||
token.secret = undefined;
|
||||
const upstream = await fetcher(host + url.pathname + url.search, { method: req.method, headers, ...(body ? { body: body.toString() } : {}), signal: controller.signal, redirect: 'error' });
|
||||
if (upstream.status === 401 || upstream.status === 403) { denied = true; await upstream.body?.cancel(); error(res, 401); return; }
|
||||
if (!upstream.ok || !upstream.body) { await upstream.body?.cancel(); error(res, 502); return; }
|
||||
// The canonical buzz-agent consumer is non-streaming. Bound before exposing
|
||||
// a response, and never relay upstream diagnostics or redirect headers.
|
||||
const reader = upstream.body.getReader(), chunks: Uint8Array[] = []; let size = 0;
|
||||
try { while (true) { const next = await reader.read(); if (next.done) break; size += next.value.length; if (size > 16 * 1024 * 1024) throw Error(); chunks.push(next.value); } }
|
||||
finally { await reader.cancel(); reader.releaseLock(); }
|
||||
controller.signal.throwIfAborted();
|
||||
if (stopped || res.destroyed) return;
|
||||
res.writeHead(200, { 'content-type': 'application/json' }); res.end(Buffer.concat(chunks));
|
||||
} catch { error(res, 503); }
|
||||
finally { clearTimeout(timer); req.removeListener('aborted', abort); res.removeListener('close', abort); active.delete(controller); }
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); });
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') { server.close(); throw Error('Runtime listener unavailable'); }
|
||||
return {
|
||||
endpoint: `http://127.0.0.1:${address.port}`, capability,
|
||||
/** Fence acceptance first, abort network/helpers, then await their close. */
|
||||
stop(): Promise<void> {
|
||||
return closing ??= (async () => {
|
||||
stopped = true; for (const controller of active) controller.abort();
|
||||
server.closeAllConnections();
|
||||
await Promise.all([new Promise<void>(resolve => server.close(() => resolve())), ...jobs]);
|
||||
})();
|
||||
},
|
||||
};
|
||||
}
|
||||
170
beehive/src/harness-contract.json
Normal file
170
beehive/src/harness-contract.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"revision": "78618804ec86a014524ad7d1fb55928e8f5c3edf",
|
||||
"sources": [
|
||||
"desktop/src-tauri/src/managed_agents/discovery/catalog.rs",
|
||||
"desktop/src-tauri/src/managed_agents/discovery/presets.rs"
|
||||
],
|
||||
"codexMinimum": [
|
||||
1,
|
||||
10,
|
||||
0
|
||||
],
|
||||
"managedNodeVersion": "v24.18.0",
|
||||
"runtimes": [
|
||||
{
|
||||
"id": "goose",
|
||||
"label": "Goose",
|
||||
"commands": [
|
||||
"goose"
|
||||
],
|
||||
"underlyingCli": "goose",
|
||||
"modelEnv": "GOOSE_MODEL",
|
||||
"providerEnv": "GOOSE_PROVIDER",
|
||||
"effortEnv": "GOOSE_THINKING_EFFORT"
|
||||
},
|
||||
{
|
||||
"id": "claude",
|
||||
"label": "Claude Code",
|
||||
"commands": [
|
||||
"claude-agent-acp",
|
||||
"claude-code-acp"
|
||||
],
|
||||
"underlyingCli": "claude",
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "codex",
|
||||
"label": "Codex",
|
||||
"commands": [
|
||||
"codex-acp"
|
||||
],
|
||||
"underlyingCli": "codex",
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "buzz-agent",
|
||||
"label": "Buzz Agent",
|
||||
"commands": [
|
||||
"buzz-agent"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": "BUZZ_AGENT_MODEL",
|
||||
"providerEnv": "BUZZ_AGENT_PROVIDER",
|
||||
"effortEnv": "BUZZ_AGENT_THINKING_EFFORT"
|
||||
},
|
||||
{
|
||||
"id": "pi",
|
||||
"label": "Pi",
|
||||
"commands": [
|
||||
"buzz-pi-acp"
|
||||
],
|
||||
"underlyingCli": "pi",
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "devin",
|
||||
"label": "Devin",
|
||||
"commands": [
|
||||
"devin"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "cursor",
|
||||
"label": "Cursor",
|
||||
"commands": [
|
||||
"cursor-agent"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "omp",
|
||||
"label": "Oh My Pi",
|
||||
"commands": [
|
||||
"omp"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "grok",
|
||||
"label": "Grok Build",
|
||||
"commands": [
|
||||
"grok"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "opencode",
|
||||
"label": "OpenCode",
|
||||
"commands": [
|
||||
"opencode"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "kimi",
|
||||
"label": "Kimi Code",
|
||||
"commands": [
|
||||
"kimi"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "amp",
|
||||
"label": "Amp",
|
||||
"commands": [
|
||||
"amp-acp"
|
||||
],
|
||||
"underlyingCli": "amp",
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "hermes",
|
||||
"label": "Hermes Agent",
|
||||
"commands": [
|
||||
"hermes-acp"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
},
|
||||
{
|
||||
"id": "openclaw",
|
||||
"label": "OpenClaw",
|
||||
"commands": [
|
||||
"openclaw"
|
||||
],
|
||||
"underlyingCli": null,
|
||||
"modelEnv": null,
|
||||
"providerEnv": null,
|
||||
"effortEnv": null
|
||||
}
|
||||
]
|
||||
}
|
||||
86
beehive/src/harness-discovery.ts
Normal file
86
beehive/src/harness-discovery.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import contract from './harness-contract.json' with { type: 'json' };
|
||||
import { accessSync, constants, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
||||
import { delimiter, join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnOwned } from './owned.ts';
|
||||
|
||||
/** Public capabilities, never login evidence. Non-Buzz adapters remain visible,
|
||||
* but cannot consume this catalog's OS-backed provider contract without an adapter. */
|
||||
export type DetectedHarness = { id: string; label: string; executable?: string; cli?: string; state: 'available' | 'not-installed' | 'cli-missing' | 'incompatible'; providers: string[]; reason: string };
|
||||
const executable = (path: string) => { try { const p = realpathSync(path); accessSync(p, constants.X_OK); return statSync(p).isFile() ? p : undefined; } catch { return undefined; } };
|
||||
const version = (s: string) => { const last = s.trim().split(/\s+/).at(-1) ?? ''; return /^\d+\.\d+\.\d+$/.test(last) ? last.split('.').map(BigInt) : undefined; };
|
||||
/** Desktop's strict adapter floor: unknown/prerelease/partial versions fail closed. */
|
||||
export function compatibleCodex(output: string) {
|
||||
const v = version(output); if (!v) return false;
|
||||
const floor = contract.codexMinimum.map(BigInt);
|
||||
for (let i=0;i<3;i++) if (v[i] !== floor[i]) return v[i]! > floor[i]!;
|
||||
return true;
|
||||
}
|
||||
/** Bounded metadata probe only. Closed environment, output cap, awaited group Stop;
|
||||
* descendant-held stdout cannot extend the deadline. No auth/status subcommands. */
|
||||
export async function harnessProbe(command: string, args: string[], path: string, home: string, signal: AbortSignal, deadline = 5000): Promise<string | undefined> {
|
||||
signal.throwIfAborted();
|
||||
const owned = spawnOwned(command, args, home, { PATH: path, HOME: home });
|
||||
let output = '', failed = false;
|
||||
try {
|
||||
return await new Promise<string | undefined>(resolve => {
|
||||
const finish = () => { clearTimeout(timer); signal.removeEventListener('abort', abort); resolve(failed ? undefined : output); };
|
||||
const abort = () => { failed = true; finish(); };
|
||||
const timer = setTimeout(abort, deadline);
|
||||
signal.addEventListener('abort', abort, { once: true });
|
||||
owned.child.stdout.on('data', (chunk: Buffer) => { if (Buffer.byteLength(output) + chunk.length > 4096) abort(); else output += chunk.toString(); });
|
||||
owned.child.stderr.resume();
|
||||
owned.child.on('message', (m: any) => { if (m.type === 'runner-error' || m.type === 'runner-exit') { failed ||= m.type === 'runner-error' || m.code !== 0; finish(); } });
|
||||
void owned.ready.catch(abort);
|
||||
if (signal.aborted) abort();
|
||||
});
|
||||
} finally { await owned.stop(); }
|
||||
}
|
||||
function nvmBin(home: string): string[] {
|
||||
const root = join(home, '.nvm'), versions = join(root, 'versions/node');
|
||||
const safe = (tag: string) => /^[a-zA-Z0-9.\-/_]+$/.test(tag) && !tag.startsWith('/') && !tag.split('/').includes('..');
|
||||
const directory = (tag: string) => { try { const p = join(versions, tag, 'bin'); return safe(tag) && statSync(p).isDirectory() ? [p] : []; } catch { return []; } };
|
||||
const read = (p: string) => { if (statSync(p).size > 4096) throw Error(); return readFileSync(p, 'utf8').trim(); };
|
||||
try { const tag = read(join(root, 'alias/default')); if (safe(tag)) { const direct = directory(tag); if (direct.length) return direct; const hop = directory(read(join(root, 'alias', tag))); if (hop.length) return hop; } } catch { /* Missing tool metadata. */ }
|
||||
try { const tags = readdirSync(versions).slice(0, 1024).filter(t => /^v\d+\.\d+\.\d+(?:-.*)?$/.test(t)); tags.sort((a,b) => { const x = version(a.slice(1).split('-')[0]!)!, y = version(b.slice(1).split('-')[0]!)!; for (let i=0;i<3;i++) if (x[i] !== y[i]) return x[i]! > y[i]! ? -1 : 1; return 0; }); return tags[0] ? directory(tags[0]) : []; } catch { return []; }
|
||||
}
|
||||
/** Explicit form-open discovery; no passive polling or cached stale generations.
|
||||
* Inputs permit completely isolated fixture roots and fake probe executables. */
|
||||
export async function discoverHarnesses(signal: AbortSignal, options: { home?: string; path?: string; bundled?: string[]; common?: string[]; loginShells?: string[]; probe?: typeof harnessProbe } = {}): Promise<DetectedHarness[]> {
|
||||
const home = options.home ?? homedir(), path = options.path ?? process.env.PATH ?? '', probe = options.probe ?? harnessProbe;
|
||||
const bundled = options.bundled ?? [fileURLToPath(new URL('../bin', import.meta.url)), '/Applications/Buzz.app/Contents/MacOS'];
|
||||
const data = process.platform === 'darwin' ? join(home, 'Library/Application Support') : join(home, '.local/share');
|
||||
const platform = `${process.platform === 'darwin' ? 'darwin' : 'linux'}-${process.arch === 'arm64' ? 'arm64' : 'x64'}`;
|
||||
const managed = [join(data, 'Buzz/node-tools/bin'), join(data, 'Buzz/runtimes/node', contract.managedNodeVersion, platform, 'bin')];
|
||||
const common = options.common ?? ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/home/linuxbrew/.linuxbrew/bin', ...['.local/share/mise/shims', '.local/bin', '.volta/bin', '.asdf/shims', '.bun/bin'].map(p => join(home,p))];
|
||||
const direct = (command: string, roots: string[]) => roots.filter(p => p.startsWith('/')).slice(0, 128).map(p => executable(join(p,command))).find(Boolean);
|
||||
let loginPath: string | undefined;
|
||||
async function resolve(command: string): Promise<string | undefined> {
|
||||
const found = direct(command, [...bundled, ...(['codex-acp','claude-agent-acp','claude-code-acp','node','npm'].includes(command) ? managed : []), ...path.split(delimiter)]); if (found) return found;
|
||||
if (loginPath === undefined) {
|
||||
loginPath = '';
|
||||
for (const shell of options.loginShells ?? ['/bin/zsh', '/bin/bash']) {
|
||||
if (!executable(shell)) continue;
|
||||
const output = await probe(shell, ['-l', '-c', 'printf "\\n%s\\n" "$PATH"'], path, home, signal, 10000);
|
||||
signal.throwIfAborted();
|
||||
if (output?.trim()) { loginPath = output.trim().split('\n').at(-1) ?? ''; break; }
|
||||
}
|
||||
}
|
||||
return direct(command, [...loginPath.split(delimiter), ...managed, ...common, ...nvmBin(home)]);
|
||||
}
|
||||
const result: DetectedHarness[] = [];
|
||||
for (const row of contract.runtimes) {
|
||||
signal.throwIfAborted();
|
||||
let binary: string | undefined;
|
||||
for (const command of row.commands) { binary = await resolve(command); if (binary) break; }
|
||||
const cli = row.underlyingCli ? await resolve(row.underlyingCli) : undefined;
|
||||
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' && row.id === 'buzz-agent' ? ['openai', 'databricks_v2'] : [];
|
||||
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 });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { agentNsec } from './settings-credentials.ts';
|
||||
import { publicKey } from './protocol.ts';
|
||||
import { fetchAgentProfile } from './agent-profile.ts';
|
||||
import { serviceStatus, startService, stopService, type ServiceStatus } from './host-service.ts';
|
||||
import { detectBuzzAgent } from './settings-models.ts';
|
||||
import { discoverHarnesses, type DetectedHarness } from './harness-discovery.ts';
|
||||
import { join } from 'node:path';
|
||||
import { hostname } from 'node:os';
|
||||
import { managerCredential } from './manager-credential.ts';
|
||||
@@ -19,7 +19,7 @@ import { profileDrafts, editProfileDraft } from './profile-drafts.ts';
|
||||
|
||||
export type ManagerRequest = { id: number; action: string; values?: Record<string, string>; target?: string; revision?: number };
|
||||
export type ManagerItem = { id: string; label: string; detail: string; evidence?: string; disabled?: Record<string, string> };
|
||||
export type ManagerSnapshot = { local: ManagerItem[]; agents: (ManagerItem & { revision: number; configurations: string[] })[]; routing?: { owner: string; relay: string }; owner?: string; status: string; settings?: Settings; service?: ServiceStatus; hostRelay?: string; profilePreview?: RegisteredAgent; models?: string[]; runtimeExecutable?: string; databricksHost?: string };
|
||||
export type ManagerSnapshot = { local: ManagerItem[]; agents: (ManagerItem & { revision: number; configurations: string[] })[]; routing?: { owner: string; relay: string }; owner?: string; status: string; settings?: Settings; service?: ServiceStatus; hostRelay?: string; profilePreview?: RegisteredAgent; models?: 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. */
|
||||
@@ -136,6 +136,7 @@ export class ManagerController {
|
||||
private profilePreview?: RegisteredAgent;
|
||||
private models?: string[];
|
||||
private runtimeExecutable?: string;
|
||||
private harnesses?: DetectedHarness[];
|
||||
private databricksHost?: string;
|
||||
private lastId = 0;
|
||||
private status = 'Local Host does not require owner sign-in. Quitting or signing out does not stop hosts or agents.';
|
||||
@@ -195,7 +196,7 @@ A Stop result is not a recent host report that confirms the agent is stopped.` }
|
||||
try { routing = readControllerConfig(this.ownerDirectory); } catch { /* Invalid retained routing is refused by sign-in; never reset here. */ }
|
||||
let settings: Settings | undefined, hostRelay: string | undefined;
|
||||
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. */ }
|
||||
return { settings, service: this.service, hostRelay, profilePreview: this.profilePreview, models: this.models, runtimeExecutable: this.runtimeExecutable, databricksHost: this.databricksHost, local, agents, routing: routing ? { owner: routing.owner, relay: routing.relay } : undefined, owner: this.owner, status: this.status };
|
||||
return { settings, service: this.service, hostRelay, profilePreview: this.profilePreview, models: this.models, runtimeExecutable: this.runtimeExecutable, harnesses: this.harnesses, databricksHost: this.databricksHost, local, agents, routing: routing ? { owner: routing.owner, relay: routing.relay } : undefined, owner: this.owner, status: this.status };
|
||||
}
|
||||
|
||||
private fresh(m: Message) { return Boolean(this.client?.connected && Date.now() - Number(m.body.observedAt) <= 6000); }
|
||||
@@ -237,15 +238,18 @@ A Stop result is not a recent host report that confirms the agent is stopped.` }
|
||||
} 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 === 'runtime-form') {
|
||||
this.runtimeExecutable = detectBuzzAgent(); this.models = undefined; this.status = this.runtimeExecutable ? 'Buzz Agent found. Model access is not yet verified.' : 'Buzz Agent was not found on PATH. No installed harness was run.';
|
||||
this.runtimeExecutable = undefined; this.harnesses = undefined; this.models = 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.status = this.harnesses.map(h => `${h.label}: ${h.reason}`).join(' · ');
|
||||
} else if (request.action === 'models') {
|
||||
this.models = 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.';
|
||||
} else if (request.action === 'add-runtime') {
|
||||
if (!this.runtimeExecutable) throw plain('No supported executable found.');
|
||||
if (!this.runtimeExecutable || (v.harness ?? 'buzz-agent') !== 'buzz-agent') throw plain('No supported executable found.');
|
||||
const previous = readSettings(this.hostDirectory);
|
||||
saveSettings(this.hostDirectory,{ ...previous, runtimes: [...previous.runtimes,{ id: settingsId(), name: v.name ?? '', harness: 'buzz-agent', executable: this.runtimeExecutable, providerId: v.provider ?? '', model: v.model ?? '' }] },previous.revision);
|
||||
saveSettings(this.hostDirectory,{ ...previous, runtimes: [...previous.runtimes,{ id: settingsId(), name: v.name ?? '', harness: 'buzz-agent', executable: this.runtimeExecutable, providerId: v.provider ?? '', model: v.model ?? '', ...(v.effort ? { effort: v.effort } : {}) }] },previous.revision);
|
||||
this.status = 'Runtime saved for new runs. Running agents did not change. Host loading is reported separately.';
|
||||
} else if (request.action === 'host-start') {
|
||||
this.service = await startService(this.hostDirectory); check(); this.status = 'Host running. Registration and settings do not start agents.';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeForm } from './runtime-form.ts';
|
||||
import { createCliRenderer } from '@opentui/core';
|
||||
import { createReadStream, createWriteStream } from 'node:fs';
|
||||
import { createInterface } from 'node:readline';
|
||||
@@ -86,19 +87,7 @@ function render() {
|
||||
if (await screen.confirm(`Save provider ${name}? No agent will start.`)) await request('add-openai',{ name, secret });
|
||||
secret = '';
|
||||
} },
|
||||
{ label: 'Add runtime', run: async () => {
|
||||
await request('runtime-form'); if (!snapshot.runtimeExecutable) return;
|
||||
const harness = await screen.choose('Supported harness',['Buzz Agent']); if (!harness) return;
|
||||
const providers = snapshot.settings?.providers ?? [];
|
||||
if (!providers.length) { screen.notice('Add a provider first.'); return; }
|
||||
const choice = await screen.choose('Provider',providers.map(p => `${p.name} · ${p.id}`)); if (!choice) return;
|
||||
const provider = providers.find(p => choice === `${p.name} · ${p.id}`)!;
|
||||
await request('models',{ provider: provider.id });
|
||||
const choiceModel = await screen.choose(snapshot.models ? 'Model' : 'Model listing unavailable · Custom model allowed',[...(snapshot.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 name = await screen.input('Runtime name',model); if (!name) return;
|
||||
if (await screen.confirm(`Save ${name}?\nBuzz Agent · ${provider.name} · ${model}\nEffort control is unavailable in this candidate.\nSaved for new runs. Running agents do not change.`)) await request('add-runtime',{ name, model, provider: provider.id });
|
||||
} },
|
||||
{ label: 'Add runtime', run: () => runtimeForm(screen, () => snapshot, request) },
|
||||
{ label: 'Start', disabled: snapshot.service?.state === 'unknown' ? 'Host ownership is unknown. No process will be adopted.' : undefined, run: async () => {
|
||||
if (!configured) { await routing('configure'); return; }
|
||||
if (await screen.confirm('Start the local host service? It keeps running when you quit. The OS may ask for key access. This does not start an agent.')) await request('host-start');
|
||||
|
||||
1684
beehive/src/model-capabilities.json
Normal file
1684
beehive/src/model-capabilities.json
Normal file
File diff suppressed because it is too large
Load Diff
35
beehive/src/runtime-effort.ts
Normal file
35
beehive/src/runtime-effort.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
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. */
|
||||
export function runtimeEfforts(provider: string, model: string): readonly 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))) return [];
|
||||
const lower = model.toLowerCase();
|
||||
const exact = manifest.exact_records.find(r => r.provider === canon && r.raw_model_id.toLowerCase() === lower);
|
||||
if (exact) return exact.thinking_mode === 'omit-fields' ? [] : exact.supported_efforts;
|
||||
let start = lower.length;
|
||||
for (const token of manifest.family_tokens) {
|
||||
let from = 0;
|
||||
while (true) {
|
||||
const index = lower.indexOf(token, from); if (index < 0) break;
|
||||
if (index === 0 || !/[a-z0-9]/.test(lower[index - 1]!)) { start = Math.min(start, index); break; }
|
||||
from = index + 1;
|
||||
}
|
||||
}
|
||||
const stripped = start === lower.length ? lower : lower.slice(start);
|
||||
const matches = manifest.family_rules.flatMap(rule => {
|
||||
if (!rule.providers.includes(canon)) return [];
|
||||
const tokens = [rule.match_value, ...('match_aliases' in rule ? rule.match_aliases as string[] : [])];
|
||||
const lengths = tokens.filter(token => rule.match_kind === 'exact' ? stripped === token : stripped.startsWith(token) && (!stripped[token.length] || !/[a-z0-9]/.test(stripped[token.length]!))).map(token => token.length);
|
||||
return lengths.length ? [{ rule, length: Math.max(...lengths) }] : [];
|
||||
}).sort((a, b) => b.length - a.length || (a.rule.id < b.rule.id ? -1 : 1));
|
||||
const rule = matches[0]?.rule;
|
||||
return !rule || rule.thinking_mode === 'omit-fields' ? [] : rule.supported_efforts;
|
||||
}
|
||||
|
||||
/** Recheck at save AND actual launch; no caller can inject a universal level. */
|
||||
export function validateRuntimeEffort(provider: string, model: string, effort?: string) {
|
||||
if (effort !== undefined && !runtimeEfforts(provider, model).includes(effort)) throw Error('Effort is not supported by this provider/model');
|
||||
}
|
||||
30
beehive/src/runtime-form.ts
Normal file
30
beehive/src/runtime-form.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { runtimeEfforts } from './runtime-effort.ts';
|
||||
import type { ManagerSnapshot } from './manager-controller.ts';
|
||||
|
||||
/** Same short form used by the actual renderer and synthetic key/form fixtures. */
|
||||
export async function runtimeForm(screen: {
|
||||
choose(label: string, options: string[]): Promise<string | undefined>;
|
||||
input(label: string, initial?: string): Promise<string | undefined>;
|
||||
confirm(label: string): Promise<boolean>;
|
||||
notice(text: string): void;
|
||||
}, snapshot: () => ManagerSnapshot, request: (action: string, values?: Record<string,string>) => Promise<void>) {
|
||||
await request('runtime-form');
|
||||
const harnesses = snapshot().harnesses ?? [];
|
||||
if (!harnesses.length) { screen.notice(snapshot().status); return; }
|
||||
const labels = harnesses.map(h => `${h.label} · ${h.providers.length ? 'Available' : h.state === 'available' ? 'Provider integration unavailable' : h.state}`);
|
||||
const harness = await screen.choose('Harness · detection is not sign-in', labels); if (!harness) return;
|
||||
const selected = harnesses[labels.indexOf(harness)];
|
||||
if (!selected?.providers.length) { screen.notice(selected?.reason ?? 'Unsupported harness'); return; }
|
||||
const providers = (snapshot().settings?.providers ?? []).filter(p => selected.providers.includes(p.type));
|
||||
if (!providers.length) { screen.notice('Add a supported provider first.'); return; }
|
||||
const choice = await screen.choose('Provider', providers.map(p => `${p.name} · ${p.id}`)); if (!choice) return;
|
||||
const provider = providers.find(p => choice === `${p.name} · ${p.id}`)!;
|
||||
await request('models', { provider: provider.id });
|
||||
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 efforts = runtimeEfforts(provider.type, model);
|
||||
const effort = efforts.length ? await screen.choose('Effort · new runs only', ['Inherit', ...efforts]) : 'Inherit'; if (!effort) return;
|
||||
const name = await screen.input('Runtime name', model); if (!name) return;
|
||||
if (await screen.confirm(`Save ${name}?\n${selected.label} · ${provider.name} · ${model}\n${efforts.length ? `Effort: ${effort}` : 'No supported effort control for this model.'}\nSaved for new runs. Running agents do not change.`)) await request('add-runtime', { name, model, provider: provider.id, harness: selected.id, ...(effort !== 'Inherit' ? { effort } : {}) });
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function runtimeBindings(base: Setup, settings: Settings): Record<string,
|
||||
runner: runtime.executable, args: [], workspace: base.workspace, allowedWorkspaces: base.allowedWorkspaces,
|
||||
serviceHome: base.serviceHome, configDirectory: base.configDirectory,
|
||||
...(base.conversation ? { conversation: base.conversation } : {}), mode: 'buzz-agent-api-key',
|
||||
buzzProvider: provider.type === 'databricks_v2' ? { provider:'databricks_v2',auth:'token',baseUrl:provider.endpoint,credential:provider.key,models:[runtime.model] } : { provider: 'openai-compat', baseUrl: provider.endpoint, credential: provider.key, wire: 'auto', models: [runtime.model] },
|
||||
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 } : {}) },
|
||||
})];
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { validateRuntimeEffort } from './runtime-effort.ts';
|
||||
import { databricksHost } from './databricks.ts';
|
||||
import { existsSync, mkdirSync, rmdirSync, lstatSync } from 'node:fs';
|
||||
import { join, isAbsolute } from 'node:path';
|
||||
@@ -10,7 +11,7 @@ export type RegisteredAgent = { publicKey: string; key: CredentialReference; pro
|
||||
/** 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 SavedRuntime = { id: string; name: string; harness: 'buzz-agent'; executable: string; providerId: string; model: string };
|
||||
export type SavedRuntime = { id: string; name: string; harness: 'buzz-agent'; 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');
|
||||
const label = (s: unknown) => typeof s === 'string' && s.length > 0 && s.length <= 128 && !/[\x00-\x1f\x7f]/.test(s);
|
||||
@@ -26,12 +27,13 @@ export function validateSettings(value: Settings): Settings {
|
||||
}
|
||||
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 r of value.runtimes) if (!label(r.id) || !label(r.name) || r.harness !== 'buzz-agent' || !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) 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']); 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 r of value.runtimes) fields(r, ['id','name','harness','executable','providerId','model']);
|
||||
for (const r of value.runtimes) fields(r, ['id','name','harness','executable','providerId','model','effort']);
|
||||
return structuredClone(value);
|
||||
}
|
||||
/** Absent settings means an empty catalog, never reconstructed credentials. */
|
||||
|
||||
@@ -21,7 +21,7 @@ for await (const line of createInterface({ input: process.stdin })) {
|
||||
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 (mode === 'databricks-os' && (process.env.DATABRICKS_TOKEN !== 'synthetic-provider-key' || process.env.DATABRICKS_HOST !== 'https://fixture.example')) 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); }
|
||||
const update = { jsonrpc: '2.0', method: 'session/update', params: { sessionId: mode === 'wrong-session' ? 'other-session' : sessionId, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'Fixture greeting, not live model proof.' } } } };
|
||||
|
||||
@@ -88,15 +88,16 @@ for (const providerType of ['openai','databricks_v2'] as const) test(`${provider
|
||||
if (providerType === 'openai') 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 = providerType === 'openai' ? 'gpt-5' : 'databricks-gpt-5-4';
|
||||
const executable = join(root,'fixture-buzz-agent');
|
||||
writeFileSync(executable,`#!/bin/sh\nexec '${process.execPath}' '${fileURLToPath(new URL('./acp-fixture.ts',import.meta.url))}' ${providerType === 'openai' ? 'openai' : 'databricks-os'}\n`,{mode:0o700});
|
||||
saveSettings(root,{ ...prior, runtimes: [{ id, name: 'Future', harness: 'buzz-agent', executable, providerId: prior.providers[0]!.id, model: 'custom-future' }] },prior.revision);
|
||||
writeFileSync(executable,`#!/bin/sh\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});
|
||||
saveSettings(root,{ ...prior, runtimes: [{ id, name: 'Future', harness: 'buzz-agent', executable, providerId: prior.providers[0]!.id, model, effort: 'high' }] },prior.revision);
|
||||
await wait(() => running.settingsRevision === 2);
|
||||
await wait(() => reports.some(m => m.type === 'inventory' && (m.body.harnessSetups as any[]).some(r => r.id === `runtime:${id}`)));
|
||||
const inventory = reports.filter(m => m.type === 'inventory').at(-1)!;
|
||||
assert.deepEqual(inventory.body.actualRun,active);
|
||||
const binding = (inventory.body.harnessSetups as any[]).find(r => r.id === `runtime:${id}`);
|
||||
const save = message('save',hostKey,publicKey(agent),inventory.revision,{ model: 'custom-future', workspace: root, profile: 'default', harnessSetup: { id: binding.id, fingerprint: binding.fingerprint } });
|
||||
const save = message('save',hostKey,publicKey(agent),inventory.revision,{ model, workspace: root, profile: 'default', harnessSetup: { id: binding.id, fingerprint: binding.fingerprint } });
|
||||
receive(save); await wait(() => reports.some(m => m.body.operation === save.id));
|
||||
assert.equal(reports.find(m => m.body.operation === save.id)?.body.result,'saved; running configuration unchanged');
|
||||
assert.deepEqual(reports.filter(m => m.type === 'inventory').at(-1)!.body.actualRun,active);
|
||||
@@ -106,7 +107,7 @@ for (const providerType of ['openai','databricks_v2'] as const) test(`${provider
|
||||
assert.equal(stopped.body.phase,'stopped');
|
||||
const nextStart = message('start',hostKey,publicKey(agent),stopped.revision); receive(nextStart); await wait(() => reports.some(m => m.body.operation === nextStart.id));
|
||||
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,'custom-future');
|
||||
assert.equal((reports.filter(m => m.type === 'inventory').at(-1)!.body.actualRun as any).selection.model,model);
|
||||
assert.equal(providerReads,1);
|
||||
assert.ok(!JSON.stringify(reports).includes('synthetic-provider-key'));
|
||||
assert.ok(!readFileSync(join(root,'journal.json'),'utf8').includes('synthetic-provider-key'));
|
||||
|
||||
@@ -4,6 +4,8 @@ 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/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 (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'); }" };
|
||||
|
||||
180
beehive/test/runtime-integration.test.ts
Normal file
180
beehive/test/runtime-integration.test.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { databricksRuntime } from '../src/databricks-runtime.ts';
|
||||
import { runtimeEfforts } from '../src/runtime-effort.ts';
|
||||
import { compatibleCodex, discoverHarnesses, harnessProbe } from '../src/harness-discovery.ts';
|
||||
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, realpathSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { databricksNative } from '../src/databricks.ts';
|
||||
import { validateSettings, type Settings } from '../src/settings.ts';
|
||||
import { runtimeForm } from '../src/runtime-form.ts';
|
||||
import type { ManagerSnapshot } from '../src/manager-controller.ts';
|
||||
import { AgentSession } from '../src/acp.ts';
|
||||
import { prepareConversation } from '../src/conversation.ts';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const key = { service: 'beehive' as const, account: 'provider:00000000-0000-0000-0000-000000000000' };
|
||||
const input = { host: 'https://first.example', key, model: 'databricks-gpt-5-4' };
|
||||
const route = '/ai-gateway/openai/v1/responses';
|
||||
const request = (bridge: { endpoint: string; capability: string }, model = input.model, path = route) => fetch(bridge.endpoint + path, { method: 'POST', headers: { authorization: `Bearer ${bridge.capability}` }, body: JSON.stringify({ model, input: 'synthetic request', reasoning: { effort: 'high' } }) });
|
||||
const fixture = (t: test.TestContext) => { const p = realpathSync(mkdtempSync(join(tmpdir(), 'beehive-runtime-fixture-'))); t.after(() => rmSync(p, { recursive:true, force:true })); return p; };
|
||||
|
||||
test('ongoing Databricks transport refreshes between requests; exact workspace/model survives and revocation fails closed', async t => {
|
||||
let expiry = false, revoked = false, reads = 0;
|
||||
const observed: string[] = [];
|
||||
const bridge = await databricksRuntime(input, async (call, signal) => {
|
||||
assert.equal(call.host, input.host); assert.deepEqual(call.key, key); assert.equal(call.action, 'token'); signal.throwIfAborted(); reads++;
|
||||
if (revoked) throw Error('synthetic refresh revoked');
|
||||
return { ok:true, secret: expiry ? 'synthetic-rotated' : 'synthetic-original' };
|
||||
}, async (url, options) => {
|
||||
assert.equal(String(url), input.host + route); assert.equal(JSON.parse(String(options?.body)).model, input.model);
|
||||
assert.equal(JSON.parse(String(options?.body)).reasoning.effort, 'high'); assert.equal(options?.redirect, 'error');
|
||||
observed.push(new Headers(options?.headers).get('authorization')!);
|
||||
return new Response('{"output":[]}');
|
||||
});
|
||||
t.after(() => bridge.stop());
|
||||
assert.equal((await request(bridge)).status, 200);
|
||||
expiry = true;
|
||||
assert.equal((await request(bridge)).status, 200);
|
||||
assert.deepEqual(observed, ['Bearer synthetic-original', 'Bearer synthetic-rotated']);
|
||||
revoked = true;
|
||||
const denied = await request(bridge); assert.equal(denied.status, 503); assert.match(await denied.text(), /sign-in/);
|
||||
assert.equal(observed.length, 2); assert.equal(reads, 3);
|
||||
assert.equal((await request(bridge, 'other-model')).status, 403);
|
||||
assert.equal((await request(bridge, input.model, '/oidc/token')).status, 403);
|
||||
assert.equal((await request({ ...bridge, capability: 'foreign-runtime' })).status, 403);
|
||||
assert.equal(reads, 3);
|
||||
});
|
||||
|
||||
test('concurrent runtimes isolate capabilities/workspaces; upstream auth rejection latches without fallback', async t => {
|
||||
const calls: string[] = [];
|
||||
const make = async (host: string) => databricksRuntime({ ...input, host }, async call => ({ ok:true, secret: call.host === input.host ? 'first-token' : 'second-token' }), async (url, options) => {
|
||||
calls.push(`${url}:${new Headers(options?.headers).get('authorization')}`);
|
||||
return new Response('withheld upstream diagnostic', { status: String(url).startsWith(input.host) ? 401 : 200 });
|
||||
});
|
||||
const a = await make(input.host), b = await make('https://second.example'); t.after(async () => { await a.stop(); await b.stop(); });
|
||||
assert.equal((await request({ endpoint:a.endpoint, capability:b.capability })).status, 403);
|
||||
const responses = await Promise.all([request(a), request(b)]);
|
||||
assert.deepEqual(responses.map(r => r.status), [401,200]);
|
||||
assert.ok(!(await responses[0]!.text()).includes('withheld'));
|
||||
assert.equal((await request(a)).status, 401); assert.equal(calls.length, 2);
|
||||
assert.ok(calls.includes(input.host + route + ':Bearer first-token'));
|
||||
assert.ok(calls.includes('https://second.example' + route + ':Bearer second-token'));
|
||||
});
|
||||
|
||||
test('Stop fences delayed native completion and aborts an ongoing upstream request', async () => {
|
||||
let enter!: () => void, finish!: () => void;
|
||||
const entered = new Promise<void>(r => enter = r), released = new Promise<void>(r => finish = r);
|
||||
let aborted = false, fetched = false;
|
||||
const bridge = await databricksRuntime(input, async (_call, signal) => { enter(); signal.addEventListener('abort', () => aborted = true); await released; return { ok:true, secret:'late-token' }; }, async () => { fetched = true; throw Error(); });
|
||||
const pending = request(bridge).catch(() => undefined); await entered;
|
||||
let stopped = false; const stop = bridge.stop().then(() => stopped = true);
|
||||
await Promise.resolve(); assert.equal(aborted,true); assert.equal(stopped,false);
|
||||
finish(); await stop; await pending; assert.equal(fetched,false);
|
||||
await assert.rejects(request(bridge));
|
||||
let fetching!: () => void; const started = new Promise<void>(r => fetching = r);
|
||||
const second = await databricksRuntime(input, async () => ({ok:true, secret:'fake'}), async (_url, options) => new Promise((_resolve,reject) => { fetching(); options!.signal!.addEventListener('abort', () => reject(Error('cancelled'))); }));
|
||||
const network = request(second).catch(() => undefined); await started; await second.stop(); await network;
|
||||
});
|
||||
|
||||
test('runtime Stop cancels an actual owned native helper, with no OS-store/browser access', async t => {
|
||||
const root = fixture(t), helper = join(root,'helper');
|
||||
writeFileSync(helper, '#!/bin/sh\nexec /bin/sleep 60\n', { mode:0o700 });
|
||||
let enter!: () => void; const entered = new Promise<void>(r => enter = r);
|
||||
const bridge = await databricksRuntime(input, (call, signal) => { const result = databricksNative(call, signal, helper); enter(); return result; }, async () => { throw Error('network forbidden'); });
|
||||
const pending = request(bridge).catch(() => undefined); await entered; await bridge.stop(); await pending;
|
||||
});
|
||||
|
||||
test('canonical catalogs preserve query paths; no redirects, provider diagnostics, or arbitrary proxy destinations', async t => {
|
||||
const paths: string[] = [];
|
||||
const bridge = await databricksRuntime(input, async () => ({ok:true, secret:'fake'}), async url => { paths.push(String(url)); return new Response('{"endpoints":[]}'); });
|
||||
t.after(() => bridge.stop());
|
||||
for (const path of ['/api/ai-gateway/v2/endpoints?page_size=100', '/api/2.1/unity-catalog/model-services?page_size=100&view=FULL&page_token=next']) assert.equal((await fetch(bridge.endpoint+path, {headers:{authorization:`Bearer ${bridge.capability}`}})).status,200);
|
||||
assert.deepEqual(paths, [input.host+'/api/ai-gateway/v2/endpoints?page_size=100', input.host+'/api/2.1/unity-catalog/model-services?page_size=100&view=FULL&page_token=next']);
|
||||
});
|
||||
|
||||
test('Desktop contract detects fake adapter/CLI independently, strict Codex version and current Pi command', async t => {
|
||||
const root = fixture(t);
|
||||
const make = (name: string, output = '') => { const p=join(root,name); writeFileSync(p, `#!/bin/sh\nprintf '%s\\n' '${output}'\n`, {mode:0o700}); return p; };
|
||||
make('buzz-agent'); make('buzz-pi-acp'); make('pi'); make('codex-acp','@agentclientprotocol/codex-acp 1.10.0'); make('codex'); make('claude-agent-acp');
|
||||
const rows = await discoverHarnesses(new AbortController().signal, {home:root,path:root,bundled:[],common:[],loginShells:[]});
|
||||
assert.equal(rows.find(r => r.id === 'pi')?.executable,join(root,'buzz-pi-acp'));
|
||||
assert.equal(rows.find(r => r.id === 'pi')?.state,'available');
|
||||
assert.equal(rows.find(r => r.id === 'codex')?.state,'available');
|
||||
assert.equal(rows.find(r => r.id === 'claude')?.state,'cli-missing');
|
||||
assert.equal(rows.find(r => r.id === 'goose')?.state,'not-installed');
|
||||
assert.deepEqual(rows.filter(r => r.providers.length).map(r => r.id),['buzz-agent']);
|
||||
for (const value of ['1.9.99','1.10','1.10.0-rc1','not-version']) assert.equal(compatibleCodex(value),false);
|
||||
for (const value of ['1.10.0','pkg 2.0.0']) assert.equal(compatibleCodex(value),true);
|
||||
const slow = join(root,'slow'); writeFileSync(slow,'#!/bin/sh\nexec /bin/sleep 60\n',{mode:0o700});
|
||||
assert.equal(await harnessProbe(slow,['--version'],root,root,new AbortController().signal,30),undefined);
|
||||
});
|
||||
|
||||
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('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});
|
||||
const settings: Settings = {version:1,revision:1,agents:[],providers:[{id:'p',name:'OpenAI',type:'openai',endpoint:'https://api.openai.com/v1',key}],runtimes:[{id:'r',name:'Reasoning',harness:'buzz-agent',executable:runner,providerId:'p',model:'gpt-5',effort:'high'}]};
|
||||
validateSettings(settings);
|
||||
assert.throws(() => validateSettings({...settings,runtimes:[{...settings.runtimes[0]!,effort:'banana'}]}),/Effort/);
|
||||
// Actual runtime projection is also covered by the host Save/Start fixture.
|
||||
const session = new AgentSession({ executable:runner,args:[],workspace:root,home:root,configDirectory:root,databricksHost:'',model:'gpt-5',resolvedProviderKey:'synthetic-provider-key',buzzProvider:{provider:'openai-compat',baseUrl:settings.providers[0]!.endpoint,wire:'auto',credential:key,models:['gpt-5'],effort:'high'} });
|
||||
t.after(() => session.owned.stop());
|
||||
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 () => {
|
||||
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 () => inputs.shift(),confirm:async label => {assert.match(label,/Running agents do not change/);return true;},notice:() => {}}, () => snapshot, async (action,values) => {submitted.push({action,values});});
|
||||
await run(['Buzz Agent · Available','OpenAI · p','gpt-5','high'],['Reasoning']);
|
||||
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']);
|
||||
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'],[]);
|
||||
assert.deepEqual(submitted.map(s => s.action),['runtime-form']);
|
||||
});
|
||||
|
||||
test('one actual wrapper/harness run makes requests before expiry, after refresh, and after revocation without restart', async t => {
|
||||
const root = fixture(t), loader = join(root,'isolation.mjs'), harness=join(root,'harness.mjs'), executable=join(root,'entry');
|
||||
// This explicit loader is passed only to the fixture wrapper process. It blocks
|
||||
// native custody completely and rejects every nonfixture upstream destination.
|
||||
writeFileSync(loader, `import { registerHooks } from 'node:module';
|
||||
let tokens=0;
|
||||
registerHooks({load(url,context,next) {const result=next(url,context); if(url.endsWith('/src/databricks.ts')) return {...result,source: "export function databricksHost(s) { if(s !== 'https://first.example') throw Error(); return s; } export async function databricksNative(input,signal) { signal.throwIfAborted(); if(input.action !== 'token') throw Error(); return globalThis.fixtureToken(); }"}; return result;}});
|
||||
globalThis.fixtureToken=() => { if(++tokens===3) throw Error('synthetic revoked'); return {ok:true,secret:tokens===1?'fixture-before':'fixture-after'}; };
|
||||
globalThis.fetch=async (url,options) => { if(url !== 'https://first.example/ai-gateway/openai/v1/responses') throw Error('network forbidden'); const bearer=new Headers(options.headers).get('authorization'); if(bearer !== (tokens===1?'Bearer fixture-before':'Bearer fixture-after')) throw Error('wrong refreshed token'); return new Response(JSON.stringify({output:[],token:tokens})); };
|
||||
`);
|
||||
writeFileSync(harness, `import {createInterface} from 'node:readline';
|
||||
const endpoint=process.env.DATABRICKS_HOST, capability=process.env.DATABRICKS_TOKEN;
|
||||
if(!/^http:\\/\\/127\\.0\\.0\\.1:\\d+$/.test(endpoint) || !/^[0-9a-f]{64}$/.test(capability) || process.env.BEEHIVE_DATABRICKS_RUNTIME) process.exit(8);
|
||||
createInterface({input:process.stdin}).on('line',async line=>{const m=JSON.parse(line);let result;
|
||||
if(m.method==='initialize') result={protocolVersion:2,agentInfo:{name:'buzz-agent'}};
|
||||
else if(m.method==='session/new') result={sessionId:'same-run',models:{availableModels:[{modelId:'databricks-gpt-5-4'}]}};
|
||||
else if(m.method==='session/set_model') result={sessionId:'same-run',modelId:'databricks-gpt-5-4'};
|
||||
else if(m.method==='session/prompt') {for(let i=1;i<=3;i++){const r=await fetch(endpoint+'/ai-gateway/openai/v1/responses',{method:'POST',headers:{authorization:'Bearer '+capability},body:JSON.stringify({model:'databricks-gpt-5-4',input:'fake'})}); if(r.status !== (i===3?503:200)) process.exit(9);if(i<3 && (await r.json()).token !== i) process.exit(10);} console.log(JSON.stringify({jsonrpc:'2.0',method:'session/update',params:{sessionId:'same-run',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'one run refreshed and refused revoked access'}}}}));result={stopReason:'end_turn'};}
|
||||
console.log(JSON.stringify({jsonrpc:'2.0',id:m.id,result}));});
|
||||
`);
|
||||
const wrapper=fileURLToPath(new URL('../src/databricks-runtime-child.ts',import.meta.url));
|
||||
// Public reference only in shell/argv; fake bearers are created exclusively in
|
||||
// the isolated loader. No retained config, OS credentials or real harness.
|
||||
writeFileSync(executable, `#!/bin/sh\nexport BEEHIVE_DATABRICKS_RUNTIME='${JSON.stringify(input)}'\nexec '${process.execPath}' --import '${loader}' '${wrapper}' '${process.execPath}' '${harness}'\n`, {mode:0o700});
|
||||
const session=new AgentSession({executable,args:[],workspace:root,home:root,configDirectory:root,databricksHost:input.host,model:input.model});
|
||||
t.after(() => session.owned.stop());
|
||||
await session.catalog(); const evidence=await session.verify(); assert.equal(evidence.session,'same-run'); assert.equal(evidence.model,input.model);
|
||||
});
|
||||
|
||||
test('relay runtime never receives the OS-backed Databricks bearer', t => {
|
||||
const root=fixture(t);
|
||||
const plan=prepareConversation({executable:realpathSync(process.execPath),relay:'ws://127.0.0.1:1'}, {executable:realpathSync(process.execPath),args:[],workspace:root,home:root,configDirectory:root,databricksHost:'',model:input.model,resolvedProviderKey:'synthetic-provider-token',buzzProvider:{provider:'databricks_v2',auth:'token',baseUrl:input.host,credential:key,models:[input.model]}}, '1'.repeat(64), '2'.repeat(64));
|
||||
assert.equal(plan.env.DATABRICKS_TOKEN,undefined);
|
||||
assert.ok(!JSON.stringify(plan.env).includes('synthetic-provider-token'));
|
||||
});
|
||||
29
beehive/tools/export-harnesses.py
Normal file
29
beehive/tools/export-harnesses.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Export Desktop static discovery data; run explicitly at a reviewed revision.
|
||||
No Rust invocation, executable probe, credentials, or package dependency required.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
revision = sys.argv[1]
|
||||
base = 'desktop/src-tauri/src/managed_agents/discovery/'
|
||||
rows = []
|
||||
for name, struct in [('catalog.rs', 'KnownAcpRuntime'), ('presets.rs', 'PresetHarness')]:
|
||||
source = subprocess.check_output(['git', 'show', f'{revision}:{base}{name}'], text=True)
|
||||
source = source.split('const ' + ('KNOWN_ACP_RUNTIMES' if name == 'catalog.rs' else 'PRESET_HARNESSES'), 1)[1].split('\n];', 1)[0]
|
||||
for block in source.split(struct + ' {')[1:]:
|
||||
match = re.search(r'\bid: "([^"]+)"', block)
|
||||
if not match:
|
||||
continue
|
||||
def string(field):
|
||||
m = re.search(r'\b' + field + r': (?:Some\()?"([^"]+)"', block)
|
||||
return m[1] if m else None
|
||||
commands = re.search(r'commands: &\[([^]]+)\]', block)
|
||||
rows.append(dict(id=match[1], label=string('label'), commands=re.findall(r'"([^"]+)"', commands[1]) if commands else [string('command')], underlyingCli=string('underlying_cli'), modelEnv=string('model_env_var'), providerEnv=string('provider_env_var'), effortEnv=string('thinking_env_var')))
|
||||
discovery = subprocess.check_output(['git', 'show', f'{revision}:desktop/src-tauri/src/managed_agents/discovery.rs'], text=True)
|
||||
minimum = [int(v.strip()) for v in re.search(r'MIN_CODEX_ACP_VERSION: \(u64, u64, u64\) = \(([^)]+)\)', discovery)[1].split(',')]
|
||||
managed = subprocess.check_output(['git', 'show', f'{revision}:desktop/src-tauri/src/managed_agents/managed_node_paths.rs'], text=True)
|
||||
node_version = re.search(r'BUZZ_MANAGED_NODE_VERSION: &str = "([^"]+)"', managed)[1]
|
||||
Path('beehive/src/harness-contract.json').write_text(json.dumps(dict(revision=revision, sources=[base+'catalog.rs', base+'presets.rs'], codexMinimum=minimum, managedNodeVersion=node_version, runtimes=rows), indent=2)+'\n')
|
||||
Reference in New Issue
Block a user