Rework core integration: validated capability layer, docs, and session state
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Veskslop documentation index
|
||||
|
||||
This file lists the project documents.
|
||||
|
||||
## Design documents
|
||||
|
||||
| Document | Scope |
|
||||
| --- | --- |
|
||||
| [architecture.md](architecture.md) | System parts, boundaries, and data flow |
|
||||
| [core-bridge.md](core-bridge.md) | The Vesktop-side capability layer |
|
||||
| [daemon.md](daemon.md) | The external MCP daemon |
|
||||
| [communication.md](communication.md) | Trust model, commands, and the trigger log |
|
||||
| [auth.md](auth.md) | QR login and token persistence |
|
||||
| [testing.md](testing.md) | Tests and the live test setup |
|
||||
| [decisions.md](decisions.md) | Design decisions and their reasons |
|
||||
|
||||
## Documents outside `docs/`
|
||||
|
||||
| File | Scope |
|
||||
| --- | --- |
|
||||
| [../AGENTS.md](../AGENTS.md) | Agent entry point and guardrails |
|
||||
| [../poc/README.md](../poc/README.md) | Spike and daemon run commands |
|
||||
| [../README.md](../README.md) | Project introduction |
|
||||
@@ -0,0 +1,74 @@
|
||||
# Architecture
|
||||
|
||||
## Purpose
|
||||
|
||||
Veskslop controls one or more Discord user accounts without a visible client window. It uses the
|
||||
real Vesktop client, so the traffic comes from the official client.
|
||||
|
||||
## Parts
|
||||
|
||||
The system has four parts:
|
||||
|
||||
1. **Vesktop core** — the real Vesktop client. It runs headless. It exposes a small capability layer.
|
||||
2. **Core bridge** — the capability layer. It accepts validated actions and pushes events.
|
||||
3. **Veskslop daemon** — the external process. It holds all logic. It exposes MCP.
|
||||
4. **MCP clients** — agents and tools. They call the daemon.
|
||||
|
||||
```
|
||||
MCP clients (agents)
|
||||
│ Streamable HTTP (MCP)
|
||||
▼
|
||||
Veskslop daemon ──── JSON lines over a Unix socket ────▶ Vesktop core
|
||||
│ │
|
||||
│ QR login, pacing, trigger log, │ validated actions + events
|
||||
│ trust list, MCP tools ▼
|
||||
│ Discord (official client traffic)
|
||||
▼
|
||||
Local state files (.token, .veskslop-state.json)
|
||||
```
|
||||
|
||||
## Boundary rules
|
||||
|
||||
The boundary is narrow on purpose.
|
||||
|
||||
- The core exposes a fixed action list. The list has no general "run this code" action.
|
||||
- The core validates every input. Bad input returns a structured error. The client does not crash.
|
||||
- The core rate limits all outbound actions. A bug in the daemon cannot cause a send flood.
|
||||
- The daemon holds the logic: triggers, trust list, command parsing, the trigger log, and pacing.
|
||||
- The daemon holds no Discord protocol code.
|
||||
|
||||
These rules keep upstream rebases small. Logic changes do not touch the core. The core changes only
|
||||
when the action list changes.
|
||||
|
||||
## Components and status
|
||||
|
||||
| Component | Location | Status |
|
||||
| --- | --- | --- |
|
||||
| Action protocol | `src/shared/puppetProtocol.ts` | Implemented |
|
||||
| Validated actions | `src/renderer/puppet/actions.ts` | Implemented |
|
||||
| Event push | `src/renderer/puppet/events.ts` | Pending |
|
||||
| Main bridge | `src/main/puppetBridge.ts` | Pending |
|
||||
| Core hooks | `src/main/main.ts`, `src/renderer/ipcCommands.ts`, `src/shared/IpcEvents.ts`, `src/preload/VesktopNative.ts` | Pending |
|
||||
| Daemon (CDP transport) | `poc/src/` | Implemented and verified live |
|
||||
| Daemon (core-bridge transport) | `poc/src/core-client.ts` | Pending |
|
||||
| QR login | `poc/src/qr-login.ts` | Implemented and verified live |
|
||||
| Pacing | `poc/src/pacing.ts` | Implemented |
|
||||
| Trigger log and trust list | `poc/src/communication.ts`, `poc/src/puppet.ts` | Implemented on the CDP transport |
|
||||
| Voice, audio, video | Not started | Not started |
|
||||
|
||||
## Data flow
|
||||
|
||||
1. An agent calls an MCP tool on the daemon.
|
||||
2. The daemon applies pacing and policy.
|
||||
3. The daemon sends one JSON-line action to the core bridge.
|
||||
4. The bridge forwards the action to the renderer.
|
||||
5. The renderer validates the action and calls the official client.
|
||||
6. The renderer returns a result or a structured error.
|
||||
7. The bridge returns the response to the daemon.
|
||||
|
||||
For inbound messages, the flow goes the other way:
|
||||
|
||||
1. The client receives a gateway event.
|
||||
2. The renderer normalizes the event.
|
||||
3. The bridge pushes the event to subscribed daemon connections.
|
||||
4. The daemon applies the trigger rules and updates the log.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Authentication
|
||||
|
||||
This document describes account login and token persistence.
|
||||
|
||||
## QR login
|
||||
|
||||
`poc/src/qr-login.ts` logs in to an account with a QR code. The tool connects to the Discord
|
||||
remote-auth gateway. It shows the QR code in the terminal. The user scans the code with the Discord
|
||||
mobile app. The tool then writes the account token to a file.
|
||||
|
||||
```sh
|
||||
pnpm poc:qr
|
||||
TOKEN_FILE=/path/to/token pnpm poc:qr
|
||||
```
|
||||
|
||||
The tool prints the QR code, the approval result, and the token file path. If Discord asks for a
|
||||
captcha, the tool shows the challenge and reads a key from `HCAPTCHA_KEY` or from the terminal.
|
||||
|
||||
The flow was verified end-to-end on 2026-09-11.
|
||||
|
||||
## Token file
|
||||
|
||||
- Default path: `poc/.token`, relative to the `poc/` directory.
|
||||
- Override: `TOKEN_FILE`.
|
||||
- File mode: `600`.
|
||||
- The file is gitignored.
|
||||
- The token belongs to the puppet account. zzib is only the DM test target.
|
||||
|
||||
## Client session persistence
|
||||
|
||||
The client session is separate from the token file. The session lives in the Electron profile.
|
||||
|
||||
The design has three parts:
|
||||
|
||||
1. **Pinned profile directory.** `DATA_DIR` is `$VENCORD_USER_DATA_DIR`, else `$VESKSLOP_DATA_DIR`,
|
||||
else `<appData>/veskslop`. The path does not depend on the app name. A rename or a version update
|
||||
does not move the profile.
|
||||
2. **Token seeding.** On startup, the preload reads the token file through a synchronous IPC call.
|
||||
The preload writes `localStorage.token` before the client boots. The seed happens only when no
|
||||
session exists. `VESKSLOP_FORCE_TOKEN=1` overrides an existing session.
|
||||
3. **Recovery.** If Discord invalidates the session, run `pnpm poc:qr` again. Then update the token
|
||||
file.
|
||||
|
||||
The client stores the session in `<DATA_DIR>/sessionData`. A version update keeps the directory.
|
||||
|
||||
## Devtools caution
|
||||
|
||||
Discord deletes the stored token when it thinks that devtools opened. Vesktop already patches this
|
||||
detector in `src/renderer/patches/devtoolsFixes.ts`. Keep this patch. Without it, random logouts
|
||||
can occur.
|
||||
|
||||
## Status
|
||||
|
||||
| Part | Status |
|
||||
| --- | --- |
|
||||
| QR login and token file | Implemented and verified |
|
||||
| Pinned profile directory | Implemented and verified. Currently out of the tree after the CDP pivot. |
|
||||
| Token seeding | Implemented and verified. Currently out of the tree after the CDP pivot. |
|
||||
| Headless first-launch skip | Implemented and verified. Currently out of the tree after the CDP pivot. |
|
||||
| Devtools patch | Upstream Vesktop code. Keep it. |
|
||||
|
||||
The three out-of-tree parts are small. Add them again with the core bridge, at the same time as the
|
||||
bridge hooks.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Communication
|
||||
|
||||
This document describes the trigger rules, the trust model, the commands, and the trigger log.
|
||||
|
||||
## Trigger rules
|
||||
|
||||
A message is a trigger when one of these conditions is true:
|
||||
|
||||
- The message is a direct message to the puppet. A direct message is a one-to-one DM, not a group.
|
||||
- The message mentions the puppet. This applies in guild channels and group DMs.
|
||||
|
||||
All other messages are ignored.
|
||||
|
||||
## Trust model
|
||||
|
||||
The puppet has one owner and a trust list.
|
||||
|
||||
- The owner is always trusted. The default owner is `177117296140091393` (zzib).
|
||||
- The trust list holds additional user IDs.
|
||||
- A trusted user gets the 100-entry log. An untrusted user gets the 30-entry log.
|
||||
|
||||
The owner controls the trust list. Commands from other users do not change the list.
|
||||
|
||||
## Commands
|
||||
|
||||
The owner sends commands from Discord. In a direct message, the text is the command. In a guild or
|
||||
a group DM, mention the puppet first.
|
||||
|
||||
| Command | Effect |
|
||||
| --- | --- |
|
||||
| `whitelist list` | Show the owner and the trusted users. |
|
||||
| `whitelist add @user` | Add one or more users to the trust list. |
|
||||
| `whitelist remove @user` | Remove one or more users from the trust list. |
|
||||
|
||||
Notes:
|
||||
|
||||
- The owner cannot be removed.
|
||||
- The command accepts mentions and raw user IDs.
|
||||
- The daemon replies with a confirmation. The reply uses the same pacing as all other sends.
|
||||
|
||||
## Trigger log
|
||||
|
||||
The log has two ring buffers.
|
||||
|
||||
| Buffer | Limit | Content |
|
||||
| --- | --- | --- |
|
||||
| Trusted | 100 entries | Triggers from the owner and trusted users |
|
||||
| Untrusted | 30 entries | Triggers from all other users |
|
||||
|
||||
One entry holds these fields:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `id` | The message ID |
|
||||
| `channelId` | The channel ID |
|
||||
| `guildId` | The guild ID, or absent for a DM |
|
||||
| `authorId` | The author user ID |
|
||||
| `authorName` | The author username |
|
||||
| `content` | The raw message content |
|
||||
| `timestamp` | The message timestamp |
|
||||
| `trusted` | True for a trusted author |
|
||||
| `command` | The parsed command text, when the message is a command |
|
||||
|
||||
## Event flow
|
||||
|
||||
1. The client receives a `MESSAGE_CREATE` gateway event.
|
||||
2. The core renderer normalizes the event.
|
||||
3. The core bridge pushes the event to the daemon.
|
||||
4. The daemon applies the trigger rules.
|
||||
5. The daemon adds a qualifying message to the log.
|
||||
6. The daemon runs a trusted command, when the message is a command.
|
||||
|
||||
## State file
|
||||
|
||||
The daemon stores the owner ID and the trust list in JSON.
|
||||
|
||||
- Default path: `poc/.veskslop-state.json`.
|
||||
- Override: `VESKSLOP_STATE_FILE`.
|
||||
- The trigger log is in memory only. The log resets at restart.
|
||||
|
||||
## How to add a trigger rule
|
||||
|
||||
1. Add the rule in `poc/src/puppet.ts`, in `handleEvent`.
|
||||
2. Keep the rule small and testable.
|
||||
3. Add a unit test in `poc/src/test-communication.ts` when the rule is pure logic.
|
||||
4. Run `pnpm test:comm`.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Core bridge
|
||||
|
||||
The core bridge is the only surface between the Vesktop core and the external daemon. This document
|
||||
describes the protocol, the validation rules, and the hooks into upstream files.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Role | Status |
|
||||
| --- | --- | --- |
|
||||
| `src/shared/puppetProtocol.ts` | Action names, error codes, limits, and shared types | Implemented |
|
||||
| `src/renderer/puppet/actions.ts` | Validated action handlers and the core rate limit | Implemented |
|
||||
| `src/renderer/puppet/events.ts` | Event normalization and push to main | Pending |
|
||||
| `src/renderer/puppet/index.ts` | Renderer initialization | Pending |
|
||||
| `src/main/puppetBridge.ts` | Unix socket server, request forwarding, event fan-out | Pending |
|
||||
|
||||
## Transport
|
||||
|
||||
The core bridge uses a Unix socket. On Linux the path is `$XDG_RUNTIME_DIR/vesktop-puppet.sock`.
|
||||
On Linux without `XDG_RUNTIME_DIR`, the path is `/tmp/vesktop-puppet.sock`. On Windows the path is
|
||||
the named pipe `\\.\pipe\vesktop-puppet.sock`.
|
||||
|
||||
The protocol is newline-delimited JSON. Each request gets one response line. An event subscriber
|
||||
gets event lines until the connection closes.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{ "id": 1, "action": "message.send", "params": { "channelId": "123", "content": "hello" } }
|
||||
```
|
||||
|
||||
Success response:
|
||||
|
||||
```json
|
||||
{ "id": 1, "ok": true, "result": { "channelId": "123", "sent": true } }
|
||||
```
|
||||
|
||||
Error response:
|
||||
|
||||
```json
|
||||
{ "id": 1, "ok": false, "error": { "code": "CONTENT_EMPTY", "message": "content is empty" } }
|
||||
```
|
||||
|
||||
Event line:
|
||||
|
||||
```json
|
||||
{ "type": "event", "event": { "type": "MESSAGE_CREATE", "channelId": "123" } }
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
| Action | Parameters | Result |
|
||||
| --- | --- | --- |
|
||||
| `ping` | none | `"pong"` |
|
||||
| `events.subscribe` | none | `{ "subscribed": true }`. The socket becomes an event subscriber. |
|
||||
| `state.get` | none | The current user, the guilds, and the channels. Cache-only. |
|
||||
| `message.send` | `channelId`, `content` | `{ channelId, sent: true }` |
|
||||
| `message.read` | `channelId`, `limit` | `{ cached, messages }`. Cache-only. |
|
||||
| `dm.send` | `userId`, `content` | `{ userId, channelId, sent: true }` |
|
||||
|
||||
## Validation rules
|
||||
|
||||
The core validates all input. The daemon cannot bypass these rules.
|
||||
|
||||
- A channel ID and a user ID must match the snowflake format. The format is 17 to 20 digits.
|
||||
- `content` must be a string. The content must not be empty after a trim.
|
||||
- `content` must not contain control characters.
|
||||
- `content` must not be longer than 2000 characters.
|
||||
- The target channel must exist. The channel type must accept text. The allowed types are
|
||||
`GUILD_TEXT`, `DM`, `GROUP_DM`, `GUILD_ANNOUNCEMENT`, `ANNOUNCEMENT_THREAD`, `PUBLIC_THREAD`,
|
||||
and `PRIVATE_THREAD`.
|
||||
- `limit` must be an integer from 1 to 100.
|
||||
- A request line must not be larger than 64 KiB.
|
||||
|
||||
## Core rate limit
|
||||
|
||||
The core rate limit is a safety net. It does not replace the daemon pacing.
|
||||
|
||||
- The minimum interval between two outbound actions is 500 ms.
|
||||
- The maximum is 30 outbound actions per minute.
|
||||
- The limit applies to `message.send` and `dm.send`.
|
||||
|
||||
## Error codes
|
||||
|
||||
| Code | Meaning |
|
||||
| --- | --- |
|
||||
| `INVALID_REQUEST` | The line is not valid JSON. |
|
||||
| `PAYLOAD_TOO_LARGE` | The request line is larger than 64 KiB. |
|
||||
| `UNKNOWN_ACTION` | The action name is not in the action list. |
|
||||
| `CORE_UNAVAILABLE` | The bridge cannot reach the renderer. |
|
||||
| `NOT_LOGGED_IN` | No user is logged in. |
|
||||
| `INVALID_SNOWFLAKE` | The ID is not a snowflake. |
|
||||
| `INVALID_PARAM` | A parameter has the wrong type or value. |
|
||||
| `CONTENT_EMPTY` | The content is empty. |
|
||||
| `CONTENT_TOO_LONG` | The content is longer than 2000 characters. |
|
||||
| `CHANNEL_NOT_FOUND` | The channel is not in the client cache. |
|
||||
| `NOT_SENDABLE` | The channel type cannot receive messages. |
|
||||
| `SEND_FAILED` | Discord rejected the message. |
|
||||
| `DM_UNAVAILABLE` | The client cannot create a DM channel. |
|
||||
| `RATE_LIMITED` | The core rate limit rejected the action. |
|
||||
| `INTERNAL` | An unexpected error occurred. The details go to the local log only. |
|
||||
|
||||
## Hooks into upstream files
|
||||
|
||||
The bridge uses a small number of hooks. Keep these changes minimal.
|
||||
|
||||
| File | Change |
|
||||
| --- | --- |
|
||||
| `src/main/main.ts` | Import the bridge. Call `initPuppetBridge()` after `app.whenReady()`. |
|
||||
| `src/shared/IpcEvents.ts` | Add `PUPPET_EVENT` and `IpcCommands.PUPPET_COMMAND`. |
|
||||
| `src/renderer/index.ts` | Import `./puppet`. |
|
||||
| `src/renderer/ipcCommands.ts` | Register `PUPPET_COMMAND` and call `handlePuppetAction`. |
|
||||
| `src/preload/VesktopNative.ts` | Add `puppet.emit(event)` for event push. |
|
||||
|
||||
## How to add an action
|
||||
|
||||
1. Add the action name to `PuppetAction` in `src/shared/puppetProtocol.ts`.
|
||||
2. Add a validation case in `src/renderer/puppet/actions.ts`.
|
||||
3. Add the action to the known-action set in `src/main/puppetBridge.ts`.
|
||||
4. Add a tool in the daemon (`poc/src/control-bridge.ts`).
|
||||
5. Add a unit test for the validation path.
|
||||
6. Run `pnpm testTypes`, `pnpm lint`, and `pnpm build`.
|
||||
|
||||
## Why this shape
|
||||
|
||||
The shape gives three properties:
|
||||
|
||||
- The daemon cannot run arbitrary code in the client.
|
||||
- A daemon bug cannot crash the client or send malformed data to Discord.
|
||||
- The core patch stays small, so an upstream rebase is easy.
|
||||
@@ -0,0 +1,93 @@
|
||||
# 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 | Implemented on CDP |
|
||||
| `poc/src/puppet.ts` | Trust list, command handling, trigger log | Implemented |
|
||||
| `poc/src/communication.ts` | Pure log and parser logic | Implemented |
|
||||
| `poc/src/pacing.ts` | Paced queue and query coalescing | Implemented |
|
||||
| `poc/src/cdp-client.ts` | Chrome DevTools Protocol client | Implemented (current transport) |
|
||||
| `poc/src/discord.ts` | Client actions over CDP | Implemented (current transport) |
|
||||
| `poc/src/core-client.ts` | Client actions over the core bridge | Pending (target transport) |
|
||||
| `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/cdp-probe.ts` | CDP feasibility probe | Implemented (dev tool) |
|
||||
|
||||
## Transport
|
||||
|
||||
The daemon supports two transports.
|
||||
|
||||
1. **CDP (current)** — the daemon starts Vesktop with `--remote-debugging-port=9222`. The daemon
|
||||
connects to the Discord page target. It evaluates the action code in the page and receives
|
||||
events through a CDP binding. This transport needs no Vesktop patch. The project used it to
|
||||
prove the concept and to test the client.
|
||||
2. **Core bridge (target)** — the daemon connects to `vesktop-puppet.sock`. All actions go through
|
||||
the validated capability layer. This transport gives the core-side guardrails.
|
||||
|
||||
The daemon logic is the same on both transports. Only the client class changes.
|
||||
|
||||
## Pacing
|
||||
|
||||
The daemon paces all commands. The policy is a random delay of 500 to 2800 ms. The queue
|
||||
serializes the commands. The queue coalesces identical concurrent reads. The daemon also 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_message_log` | Read the trigger log. |
|
||||
| `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 client on CDP port 9222.
|
||||
pnpm control:bridge
|
||||
|
||||
# Send one test DM. The only permitted live target is zzib.
|
||||
pnpm poc:dm
|
||||
|
||||
# Run the pure-logic unit tests.
|
||||
pnpm test:comm
|
||||
|
||||
# Probe CDP access and events.
|
||||
pnpm cdp:probe
|
||||
```
|
||||
|
||||
## Health endpoint
|
||||
|
||||
`GET /health` returns the CDP state, the owner ID, and the query count.
|
||||
|
||||
```json
|
||||
{ "ok": true, "cdpConnected": true, "ownerId": "177117296140091393", "queries": 4 }
|
||||
```
|
||||
|
||||
## 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.
|
||||
- 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).
|
||||
@@ -0,0 +1,81 @@
|
||||
# Decisions
|
||||
|
||||
This document records the main design decisions and their reasons.
|
||||
|
||||
## ADR-1: Use the real client
|
||||
|
||||
**Decision.** Run the real Vesktop client. Do not use a third-party selfbot protocol library.
|
||||
|
||||
**Reason.** The popular selfbot libraries are archived or deprecated. Stale protocol emulation is a
|
||||
ban risk. The real client sends official traffic.
|
||||
|
||||
## ADR-2: Validated capability layer
|
||||
|
||||
**Decision.** The core exposes a small, fixed action list. The core validates all input. The core
|
||||
returns structured errors. All logic lives in the external daemon.
|
||||
|
||||
**Reason.** The split gives three properties:
|
||||
|
||||
- The daemon cannot run arbitrary code in the client.
|
||||
- A daemon bug cannot crash the client or send malformed data to Discord.
|
||||
- Core patches stay small, so an upstream rebase is easy.
|
||||
|
||||
**Consequence.** A new capability needs a small core change. The action list is the contract.
|
||||
|
||||
## ADR-3: CDP is a fallback, not the primary transport
|
||||
|
||||
**Decision.** The project validated control over Chrome DevTools Protocol. The primary transport is
|
||||
the validated core bridge.
|
||||
|
||||
**Reason.** CDP needs no Vesktop patch. However, CDP evaluates raw code in the page. It bypasses the
|
||||
core-side validation and rate limits. The project keeps the probe as a development tool.
|
||||
|
||||
**Status.** The daemon uses CDP at this time. The core-bridge transport is pending.
|
||||
|
||||
## ADR-4: No bespoke client
|
||||
|
||||
**Decision.** Do not write a new Electron shell for Discord.
|
||||
|
||||
**Reason.** Vesktop maintains the desktop integration for Vencord, for example settings, themes,
|
||||
quick CSS, and the native plugin bridge. A bespoke shell must reimplement that surface. Vesktop
|
||||
already does this work.
|
||||
|
||||
**Consequence.** Keep the patch small and isolated. This approach is cheaper than a new shell.
|
||||
|
||||
## ADR-5: Two pacing layers
|
||||
|
||||
**Decision.** The daemon paces all commands with a random delay of 500 to 2800 ms. The core adds a
|
||||
hard limit of 500 ms between sends and 30 sends per minute.
|
||||
|
||||
**Reason.** The daemon policy looks human. The core limit is a safety net. A daemon bug cannot cause
|
||||
a send flood.
|
||||
|
||||
## ADR-6: Pin the profile directory
|
||||
|
||||
**Decision.** `DATA_DIR` is `$VENCORD_USER_DATA_DIR`, else `$VESKSLOP_DATA_DIR`, else
|
||||
`<appData>/veskslop`.
|
||||
|
||||
**Reason.** The Discord session lives in the Electron profile. An app rename or a version update
|
||||
must not move the profile. A stable path keeps the login.
|
||||
|
||||
## ADR-7: Separate token file
|
||||
|
||||
**Decision.** The QR tool writes the account token to a file. The client seeds the token at startup
|
||||
when no session exists.
|
||||
|
||||
**Reason.** The token file survives a profile reset. It also survives a change in the Discord
|
||||
storage format. The recovery path is one QR scan.
|
||||
|
||||
## ADR-8: Trigger log limits
|
||||
|
||||
**Decision.** Keep the last 100 triggers from trusted users and the last 30 triggers from untrusted
|
||||
users.
|
||||
|
||||
**Reason.** The limits keep memory small. The trusted limit is larger because those messages matter
|
||||
more.
|
||||
|
||||
## ADR-9: One live test target
|
||||
|
||||
**Decision.** The only permitted live-send target is user zzib (`177117296140091393`).
|
||||
|
||||
**Reason.** Tests must not send messages to other users. DMs to zzib are safe and permitted.
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Testing
|
||||
|
||||
This document describes the test setup, the run commands, and the known gaps.
|
||||
|
||||
## Unit tests
|
||||
|
||||
The pure daemon logic has unit tests. The tests cover the ring buffers, the mention helpers, the
|
||||
command parser, the trust check, and the pacing range.
|
||||
|
||||
```sh
|
||||
cd poc
|
||||
pnpm test:comm
|
||||
```
|
||||
|
||||
The file is `poc/src/test-communication.ts`. The tests do not need Discord or Vesktop.
|
||||
|
||||
## Type checks
|
||||
|
||||
```sh
|
||||
cd poc && pnpm check # daemon and spike code
|
||||
cd .. && pnpm testTypes # Vesktop core
|
||||
```
|
||||
|
||||
Both commands must pass before a change is complete.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
|
||||
The build writes the application to `dist/`.
|
||||
|
||||
## CDP probe
|
||||
|
||||
The probe checks the current CDP transport. Start the client with
|
||||
`--remote-debugging-port=9222` first.
|
||||
|
||||
```sh
|
||||
cd poc
|
||||
pnpm cdp:probe
|
||||
```
|
||||
|
||||
A good result shows the Vencord global, the Flux dispatcher, the current user, and one binding
|
||||
event.
|
||||
|
||||
## Live test setup
|
||||
|
||||
The container has no display and no PipeWire. Use a virtual display.
|
||||
|
||||
```sh
|
||||
# 1. Start a virtual display. On Linux, install Xvfb or extract it locally.
|
||||
Xvfb :99 -screen 0 1280x720x24 &
|
||||
|
||||
# 2. Start the stock client with a pinned profile and remote debugging.
|
||||
DISPLAY=:99 \
|
||||
VENCORD_USER_DATA_DIR=~/.config/veskslop \
|
||||
electron . --no-sandbox --disable-gpu --disable-dev-shm-usage --remote-debugging-port=9222
|
||||
|
||||
# 3. Start the daemon.
|
||||
cd poc && pnpm control:bridge
|
||||
|
||||
# 4. Check the health endpoint.
|
||||
curl http://127.0.0.1:8788/health
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `VENCORD_USER_DATA_DIR` pins the Electron profile. The profile holds the client session.
|
||||
- Write `{"firstLaunch": false}` to `<DATA_DIR>/state.json` before the first start. This step skips
|
||||
the first-launch window. The `VESKSLOP_HEADLESS=1` flag does the same job when it is in the tree.
|
||||
- The container needs `libasound.so.2` for Electron. Extract the library locally when the system
|
||||
package is not available.
|
||||
|
||||
## Sanctioned live target
|
||||
|
||||
The only permitted live-send target is user **zzib** (`177117296140091393`). Send a test message
|
||||
with this command:
|
||||
|
||||
```sh
|
||||
cd poc
|
||||
pnpm poc:dm
|
||||
```
|
||||
|
||||
## CDP live results
|
||||
|
||||
The CDP transport was verified on 2026-09-11:
|
||||
|
||||
- `get_state` returned the real account and 7 guilds.
|
||||
- `send_dm` delivered a real DM to zzib.
|
||||
- The client ran headless under Xvfb.
|
||||
- The token was seeded from the token file.
|
||||
|
||||
## Known gaps
|
||||
|
||||
| Gap | Effect |
|
||||
| --- | --- |
|
||||
| Mock core is not in the tree. | The daemon tests need the real client. |
|
||||
| The trigger event path needs an incoming message. | Use zzib's account for a live test. |
|
||||
| `message.read` is cache-only. | A channel that the client never opened returns zero messages. |
|
||||
| Voice, audio, and video are not started. | No test coverage. |
|
||||
Reference in New Issue
Block a user