diff --git a/beehive/README.md b/beehive/README.md index 47f902aa4e..bdd14cdca1 100644 --- a/beehive/README.md +++ b/beehive/README.md @@ -872,3 +872,18 @@ CHECKPOINT's MODEL_PROVIDER_CORRECTION entry for validation and observation limi Public identity editing/publication is separate from private instructions: see [PUBLIC_METADATA.md](PUBLIC_METADATA.md) for offline metadata drafts, deliberate agent-signed kind0 publication while stopped, and pending/error recovery. + +### Standalone harness inventory + +Beehive ships its built-in runtime catalog in `src/harness-contract.json`. +Desktop is not required. Additional local detection definitions live in +`~/.beehive/custom_harnesses/*.json` on every platform (under `BEEHIVE_HOME` +when set). A definition has `id`, `label`, and `command` fields; `command` is +an absolute executable path or a command name resolved through local discovery. +Built-in IDs cannot be overridden. Malformed definitions are ignored. + +Beehive does not read or automatically import Desktop's `custom_harnesses`. +To reuse a local definition, explicitly copy its JSON into the Beehive directory +and choose **Refresh harnesses**. Only detection fields are read: launch arguments, +environment variables, credentials, and Desktop agent state are not imported. +Refresh replaces the saved inventory; saved runtime configurations are unchanged. diff --git a/beehive/src/harness-discovery.ts b/beehive/src/harness-discovery.ts index b736a23949..7d08bcd5c6 100644 --- a/beehive/src/harness-discovery.ts +++ b/beehive/src/harness-discovery.ts @@ -57,13 +57,13 @@ function customHarnesses(directory: string, reserved: Set): HarnessDefin const value = JSON.parse(readFileSync(path, 'utf8')) as Partial; if (!value.id || !validCustomId(value.id) || reserved.has(value.id) || typeof value.label !== 'string' || !value.label.trim() || typeof value.command !== 'string' || !value.command.trim()) continue; reserved.add(value.id); result.push({ id: value.id, label: value.label, command: value.command }); - } catch { /* One malformed Desktop definition must not suppress the rest. */ } + } catch { /* One malformed local definition must not suppress the rest. */ } return result; } /** Forced local discovery at startup or explicit refresh. Inputs permit isolated - * fixture roots and fake probe executables. Desktop custom definitions are - * included from the same app-data directory when that directory exists. */ + * fixture roots and fake probe executables. Beehive owns its custom definitions; + * Desktop installation state is not a catalog input. */ export async function discoverHarnesses(signal: AbortSignal, options: { home?: string; path?: string; bundled?: string[]; common?: string[]; loginShells?: string[]; customDirectories?: string[]; probe?: typeof harnessProbe } = {}): Promise { 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']; @@ -107,11 +107,11 @@ export async function discoverHarnesses(signal: AbortSignal, options: { home?: s result.push({ id: row.id, label: row.label!, executable: binary, cli, ...(detectedVersion ? { version: detectedVersion } : {}), state, providers, reason }); } const reserved = new Set(result.map(row => row.id)); - for (const directory of options.customDirectories ?? (process.platform === 'darwin' ? [join(data, 'xyz.block.buzz.app/custom_harnesses')] : [])) { + for (const directory of options.customDirectories ?? [join(home, '.beehive', 'custom_harnesses')]) { for (const definition of customHarnesses(directory, reserved)) { signal.throwIfAborted(); const binary = await resolve(definition.command); - result.push({ id: definition.id, label: definition.label, ...(binary ? { executable: binary } : {}), state: binary ? 'available' : 'not-installed', providers: [], reason: binary ? 'Detected from Buzz Desktop custom harnesses; native configuration is retained by that harness.' : 'Custom harness executable not installed' }); + result.push({ id: definition.id, label: definition.label, ...(binary ? { executable: binary } : {}), state: binary ? 'available' : 'not-installed', providers: [], reason: binary ? 'Detected from Beehive custom harnesses; native configuration is retained by that harness.' : 'Custom harness executable not installed' }); } } return result; diff --git a/beehive/test/harness-inventory.test.ts b/beehive/test/harness-inventory.test.ts index 97d77d2f00..03db19e1c8 100644 --- a/beehive/test/harness-inventory.test.ts +++ b/beehive/test/harness-inventory.test.ts @@ -83,15 +83,21 @@ test('production adapter discovers and durably saves only an explicit isolated H } finally { rmSync(home, { recursive: true, force: true }); } }); -test('discovers Buzz Desktop custom harness definitions with absolute commands and skips malformed entries', async () => { +test('discovers Beehive-owned definitions without Desktop and skips malformed entries', async () => { const root = mkdtempSync(join(tmpdir(), 'beehive-custom-harnesses-')); try { const command = join(root, 'pic-pi-acp'); writeFileSync(command, '#!/bin/sh\necho custom\n'); chmodSync(command, 0o700); - const definitions = join(root, 'custom_harnesses'); mkdirSync(definitions); + const definitions = join(root, '.beehive', 'custom_harnesses'); mkdirSync(definitions, { recursive: true }); writeFileSync(join(definitions, 'custom.json'), JSON.stringify({ id: 'pic-pi', label: 'Pi custom', command, args: [] })); writeFileSync(join(definitions, 'bad.json'), '{'); + // Desktop-only entries must not leak into Beehive, even when installed. + const desktop = join(root, 'Library/Application Support/xyz.block.buzz.app/custom_harnesses'); + mkdirSync(desktop, { recursive: true }); + writeFileSync(join(desktop, 'desktop.json'), JSON.stringify({ id: 'desktop-only', label: 'Desktop only', command })); const { discoverHarnesses } = await import('../src/harness-discovery.ts'); - const rows = await discoverHarnesses(new AbortController().signal, { home: root, path: '', bundled: [], common: [], loginShells: [], customDirectories: [definitions] }); + const rows = await discoverHarnesses(new AbortController().signal, { home: root, path: '', bundled: [], common: [], loginShells: [] }); + assert.equal(rows.some(value => value.id === 'desktop-only'), false); + assert.equal(rows.some(value => value.id === 'buzz-agent'), true, 'built-in catalog requires no Desktop'); assert.deepEqual(rows.filter(value => value.id === 'pic-pi').map(value => ({ label: value.label, state: value.state, executable: value.executable })), [{ label: 'Pi custom', state: 'available', executable: realpathSync(command) }]); } finally { rmSync(root, { recursive: true, force: true }); } });