12 KiB
Daemon
The daemon runs outside Vesktop. It holds all logic. It talks to the core bridge and exposes MCP.
Files
| File | Role | Status |
|---|---|---|
poc/src/control-bridge.ts |
MCP server, tool list, health endpoint, core reconnect | Implemented |
poc/src/core-process.ts |
Start, stop, and restart the Vesktop core process | Implemented |
poc/src/qr-session.ts |
Remote-auth QR login session (shared with the web UI) | Implemented |
poc/src/auth-service.ts |
Auth state for the web UI | Implemented |
poc/src/puppet.ts |
Trust list, command handling, trigger log, response policy | Implemented |
poc/src/communication.ts |
Pure log, parser, and context logic | Implemented |
poc/src/message-cache.ts |
Channel message cache in a tmp folder | Implemented |
poc/src/action-log.ts |
Persistent JSONL action log with rotation | Implemented |
poc/src/pacing.ts |
Paced send queue and read queries | Implemented |
poc/src/webui.ts |
Web UI listener and JSON API | First draft |
poc/public/ |
Static web UI (HTML, CSS, JS) | First draft |
poc/src/core-client.ts |
Client actions over the core bridge | Implemented (primary transport) |
poc/src/cdp-client.ts |
Chrome DevTools Protocol client | Development fallback |
poc/src/qr-login.ts |
Terminal QR login | Implemented and verified |
poc/src/test-communication.ts |
Unit tests for the pure logic | Implemented |
poc/src/test-cache.ts |
Unit tests for the message cache | Implemented |
poc/src/mock-core.ts |
Mock core bridge for daemon tests | Implemented |
poc/src/test-trigger.ts |
Inject a trigger event into the mock core | Implemented |
poc/src/cdp-probe.ts |
CDP feasibility probe | Development tool |
poc/src/cdp-dispatch.ts |
Run one CDP expression in the client | Development tool |
Transport
The primary transport is the validated core bridge. The daemon connects to
vesktop-puppet.sock. All actions go through the validated capability layer. See
core-bridge.md.
The CDP transport is a development fallback. It needs no Vesktop patch. It evaluates raw code in the page, so it has no core-side guardrails. Use it for debugging only.
Pacing
The daemon paces all outbound sends. A send is message.send or dm.send.
- The first send of a burst waits 30 to 200 ms.
- Later sends in the same burst wait 500 to 2800 ms.
- A burst ends after a random 5 to 20 seconds of quiet. The next send is a fast first action.
Read queries are serialized with a small 100 to 300 ms gap. Reads do not reset the send burst. Identical concurrent reads are coalesced. The daemon prefers cached state over queries.
The core adds a second, hard rate limit. See core-bridge.md.
MCP tools
The daemon exposes MCP over Streamable HTTP. The default address is http://127.0.0.1:8788/mcp.
| Tool | Purpose |
|---|---|
get_state |
List the user, the guilds, and the channels. |
send_message |
Send a message to a channel. |
read_messages |
Read cached messages from a channel. |
send_dm |
Send a DM to a user. |
get_voice_state |
Which voice channel a user is in and who else is there. |
get_voice_overview |
Occupied voice channels in a guild and their members. |
get_user_volume |
Per-user playback volume and the client output volume. |
set_user_volume |
Set a user's playback volume (0 to 200). |
voice_join |
Join a voice channel, muted by default. |
voice_leave |
Leave the current voice channel. |
voice_move |
Move to another voice channel. |
get_audio_status |
Audio engine, TTS, queue, ducking, and capture state. |
speak |
Queue text to speech into the virtual microphone (text, provider?, voice?, speed?, pitch?). |
start_tts_engine |
Warm up the TTS engine so the first line is fast (engine?). |
stop_audio |
Stop speech and clear the queue. |
start_capture / stop_capture |
Record or stop recording the client output sink. |
list_audio_devices |
The client's input and output audio devices. |
set_audio_devices |
Set the client's input and output devices. |
get_transcript |
Read captured call segments with speaker, WAV path, and text. |
search_users |
Search the client user cache by username, global name, or nickname. |
list_dms |
List cached DM and group DM channels with recipients. |
get_message_log |
Read the trigger log. |
get_action_log |
Read the persistent action log. |
scrape_media |
Start a media scrape of a channel (channelId, guildId?, mode?, confirm?). |
list_media_scrapes |
Media scrapes with state, pages, messages, and counts. |
get_media_scrape |
One scrape by ID. |
stop_media_scrape |
Stop a scrape; it pauses after the current page. |
get_media_storage |
Usage against the limit, per-kind sizes, and the scrape list. |
query_media |
Media items across scrapes (kind?, scrapeId?, limit?, offset?). |
get_media_item |
One media item by scrapeId and itemId. |
delete_media_scrape |
Delete a scrape and its files. Irreversible. |
run_commands |
Run several read-only tools in one call, in order. |
get_context |
Assemble conversation context for a channel. Fetches new messages by default. |
get_puppet_config |
Read the response policy and context settings. |
set_puppet_config |
Change the response policy and context settings. |
whitelist_list |
List the owner and the trusted users. |
whitelist_add |
Add users to the trust list. |
whitelist_remove |
Remove users from the trust list. |
Run commands
# Start the daemon. It connects to the core bridge and starts the web UI.
pnpm control:bridge
# Start the daemon with a different web UI port.
WEBUI_PORT=18889 pnpm control:bridge
# Start a mock core for tests without Electron.
VESKSLOP_SOCKET=/tmp/vesktop-puppet-mock.sock pnpm control:mock
# Inject a trigger event into the mock core.
VESKSLOP_SOCKET=/tmp/vesktop-puppet-mock.sock pnpm test:trigger
# Send one test DM. The only permitted live target is zzib.
pnpm poc:dm
# Run the pure-logic unit tests.
pnpm test:comm
pnpm test:cache
# Development tools for the CDP fallback.
pnpm cdp:probe
CDP_EXPRESSION='...' pnpm cdp:dispatch
Core process
The daemon can start, stop, and restart the Vesktop client (poc/src/core-process.ts). The web UI
Core tab and the /api/core/* routes use it.
- The spawn sets
VESKSLOP_HEADLESS=1andVESKSLOP_TOKEN_FILE(the daemon token file). - The default command is
node_modules/.bin/electron .from the repo root.VESKSLOP_CORE_CMDreplaces it.VESKSLOP_CORE_ARGSreplaces the default flags. - On Linux without a
DISPLAY, the daemon starts Xvfb on the first free display from:99and points the client at it. An existingDISPLAYwins.VESKSLOP_XVFB=0disables auto-start. - The status holds the last exit code and signal, and the last 20 output lines. The Core tab shows both, so a failed start explains itself.
- The daemon stops a client that it started, and any Xvfb it started, when the daemon exits. An
externally started client is reported as
externaland is not managed. - When the core bridge drops, the daemon retries every 2 s. A restart moves the bridge connection to the new client. The in-memory trigger log resets; the state file and the action log survive.
- QR login lives in
poc/src/qr-session.tsand is shared by the terminal tool and the web UI. See auth.md.
Verified live on 2026-09-13: the web UI started the client under auto-Xvfb, the bridge connected,
state.get returned the real account and guilds, and stop exited with code 0.
Health endpoint
GET /health returns the core connection state, the socket path, the owner ID, and the query count.
{ "ok": true, "coreConnected": true, "socketPath": "/run/user/1000/vesktop-puppet.sock", "ownerId": "177117296140091393", "queries": 4 }
Settings and paths
| Setting | Default | Purpose |
|---|---|---|
VESKSLOP_SOCKET |
$XDG_RUNTIME_DIR/vesktop-puppet.sock |
Core bridge socket |
VESKSLOP_REQUEST_TIMEOUT_MS |
30000 |
How long a core request waits before it fails. |
VESKSLOP_STATE_FILE |
poc/.veskslop-state.json |
Owner, trust list, and config |
VESKSLOP_ACTION_LOG |
<state dir>/actions.jsonl |
Persistent action log |
VESKSLOP_ACTION_LOG_MAX_BYTES |
5 MiB | Action log rotation limit |
VESKSLOP_CACHE_DIR |
<tmp>/veskslop-cache |
Context cache folder |
VESKSLOP_CACHE_MAX_BYTES |
50 MiB | Context cache size limit |
MCP_HOST / MCP_PORT |
127.0.0.1 / 8788 |
MCP listen address |
WEBUI_HOST / WEBUI_PORT |
0.0.0.0 / 8789 |
Web UI listen address. No authentication. |
WEBUI_TLS / WEBUI_TLS_KEY / WEBUI_TLS_CERT |
1 / unset |
Self-signed HTTPS, or an existing certificate. |
VESKSLOP_CORE_CMD |
unset | Full command that starts the client. Overrides the default. |
VESKSLOP_CORE_ARGS |
--no-sandbox --disable-gpu --disable-dev-shm-usage |
Flags for the default Electron command. |
VESKSLOP_XVFB |
unset | Set to 0 to disable automatic Xvfb start on Linux. |
VESKSLOP_AUDIO_AUTOCONNECT |
1 |
Point the client at the virtual devices on connect. |
VESKSLOP_AUDIO_MANAGE_SERVER |
1 |
Start pulseaudio when pactl cannot reach a server. |
VESKSLOP_AUDIO_SINK / _SOURCE / _CAPTURE_SINK |
veskslop_tts / veskslop_mic / veskslop_capture |
Virtual device names. |
VESKSLOP_AUDIO_CLIENT_MATCH |
vesktop,electron,chromium,discord |
Stream name patterns moved into the capture sink. |
VESKSLOP_MEDIA_DB_DIR |
<repo>/media-scrape/db |
Media archive root: per-scrape databases, downloads, previews. |
VESKSLOP_MEDIA_MAX_FILE_BYTES |
52428800 |
Download size cap (50 MiB). Larger files are skipped. |
VESKSLOP_MEDIA_DOWNLOAD_MIN_MS / _MAX_MS |
1000 / 3000 |
Gap between downloaded files. |
VESKSLOP_AUDIO_DUCK |
30 |
Sink volume percent while others speak. |
VESKSLOP_TTS_LEAD_MS |
150 |
Lead-in silence before each TTS line (0 disables, max 2000). |
VESKSLOP_FFPLAY / VESKSLOP_FFMPEG |
ffplay / ffmpeg |
Playback and capture binaries. |
VESKSLOP_TTS_CMD |
unset | Local TTS command with {out} and {text_file} (also {voice}, {speed}, {pitch}). |
VESKSLOP_TTS_NAME |
custom engine |
Display name for the command engine in the web UI. |
VESKSLOP_TTS_API_URL / _API_KEY / _PROVIDER |
unset | HTTP TTS provider and bearer token. |
VESKSLOP_TTS_VOICES |
unset | Voice list for a custom command: `id[ |
VESKSLOP_TTS_SPEED / _PITCH |
unset | Exported to the command only when a value is set. |
VESKSLOP_TTS_EXPRESSION |
unset | Flextalk Expression profile value (0-100), exported when set. |
VESKSLOP_TRANSCRIPT |
<state dir>/transcripts.jsonl |
Transcript store path. |
VESKSLOP_PREFERENCES |
<state dir>/preferences.json |
Web UI preferences (visible guilds, TTS). |
VESKSLOP_CAPTURE_AUTO |
0 |
Start capture when the bot joins a call. |
VESKSLOP_VAD_THRESHOLD_DB / _MIN_SPEECH_MS / _SILENCE_MS / _PRE_ROLL_MS / _MAX_SEGMENT_MS |
-35 / 120 / 600 / 300 / 30000 |
VAD tuning. |
VESKSLOP_STT_CMD / _API_URL / _API_KEY |
unset | Speech-to-text provider. |
VESKSLOP_VOSK_MODEL / _VOSK_DIR |
poc/models/vosk-model* |
Local Vosk model, auto-detected when missing. |
The daemon warns at startup when dist/ is older than the core sources. Rebuild with pnpm build
and restart the client, or new actions fail with UNKNOWN_ACTION.
How to add an MCP tool
- Add the action to the core bridge. See core-bridge.md.
- Add a
server.registerToolcall inpoc/src/control-bridge.ts. - Wrap outbound calls in
paced. - Add a unit test if the tool has local logic.
- Run
pnpm checkinpoc/.
Security notes
- The MCP server binds to localhost by default.
- The web UI binds to all interfaces by default and has no authentication. Use it over LAN or Tailscale only. Do not expose the port to the public internet. See webui.md.
- Remote access must go over LAN or Tailscale. Do not expose the daemon to the public internet.
- The daemon holds no Discord credentials. The client holds the session.
- The daemon token file is separate from the client session. See auth.md.