Releases: GooDAnDReaDY/dsh-voice
Release list
dsh-voice 0.4.1
The settings card sometimes opened blank — chains with no rows, empty numbers, empty paths — and stayed that way until the page was reloaded. The fields were editable, so it looked like a working form with nothing in it.
Why
Two things outside this plugin had to line up.
The Host answers HTTP about 1.4 seconds after start, while plugin registration keeps going for roughly fifteen more. A page loaded inside that window reads settings.describe and gets a view without the namespaces that have not registered yet.
The browser's settings mirror then never re-reads it. It refreshes on exactly two signals — a document commit and a (re)connect — and a namespace registering is neither: settings.register adds the registration and does not emit settings/document-updated, which only a value change or a commit does.
In that state the bound scope reports status: "unavailable" with no value, while writable still comes from the document and stays true. The card read the value, ignored the status, and seeded its draft from the first snapshot that arrived — so an empty one was frozen for good.
What changed
The card reads the status now. It seeds the draft only from a ready snapshot, renders an honest line instead of a fake empty form while it is not ready, and asks the mirror to re-read (once a second, at most fifteen times) for as long as the namespace is missing. The mirror is shared, so that also refreshes every other settings surface.
Verified by reproducing the state rather than waiting for it: with registration disabled in the installed host copy, the old card rendered the blank editable form and the new one renders the message and no fields; once the namespace appeared the card seeded itself without a page reload.
The real fix belongs upstream: either open the port after registration finishes, or invalidate the mirror when a namespace registers.
Install
dsh plugin --profile web add @goodandready/dsh-voicedsh-voice 0.4.0
Two things that only make sense together: the plugin could not be configured from its own settings card at all, so a new provider would have been unreachable.
Your own recognition providers
Any OpenAI-compatible API can now be declared in the settings and used in both fallback chains next to the built-in four. Two templates, because those APIs disagree on how audio is sent:
| Template | Endpoint | Request | Transcript read from |
|---|---|---|---|
openai-transcriptions |
{baseURL}/audio/transcriptions |
multipart: file, model, language | text |
openai-chat-audio |
{baseURL}/chat/completions |
JSON with input_audio: base64 and format |
choices[0].message.content |
OpenRouter has no /audio/transcriptions endpoint at all and needs the chat template:
- id: dsh-voice
config:
customProviders:
- key: openrouter
template: openai-chat-audio
baseURL: https://openrouter.ai/api/v1
model: google/gemini-2.5-flash
keyEnv: OPENROUTER_API_KEY
message:
chain:
- provider: openrouter
- provider: local-whisperThe chat template accepts WAV and MP3 only, while the browser records webm/opus, so the audio goes through the same ffmpeg conversion the local whisper provider already used — ffmpeg is required for openai-chat-audio. A custom name cannot shadow a built-in one: a typo would otherwise silently replace a working provider in someone's chain.
Fixed: the settings card was blank and read-only
The host half never called settings.register, so the namespace the card binds to was undeclared: the snapshot came back empty with writable: false and the card rendered grey fields with nothing to save. Configuration is now read on every call, so an edit applies to the next request instead of after a restart.
Install
dsh plugin --profile web add @goodandready/dsh-voicedsh-voice 0.3.0
First public release.
Two voice input modes for the DeepSeek Harness Web GUI:
- Dictation — speech is cut into phrases on silence and each phrase is transcribed on its own, so text lands in the composer as you talk. Sending stays manual.
- Voice message — one recording, transcribed and sent to the agent after a cancel window.
Each mode has its own provider fallback chain (Deepgram, Groq, HuggingFace, local whisper.cpp), configurable from a dedicated settings page along with per-provider models, language, pause threshold and cancel window.
Also registers the transcribe_audio tool for the agent.
API keys never reach the browser: audio is posted to the plugin route and the host talks to the providers through the DSH credentials service.
Requires ffmpeg for the local whisper provider, which accepts WAV only.
Install
dsh plugin --profile web add @goodandready/dsh-voice