8ab08a4c5b
Co-authored-by: Kandev Agent <agent@kandev.dev>
88 lines
5.0 KiB
Markdown
88 lines
5.0 KiB
Markdown
---
|
|
status: shipped
|
|
created: 2026-08-12
|
|
owner: kandev
|
|
---
|
|
|
|
# Voice Mode Leaves Core
|
|
|
|
## Why
|
|
|
|
Voice Mode is an optional way to put text into a composer. It is not part of what kandev is for, and
|
|
carrying it in core cost every install a 100 MB browser ML dependency, a per-user settings block in
|
|
the shared user-settings row, an unauthenticated transcription route, and a server-side OpenAI key
|
|
whether or not anyone dictated a word.
|
|
|
|
The host prerequisites for moving it out shipped separately
|
|
([voice-extraction-host](voice-extraction-host.md), ADR
|
|
[composer access and authenticated webhooks](../../decisions/2026-08-11-composer-access-authenticated-webhooks.md)).
|
|
This spec records the extraction itself: what the plugin owns, what core stops owning, and what an
|
|
existing user has to do.
|
|
|
|
## What
|
|
|
|
- Voice Mode ships as [`kdlbs/kandev-plugin-voice`](https://github.com/kdlbs/kandev-plugin-voice),
|
|
installed like any other plugin. Core contains no voice code.
|
|
- The plugin reaches behaviour parity with the removed feature: the same three engines (browser
|
|
speech, in-browser Whisper, server-side Whisper), insertion at the current selection with the same
|
|
word-boundary spacing rule, optional auto-send, hold and toggle activation with the coarse-pointer
|
|
override, cancellation on task/session change, the same `Cmd/Ctrl+Shift+M` shortcut, and the same
|
|
four composers on desktop and phone: task chat, Quick Chat, task creation, new-session creation.
|
|
- The plugin uses only published host contracts: the composer action slots, `host.storage` for
|
|
per-user preferences, the owner-scoped `plugin-settings` slot for its settings form,
|
|
`registerKeybinding` for the shortcut, the `/api/plugins/{id}/ui/*` asset route for its Whisper worker, and one authenticated
|
|
webhook for the server relay. It adds no core-specific coupling.
|
|
- Core keeps every generic host API the extraction needed. None of them mention voice.
|
|
|
|
### Removed from core
|
|
|
|
| Removed | Replacement |
|
|
|---|---|
|
|
| `POST /api/v1/transcribe` (unauthenticated) | the plugin's `transcribe` webhook, which requires a signed-in user |
|
|
| `voice.openAIApiKey` / `KANDEV_VOICE_OPENAI_API_KEY` | the plugin's own **OpenAI API key** setting |
|
|
| `userSettings.voiceMode` (backend model, DTO, store, boot payload, frontend types) | the plugin's per-user `host.storage` entry |
|
|
| The Voice Mode section of **Settings > Task Behavior** | **Settings > Plugins > Voice Mode** |
|
|
| The `VOICE_INPUT_TOGGLE` core shortcut | the plugin keybinding, listed and rebindable in the same **Settings > Keyboard shortcuts** page |
|
|
| `@huggingface/transformers` in `apps/web` | bundled inside the plugin's Whisper worker |
|
|
|
|
### Migration
|
|
|
|
Both migration steps are manual and deliberate; neither is silent.
|
|
|
|
- **The OpenAI key does not move.** An operator who set `KANDEV_VOICE_OPENAI_API_KEY` re-enters the
|
|
key in the plugin's settings form. Core never had a way to write into a plugin's secret storage
|
|
and should not gain one for a single plugin.
|
|
- **Per-user preferences do not move.** Everyone's engine, language, model, activation mode and
|
|
auto-send return to the plugin's defaults, which match the previous core defaults except that the
|
|
engine list now hides server transcription until an operator saves a key. Copying them across
|
|
would require core to write into one named plugin's per-user storage, which is exactly the
|
|
coupling the extraction exists to remove.
|
|
- A stale `voice_mode` object left inside an existing `users.settings` row is inert. Nothing reads
|
|
it and nothing needs to delete it.
|
|
|
|
## Scenarios
|
|
|
|
- **GIVEN** kandev with no plugins installed, **WHEN** a user opens task chat, **THEN** no
|
|
microphone control appears and nothing in Settings mentions voice.
|
|
- **GIVEN** the Voice plugin installed and enabled, **WHEN** a user dictates into task chat, Quick
|
|
Chat, task creation or new-session creation on desktop or a phone, **THEN** the transcript is
|
|
inserted at the caret and the native submit path owns sending it.
|
|
- **GIVEN** no operator OpenAI key, **WHEN** a user opens the plugin's settings, **THEN** server
|
|
transcription is shown as unavailable and the two browser engines still work.
|
|
- **GIVEN** an operator key saved in the plugin, **WHEN** a signed-in user dictates with the server
|
|
engine, **THEN** the audio reaches OpenAI through the plugin's authenticated webhook and the key
|
|
never reaches the browser.
|
|
- **GIVEN** an anonymous caller on an install with authentication enabled, **WHEN** it posts to the
|
|
transcription webhook, **THEN** the request fails before the plugin runs and cannot spend the
|
|
operator's budget.
|
|
- **GIVEN** a user who had Voice Mode configured before upgrading, **WHEN** they install the plugin,
|
|
**THEN** dictation works at the plugin's defaults and their previous engine and language choices
|
|
are not carried over.
|
|
|
|
## Out Of Scope
|
|
|
|
- Publishing the plugin to the official marketplace catalog.
|
|
- Localizing the plugin. Kandev has no host i18n API for plugins yet; the plugin keeps its copy in
|
|
one module so a catalog can be added once that API exists.
|
|
- Migrating operator or per-user configuration automatically, for the reasons above.
|