239 lines
8.9 KiB
Markdown
239 lines
8.9 KiB
Markdown
# Communication
|
|
|
|
This document describes the trigger rules, trust tiers, commands, the trigger log, context, the
|
|
continuation window, and the response policy.
|
|
|
|
## Trigger rules
|
|
|
|
A message is a trigger when both conditions are true:
|
|
|
|
- The author is trusted. Each tier 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.
|
|
- The message replies to a message the puppet sent.
|
|
|
|
All other messages are ignored. Untrusted messages do not enter the log at this time. Bots and the
|
|
puppet's own messages are ignored.
|
|
|
|
The daemon processes events one at a time. This order matters for continuation windows.
|
|
|
|
## Trust tiers
|
|
|
|
| Tier | Meaning |
|
|
| --- | --- |
|
|
| `user` | Can trigger the puppet. This is the default tier. |
|
|
| `trusted` | Can trigger the puppet. Future chat commands need this tier. |
|
|
| `admin` | Can manage the trust list and the config. The owner is always an admin. |
|
|
|
|
The owner controls the trust list. Commands from other users do not change the list.
|
|
|
|
## Commands
|
|
|
|
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.
|
|
|
|
Every command starts with the **wakeword** and a space: the default is `!s`, so
|
|
`!s whitelist list`. The wakeword must sit at the start of the message, and a space must follow it.
|
|
`whitelist list` alone, `!space`, and `hey!s whitelist list` are not commands. Settings → Commands
|
|
changes the wakeword: presets near the WASD keys plus `s`, or a custom string of 1-8 characters
|
|
with no spaces (the `!` prefill can become any symbol or letter). The match ignores letter case.
|
|
|
|
A trusted message without the wakeword goes to the reply-only agent as free chat.
|
|
|
|
| Command | Effect |
|
|
| --- | --- |
|
|
| `whitelist list` | Show the owner and the trusted users with their tiers. |
|
|
| `whitelist add @user [user\|trusted\|admin]` | Add one or more users. The default tier is `user`. |
|
|
| `whitelist remove @user` | Remove one or more users from the trust list. |
|
|
| `speak <text>` | Speak text into the bot's current voice call. `speak stop` stops it. |
|
|
| `poll ...` | Start a reaction poll. Paused at this time. |
|
|
| `stay @user` | Planned with the voice backend. Pin the bot to a voice user. See [voice.md](voice.md). |
|
|
|
|
Notes:
|
|
|
|
- The owner cannot be removed.
|
|
- The command accepts mentions and raw user IDs.
|
|
- `whitelist` commands need the admin tier.
|
|
- A bare `whitelist` or `whitelist help` shows the help.
|
|
- The reply follows the response policy.
|
|
|
|
## Polls
|
|
|
|
**Paused.** `POLL_ENABLED` is `false` in `poc/src/puppet.ts`. The parser, the tests, and the code
|
|
stay in place. The daemon does not run a `poll` command.
|
|
|
|
The `poll` command posts a message and adds one number emoji per option. Discord users vote by
|
|
reacting. The poll closes after `pollSeconds` (default 120) and the daemon posts the result.
|
|
|
|
Format:
|
|
|
|
```
|
|
poll "Question" Option 1 // Option 2 [// Option 3 [// Option 4]]
|
|
poll Question | Option 1 | Option 2
|
|
```
|
|
|
|
- The question can be in quotes. Then the first option follows after the closing quote.
|
|
- Separators are `|` or `//`. The `//` separator needs whitespace on both sides. This keeps URLs
|
|
such as `https://example.com` in one piece.
|
|
- A bare `poll` or `poll help` shows the help.
|
|
- Options: 2 to 4. Each option is at most 80 characters.
|
|
|
|
Voting:
|
|
|
|
- Emojis: `1️⃣`, `2️⃣`, `3️⃣`, `4️⃣`.
|
|
- Multiple reactions per user are allowed. Each reaction counts.
|
|
- Removing a reaction removes that one selection.
|
|
- The puppet's own reactions do not count.
|
|
- Votes do not need a trust tier. Friends can vote.
|
|
- The result message goes to the poll channel.
|
|
|
|
Examples:
|
|
|
|
```
|
|
@puppet poll "What country is best" Sweden // Denmark // Madagaskar
|
|
@puppet poll Lunch? | Pizza | Sushi
|
|
```
|
|
|
|
## 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
|
|
|
|
The log has two ring buffers.
|
|
|
|
| Buffer | Limit | Content |
|
|
| --- | --- | --- |
|
|
| Trusted | 100 entries | Triggers from trusted users |
|
|
| Untrusted | 30 entries | Reserved. No triggers enter this buffer at this time. |
|
|
|
|
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 looks like a command |
|
|
| `context` | Prior messages. Present on mention triggers. Follow-ups join this list. |
|
|
|
|
## 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.
|
|
- Each line holds a time stamp, the author, the message text, and 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.
|
|
|
|
The fetch cooldown stops a burst of queries. The setting `fetchCooldownSeconds` (default 5) is the
|
|
minimum gap between two fetches for one channel.
|
|
|
|
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.
|
|
|
|
## Continuation window
|
|
|
|
A user can send a second message right after a request. The second message often adds a missing
|
|
detail. The daemon appends such messages to the request context.
|
|
|
|
- Window: `continuationSeconds`, default 10.
|
|
- The window starts at the trigger and resets on each follow-up.
|
|
- Only messages from the same author in the same channel count.
|
|
- A follow-up does not need a mention.
|
|
- A follow-up needs a trusted author.
|
|
|
|
## Action log
|
|
|
|
The daemon writes a persistent log beside the state file: `actions.jsonl`. One JSON line holds one
|
|
event. The log rotates at the size limit.
|
|
|
|
| Event | Content |
|
|
| --- | --- |
|
|
| `trigger` | Message ID, channel, guild, author, tier, command, context count |
|
|
| `continuation` | Follow-up message ID and the parent entry ID |
|
|
| `command` | Command text, requester, result |
|
|
| `reply` | Command, mode, target, result, error |
|
|
| `voice` | Voice action, channel, mute |
|
|
| `media` | Scrape start or stop: scrape ID, channel, mode |
|
|
| `audio` | Audio action (speak, stop, devices, capture), provider, size |
|
|
| `message-edit` | Message ID, channel, author, content length, whether the cache had it |
|
|
| `message-delete` | Message ID, channel, whether the cache had it |
|
|
| `reaction` | Add or remove, message, channel, user, emoji (trusted users only) |
|
|
| `manual` | Web UI send: mode, channel or user, content length |
|
|
| `error` | Scope and message |
|
|
|
|
Secret-looking strings are redacted before the write. Discord token shapes and `mfa.` strings
|
|
become `[redacted]`.
|
|
|
|
Settings:
|
|
|
|
| Setting | Default |
|
|
| --- | --- |
|
|
| `VESKSLOP_ACTION_LOG` | `<state dir>/actions.jsonl` |
|
|
| `VESKSLOP_ACTION_LOG_MAX_BYTES` | 5 MiB |
|
|
|
|
Read the log with the `get_action_log` MCP tool.
|
|
|
|
## Event flow
|
|
|
|
1. The client receives a `MESSAGE_CREATE` gateway event.
|
|
2. The core normalizes the event.
|
|
3. The core bridge pushes the event to the daemon.
|
|
4. The daemon queues the event. Events run one at a time.
|
|
5. The daemon drops a replayed event.
|
|
6. The daemon applies the trigger rules.
|
|
7. The daemon collects context for a mention trigger.
|
|
8. The daemon adds the entry to the log and writes the action log.
|
|
9. The daemon runs a trusted command and replies through the response policy.
|
|
|
|
## State file
|
|
|
|
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 trust list is an object: user ID to tier.
|
|
- The trigger log is in memory only. The action log is persistent.
|
|
|
|
## 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` and `pnpm test:cache`.
|