Add text features: trusted triggers, context cache, response policy, MCP config tools

This commit is contained in:
Fisto
2026-09-11 15:31:23 +02:00
parent 23a1a8e593
commit 82f78bb2b4
20 changed files with 841 additions and 88 deletions
+1
View File
@@ -54,6 +54,7 @@ when the action list changes.
| 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 and verified live |
| Context cache and response policy | `poc/src/message-cache.ts`, `poc/src/puppet.ts` | Implemented and verified with the mock |
| Voice, audio, video | Not started | Not started |
## Data flow
+67 -20
View File
@@ -1,30 +1,32 @@
# Communication
This document describes the trigger rules, the trust model, the commands, and the trigger log.
This document describes the trigger rules, the trust model, the commands, the trigger log, the
context cache, and the response policy.
## Trigger rules
A message is a trigger when one of these conditions is true:
A message is a trigger when both conditions are 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.
- The author is trusted. The owner counts as trusted.
- One of these conditions is true:
- The message is a one-to-one DM to the puppet.
- The message mentions the puppet in a guild channel or a group DM.
All other messages are ignored.
All other messages are ignored. Untrusted messages do not enter the log at this time. The daemon
ignores bots and its own messages.
## 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.
- The trust list holds more user IDs.
- 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.
The owner sends commands from Discord. In a DM, the text is the command. In a guild or a group DM,
mention the puppet first.
| Command | Effect |
| --- | --- |
@@ -36,7 +38,23 @@ 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.
- The reply follows the response policy.
## Response policy
The policy decides where a reply goes. Sensitive replies go by DM.
| Setting | Meaning | Default |
| --- | --- | --- |
| `defaultMode` | The mode for normal replies: `channel` or `dm` | `channel` |
| `sensitiveCommands` | Commands that always reply by DM | `whitelist`, `config` |
| `overrides` | A per-command mode | empty |
Rules:
- An override wins over the sensitive list. The sensitive list wins over the default mode.
- A DM reply failure does not fall back to the origin channel. The daemon logs the error only.
- Read the policy with `get_puppet_config`. Change it with `set_puppet_config`.
## Trigger log
@@ -45,7 +63,7 @@ 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 |
| Untrusted | 30 entries | Reserved. No triggers enter this buffer at this time. |
One entry holds these fields:
@@ -59,28 +77,57 @@ One entry holds these fields:
| `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 |
| `command` | The parsed command text, when the message looks like a command |
| `context` | Prior messages. Present on mention triggers. |
## Context
The daemon collects prior messages for a mention trigger. Direct messages do not need context.
- Message limit: `contextMessages`, default 15.
- Character limit: `contextChars`, default 4000.
- The lower limit stops the collection.
- The content is the message text and the attachment file names. Media bodies stay on Discord.
- The trigger message is not part of its own context.
The cache makes the fetch small.
1. The daemon reads the newest cached message ID for the channel.
2. It fetches messages after that ID. On the first run, it fetches the newest messages.
3. It merges the new messages into the cache.
4. It assembles the context from the merged list.
Cache settings:
| Setting | Default |
| --- | --- |
| `VESKSLOP_CACHE_DIR` | `<tmp>/veskslop-cache` |
| `VESKSLOP_CACHE_MAX_BYTES` | 50 MiB |
| Messages per channel | 200 |
Eviction removes the least recently written channel files until the folder fits the size limit.
## Event flow
1. The client receives a `MESSAGE_CREATE` gateway event.
2. The core renderer normalizes the event.
2. The core 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.
5. The daemon collects context for a mention trigger.
6. The daemon adds the entry to the log.
7. The daemon runs a trusted command and replies through the response policy.
## State file
The daemon stores the owner ID and the trust list in JSON.
The daemon stores the owner ID, the trust list, and the config 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
## How to add a trigger rule or a command
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`.
4. Run `pnpm test:comm` and `pnpm test:cache`.
+4
View File
@@ -59,6 +59,7 @@ Event line:
| `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. |
| `message.fetch` | `channelId`, `limit`, optional `before` or `after` | `{ fetched, messages }`. Fetches from Discord. |
| `dm.send` | `userId`, `content` | `{ userId, channelId, sent: true }` |
## Validation rules
@@ -73,6 +74,8 @@ The core validates all input. The daemon cannot bypass these rules.
`GUILD_TEXT`, `DM`, `GROUP_DM`, `GUILD_ANNOUNCEMENT`, `ANNOUNCEMENT_THREAD`, `PUBLIC_THREAD`,
and `PRIVATE_THREAD`.
- `limit` must be an integer from 1 to 100.
- `before` and `after` must be snowflakes. They are mutually exclusive. `message.fetch` uses them
to walk history.
- A request line must not be larger than 64 KiB.
## Core rate limit
@@ -100,6 +103,7 @@ The core rate limit is a safety net. It does not replace the daemon pacing.
| `NOT_SENDABLE` | The channel type cannot receive messages. |
| `SEND_FAILED` | Discord rejected the message. |
| `DM_UNAVAILABLE` | The client cannot create a DM channel. |
| `NOT_SUPPORTED` | The client does not offer the internal action. |
| `RATE_LIMITED` | The core rate limit rejected the action. |
| `INTERNAL` | An unexpected error occurred. The details go to the local log only. |
+18 -2
View File
@@ -7,13 +7,15 @@ The daemon runs outside Vesktop. It holds all logic. It talks to the core bridge
| File | Role | Status |
| --- | --- | --- |
| `poc/src/control-bridge.ts` | MCP server, tool list, health endpoint | Implemented |
| `poc/src/puppet.ts` | Trust list, command handling, trigger log | Implemented |
| `poc/src/communication.ts` | Pure log and parser logic | 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/pacing.ts` | Paced queue and query coalescing | Implemented |
| `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 |
@@ -47,6 +49,9 @@ The daemon exposes MCP over Streamable HTTP. The default address is `http://127.
| `read_messages` | Read cached messages from a channel. |
| `send_dm` | Send a DM to a user. |
| `get_message_log` | Read the trigger log. |
| `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. |
@@ -68,6 +73,7 @@ pnpm poc:dm
# Run the pure-logic unit tests.
pnpm test:comm
pnpm test:cache
# Development tools for the CDP fallback.
pnpm cdp:probe
@@ -82,6 +88,16 @@ CDP_EXPRESSION='...' pnpm cdp:dispatch
{ "ok": true, "cdpConnected": true, "ownerId": "177117296140091393", "queries": 4 }
```
## Settings and paths
| Setting | Default | Purpose |
| --- | --- | --- |
| `VESKSLOP_SOCKET` | `$XDG_RUNTIME_DIR/vesktop-puppet.sock` | Core bridge socket |
| `VESKSLOP_STATE_FILE` | `poc/.veskslop-state.json` | Owner, trust list, and config |
| `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 |
## How to add an MCP tool
1. Add the action to the core bridge. See [core-bridge.md](core-bridge.md).