mercury-agent 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +438 -0
- package/container/Dockerfile +127 -0
- package/container/Dockerfile.base +109 -0
- package/container/Dockerfile.power +17 -0
- package/container/agent-package.json +8 -0
- package/container/build.sh +54 -0
- package/docs/TODOS.md +147 -0
- package/docs/auth/dashboard.md +28 -0
- package/docs/auth/overview.md +109 -0
- package/docs/auth/whatsapp.md +173 -0
- package/docs/configuration.md +54 -0
- package/docs/container-lifecycle.md +349 -0
- package/docs/context-architecture.md +87 -0
- package/docs/deployment.md +199 -0
- package/docs/extensions.md +375 -0
- package/docs/graceful-shutdown.md +62 -0
- package/docs/kb-distillation.md +77 -0
- package/docs/media/overview.md +140 -0
- package/docs/media/whatsapp.md +171 -0
- package/docs/memory.md +137 -0
- package/docs/permissions.md +217 -0
- package/docs/pipeline.md +228 -0
- package/docs/prd-chat-memory.md +76 -0
- package/docs/prd-config-load.md +82 -0
- package/docs/rate-limiting.md +166 -0
- package/docs/scheduler.md +288 -0
- package/docs/setup-discord.md +100 -0
- package/docs/setup-slack.md +119 -0
- package/docs/setup-whatsapp.md +94 -0
- package/docs/subagents.md +166 -0
- package/docs/web-search.md +62 -0
- package/examples/extensions/README.md +12 -0
- package/examples/extensions/charts/index.ts +13 -0
- package/examples/extensions/charts/skill/SKILL.md +98 -0
- package/examples/extensions/gws/README.md +52 -0
- package/examples/extensions/gws/index.ts +106 -0
- package/examples/extensions/gws/skill/SKILL.md +57 -0
- package/examples/extensions/gws/skill/references/calendar.md +101 -0
- package/examples/extensions/gws/skill/references/docs.md +65 -0
- package/examples/extensions/gws/skill/references/drive.md +79 -0
- package/examples/extensions/gws/skill/references/gmail.md +85 -0
- package/examples/extensions/gws/skill/references/sheets.md +60 -0
- package/examples/extensions/napkin/index.ts +821 -0
- package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
- package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
- package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
- package/examples/extensions/napkin/skill/SKILL.md +728 -0
- package/examples/extensions/pdf/index.ts +23 -0
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
- package/examples/extensions/pdf/skill/SKILL.md +314 -0
- package/examples/extensions/pdf/skill/forms.md +294 -0
- package/examples/extensions/pdf/skill/reference.md +612 -0
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/examples/extensions/permission-guard/index.ts +65 -0
- package/examples/extensions/pinchtab/index.ts +199 -0
- package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
- package/examples/extensions/pinchtab/skill/references/api.md +297 -0
- package/examples/extensions/pinchtab/skill/references/env.md +45 -0
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
- package/examples/extensions/tradestation/host/refresh.ts +102 -0
- package/examples/extensions/tradestation/index.ts +153 -0
- package/examples/extensions/tradestation/skill/SKILL.md +67 -0
- package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
- package/examples/extensions/voice-synth/index.ts +94 -0
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
- package/examples/extensions/voice-transcribe/index.ts +381 -0
- package/examples/extensions/voice-transcribe/requirements.txt +8 -0
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
- package/examples/extensions/web-search/index.ts +22 -0
- package/examples/extensions/web-search/skill/SKILL.md +114 -0
- package/examples/extensions/web-search/skill/references/apartments.md +178 -0
- package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
- package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
- package/examples/extensions/web-search/skill/references/flights.md +133 -0
- package/examples/extensions/web-search/skill/references/hotels.md +148 -0
- package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
- package/examples/extensions/yahoo-mail/cli/package.json +13 -0
- package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
- package/examples/extensions/yahoo-mail/index.ts +57 -0
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
- package/package.json +106 -0
- package/resources/agents/explore.md +50 -0
- package/resources/agents/worker.md +24 -0
- package/resources/builtin-extensions.txt +3 -0
- package/resources/connection-env-vars.json +25 -0
- package/resources/extensions/.gitkeep +0 -0
- package/resources/pi-extensions/subagent/agents.ts +126 -0
- package/resources/pi-extensions/subagent/index.ts +964 -0
- package/resources/profiles/coding/AGENTS.md +43 -0
- package/resources/profiles/coding/mercury-profile.yaml +15 -0
- package/resources/profiles/general/AGENTS.md +31 -0
- package/resources/profiles/general/mercury-profile.yaml +15 -0
- package/resources/profiles/research/AGENTS.md +40 -0
- package/resources/profiles/research/mercury-profile.yaml +15 -0
- package/resources/skills/config/SKILL.md +25 -0
- package/resources/skills/context/SKILL.md +33 -0
- package/resources/skills/conversation-recap/SKILL.md +19 -0
- package/resources/skills/media/SKILL.md +27 -0
- package/resources/skills/mutes/SKILL.md +31 -0
- package/resources/skills/permissions/SKILL.md +19 -0
- package/resources/skills/preferences/SKILL.md +31 -0
- package/resources/skills/recall/SKILL.md +24 -0
- package/resources/skills/roles/SKILL.md +18 -0
- package/resources/skills/spaces/SKILL.md +18 -0
- package/resources/skills/tasks/SKILL.md +45 -0
- package/resources/templates/AGENTS.md +157 -0
- package/resources/templates/env.template +34 -0
- package/resources/templates/mercury.example.yaml +75 -0
- package/src/adapters/discord-native.ts +534 -0
- package/src/adapters/discord.ts +38 -0
- package/src/adapters/setup.ts +89 -0
- package/src/adapters/slack.ts +9 -0
- package/src/adapters/whatsapp-media.ts +337 -0
- package/src/adapters/whatsapp.ts +629 -0
- package/src/agent/api-socket.ts +127 -0
- package/src/agent/container-entry.ts +967 -0
- package/src/agent/container-error.ts +49 -0
- package/src/agent/container-runner.ts +1272 -0
- package/src/agent/model-capabilities-core.ts +23 -0
- package/src/agent/model-capabilities.ts +231 -0
- package/src/agent/pi-failure-class.ts +83 -0
- package/src/agent/pi-jsonl-parser.ts +306 -0
- package/src/agent/preferences-prompt.ts +20 -0
- package/src/agent/user-error-messages.ts +78 -0
- package/src/bridges/discord.ts +171 -0
- package/src/bridges/slack.ts +177 -0
- package/src/bridges/teams.ts +160 -0
- package/src/bridges/telegram.ts +571 -0
- package/src/bridges/whatsapp.ts +290 -0
- package/src/chat-shim.ts +259 -0
- package/src/cli/mercury.ts +2508 -0
- package/src/cli/mrctl-http.ts +27 -0
- package/src/cli/mrctl.ts +611 -0
- package/src/cli/whatsapp-auth.ts +260 -0
- package/src/config-file.ts +397 -0
- package/src/config-model-chain.ts +30 -0
- package/src/config.ts +316 -0
- package/src/core/api-types.ts +58 -0
- package/src/core/api.ts +105 -0
- package/src/core/commands.ts +76 -0
- package/src/core/conversation.ts +47 -0
- package/src/core/handler.ts +206 -0
- package/src/core/media.ts +200 -0
- package/src/core/mute-duration.ts +22 -0
- package/src/core/outbox.ts +76 -0
- package/src/core/permissions.ts +192 -0
- package/src/core/profiles.ts +245 -0
- package/src/core/rate-limiter.ts +127 -0
- package/src/core/router.ts +191 -0
- package/src/core/routes/chat.ts +172 -0
- package/src/core/routes/config-builtin.ts +107 -0
- package/src/core/routes/config.ts +81 -0
- package/src/core/routes/connections.ts +190 -0
- package/src/core/routes/console.ts +668 -0
- package/src/core/routes/control.ts +46 -0
- package/src/core/routes/conversations.ts +66 -0
- package/src/core/routes/dashboard.ts +2491 -0
- package/src/core/routes/extensions.ts +37 -0
- package/src/core/routes/index.ts +14 -0
- package/src/core/routes/media.ts +72 -0
- package/src/core/routes/messages.ts +37 -0
- package/src/core/routes/mutes.ts +89 -0
- package/src/core/routes/prefs.ts +95 -0
- package/src/core/routes/roles.ts +125 -0
- package/src/core/routes/spaces.ts +60 -0
- package/src/core/routes/storage.ts +126 -0
- package/src/core/routes/tasks.ts +189 -0
- package/src/core/routes/tradestation.ts +268 -0
- package/src/core/routes/tts.ts +51 -0
- package/src/core/runtime.ts +1140 -0
- package/src/core/space-queue.ts +103 -0
- package/src/core/storage-cleanup.ts +140 -0
- package/src/core/storage-guard.ts +24 -0
- package/src/core/task-scheduler.ts +132 -0
- package/src/core/telegram-format.ts +178 -0
- package/src/core/trigger.ts +142 -0
- package/src/dashboard/index.html +729 -0
- package/src/dashboard/tokens.css +53 -0
- package/src/extensions/api.ts +252 -0
- package/src/extensions/catalog.ts +117 -0
- package/src/extensions/config-registry.ts +83 -0
- package/src/extensions/context.ts +36 -0
- package/src/extensions/hooks.ts +156 -0
- package/src/extensions/image-builder.ts +617 -0
- package/src/extensions/installer.ts +306 -0
- package/src/extensions/jobs.ts +122 -0
- package/src/extensions/loader.ts +271 -0
- package/src/extensions/permission-guard.ts +52 -0
- package/src/extensions/reserved.ts +28 -0
- package/src/extensions/skills.ts +123 -0
- package/src/extensions/types.ts +462 -0
- package/src/logger.ts +174 -0
- package/src/main.ts +586 -0
- package/src/server.ts +391 -0
- package/src/storage/db.ts +1624 -0
- package/src/storage/memory.ts +45 -0
- package/src/storage/pi-auth.ts +95 -0
- package/src/text/markdown.ts +117 -0
- package/src/text/rtl.ts +38 -0
- package/src/tradestation/host-api.ts +77 -0
- package/src/tradestation/pending-orders.ts +69 -0
- package/src/tts/azure.ts +52 -0
- package/src/tts/google.ts +128 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/language.ts +20 -0
- package/src/tts/synthesize.ts +133 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# WhatsApp Media Handling
|
|
2
|
+
|
|
3
|
+
Downloads media from WhatsApp messages using the Baileys library.
|
|
4
|
+
|
|
5
|
+
## Supported Media Types
|
|
6
|
+
|
|
7
|
+
| WhatsApp Type | Detected As | Default MIME | Extensions |
|
|
8
|
+
|---------------|-------------|--------------|------------|
|
|
9
|
+
| `imageMessage` | `image` | `image/jpeg` | jpg, png, gif, webp |
|
|
10
|
+
| `videoMessage` | `video` | `video/mp4` | mp4, 3gp |
|
|
11
|
+
| `audioMessage` (ptt=true) | `voice` | `audio/ogg` | ogg |
|
|
12
|
+
| `audioMessage` (ptt=false) | `audio` | `audio/mpeg` | mp3, m4a, aac, ogg |
|
|
13
|
+
| `documentMessage` | `document` | `application/octet-stream` | pdf, doc, docx, xls, xlsx, txt, etc. |
|
|
14
|
+
| `stickerMessage` | `image` | `image/webp` | webp |
|
|
15
|
+
|
|
16
|
+
## Data Flow
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
WAMessage (Baileys)
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
detectWhatsAppMedia(msg.message)
|
|
23
|
+
│
|
|
24
|
+
├─▶ null (no media)
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
WhatsAppMediaInfo { type, mimeType, fileLength?, filename? }
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
downloadWhatsAppMedia(msg, sock, options)
|
|
31
|
+
│
|
|
32
|
+
├─▶ null (too large / download failed)
|
|
33
|
+
│
|
|
34
|
+
▼
|
|
35
|
+
MessageAttachment { path, type, mimeType, sizeBytes?, filename? }
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
Saved to: {workspace}/inbox/{timestamp}-{type}.{ext}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Implementation
|
|
42
|
+
|
|
43
|
+
### Detection
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// src/adapters/whatsapp-media.ts
|
|
47
|
+
function detectWhatsAppMedia(message: proto.IMessage): WhatsAppMediaInfo | null
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Checks message for media fields in order:
|
|
51
|
+
1. `audioMessage?.ptt` → voice note
|
|
52
|
+
2. `audioMessage` → audio
|
|
53
|
+
3. `imageMessage` → image
|
|
54
|
+
4. `videoMessage` → video
|
|
55
|
+
5. `documentMessage` → document
|
|
56
|
+
6. `stickerMessage` → image
|
|
57
|
+
|
|
58
|
+
### Download
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// src/adapters/whatsapp-media.ts
|
|
62
|
+
async function downloadWhatsAppMedia(
|
|
63
|
+
msg: WAMessage,
|
|
64
|
+
sock: WASocket,
|
|
65
|
+
options: MediaDownloadOptions,
|
|
66
|
+
): Promise<MessageAttachment | null>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Uses Baileys' `downloadMediaMessage()`:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const buffer = await downloadMediaMessage(
|
|
73
|
+
msg,
|
|
74
|
+
"buffer",
|
|
75
|
+
{},
|
|
76
|
+
{
|
|
77
|
+
logger: silentLogger,
|
|
78
|
+
reuploadRequest: sock.updateMediaMessage,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Adapter Integration
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// src/adapters/whatsapp.ts
|
|
87
|
+
createWhatsAppBaileysAdapter({
|
|
88
|
+
mediaEnabled: true,
|
|
89
|
+
mediaMaxSizeBytes: 10 * 1024 * 1024,
|
|
90
|
+
getWorkspace: (spaceId) => ensureSpaceWorkspace(spacesDir, spaceId),
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Size Limits
|
|
95
|
+
|
|
96
|
+
Files are checked against `mediaMaxSizeBytes` twice:
|
|
97
|
+
|
|
98
|
+
1. **Before download** — using `fileLength` from message metadata (may be missing)
|
|
99
|
+
2. **After download** — using actual buffer size
|
|
100
|
+
|
|
101
|
+
Files exceeding the limit are logged and skipped:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[WARN] Skipping large media file messageId=ABC type=video sizeBytes=52428800 maxBytes=10485760
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Reply Context
|
|
108
|
+
|
|
109
|
+
When replying to a message with media, the reply context includes media info:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Attributes added to <reply_to>
|
|
113
|
+
media_type="image"
|
|
114
|
+
media_mime="image/jpeg"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```xml
|
|
119
|
+
<reply_to name="John" jid="123@wa" message_id="ABC" media_type="image" media_mime="image/jpeg">
|
|
120
|
+
[image]
|
|
121
|
+
</reply_to>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## File Naming
|
|
125
|
+
|
|
126
|
+
Pattern: `{timestamp}-{type}.{ext}` or `{timestamp}-{filename}` for documents
|
|
127
|
+
|
|
128
|
+
| Input | Output |
|
|
129
|
+
|-------|--------|
|
|
130
|
+
| image/jpeg | `1709012345-image.jpg` |
|
|
131
|
+
| audio/ogg (voice) | `1709012345-voice.ogg` |
|
|
132
|
+
| application/pdf, "report.pdf" | `1709012345-report.pdf` |
|
|
133
|
+
|
|
134
|
+
## MIME to Extension Mapping
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const MIME_TO_EXT: Record<string, string> = {
|
|
138
|
+
"image/jpeg": "jpg",
|
|
139
|
+
"image/png": "png",
|
|
140
|
+
"image/gif": "gif",
|
|
141
|
+
"image/webp": "webp",
|
|
142
|
+
"audio/ogg": "ogg",
|
|
143
|
+
"audio/mpeg": "mp3",
|
|
144
|
+
"audio/mp4": "m4a",
|
|
145
|
+
"video/mp4": "mp4",
|
|
146
|
+
"video/3gpp": "3gp",
|
|
147
|
+
"application/pdf": "pdf",
|
|
148
|
+
// ... etc
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Unknown MIME types default to `.bin`.
|
|
153
|
+
|
|
154
|
+
## Limitations
|
|
155
|
+
|
|
156
|
+
1. **No re-download** — Media is downloaded once when the message arrives. If the file is deleted, it's gone.
|
|
157
|
+
|
|
158
|
+
2. **No transcription** — Voice notes are saved as audio files. pi cannot play them. Future: add Whisper transcription.
|
|
159
|
+
|
|
160
|
+
3. **Reply context doesn't include file** — When replying to a media message, we include metadata but not the actual file path. The original attachment would need to be looked up.
|
|
161
|
+
|
|
162
|
+
4. **Ephemeral media** — WhatsApp media URLs expire. Download must happen immediately when the message arrives.
|
|
163
|
+
|
|
164
|
+
## Error Handling
|
|
165
|
+
|
|
166
|
+
| Error | Behavior |
|
|
167
|
+
|-------|----------|
|
|
168
|
+
| Download fails | Log error, continue without attachment |
|
|
169
|
+
| Buffer empty | Log error, return null |
|
|
170
|
+
| Size exceeded | Log warning, discard buffer |
|
|
171
|
+
| Write fails | Throw error (propagates up) |
|
package/docs/memory.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Memory
|
|
2
|
+
|
|
3
|
+
Mercury stores memory per **space** in the space workspace directory. Memory behavior is extension-driven (for example, via a napkin extension).
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
Each space gets a workspace at:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
.mercury/spaces/<space-id>/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Core directories Mercury manages:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
.mercury/spaces/<space-id>/
|
|
17
|
+
├── inbox/ # Media received from users
|
|
18
|
+
├── outbox/ # Files produced by the agent
|
|
19
|
+
├── AGENTS.md # Space instructions
|
|
20
|
+
└── .mercury.session.jsonl
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Additional vault structure (for example `.obsidian/`, `knowledge/`, `daily/`, `entities/`) is created by installed extensions.
|
|
24
|
+
|
|
25
|
+
## Vault Structure
|
|
26
|
+
|
|
27
|
+
There is no single required memory schema in core Mercury. The exact structure depends on your extension setup.
|
|
28
|
+
|
|
29
|
+
A common pattern (napkin example) is:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
knowledge/
|
|
33
|
+
├── people/
|
|
34
|
+
├── projects/
|
|
35
|
+
├── references/
|
|
36
|
+
└── daily/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Conversations do not get their own vaults — multiple platform conversations can link into the same space.
|
|
40
|
+
|
|
41
|
+
## Agent Capabilities
|
|
42
|
+
|
|
43
|
+
The agent discovers extension commands via installed skills (for example `napkin` skill in `.mercury/extensions/napkin/skill/`).
|
|
44
|
+
|
|
45
|
+
### Reading
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
napkin search "query" # Find relevant files
|
|
49
|
+
napkin read "filename" # Read a specific file
|
|
50
|
+
napkin link back --file "name" # See what links to a file
|
|
51
|
+
napkin daily read # Read today's daily note
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Writing
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
napkin create --name "Liat" --path entities --content "..."
|
|
58
|
+
napkin append --file "Liat" --content "..."
|
|
59
|
+
napkin property set --file "Liat" --name birthday --value "April 15"
|
|
60
|
+
napkin daily append --content "- Discussed vacation plans"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Wikilinks
|
|
64
|
+
|
|
65
|
+
The agent uses `[[wikilinks]]` when mentioning entities. This creates a navigable graph:
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
---
|
|
69
|
+
type: person
|
|
70
|
+
relationship: wife
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
# Liat
|
|
74
|
+
|
|
75
|
+
[[Michael]]'s wife. Planning a surprise party at [[Dizengoff Italian Place]].
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## User Interaction
|
|
79
|
+
|
|
80
|
+
Users manage memory through natural chat:
|
|
81
|
+
|
|
82
|
+
| User says | What happens |
|
|
83
|
+
|-----------|--------------|
|
|
84
|
+
| "Remember that Liat's birthday is April 15" | Agent writes to `entities/Liat.md` |
|
|
85
|
+
| "What do you know about Liat?" | Agent reads and summarizes the entity file |
|
|
86
|
+
| "Forget everything about the project" | Agent deletes the entity file |
|
|
87
|
+
|
|
88
|
+
## Entity Format
|
|
89
|
+
|
|
90
|
+
Entities are markdown files with optional YAML frontmatter:
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
---
|
|
94
|
+
type: person
|
|
95
|
+
birthday: April 15
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
# Liat
|
|
99
|
+
|
|
100
|
+
Context and notes about Liat.
|
|
101
|
+
|
|
102
|
+
_2026-02-20:_ Booked the Italian place for April 12.
|
|
103
|
+
_2026-02-25:_ Changed venue to [[Cafe Nimrod]].
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **Frontmatter** — Structured attributes (replace semantics)
|
|
107
|
+
- **Body** — Accumulated context (append semantics, timestamped)
|
|
108
|
+
- **Wikilinks** — Connections to other entities
|
|
109
|
+
|
|
110
|
+
## Conditional Context
|
|
111
|
+
|
|
112
|
+
Mercury can skip loading the full session for standalone prompts (e.g. "what's 2+2?"), reducing token usage. After the run, the prompt and reply are merged back into the session so history stays complete.
|
|
113
|
+
|
|
114
|
+
See [conditional-context.md](conditional-context.md) for details and configuration.
|
|
115
|
+
|
|
116
|
+
## Persistence
|
|
117
|
+
|
|
118
|
+
Memory persists because the agent writes to disk during conversation. When a session compacts or restarts, the vault files remain — the agent reads them fresh on next interaction.
|
|
119
|
+
|
|
120
|
+
The vault is plain markdown. You can:
|
|
121
|
+
- Open it in Obsidian and browse the graph
|
|
122
|
+
- Edit files directly from the host
|
|
123
|
+
- Back it up like any other directory
|
|
124
|
+
|
|
125
|
+
## Space preferences (built-in)
|
|
126
|
+
|
|
127
|
+
Short, structured **per-space** hints (sources, habits, rules) can be stored in SQLite and managed from chat via `mrctl prefs` (see the built-in **preferences** skill). The host injects them into each run as a `<preferences>` block in the user prompt. See [prd-chat-memory.md](prd-chat-memory.md).
|
|
128
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
Memory behavior is controlled by installed extensions and their config.
|
|
132
|
+
|
|
133
|
+
To use a shared Obsidian vault across tools, symlink or mount it:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
ln -s /path/to/your/vault .mercury/spaces/main
|
|
137
|
+
```
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Permissions
|
|
2
|
+
|
|
3
|
+
Mercury uses role-based access control (RBAC) per space. Each user has a role, and each role has a set of permissions.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Message arrives
|
|
9
|
+
│
|
|
10
|
+
├─► Resolve caller's role
|
|
11
|
+
│ • System caller? → role = "system"
|
|
12
|
+
│ • Seeded admin? → grant admin, store in DB
|
|
13
|
+
│ • Existing role in DB? → use it
|
|
14
|
+
│ • Otherwise → "member"
|
|
15
|
+
│
|
|
16
|
+
├─► Load role's permissions
|
|
17
|
+
│ • Check space_config for override
|
|
18
|
+
│ • Fall back to built-in defaults
|
|
19
|
+
│
|
|
20
|
+
└─► Check permission for action
|
|
21
|
+
• Has permission → proceed
|
|
22
|
+
• Denied → return error
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Roles
|
|
26
|
+
|
|
27
|
+
| Role | Default Permissions | Description |
|
|
28
|
+
|------|---------------------|-------------|
|
|
29
|
+
| `system` | All | Internal system caller (scheduler, etc.) — not assignable |
|
|
30
|
+
| `admin` | All | Full control over the space |
|
|
31
|
+
| `member` | `prompt`, `prefs.get` | Can chat and read space preferences (default for new users) |
|
|
32
|
+
|
|
33
|
+
Custom roles can be created by assigning permissions to any role name.
|
|
34
|
+
|
|
35
|
+
## Permissions
|
|
36
|
+
|
|
37
|
+
| Permission | Description |
|
|
38
|
+
|------------|-------------|
|
|
39
|
+
| `prompt` | Send messages to the assistant |
|
|
40
|
+
| `stop` | Abort running agent and clear queue |
|
|
41
|
+
| `compact` | Reset session boundary (fresh context) |
|
|
42
|
+
| `tasks.list` | View scheduled tasks |
|
|
43
|
+
| `tasks.create` | Create new scheduled tasks |
|
|
44
|
+
| `tasks.pause` | Pause scheduled tasks |
|
|
45
|
+
| `tasks.resume` | Resume paused tasks |
|
|
46
|
+
| `tasks.delete` | Delete scheduled tasks |
|
|
47
|
+
| `config.get` | Read space configuration |
|
|
48
|
+
| `config.set` | Modify space configuration |
|
|
49
|
+
| `prefs.get` | Read space preferences (`mrctl prefs list/get`) |
|
|
50
|
+
| `prefs.set` | Create, update, or delete space preferences (`mrctl prefs set/delete`) |
|
|
51
|
+
| `roles.list` | View roles in the space |
|
|
52
|
+
| `roles.grant` | Assign roles to users |
|
|
53
|
+
| `roles.revoke` | Remove roles from users |
|
|
54
|
+
| `permissions.get` | View role permissions |
|
|
55
|
+
| `permissions.set` | Modify role permissions |
|
|
56
|
+
| `spaces.list` | View all spaces |
|
|
57
|
+
| `spaces.rename` | Rename a space and link/unlink conversations |
|
|
58
|
+
| `spaces.delete` | Delete current space and all related DB data |
|
|
59
|
+
|
|
60
|
+
## Mutes
|
|
61
|
+
|
|
62
|
+
Muted users’ messages are dropped for the space until the mute expires or is cleared. The agent can mute via `mrctl mute` (with API confirmation). **Operators** with dashboard access (`MERCURY_API_SECRET`) can list active mutes, unmute, or add a timed mute from **Spaces → (space) → Muted users**.
|
|
63
|
+
|
|
64
|
+
## Managing Roles
|
|
65
|
+
|
|
66
|
+
The agent uses `mrctl` to manage roles:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# List all roles in the current space
|
|
70
|
+
mrctl roles list
|
|
71
|
+
|
|
72
|
+
# Grant admin role to a user
|
|
73
|
+
mrctl roles grant 1234567890@s.whatsapp.net --role admin
|
|
74
|
+
|
|
75
|
+
# Grant a custom role
|
|
76
|
+
mrctl roles grant 1234567890@s.whatsapp.net --role moderator
|
|
77
|
+
|
|
78
|
+
# Revoke role (user becomes member)
|
|
79
|
+
mrctl roles revoke 1234567890@s.whatsapp.net
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Managing Permissions
|
|
83
|
+
|
|
84
|
+
Permissions are per-role, per-space:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Show permissions for all roles
|
|
88
|
+
mrctl permissions show
|
|
89
|
+
|
|
90
|
+
# Show permissions for a specific role
|
|
91
|
+
mrctl permissions show --role member
|
|
92
|
+
|
|
93
|
+
# Give members ability to stop the agent
|
|
94
|
+
mrctl permissions set member prompt,stop
|
|
95
|
+
|
|
96
|
+
# Create a moderator role with task management
|
|
97
|
+
mrctl permissions set moderator prompt,stop,tasks.list,tasks.pause,tasks.resume
|
|
98
|
+
|
|
99
|
+
# Give a role full task control
|
|
100
|
+
mrctl permissions set taskmaster prompt,tasks.list,tasks.create,tasks.pause,tasks.resume,tasks.delete
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Managing Spaces
|
|
104
|
+
|
|
105
|
+
Spaces can be listed and managed via `mrctl`:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# List all spaces with their names
|
|
109
|
+
mrctl spaces list
|
|
110
|
+
|
|
111
|
+
# Get current space's display name
|
|
112
|
+
mrctl spaces name
|
|
113
|
+
|
|
114
|
+
# Set current space's display name
|
|
115
|
+
mrctl spaces name "Startup Buddies"
|
|
116
|
+
|
|
117
|
+
# Delete current space (tasks, messages, roles, config)
|
|
118
|
+
mrctl spaces delete
|
|
119
|
+
|
|
120
|
+
# List discovered conversations
|
|
121
|
+
mrctl conversations list
|
|
122
|
+
|
|
123
|
+
# Show only conversations that are not yet linked
|
|
124
|
+
mrctl conversations list --unlinked
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Space names are stored in the database and shown in logs/dashboard for easier identification. Conversations remain platform-native and are linked into spaces. Conversation listing uses `spaces.list`; linking and unlinking use `spaces.rename`.
|
|
128
|
+
|
|
129
|
+
## Seeding Admins
|
|
130
|
+
|
|
131
|
+
Pre-configure admin users via environment variable. They're granted admin on first interaction with each space:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
MERCURY_ADMINS=1234567890@s.whatsapp.net,0987654321@s.whatsapp.net
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This is useful for bootstrapping — the first admin can then grant roles to others.
|
|
138
|
+
|
|
139
|
+
## Storage
|
|
140
|
+
|
|
141
|
+
Roles and permissions are stored in SQLite:
|
|
142
|
+
|
|
143
|
+
| Table | Purpose |
|
|
144
|
+
|-------|---------|
|
|
145
|
+
| `space_roles` | Maps `(space_id, platform_user_id)` → `role` |
|
|
146
|
+
| `space_config` | Stores `role.<name>.permissions` overrides |
|
|
147
|
+
|
|
148
|
+
Built-in defaults (`admin` = all, `member` = `prompt` + `prefs.get`) are not stored — they're applied when no override exists.
|
|
149
|
+
|
|
150
|
+
## System Caller
|
|
151
|
+
|
|
152
|
+
The `system` role is special:
|
|
153
|
+
|
|
154
|
+
- Always has all permissions
|
|
155
|
+
- Cannot be modified or assigned
|
|
156
|
+
- Used for internal callers: scheduled tasks, system triggers
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
if (isSystemCaller(callerId)) return "system";
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Extension Permissions
|
|
163
|
+
|
|
164
|
+
Extensions register additional permissions at runtime via the extension API:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
mercury.permission({ defaultRoles: ["admin", "member"] });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This registers a permission named after the extension (e.g., `napkin`). The behavior:
|
|
171
|
+
|
|
172
|
+
- **Admin** always gets all permissions (built-in + extension)
|
|
173
|
+
- **Extension `defaultRoles`** are respected — if `["member"]` is specified, members get that permission by default
|
|
174
|
+
- **Per-space overrides** still take precedence over defaults
|
|
175
|
+
- **Built-in permission names** cannot be overridden by extensions
|
|
176
|
+
|
|
177
|
+
Extension CLIs are called directly by the agent in bash. Permission enforcement is handled by a pi extension that blocks denied CLIs at the bash tool level, based on the caller's role and the `MERCURY_DENIED_CLIS` environment variable set by Mercury's runtime.
|
|
178
|
+
|
|
179
|
+
Extensions that declare env vars via `mercury.env()` also have those vars gated by permission — they are only injected into containers when the caller has the extension's permission. This prevents credential leakage (e.g., a blocked `gh` CLI's `GH_TOKEN` being used via `curl`).
|
|
180
|
+
|
|
181
|
+
### API
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
registerPermission(name, { defaultRoles }) // Register (called by extension loader)
|
|
185
|
+
getAllPermissions() // Built-in + extension permissions
|
|
186
|
+
isValidPermission(name) // Check if name is valid
|
|
187
|
+
resetPermissions() // Clear registered (test isolation)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Scope
|
|
191
|
+
|
|
192
|
+
Permissions are **per-space**:
|
|
193
|
+
|
|
194
|
+
- A user can be `admin` in one space and `member` in another
|
|
195
|
+
- Custom role permissions are space-specific
|
|
196
|
+
- No global roles (except seeded admins, which apply on first interaction per space)
|
|
197
|
+
|
|
198
|
+
## API
|
|
199
|
+
|
|
200
|
+
### `resolveRole(db, spaceId, platformUserId, seededAdmins)`
|
|
201
|
+
|
|
202
|
+
Determines a caller's role:
|
|
203
|
+
1. System caller → `"system"`
|
|
204
|
+
2. Seed admins if needed
|
|
205
|
+
3. Upsert member record
|
|
206
|
+
4. Return stored role or `"member"`
|
|
207
|
+
|
|
208
|
+
### `getRolePermissions(db, spaceId, role)`
|
|
209
|
+
|
|
210
|
+
Returns the permission set for a role:
|
|
211
|
+
1. System role → all permissions
|
|
212
|
+
2. Check `space_config` for `role.<name>.permissions`
|
|
213
|
+
3. Fall back to built-in defaults
|
|
214
|
+
|
|
215
|
+
### `hasPermission(db, spaceId, role, permission)`
|
|
216
|
+
|
|
217
|
+
Returns `true` if the role has the specified permission.
|