From e9d5a0d9f93e45d2fa7aa44b90f9705cdd302a90 Mon Sep 17 00:00:00 2001 From: Logan Johnson Date: Wed, 16 Sep 2026 13:07:11 -0400 Subject: [PATCH] fix(beehive): sign in with Buzz key on selection alone Selecting Sign in with Buzz key now dispatches the read of the saved Buzz owner key immediately. The extra Beehive confirmation before it is removed: selection itself is the explicit user gesture. While the read is in flight the status line keeps the custody disclosure (Keychain read only, session only, no local copy) and states that an OS keychain permission dialog is separate and never bypassed or promised away. Saved-key and matching-key import sign-in keep their confirmation and input paths; helper source, owner matching, session-only custody, cancellation and all backend behavior are unchanged. The test-only fixture gains an inert env-gated pacing knob so a PTY driver can observe the pending state and exercise Esc cancellation deterministically. Verified with the synthetic source-UI PTY driver in a fresh beehive-pairing-cli- HOME with explicit BEEHIVE_TEST_CREDENTIAL_FILE, installed loader and service fences; production Keychain never read: one selection reaches signed-in controls with no further Beehive input, and Esc still cancels the in-flight read. Focused 12/12 backend and 8/8 renderer suites and package typecheck re-passed. Signed-off-by: Logan Johnson --- beehive/BUZZ_OWNER_SIGNIN.md | 12 +++++++++++- beehive/src/manager-view.ts | 14 +++++++++----- beehive/test/manager-installed-fixture.ts | 4 ++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/beehive/BUZZ_OWNER_SIGNIN.md b/beehive/BUZZ_OWNER_SIGNIN.md index 97c54bcfe2..37488f3132 100644 --- a/beehive/BUZZ_OWNER_SIGNIN.md +++ b/beehive/BUZZ_OWNER_SIGNIN.md @@ -1,7 +1,10 @@ # Explicit Buzz owner sign-in Agents offers **Sign in with Buzz key** alongside saved-key and matching-key import. -The confirmation says Keychain read only, session only, no local copy. Merely +Selecting the action signs in immediately: selection itself is the explicit +gesture, and no extra Beehive confirmation is shown. While the key is read the +status line states Keychain read only, session only, no local copy, and that an +OS keychain permission dialog is separate and not bypassed. Merely opening Beehive, selecting Agents, configuring routing, or choosing another sign-in method never invokes this reader. It is not provider sharing/discovery. @@ -79,6 +82,13 @@ Final semantic candidate, pinned Node **24.15.0**, package-local TypeScript: A separate fresh traversal of the **unchanged installed launcher** exercised saved-key sign-in (cancel/confirm), with the same explicit isolation. This is compatibility evidence, not installation of this feature or real relay proof. +- 2025-09-16 prompt removal: the extra Beehive confirmation before the Buzz-key + read was deleted; one selection dispatches the request. The source-UI PTY + driver (`ui-single-gesture.py`) replays screen state and verified Agents → + select Buzz key once → signed-in controls with **no** Beehive confirm or other + input, Esc still cancelling the in-flight read; same fresh-HOME, + fixture-credential and service fences, production Keychain untouched. The + 12/12 backend and 8/8 renderer suites and package typecheck re-passed. - **One** default concurrent full-package run was attempted on the final semantic candidate. It exceeded the outer tool's **400 second** bound, so no final suite summary exists. Retained output reports failures in assignment, broker, and diff --git a/beehive/src/manager-view.ts b/beehive/src/manager-view.ts index 3fa442ccda..7b053d4e60 100644 --- a/beehive/src/manager-view.ts +++ b/beehive/src/manager-view.ts @@ -22,7 +22,7 @@ function request(action: string, values?: Record, target?: string const id = ++sequence; const completion = new Promise(resolve => { pending = { id, resolve }; }); const message: ManagerRequest = { id, action, values, target, revision }; - screen.setPending(true); const retire = screen.temporaryNotice(action === 'add-databricks' ? 'Signing in through your browser… Esc cancels the native helper. An OS credential write or browser window may remain; cancelled sign-in will not save a provider.' : 'Working… Esc stops waiting. Remote work and saved changes are not cancelled. Inspect before you try again.'); + screen.setPending(true); const retire = screen.temporaryNotice(action === 'add-databricks' ? 'Signing in through your browser… Esc cancels the native helper. An OS credential write or browser window may remain; cancelled sign-in will not save a provider.' : action === 'signin-buzz' ? 'Reading the saved Buzz owner key… Keychain read only · session only · no local copy. Esc stops waiting. An OS keychain permission dialog may appear; it is separate and is not bypassed.' : 'Working… Esc stops waiting. Remote work and saved changes are not cancelled. Inspect before you try again.'); output.write(JSON.stringify(message) + '\n'); return completion.finally(retire); } @@ -46,10 +46,14 @@ async function routing(action: string, secret = false) { if (key === undefined) return; values.secret = key; } - const confirmation = action === 'signin-buzz' - ? `Sign in with Buzz key\nKeychain read only · session only · no local copy\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}` - : `${action === 'configure' ? 'Configure this computer' : 'Sign in'}\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}\n${action === 'configure' ? 'Create host identity in the OS credential store?' : 'Access owner key in the OS credential store?'}`; - if (!await screen.confirm(confirmation)) { delete values.secret; return; } + // Buzz-key sign-in carries no extra Beehive prompt: selecting the action is + // itself the explicit gesture (an OS keychain permission dialog is separate + // and still applies), so the request dispatches immediately. The saved-key + // and import paths keep their confirmation. + if (action !== 'signin-buzz') { + const confirmation = `${action === 'configure' ? 'Configure this computer' : 'Sign in'}\nOwner: ${shortIdentity(owner)}\nRelay: ${relay}\n${action === 'configure' ? 'Create host identity in the OS credential store?' : 'Access owner key in the OS credential store?'}`; + if (!await screen.confirm(confirmation)) { delete values.secret; return; } + } await request(action, values); delete values.secret; } async function registerForm(continuation?: string, agent?: string) { diff --git a/beehive/test/manager-installed-fixture.ts b/beehive/test/manager-installed-fixture.ts index d8142bdfe7..7ad78e740a 100644 --- a/beehive/test/manager-installed-fixture.ts +++ b/beehive/test/manager-installed-fixture.ts @@ -21,6 +21,10 @@ export async function fixtureCredential(input: any, signal: AbortSignal) { 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-buzz') { + // Test-only pacing so a PTY driver can observe the pending state and exercise + // Esc cancellation deterministically; inert unless the driver sets it. + const paced = Number(process.env.BEEHIVE_TEST_SIGNIN_DELAY_MS ?? '0'); + if (paced > 0) await new Promise((resolve, reject) => { const timer = setTimeout(resolve, paced); signal.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason); }, { once: true }); }); const key = backend.read(credentialReference('owner', input.owner)); const result = readBuzzOwnerKey(input.owner, () => key === null ? null : JSON.stringify({identity:nip19.nsecEncode(Buffer.from(key,'hex'))})); if (!result.ok) throw Error(buzzOwnerKeyErrors[result.reason]);