From 9b185e33ced77bf886d2cccd1b95a2cf0b6cb21d Mon Sep 17 00:00:00 2001 From: Sambit Biswas Date: Thu, 17 Sep 2026 10:20:41 -0400 Subject: [PATCH 1/2] fix: enable voice-only Aiden Live by default --- .papercuts/troubleshooting.md | 1 + docs/plans/README.md | 2 +- docs/plans/gemini-live-assistant-plan.md | 7 +++--- .../services/gemini-live/feature-flag.test.ts | 22 ++++++++++++++----- main/services/gemini-live/feature-flag.ts | 12 +++++----- main/services/gemini-live/service-main.ts | 5 ++--- package-lock.json | 4 ++-- package.json | 2 +- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/.papercuts/troubleshooting.md b/.papercuts/troubleshooting.md index edd3a9c0..d70f668c 100644 --- a/.papercuts/troubleshooting.md +++ b/.papercuts/troubleshooting.md @@ -530,3 +530,4 @@ symlink with this checkout's own npm ci. Full type-check and lint then passed. - Electron's `media` permission covers camera as well as microphone; check `mediaType` on permission checks and exact `mediaTypes: ["audio"]` on permission requests instead of treating the permission name as audio-only. - A green external-review check can race a final inline comment by seconds. Re-read unresolved review threads after the check completes and make the merge command conditional on an empty result rather than chaining inspection and merge unconditionally. - React effect cleanup marks the hook unmounted before revoking picker authority; reconfiguration needs the replacement effect setup to reset UI ownership, while final unmount must not schedule state recovery. +- A release can contain a fully tested user-facing feature while still hiding it from Finder launches if its main-process capability defaults to an environment-only opt-in. Add a focused default-environment regression whenever changing a shipping feature gate. diff --git a/docs/plans/README.md b/docs/plans/README.md index 23e7f9ac..6c508ad9 100644 --- a/docs/plans/README.md +++ b/docs/plans/README.md @@ -21,7 +21,7 @@ This directory is the source of truth for Aiden's implementation plans. The engi | [Dynamic Model Catalog](dynamic-model-catalog-plan.md) | Implemented | Validated pi.dev overlays, offline `0600` cache hydration, scoped setup refresh, four-hour launch refresh, force refresh, Pi metadata fallback, and Mac/iOS projection ship on pinned Pi 0.84.4. | | [Generative UI Artifacts](generative-ui-artifacts-plan.md) | Active | Phases 0–6 shipped: chat-scoped `render_artifact`, strict sandboxed preview/export hosts, verified vendored Chart.js/Plotly/KaTeX, permission-aware `/visualize`, crash-recoverable authoritative storage/copies, descriptor-relative workspace reads, one-iframe handoff/expansion, visible failure states, and route-stable Responding/Visualizing activity. Three-agent PR review findings are remediated with focused regression coverage. | | [Generation Progress Notes](generation-progress-notes-plan.md) | Planned | No implementation yet. | -| [Aiden Live Assistant](gemini-live-assistant-plan.md) | Partial | Extended Thinking, the blue orb, Google model visibility, and session threads are implemented. Local test builds add input/output device selection, connection sounds, and session-authorized direct actions without per-action prompts; native screen-picker and real-Google operator receipts remain open. | +| [Aiden Live Assistant](gemini-live-assistant-plan.md) | Partial | Voice-only Aiden Live ships enabled by default in 0.41.5 with Extended Thinking, the blue duplex orb, device selection, connection sounds, session threads, and session-authorized direct actions. Screen capture remains independently gated pending native-picker acceptance. | | [Libghostty workspace terminal](libghostty-terminal-plan.md) | Implemented | The workspace drawer uses Ghostty's official `libghostty-vt` WASM (T3-style runtime, PTY trampoline, canvas surface); node-pty sessions are unchanged. Packaged Mac acceptance remains. | | [Logging and Diagnostics Upgrade](logging-and-diagnostics-upgrade-plan.md) | Implemented | Phases 0–7 are implemented: bounded typed desktop journals, main-owned renderer evidence, local support export/delete, native categorical parity, and CI/release gates. Signed/notarized `v0.35.0` passed packaged diagnostics acceptance; physical-device termination receipts remain. | | [Long-thread payload upgrades](long-thread-payload-upgrade-plan.md) | Partial | Investigation complete: T3’s O(N²) stdout store does not exist here. No-op `toolRunning` timeline republish is skipped; Remote gzip, stream-journal debounce, chat JSON/attachments, and transcript windowing remain planned. | diff --git a/docs/plans/gemini-live-assistant-plan.md b/docs/plans/gemini-live-assistant-plan.md index 7d3f6128..b04336e5 100644 --- a/docs/plans/gemini-live-assistant-plan.md +++ b/docs/plans/gemini-live-assistant-plan.md @@ -2,6 +2,7 @@ Status: Partial — Phases 0–4 plus the beta-labeled Aiden Live orb/setup shell and dedicated metadata-only session threads are implemented; authorized macOS screen capture and real Google beta receipts remain operator-owned Date: 2026-09-15 +Voice-default release follow-up (2026-09-17): 0.41.5 exposes ordinary voice-only Aiden Live in normal Finder-launched production builds. The existing `AIDEN_EXPERIMENTAL_GEMINI_LIVE=0`/`false` values remain an incident kill switch. Screen capture is unchanged and stays unavailable unless `AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN=1` is deliberately supplied; the native-picker acceptance receipt remains pending. Continuous orb follow-up (2026-09-17): active listening/thinking/speaking/acting now share stable Listening + Weaving canvas layers with a slow complementary blend and connection/rest crossfades. Removed visible dock status text (retained screen-reader status). First active click reveals Stop; the second stops, Escape restores the orb, and closing disables duplicate clicks. Sharing/error surfaces remain visible. Added mapping/click-policy regressions; all 230 Live tests, type-check, lint, and standalone browser interaction/motion checks pass. Downloads duplex-review.html uses the actual orb component; the installed notarized app has not been rebuilt for this change. Release hardening follow-up (2026-09-17): Pullfrog's post-merge review blocked 0.41.4 publication until screen authority is generation-fenced. Exact binding tokens now prevent stale releases from revoking replacements, late picker streams stop after teardown, rejected frame sends revoke capture, and temporary Electron permission handlers admit only audio-only media for the exact document before restoring defaults when the final binding closes. Source Change/Remove is locked across pending provider startup and start failure releases the exact current binding. Adversarial coverage was added for each path. @@ -91,9 +92,9 @@ Stop, manual reconnect, and exact-session renderer teardown/late-event fencing. The UI and main service both keep screen capture unavailable until the native macOS picker acceptance is recorded. No capture resumes automatically. -The candidate voice-first model is `gemini-3.8-live-extended-thinking`. The beta -remains acceptance-gated with `AIDEN_EXPERIMENTAL_GEMINI_LIVE=1` until the exact -model passes the credentialed Google Live and Computer Use contract. +The voice-first model is `gemini-3.8-live-extended-thinking`. Voice-only Live +ships enabled as a beta; `AIDEN_EXPERIMENTAL_GEMINI_LIVE=0` is the incident +kill switch. Screen capture remains separately acceptance-gated. Extended Thinking uses explicit `NON_BLOCKING` Computer Use declarations so it can speak brief progress updates while tools run. Every mutation remains paused behind a fresh, finalized user-voice “Allow once” or “Deny” decision. diff --git a/main/services/gemini-live/feature-flag.test.ts b/main/services/gemini-live/feature-flag.test.ts index 0c31db4c..cc23d049 100644 --- a/main/services/gemini-live/feature-flag.test.ts +++ b/main/services/gemini-live/feature-flag.test.ts @@ -6,8 +6,8 @@ import { geminiLiveScreenEnabled, } from "./feature-flag.js"; -test("Gemini 3.8 Live stays acceptance-gated behind an exact opt-in", () => { - assert.equal(geminiLiveEnabled({}), false); +test("voice-only Gemini 3.8 Live ships on with an explicit kill switch", () => { + assert.equal(geminiLiveEnabled({}), true); assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "true" }), true); assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "1" }), true); assert.equal(geminiLiveEnabled({ AIDEN_GEMINI_LIVE_REAL_ACCEPTANCE: "1" }), true); @@ -21,6 +21,10 @@ test("Gemini 3.8 Live stays acceptance-gated behind an exact opt-in", () => { const disabled = { AIDEN_EXPERIMENTAL_GEMINI_LIVE: "0" }; assert.equal(geminiLiveEnabled(disabled), false); assert.equal(experimentalGeminiLiveModel(disabled), null); + const disabledByName = { AIDEN_EXPERIMENTAL_GEMINI_LIVE: " false " }; + assert.equal(geminiLiveEnabled(disabledByName), false); + assert.equal(experimentalGeminiLiveModel(disabledByName), null); + assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "enabled" }), false); assert.equal( experimentalGeminiLiveModel({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "true", @@ -30,14 +34,14 @@ test("Gemini 3.8 Live stays acceptance-gated behind an exact opt-in", () => { ); }); -test("screen sharing needs both flags and can never outlive the Live gate", () => { +test("screen sharing stays separately opt-in and cannot outlive the Live gate", () => { const live = { AIDEN_EXPERIMENTAL_GEMINI_LIVE: "1" }; assert.equal(geminiLiveScreenEnabled({}), false); assert.equal(geminiLiveScreenEnabled(live), false); assert.equal( geminiLiveScreenEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN: "1" }), - false, - "the screen flag alone must not admit capture", + true, + "the screen flag admits capture when the default-on voice gate is healthy", ); assert.equal( geminiLiveScreenEnabled({ ...live, AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN: "true" }), @@ -47,4 +51,12 @@ test("screen sharing needs both flags and can never outlive the Live gate", () = geminiLiveScreenEnabled({ ...live, AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN: "1" }), true, ); + assert.equal( + geminiLiveScreenEnabled({ + AIDEN_EXPERIMENTAL_GEMINI_LIVE: "0", + AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN: "1", + }), + false, + "the screen gate cannot override the Live incident kill switch", + ); }); diff --git a/main/services/gemini-live/feature-flag.ts b/main/services/gemini-live/feature-flag.ts index 616d7eba..08b9cacc 100644 --- a/main/services/gemini-live/feature-flag.ts +++ b/main/services/gemini-live/feature-flag.ts @@ -3,18 +3,16 @@ export const GEMINI_LIVE_MODEL = "gemini-3.8-live-extended-thinking"; export const GEMINI_LIVE_SCREEN_FLAG = "AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN"; /** - * The exact Live + Computer Use contract stays opt-in until a credentialed - * production acceptance receipt exists for this pinned model. + * Voice-only Aiden Live ships enabled. Keep an explicit environment kill + * switch for incident recovery without coupling ordinary voice to the + * separately gated screen-capture path. */ export function geminiLiveEnabled( environment: Readonly> = process.env, ): boolean { const value = environment[GEMINI_LIVE_FEATURE_FLAG]?.trim().toLowerCase(); - return ( - value === "1" || - value === "true" || - environment.AIDEN_GEMINI_LIVE_REAL_ACCEPTANCE?.trim() === "1" - ); + if (!value) return true; + return value === "1" || value === "true"; } /** diff --git a/main/services/gemini-live/service-main.ts b/main/services/gemini-live/service-main.ts index c2d444c4..796df512 100644 --- a/main/services/gemini-live/service-main.ts +++ b/main/services/gemini-live/service-main.ts @@ -75,11 +75,10 @@ const LIVE_COMPUTER_USE_DESCRIPTION = let disposeDisplayMediaGuards: (() => void) | null = null; /** - * The acceptance-gated beta resolves only the recorded + * The voice-only beta resolves only the recorded * `gemini-3.8-live-extended-thinking` model; it * never guesses from a normal Gemini chat model or the SDK guide's preview - * string. `AIDEN_EXPERIMENTAL_GEMINI_LIVE=1` is reserved for credentialed - * acceptance and explicit experimental launches until that contract passes. + * string. Screen capture remains independently acceptance-gated. */ export const geminiLiveService = new GeminiLiveService({ credentials: piCredentialStore, diff --git a/package-lock.json b/package-lock.json index 518f6ab9..d009540f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aiden-agent", - "version": "0.41.4", + "version": "0.41.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aiden-agent", - "version": "0.41.4", + "version": "0.41.5", "hasInstallScript": true, "dependencies": { "@earendil-works/pi-agent-core": "0.84.4", diff --git a/package.json b/package.json index 678a3d7f..83b08aa5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aiden-agent", - "version": "0.41.4", + "version": "0.41.5", "private": true, "description": "A macOS AI workspace agent for local and hosted models", "keywords": [ From b17c1664022bc4866a61cbcdd0ab63c52a5918d9 Mon Sep 17 00:00:00 2001 From: Sambit Biswas Date: Thu, 17 Sep 2026 10:45:45 -0400 Subject: [PATCH 2/2] fix: fail closed on empty Live overrides --- .papercuts/troubleshooting.md | 1 + main/services/gemini-live/feature-flag.test.ts | 2 ++ main/services/gemini-live/feature-flag.ts | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.papercuts/troubleshooting.md b/.papercuts/troubleshooting.md index d70f668c..ab0fd72a 100644 --- a/.papercuts/troubleshooting.md +++ b/.papercuts/troubleshooting.md @@ -531,3 +531,4 @@ symlink with this checkout's own npm ci. Full type-check and lint then passed. - A green external-review check can race a final inline comment by seconds. Re-read unresolved review threads after the check completes and make the merge command conditional on an empty result rather than chaining inspection and merge unconditionally. - React effect cleanup marks the hook unmounted before revoking picker authority; reconfiguration needs the replacement effect setup to reset UI ownership, while final unmount must not schedule state recovery. - A release can contain a fully tested user-facing feature while still hiding it from Finder launches if its main-process capability defaults to an environment-only opt-in. Add a focused default-environment regression whenever changing a shipping feature gate. +- For a default-on environment gate, do not use trimmed-value truthiness to detect absence: an unset variable may enable the default, but explicitly empty or whitespace-only overrides must remain fail-closed. diff --git a/main/services/gemini-live/feature-flag.test.ts b/main/services/gemini-live/feature-flag.test.ts index cc23d049..3b7bb240 100644 --- a/main/services/gemini-live/feature-flag.test.ts +++ b/main/services/gemini-live/feature-flag.test.ts @@ -24,6 +24,8 @@ test("voice-only Gemini 3.8 Live ships on with an explicit kill switch", () => { const disabledByName = { AIDEN_EXPERIMENTAL_GEMINI_LIVE: " false " }; assert.equal(geminiLiveEnabled(disabledByName), false); assert.equal(experimentalGeminiLiveModel(disabledByName), null); + assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "" }), false); + assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: " " }), false); assert.equal(geminiLiveEnabled({ AIDEN_EXPERIMENTAL_GEMINI_LIVE: "enabled" }), false); assert.equal( experimentalGeminiLiveModel({ diff --git a/main/services/gemini-live/feature-flag.ts b/main/services/gemini-live/feature-flag.ts index 08b9cacc..d0d614c5 100644 --- a/main/services/gemini-live/feature-flag.ts +++ b/main/services/gemini-live/feature-flag.ts @@ -10,8 +10,9 @@ export const GEMINI_LIVE_SCREEN_FLAG = "AIDEN_EXPERIMENTAL_GEMINI_LIVE_SCREEN"; export function geminiLiveEnabled( environment: Readonly> = process.env, ): boolean { - const value = environment[GEMINI_LIVE_FEATURE_FLAG]?.trim().toLowerCase(); - if (!value) return true; + const rawValue = environment[GEMINI_LIVE_FEATURE_FLAG]; + if (rawValue === undefined) return true; + const value = rawValue.trim().toLowerCase(); return value === "1" || value === "true"; }