Introduction¶
DeepSeek Harness (DSH) is built on the philosophy that “everything is a plugin”; what an agent can do depends on which plugins are installed. However, a common inconvenience when running long tasks is that the agent’s outputs are all on the screen—if a task completes, gets stuck on a decision, or the agent thinks something is worth saying, you won’t know unless you stare at the window.
Conventional TTS solutions are one-way: you turn on the switch, and the agent’s output is read out; you are the passive receiver. dsh-voice-call inverts this relationship: the agent possesses its own voice and decides when to speak by calling you with offer_call; you hold the answering key—accept, decline, or say later—if you don’t answer, it will never play. Below is an introduction to its capabilities, installation steps, and usage.
What is this¶
dsh-voice-call is a DSH plugin maintained by PandaPolo, under the MIT license. It is a fork of Jesse-njx/dsh-voice, adding a call domain, crispasr synthesis backend, and local playback, while fixing plugin events and background task restrictions for rc.6.
Two design points to clarify first:
- Local-first, fully offline. Speech synthesis runs on the local CrispASR + Qwen3-TTS GGUF engine, resulting in ordinary audio files in the
~/.dsh/voice/directory. - No audio behavior runs automatically. To produce sound, either the model calls a tool itself, or you answer an incoming call.
Core Features¶
offer_call: Calls initiated by the agent¶
The tool signature is offer_call({ text, voice? }). After calling, the call starts ringing. The human answers: if accepted, it synthesizes and plays in the background; if declined or “say later” is chosen, the tool returns the decision as a result to the agent, letting it decide how to proceed next.
Whether to speak or not, what to say, and which voice to use is decided by the agent; whether sound actually comes out of the speaker is decided by you.
Incoming Call Card (v0.2)¶
With callMode: card configured, incoming calls ring as floating layer cards: a double-ring pulse animation, the caller’s identity, and a preview of what they want to say. Missed calls are automatically recorded as “missed” and won’t hang indefinitely. When there is no web client connection, it automatically falls back to a popup query.
speak: Direct Speech¶
speak({ text, voice?, rate? }) reads directly via a background task with real local playback: Windows uses PowerShell SoundPlayer, macOS uses afplay, and Linux uses aplay. The speaking rate is controlled by rate.
transcribe: Speech to Text¶
transcribe({ source, to? }) converts speech to text, entering the conversation as a user message. The backend is optional: whisper-local / openai / macOS native. The to parameter allows cross-session delivery of the transcription result, requiring the dsh-crosstalk plugin.
Limitation: The recording entry point transcribe({ record }) is only supported on macOS; calls on Windows and Linux will explicitly error out.
/voice Command¶
Entering /voice within a session allows you to query status, toggle the read-reply switch with on|off, and make it say a sentence immediately with speak <text>. Read replies are disabled by default.
Built-in Voices and Synthesis Backends¶
The crispasr backend includes 9 Qwen3-TTS CustomVoice voices, 2 of which are Chinese dialects: aiden, dylan (Beijing dialect), eric (Sichuan dialect), ono_anna, ryan, serena, sohee, uncle_fu, vivian.
There is more than one synthesis backend: edge-tts only synthesizes without playing; fake is used for debugging when there is no model.
Installation and Activation¶
Prerequisites:
- Node.js ≥ 20 (required for plugin execution; testing this project requires 22.18+)
- pnpm 9+
- dsh CLI:
@deepseek-ai/dsh0.1.2-rc.1 - CrispASR ≥ 0.8.28 and Qwen3-TTS GGUF model—without this engine and model, there are no local synthesis voices.
- Available LLM API credentials configured (the agent itself depends on this).
- Install the plugin:
dsh plugin --profile web add dsh-voice-call
The plugin is published to npm (dsh-voice-call@0.2.0); the command above pulls it from npm.
- Write configuration. In the
cordis.patch.ymlof the profile (default web) directory, update thedsh-voice-callentry by id. Note: The same id can only appear once; repeated insertions will cause startup crashes.
Windows Example (from plugin README, paths should be adjusted to actual directories):
- id: dsh-voice-call
config:
tts:
backend: crispasr # Local neural TTS engine
voice: dylan # Default voice, one of the 9 built-in voices
crispasr:
bin: D:\crispasr\crispasr.exe
model: D:\crispasr\models\qwen3-tts-12hz-0.6b-customvoice-q8_0.gguf
codec: D:\crispasr\models\qwen3-tts-tokenizer-12hz-q8_0.gguf
callMode: card
readReplies: false # Read reply switch
durableEvents: false # Must remain false on rc.6
audioDir: ~/.dsh/voice
bin, model, and codec under tts.crispasr are the absolute paths to the engine executable and the two GGUF models, respectively; durableEvents controls the session event log, disabled by default, and must remain false on rc.6, otherwise the session history may fail to load; audioDir is the audio output directory, defaulting to ~/.dsh/voice.
- Restart dsh web. With the steps above, opening a session allows you to verify it.
Typical Usage¶
- Check status. Enter in the session:
/voice
It should display the stt / tts / readReplies / audioDir status.
-
Read test. Ask the agent: “Use the speak tool to say ‘hello’.” Hearing the sound means success.
-
Call test. Tell the agent: “You have the offer_call tool—call me if there is anything worth saying.” After the call rings, click accept, and the sound plays from the speaker.
-
Troubleshoot configuration. Execute:
dsh --profile web --dump-config
View the complete configuration tree after synthesis to confirm if the plugin’s configuration is actually taking effect.
Suitable Scenarios and Notes¶
Suitable for:
- People running long tasks who want the agent to proactively call them when it’s worth speaking.
- People who want a local, offline TTS pipeline where the output is an ordinary audio file.
- People on macOS who want to send voice messages to the agent via voice—transcription results will enter the session as user messages.
Notes:
- The plugin runs with the permissions of the current dsh process. Before installing any third-party plugins, it is recommended to check the source code and license; this project is MIT.
- On rc.6,
durableEventsmust remain false. - The same id can only appear once in
cordis.patch.yml; repeated insertions will cause startup crashes. - Without CrispASR + Qwen3-TTS GGUF models, there are no local synthesis voices; you can use the fake backend for debugging before you have a model.
- Recording (
transcribe({ record })) is only supported on macOS.
Summary¶
dsh-voice-call turns “agent speech” into an interaction where both parties have control: the agent has the dialing right, deciding when to call and what to say; you have the answering right—if you don’t answer, there is no sound. Combined with local synthesis, offline availability, and output as ordinary audio files, the entire chain stays on your machine. One command to install, configuration centralized in cordis.patch.yml, and you can get it running following the steps above.
- GitHub: https://github.com/PandaPolo/dsh-voice-call
- Community Plugin Directory Entry: https://www.skillhub.cn/plugins/PandaPolo/dsh-voice-call