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.
Files changed (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/container/Dockerfile +127 -0
  4. package/container/Dockerfile.base +109 -0
  5. package/container/Dockerfile.power +17 -0
  6. package/container/agent-package.json +8 -0
  7. package/container/build.sh +54 -0
  8. package/docs/TODOS.md +147 -0
  9. package/docs/auth/dashboard.md +28 -0
  10. package/docs/auth/overview.md +109 -0
  11. package/docs/auth/whatsapp.md +173 -0
  12. package/docs/configuration.md +54 -0
  13. package/docs/container-lifecycle.md +349 -0
  14. package/docs/context-architecture.md +87 -0
  15. package/docs/deployment.md +199 -0
  16. package/docs/extensions.md +375 -0
  17. package/docs/graceful-shutdown.md +62 -0
  18. package/docs/kb-distillation.md +77 -0
  19. package/docs/media/overview.md +140 -0
  20. package/docs/media/whatsapp.md +171 -0
  21. package/docs/memory.md +137 -0
  22. package/docs/permissions.md +217 -0
  23. package/docs/pipeline.md +228 -0
  24. package/docs/prd-chat-memory.md +76 -0
  25. package/docs/prd-config-load.md +82 -0
  26. package/docs/rate-limiting.md +166 -0
  27. package/docs/scheduler.md +288 -0
  28. package/docs/setup-discord.md +100 -0
  29. package/docs/setup-slack.md +119 -0
  30. package/docs/setup-whatsapp.md +94 -0
  31. package/docs/subagents.md +166 -0
  32. package/docs/web-search.md +62 -0
  33. package/examples/extensions/README.md +12 -0
  34. package/examples/extensions/charts/index.ts +13 -0
  35. package/examples/extensions/charts/skill/SKILL.md +98 -0
  36. package/examples/extensions/gws/README.md +52 -0
  37. package/examples/extensions/gws/index.ts +106 -0
  38. package/examples/extensions/gws/skill/SKILL.md +57 -0
  39. package/examples/extensions/gws/skill/references/calendar.md +101 -0
  40. package/examples/extensions/gws/skill/references/docs.md +65 -0
  41. package/examples/extensions/gws/skill/references/drive.md +79 -0
  42. package/examples/extensions/gws/skill/references/gmail.md +85 -0
  43. package/examples/extensions/gws/skill/references/sheets.md +60 -0
  44. package/examples/extensions/napkin/index.ts +821 -0
  45. package/examples/extensions/napkin/prompts/consolidation-monthly.md +73 -0
  46. package/examples/extensions/napkin/prompts/consolidation-weekly.md +67 -0
  47. package/examples/extensions/napkin/prompts/kb-distillation.md +176 -0
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -0
  49. package/examples/extensions/pdf/index.ts +23 -0
  50. package/examples/extensions/pdf/skill/LICENSE.txt +30 -0
  51. package/examples/extensions/pdf/skill/SKILL.md +314 -0
  52. package/examples/extensions/pdf/skill/forms.md +294 -0
  53. package/examples/extensions/pdf/skill/reference.md +612 -0
  54. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -0
  55. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -0
  56. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -0
  57. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -0
  58. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -0
  59. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -0
  60. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -0
  61. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -0
  62. package/examples/extensions/permission-guard/index.ts +65 -0
  63. package/examples/extensions/pinchtab/index.ts +199 -0
  64. package/examples/extensions/pinchtab/lib/session-injector.ts +144 -0
  65. package/examples/extensions/pinchtab/skill/SKILL.md +224 -0
  66. package/examples/extensions/pinchtab/skill/TRUST.md +69 -0
  67. package/examples/extensions/pinchtab/skill/references/api.md +297 -0
  68. package/examples/extensions/pinchtab/skill/references/env.md +45 -0
  69. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -0
  70. package/examples/extensions/tradestation/host/refresh.ts +102 -0
  71. package/examples/extensions/tradestation/index.ts +153 -0
  72. package/examples/extensions/tradestation/skill/SKILL.md +67 -0
  73. package/examples/extensions/tradestation/skill/scripts/ts-cli.ts +111 -0
  74. package/examples/extensions/voice-synth/index.ts +94 -0
  75. package/examples/extensions/voice-synth/skill/SKILL.md +38 -0
  76. package/examples/extensions/voice-transcribe/index.ts +381 -0
  77. package/examples/extensions/voice-transcribe/requirements.txt +8 -0
  78. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -0
  79. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -0
  80. package/examples/extensions/web-search/index.ts +22 -0
  81. package/examples/extensions/web-search/skill/SKILL.md +114 -0
  82. package/examples/extensions/web-search/skill/references/apartments.md +178 -0
  83. package/examples/extensions/web-search/skill/references/car-purchase.md +132 -0
  84. package/examples/extensions/web-search/skill/references/car-rental.md +113 -0
  85. package/examples/extensions/web-search/skill/references/flights.md +133 -0
  86. package/examples/extensions/web-search/skill/references/hotels.md +148 -0
  87. package/examples/extensions/yahoo-mail/cli/bun.lock +66 -0
  88. package/examples/extensions/yahoo-mail/cli/package.json +13 -0
  89. package/examples/extensions/yahoo-mail/cli/ymail.mjs +353 -0
  90. package/examples/extensions/yahoo-mail/index.ts +57 -0
  91. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -0
  92. package/package.json +106 -0
  93. package/resources/agents/explore.md +50 -0
  94. package/resources/agents/worker.md +24 -0
  95. package/resources/builtin-extensions.txt +3 -0
  96. package/resources/connection-env-vars.json +25 -0
  97. package/resources/extensions/.gitkeep +0 -0
  98. package/resources/pi-extensions/subagent/agents.ts +126 -0
  99. package/resources/pi-extensions/subagent/index.ts +964 -0
  100. package/resources/profiles/coding/AGENTS.md +43 -0
  101. package/resources/profiles/coding/mercury-profile.yaml +15 -0
  102. package/resources/profiles/general/AGENTS.md +31 -0
  103. package/resources/profiles/general/mercury-profile.yaml +15 -0
  104. package/resources/profiles/research/AGENTS.md +40 -0
  105. package/resources/profiles/research/mercury-profile.yaml +15 -0
  106. package/resources/skills/config/SKILL.md +25 -0
  107. package/resources/skills/context/SKILL.md +33 -0
  108. package/resources/skills/conversation-recap/SKILL.md +19 -0
  109. package/resources/skills/media/SKILL.md +27 -0
  110. package/resources/skills/mutes/SKILL.md +31 -0
  111. package/resources/skills/permissions/SKILL.md +19 -0
  112. package/resources/skills/preferences/SKILL.md +31 -0
  113. package/resources/skills/recall/SKILL.md +24 -0
  114. package/resources/skills/roles/SKILL.md +18 -0
  115. package/resources/skills/spaces/SKILL.md +18 -0
  116. package/resources/skills/tasks/SKILL.md +45 -0
  117. package/resources/templates/AGENTS.md +157 -0
  118. package/resources/templates/env.template +34 -0
  119. package/resources/templates/mercury.example.yaml +75 -0
  120. package/src/adapters/discord-native.ts +534 -0
  121. package/src/adapters/discord.ts +38 -0
  122. package/src/adapters/setup.ts +89 -0
  123. package/src/adapters/slack.ts +9 -0
  124. package/src/adapters/whatsapp-media.ts +337 -0
  125. package/src/adapters/whatsapp.ts +629 -0
  126. package/src/agent/api-socket.ts +127 -0
  127. package/src/agent/container-entry.ts +967 -0
  128. package/src/agent/container-error.ts +49 -0
  129. package/src/agent/container-runner.ts +1272 -0
  130. package/src/agent/model-capabilities-core.ts +23 -0
  131. package/src/agent/model-capabilities.ts +231 -0
  132. package/src/agent/pi-failure-class.ts +83 -0
  133. package/src/agent/pi-jsonl-parser.ts +306 -0
  134. package/src/agent/preferences-prompt.ts +20 -0
  135. package/src/agent/user-error-messages.ts +78 -0
  136. package/src/bridges/discord.ts +171 -0
  137. package/src/bridges/slack.ts +177 -0
  138. package/src/bridges/teams.ts +160 -0
  139. package/src/bridges/telegram.ts +571 -0
  140. package/src/bridges/whatsapp.ts +290 -0
  141. package/src/chat-shim.ts +259 -0
  142. package/src/cli/mercury.ts +2508 -0
  143. package/src/cli/mrctl-http.ts +27 -0
  144. package/src/cli/mrctl.ts +611 -0
  145. package/src/cli/whatsapp-auth.ts +260 -0
  146. package/src/config-file.ts +397 -0
  147. package/src/config-model-chain.ts +30 -0
  148. package/src/config.ts +316 -0
  149. package/src/core/api-types.ts +58 -0
  150. package/src/core/api.ts +105 -0
  151. package/src/core/commands.ts +76 -0
  152. package/src/core/conversation.ts +47 -0
  153. package/src/core/handler.ts +206 -0
  154. package/src/core/media.ts +200 -0
  155. package/src/core/mute-duration.ts +22 -0
  156. package/src/core/outbox.ts +76 -0
  157. package/src/core/permissions.ts +192 -0
  158. package/src/core/profiles.ts +245 -0
  159. package/src/core/rate-limiter.ts +127 -0
  160. package/src/core/router.ts +191 -0
  161. package/src/core/routes/chat.ts +172 -0
  162. package/src/core/routes/config-builtin.ts +107 -0
  163. package/src/core/routes/config.ts +81 -0
  164. package/src/core/routes/connections.ts +190 -0
  165. package/src/core/routes/console.ts +668 -0
  166. package/src/core/routes/control.ts +46 -0
  167. package/src/core/routes/conversations.ts +66 -0
  168. package/src/core/routes/dashboard.ts +2491 -0
  169. package/src/core/routes/extensions.ts +37 -0
  170. package/src/core/routes/index.ts +14 -0
  171. package/src/core/routes/media.ts +72 -0
  172. package/src/core/routes/messages.ts +37 -0
  173. package/src/core/routes/mutes.ts +89 -0
  174. package/src/core/routes/prefs.ts +95 -0
  175. package/src/core/routes/roles.ts +125 -0
  176. package/src/core/routes/spaces.ts +60 -0
  177. package/src/core/routes/storage.ts +126 -0
  178. package/src/core/routes/tasks.ts +189 -0
  179. package/src/core/routes/tradestation.ts +268 -0
  180. package/src/core/routes/tts.ts +51 -0
  181. package/src/core/runtime.ts +1140 -0
  182. package/src/core/space-queue.ts +103 -0
  183. package/src/core/storage-cleanup.ts +140 -0
  184. package/src/core/storage-guard.ts +24 -0
  185. package/src/core/task-scheduler.ts +132 -0
  186. package/src/core/telegram-format.ts +178 -0
  187. package/src/core/trigger.ts +142 -0
  188. package/src/dashboard/index.html +729 -0
  189. package/src/dashboard/tokens.css +53 -0
  190. package/src/extensions/api.ts +252 -0
  191. package/src/extensions/catalog.ts +117 -0
  192. package/src/extensions/config-registry.ts +83 -0
  193. package/src/extensions/context.ts +36 -0
  194. package/src/extensions/hooks.ts +156 -0
  195. package/src/extensions/image-builder.ts +617 -0
  196. package/src/extensions/installer.ts +306 -0
  197. package/src/extensions/jobs.ts +122 -0
  198. package/src/extensions/loader.ts +271 -0
  199. package/src/extensions/permission-guard.ts +52 -0
  200. package/src/extensions/reserved.ts +28 -0
  201. package/src/extensions/skills.ts +123 -0
  202. package/src/extensions/types.ts +462 -0
  203. package/src/logger.ts +174 -0
  204. package/src/main.ts +586 -0
  205. package/src/server.ts +391 -0
  206. package/src/storage/db.ts +1624 -0
  207. package/src/storage/memory.ts +45 -0
  208. package/src/storage/pi-auth.ts +95 -0
  209. package/src/text/markdown.ts +117 -0
  210. package/src/text/rtl.ts +38 -0
  211. package/src/tradestation/host-api.ts +77 -0
  212. package/src/tradestation/pending-orders.ts +69 -0
  213. package/src/tts/azure.ts +52 -0
  214. package/src/tts/google.ts +128 -0
  215. package/src/tts/index.ts +8 -0
  216. package/src/tts/language.ts +20 -0
  217. package/src/tts/synthesize.ts +133 -0
  218. 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.