216 lines
12 KiB
Markdown
216 lines
12 KiB
Markdown
# 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](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](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?`, `backend?`, `confirm?`, `downloadDir?`, `after?`, `before?`). |
|
|
| `count_media_messages` | Discord search total for a channel or date range (slopbot guild channels only). |
|
|
| `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?`, `source?`, `scrapeId?`, `channelId?`, `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
|
|
|
|
```sh
|
|
# 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=1` and `VESKSLOP_TOKEN_FILE` (the daemon token file).
|
|
- The default command is `node_modules/.bin/electron .` from the repo root. `VESKSLOP_CORE_CMD`
|
|
replaces it. `VESKSLOP_CORE_ARGS` replaces the default flags.
|
|
- On Linux without a `DISPLAY`, the daemon starts Xvfb on the first free display from `:99` and
|
|
points the client at it. An existing `DISPLAY` wins. `VESKSLOP_XVFB=0` disables 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 `external` and 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.ts` and is shared by the terminal tool and the web UI.
|
|
See [auth.md](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.
|
|
|
|
```json
|
|
{ "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_PREVIEW_MAX_BYTES` | `5242880` | Preview size cap (5 MiB). Larger previews are skipped. |
|
|
| `VESKSLOP_FFPROBE` | `ffprobe` | Probe binary used to fill missing dimensions after a download. |
|
|
| `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[|Name[|gender[|language]]]`, comma separated. |
|
|
| `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
|
|
|
|
1. Add the action to the core bridge. See [core-bridge.md](core-bridge.md).
|
|
2. Add a `server.registerTool` call in `poc/src/control-bridge.ts`.
|
|
3. Wrap outbound calls in `paced`.
|
|
4. Add a unit test if the tool has local logic.
|
|
5. Run `pnpm check` in `poc/`.
|
|
|
|
## 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](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](auth.md).
|