Skip to content

feat(desktop): expose guarded composer voice ownership controller - #100666

Open
TheAngryPit wants to merge 1 commit into
NousResearch:mainfrom
TheAngryPit:feat/realtime-voice-ownership-core-20260901
Open

feat(desktop): expose guarded composer voice ownership controller#100666
TheAngryPit wants to merge 1 commit into
NousResearch:mainfrom
TheAngryPit:feat/realtime-voice-ownership-core-20260901

Conversation

@TheAngryPit

@TheAngryPit TheAngryPit commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What changed

Hermes Desktop already owns the composer microphone, wake-word pause/resume lifecycle, canonical message submission, and assistant state. A Desktop plugin action can render beside the composer, but it cannot safely coordinate those resources without either bypassing the canonical chat path or racing the built-in voice controller.

This PR adds a narrow, renderer-only controller to the existing composer.actions contribution context. A plugin can:

  • acquire an opaque, session-bound voice lease;
  • pause the built-in wake listener while the lease is active;
  • abort current generation through the canonical composer path;
  • submit finalized text through the canonical composer path;
  • subscribe to assistant snapshots for the active session; and
  • release the lease idempotently, re-arming wake only when the current lease still owns that responsibility.

The controller is guarded by session and epoch fencing before and after asynchronous boundaries. Navigation, stale controllers, overlapping acquisition, aborted pause operations, and unmount disposal therefore fail closed instead of mutating the next session.

The SDK exposes only the controller contract. It does not add provider code, credentials, a voice-provider registry, transport logic, or a second chat authority to Core.

Why this shape

A concrete external Desktop consumer needs ownership coordination, not a competing Realtime provider stack inside Hermes Core. Core remains authoritative for microphone/wake/composer state. The plugin remains authoritative for its provider transport and audio lifecycle.

This advances the extension seam discussed in #77111. It is intentionally separate from the broader provider abstraction in #95147 and the built-in controller lifetime work in #95180.

A companion plugin PR will consume this contract once this Core seam is available: TheSmokeDev/hermes-talk#80.

Type

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Other

Files changed

  • apps/desktop/src/app/chat/composer/hooks/use-composer-voice.ts
    • implements lease ownership, wake pause/re-arm ordering, session/epoch fencing, canonical submit/abort, and assistant subscriptions;
  • apps/desktop/src/app/chat/composer/index.tsx
    • exposes the controller through the existing composer.actions render context;
  • apps/desktop/src/sdk/index.ts
    • publishes the narrow controller and snapshot types;
  • apps/desktop/src/app/chat/composer/hooks/use-composer-voice.test.ts
    • covers lifecycle, cancellation, stale-session fencing, and subscription behavior.

Testing

On the rebased candidate against frozen base ab9866bc64df48281a2d929dfb1dfd1001973d24:

  • npm run test:ui — 702 files, 6938 tests passed;
  • focused composer voice tests — 4/4 passed;
  • npm run typecheck — passed;
  • ESLint on all changed TypeScript files — passed with zero warnings;
  • npm run build and node scripts/assert-dist-built.mjs — passed;
  • git diff --check origin/main...HEAD — passed.

Checklist

  • Tests added for the new behavior
  • Existing UI tests pass
  • TypeScript checks pass
  • Desktop production build passes
  • No credentials, provider implementation, or private operational material included
  • No user-visible UI change requiring screenshots
  • Live microphone integration canary — deferred to the companion plugin integration because Core alone has no new transport or visible control

@alt-glitch alt-glitch added type/feature New feature or request comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 1, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Substantial refactor: voice-context epoch identity + a module-global mic lease + a ComposerVoiceController context provider, so session switches can't let a stale tile's voice action land in the wrong chat. The abort-vs-pause-settlement distinction in waitForPause is well reasoned and tested.

  • submitText and interrupt fire void runVoiceControllerCallback(...) (use-composer-voice.ts:457, 469). If onSubmit/onInterrupt rejects, that's an unhandled rejection; the pre-refactor path awaited it in the conversation loop. Consider .catch or propagating to the caller.
  • The epoch bump and owner rotation happen during render (use-composer-voice.ts:281, 302). Under React StrictMode double-render the epoch increments twice; correctness holds (equality checks, not counters), but moving the mutation into an effect would be cleaner and avoids surprise dev-mode behavior.
  • subscribeAssistant only emits on $messages changes — consumers get no initial value (use-composer-voice.ts:492). If a consumer mounts while an assistant message already exists, it must call latestAssistant() separately; document that contract.
  • microphoneOwner is module-global across all tiles/windows (use-composer-voice.ts:168). Confirm the process-wide single-owner intent; if two ChatBars legitimately need concurrent voice, the lease would need per-window scoping.
  • resumeWakeIfPaused correctly dedupes via wakeResumeScheduledRef and re-checks the barrier inside the scheduled resume — the barrier-change race is handled.
  • Tests cover the abort/pause settlement, single-release, context-check-before/after submit, and subscription disposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants