mercury-agent 0.5.0 → 0.5.2

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 (125) hide show
  1. package/README.md +452 -451
  2. package/container/Dockerfile +127 -127
  3. package/container/Dockerfile.base +109 -109
  4. package/container/Dockerfile.power +17 -17
  5. package/container/agent-package.json +8 -8
  6. package/container/build.sh +54 -54
  7. package/docs/ROADMAP.md +3 -4
  8. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
  9. package/docs/auth/dashboard.md +28 -28
  10. package/docs/auth/overview.md +109 -109
  11. package/docs/auth/whatsapp.md +173 -173
  12. package/docs/authoring-profiles.md +174 -174
  13. package/docs/configuration.md +54 -54
  14. package/docs/container-lifecycle.md +349 -349
  15. package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
  16. package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
  17. package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
  18. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
  19. package/docs/deployment.md +199 -199
  20. package/docs/extensions.md +375 -375
  21. package/docs/graceful-shutdown.md +62 -62
  22. package/docs/kb-distillation.md +77 -77
  23. package/docs/media/overview.md +140 -140
  24. package/docs/media/whatsapp.md +171 -171
  25. package/docs/memory.md +137 -137
  26. package/docs/permissions.md +217 -217
  27. package/docs/pipeline.md +228 -228
  28. package/docs/prd-chat-memory.md +76 -76
  29. package/docs/prd-config-load.md +82 -82
  30. package/docs/rate-limiting.md +229 -229
  31. package/docs/runbooks/publish-checklist.md +9 -1
  32. package/docs/scheduler.md +288 -288
  33. package/docs/setup-discord.md +100 -100
  34. package/docs/setup-slack.md +119 -119
  35. package/docs/setup-whatsapp.md +94 -94
  36. package/docs/subagents.md +166 -166
  37. package/docs/web-search.md +62 -62
  38. package/examples/extensions/README.md +12 -12
  39. package/examples/extensions/charts/index.ts +13 -13
  40. package/examples/extensions/charts/skill/SKILL.md +98 -98
  41. package/examples/extensions/gws/README.md +52 -52
  42. package/examples/extensions/gws/skill/SKILL.md +57 -57
  43. package/examples/extensions/gws/skill/references/calendar.md +101 -101
  44. package/examples/extensions/gws/skill/references/docs.md +65 -65
  45. package/examples/extensions/gws/skill/references/drive.md +79 -79
  46. package/examples/extensions/gws/skill/references/gmail.md +85 -85
  47. package/examples/extensions/gws/skill/references/sheets.md +60 -60
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -728
  49. package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
  50. package/examples/extensions/pdf/skill/SKILL.md +314 -314
  51. package/examples/extensions/pdf/skill/forms.md +294 -294
  52. package/examples/extensions/pdf/skill/reference.md +611 -611
  53. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
  54. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
  55. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
  56. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
  57. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
  58. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
  59. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
  60. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
  61. package/examples/extensions/permission-guard/index.ts +65 -65
  62. package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
  63. package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
  64. package/examples/extensions/pinchtab/skill/references/api.md +297 -297
  65. package/examples/extensions/pinchtab/skill/references/env.md +45 -45
  66. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
  67. package/examples/extensions/tradestation/host/refresh.ts +102 -102
  68. package/examples/extensions/tradestation/index.ts +153 -153
  69. package/examples/extensions/tradestation/skill/SKILL.md +67 -67
  70. package/examples/extensions/voice-synth/index.ts +94 -94
  71. package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
  72. package/examples/extensions/voice-transcribe/requirements.txt +8 -8
  73. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
  74. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
  75. package/examples/extensions/yahoo-mail/cli/package.json +13 -13
  76. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
  77. package/package.json +106 -106
  78. package/resources/agents/explore.md +50 -50
  79. package/resources/agents/worker.md +24 -24
  80. package/resources/connection-env-vars.json +25 -25
  81. package/resources/pi-extensions/subagent/agents.ts +126 -126
  82. package/resources/pi-extensions/subagent/index.ts +964 -964
  83. package/resources/profiles/coding/AGENTS.md +43 -43
  84. package/resources/profiles/coding/mercury-profile.yaml +15 -15
  85. package/resources/profiles/general/AGENTS.md +31 -31
  86. package/resources/profiles/general/mercury-profile.yaml +15 -15
  87. package/resources/profiles/research/AGENTS.md +40 -40
  88. package/resources/profiles/research/mercury-profile.yaml +15 -15
  89. package/resources/skills/config/SKILL.md +25 -25
  90. package/resources/skills/context/SKILL.md +33 -33
  91. package/resources/skills/conversation-recap/SKILL.md +19 -19
  92. package/resources/skills/mutes/SKILL.md +31 -31
  93. package/resources/skills/permissions/SKILL.md +19 -19
  94. package/resources/skills/preferences/SKILL.md +31 -31
  95. package/resources/skills/recall/SKILL.md +24 -24
  96. package/resources/skills/roles/SKILL.md +18 -18
  97. package/resources/skills/spaces/SKILL.md +18 -18
  98. package/resources/skills/tasks/SKILL.md +45 -45
  99. package/resources/templates/AGENTS.md +157 -157
  100. package/resources/templates/env.template +38 -38
  101. package/resources/templates/mercury.example.yaml +99 -99
  102. package/src/agent/container-entry.ts +1 -1
  103. package/src/agent/container-runner.ts +1345 -1346
  104. package/src/cli/mercury.ts +39 -7
  105. package/src/cli/mrctl.ts +636 -636
  106. package/src/config-file.ts +540 -540
  107. package/src/config.ts +339 -339
  108. package/src/core/api.ts +125 -125
  109. package/src/core/caller-token.ts +101 -101
  110. package/src/core/permissions.ts +228 -228
  111. package/src/core/profiles.ts +271 -271
  112. package/src/core/routes/capability.ts +70 -70
  113. package/src/core/routes/index.ts +15 -15
  114. package/src/core/runtime.ts +1530 -1530
  115. package/src/dashboard/index.html +729 -729
  116. package/src/extensions/api.ts +273 -273
  117. package/src/extensions/loader.ts +286 -286
  118. package/src/extensions/types.ts +517 -517
  119. package/src/main.ts +605 -605
  120. package/docs/pending-updates/applicative-profiles.md +0 -15
  121. package/docs/pending-updates/caller-bound-capability-token.md +0 -15
  122. package/docs/pending-updates/dm-auto-space.md +0 -15
  123. /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
  124. /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
  125. /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
@@ -1,62 +1,62 @@
1
- # Graceful Shutdown
2
-
3
- On `SIGTERM` or `SIGINT`, mercury tears down all components in order instead of exiting abruptly.
4
-
5
- ## Sequence
6
-
7
- ```
8
- SIGTERM/SIGINT received
9
-
10
- ├─1─► Stop scheduler (clear poll timer)
11
- ├─2─► Cancel all pending queue entries
12
- ├─3─► Kill running containers (docker kill)
13
- ├─4─► Wait for active work to drain (up to 8s)
14
- ├─5─► Disconnect adapters (WhatsApp socket, etc.)
15
- ├─6─► Stop HTTP server
16
- ├─7─► Close SQLite database
17
- └─8─► exit(0)
18
-
19
- Second signal → force exit(1)
20
- 10s timeout → force exit(1)
21
- ```
22
-
23
- ## Why this order
24
-
25
- 1. **Scheduler first** — prevents new work from being created while we're shutting down.
26
- 2. **Cancel pending queue entries** — no point starting queued work we'll just kill.
27
- 3. **Kill containers** — uses `docker kill` for reliable termination. Falls back to `SIGKILL` if the docker command fails. Containers are labeled with `mercury.managed=true` for identification (see [container-lifecycle.md](./container-lifecycle.md)).
28
- 4. **Wait for drain** — gives active container runs a chance to finish cleanly (up to 8s).
29
- 5. **Disconnect adapters** — closes the WhatsApp socket, Slack/Discord connections. Done after containers so in-flight replies can still be posted.
30
- 6. **Stop HTTP server** — stops accepting new webhook/API requests.
31
- 7. **Close DB last** — everything above may still write to the database (message storage, task updates), so the DB connection stays open until the very end.
32
-
33
- ## Safety mechanisms
34
-
35
- | Mechanism | Behavior |
36
- |-----------|----------|
37
- | **Double-signal** | Second SIGINT/SIGTERM forces immediate `exit(1)` |
38
- | **Global timeout** | If cleanup takes longer than 10s, forced `exit(1)` |
39
- | **Idempotent** | `shutdown()` is guarded by a `shuttingDown` flag — calling it twice is a no-op |
40
- | **Hook errors** | Individual shutdown hook failures are logged and swallowed — remaining cleanup continues |
41
-
42
- ## API
43
-
44
- ### `MercuryCoreRuntime`
45
-
46
- ```ts
47
- core.installSignalHandlers() // trap SIGTERM + SIGINT
48
- core.onShutdown(async () => {}) // register cleanup callback
49
- await core.shutdown(timeoutMs?) // trigger shutdown manually (default 10s)
50
- core.isShuttingDown // boolean
51
- ```
52
-
53
- ### Component methods used during shutdown
54
-
55
- | Component | Method | What it does |
56
- |-----------|--------|-------------|
57
- | `TaskScheduler` | `stop()` | Clears the poll timer |
58
- | `SpaceQueue` | `cancelAll()` | Drops all pending entries, returns count |
59
- | `SpaceQueue` | `waitForActive(ms)` | Resolves when active count hits 0 or timeout |
60
- | `AgentContainerRunner` | `killAll()` | Kill all running containers via `docker kill` |
61
- | `Db` | `close()` | Closes SQLite connection |
62
- | `WhatsAppBaileysAdapter` | `shutdown()` | Ends the Baileys socket |
1
+ # Graceful Shutdown
2
+
3
+ On `SIGTERM` or `SIGINT`, mercury tears down all components in order instead of exiting abruptly.
4
+
5
+ ## Sequence
6
+
7
+ ```
8
+ SIGTERM/SIGINT received
9
+
10
+ ├─1─► Stop scheduler (clear poll timer)
11
+ ├─2─► Cancel all pending queue entries
12
+ ├─3─► Kill running containers (docker kill)
13
+ ├─4─► Wait for active work to drain (up to 8s)
14
+ ├─5─► Disconnect adapters (WhatsApp socket, etc.)
15
+ ├─6─► Stop HTTP server
16
+ ├─7─► Close SQLite database
17
+ └─8─► exit(0)
18
+
19
+ Second signal → force exit(1)
20
+ 10s timeout → force exit(1)
21
+ ```
22
+
23
+ ## Why this order
24
+
25
+ 1. **Scheduler first** — prevents new work from being created while we're shutting down.
26
+ 2. **Cancel pending queue entries** — no point starting queued work we'll just kill.
27
+ 3. **Kill containers** — uses `docker kill` for reliable termination. Falls back to `SIGKILL` if the docker command fails. Containers are labeled with `mercury.managed=true` for identification (see [container-lifecycle.md](./container-lifecycle.md)).
28
+ 4. **Wait for drain** — gives active container runs a chance to finish cleanly (up to 8s).
29
+ 5. **Disconnect adapters** — closes the WhatsApp socket, Slack/Discord connections. Done after containers so in-flight replies can still be posted.
30
+ 6. **Stop HTTP server** — stops accepting new webhook/API requests.
31
+ 7. **Close DB last** — everything above may still write to the database (message storage, task updates), so the DB connection stays open until the very end.
32
+
33
+ ## Safety mechanisms
34
+
35
+ | Mechanism | Behavior |
36
+ |-----------|----------|
37
+ | **Double-signal** | Second SIGINT/SIGTERM forces immediate `exit(1)` |
38
+ | **Global timeout** | If cleanup takes longer than 10s, forced `exit(1)` |
39
+ | **Idempotent** | `shutdown()` is guarded by a `shuttingDown` flag — calling it twice is a no-op |
40
+ | **Hook errors** | Individual shutdown hook failures are logged and swallowed — remaining cleanup continues |
41
+
42
+ ## API
43
+
44
+ ### `MercuryCoreRuntime`
45
+
46
+ ```ts
47
+ core.installSignalHandlers() // trap SIGTERM + SIGINT
48
+ core.onShutdown(async () => {}) // register cleanup callback
49
+ await core.shutdown(timeoutMs?) // trigger shutdown manually (default 10s)
50
+ core.isShuttingDown // boolean
51
+ ```
52
+
53
+ ### Component methods used during shutdown
54
+
55
+ | Component | Method | What it does |
56
+ |-----------|--------|-------------|
57
+ | `TaskScheduler` | `stop()` | Clears the poll timer |
58
+ | `SpaceQueue` | `cancelAll()` | Drops all pending entries, returns count |
59
+ | `SpaceQueue` | `waitForActive(ms)` | Resolves when active count hits 0 or timeout |
60
+ | `AgentContainerRunner` | `killAll()` | Kill all running containers via `docker kill` |
61
+ | `Db` | `close()` | Closes SQLite connection |
62
+ | `WhatsAppBaileysAdapter` | `shutdown()` | Ends the Baileys socket |
@@ -1,77 +1,77 @@
1
- # KB Distillation
2
-
3
- KB distillation is **extension-based** in Mercury (not built-in).
4
-
5
- As of v0.3.x, Mercury no longer ships a built-in `kb-distill` extension. The recommended approach is to use a user-installed extension (for example, `napkin`) that runs a background job and writes distilled knowledge into each space vault.
6
-
7
- ---
8
-
9
- ## Recommended Setup
10
-
11
- Use the real example extension at:
12
-
13
- - `examples/extensions/napkin/`
14
-
15
- It demonstrates:
16
-
17
- - `workspace_init` hook to scaffold vault structure
18
- - `before_container` hook to set `NAPKIN_VAULT`
19
- - background job (`distill`) for periodic extraction
20
- - dashboard widget + extension store state
21
-
22
- ---
23
-
24
- ## How Distillation Works (Extension Pattern)
25
-
26
- Typical flow:
27
-
28
- 1. Read messages from `state.db` per `space_id`
29
- 2. Export messages to `.messages/YYYY-MM-DD.jsonl`
30
- 3. Detect changed files (hash compare)
31
- 4. Run `pi` with a distillation prompt against changed files
32
- 5. Update vault files incrementally (append/update, not destructive rewrite)
33
-
34
- This keeps runs idempotent and avoids re-processing unchanged days.
35
-
36
- ---
37
-
38
- ## Data Layout (napkin example)
39
-
40
- ```text
41
- .mercury/spaces/<space-id>/
42
- ├── .messages/
43
- │ └── YYYY-MM-DD.jsonl
44
- └── knowledge/
45
- ├── people/
46
- ├── projects/
47
- ├── references/
48
- ├── daily/
49
- └── templates/
50
- ```
51
-
52
- Message export format:
53
-
54
- ```json
55
- {"ts":1709123456,"role":"ambient","content":"Alice: Great idea!"}
56
- {"ts":1709123457,"role":"user","content":"What do you think about X?"}
57
- {"ts":1709123458,"role":"assistant","content":"I think..."}
58
- ```
59
-
60
- ---
61
-
62
- ## Configuration
63
-
64
- With the napkin example extension:
65
-
66
- - `MERCURY_KB_DISTILL_INTERVAL_MS=0` → disabled (default)
67
- - `MERCURY_KB_DISTILL_INTERVAL_MS=3600000` → check every hour
68
-
69
- You can also expose interval as extension config keys (see `examples/extensions/napkin/index.ts`).
70
-
71
- ---
72
-
73
- ## Notes
74
-
75
- - Distillation behavior depends on the installed extension implementation
76
- - Mercury core provides hooks, jobs, DB access, and workspace isolation
77
- - Distillation logic/prompt lives in the extension, not in core Mercury
1
+ # KB Distillation
2
+
3
+ KB distillation is **extension-based** in Mercury (not built-in).
4
+
5
+ As of v0.3.x, Mercury no longer ships a built-in `kb-distill` extension. The recommended approach is to use a user-installed extension (for example, `napkin`) that runs a background job and writes distilled knowledge into each space vault.
6
+
7
+ ---
8
+
9
+ ## Recommended Setup
10
+
11
+ Use the real example extension at:
12
+
13
+ - `examples/extensions/napkin/`
14
+
15
+ It demonstrates:
16
+
17
+ - `workspace_init` hook to scaffold vault structure
18
+ - `before_container` hook to set `NAPKIN_VAULT`
19
+ - background job (`distill`) for periodic extraction
20
+ - dashboard widget + extension store state
21
+
22
+ ---
23
+
24
+ ## How Distillation Works (Extension Pattern)
25
+
26
+ Typical flow:
27
+
28
+ 1. Read messages from `state.db` per `space_id`
29
+ 2. Export messages to `.messages/YYYY-MM-DD.jsonl`
30
+ 3. Detect changed files (hash compare)
31
+ 4. Run `pi` with a distillation prompt against changed files
32
+ 5. Update vault files incrementally (append/update, not destructive rewrite)
33
+
34
+ This keeps runs idempotent and avoids re-processing unchanged days.
35
+
36
+ ---
37
+
38
+ ## Data Layout (napkin example)
39
+
40
+ ```text
41
+ .mercury/spaces/<space-id>/
42
+ ├── .messages/
43
+ │ └── YYYY-MM-DD.jsonl
44
+ └── knowledge/
45
+ ├── people/
46
+ ├── projects/
47
+ ├── references/
48
+ ├── daily/
49
+ └── templates/
50
+ ```
51
+
52
+ Message export format:
53
+
54
+ ```json
55
+ {"ts":1709123456,"role":"ambient","content":"Alice: Great idea!"}
56
+ {"ts":1709123457,"role":"user","content":"What do you think about X?"}
57
+ {"ts":1709123458,"role":"assistant","content":"I think..."}
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Configuration
63
+
64
+ With the napkin example extension:
65
+
66
+ - `MERCURY_KB_DISTILL_INTERVAL_MS=0` → disabled (default)
67
+ - `MERCURY_KB_DISTILL_INTERVAL_MS=3600000` → check every hour
68
+
69
+ You can also expose interval as extension config keys (see `examples/extensions/napkin/index.ts`).
70
+
71
+ ---
72
+
73
+ ## Notes
74
+
75
+ - Distillation behavior depends on the installed extension implementation
76
+ - Mercury core provides hooks, jobs, DB access, and workspace isolation
77
+ - Distillation logic/prompt lives in the extension, not in core Mercury
@@ -1,140 +1,140 @@
1
- # Media Handling
2
-
3
- Mercury downloads and processes media attachments from chat platforms, saving them to space workspaces and passing them to pi for processing. Models can also produce files via the `outbox/` directory.
4
-
5
- ## Supported Platforms
6
-
7
- | Platform | Ingress | Egress | Details |
8
- |----------|---------|--------|---------|
9
- | WhatsApp | ✅ Baileys socket | ✅ image/video/audio/document | [whatsapp.md](./whatsapp.md) |
10
- | Discord | ✅ CDN URL download | ✅ channel.send() with files | Via `DiscordBridge` |
11
- | Slack | ✅ URL download (auth'd) | ✅ files.uploadV2 | Via `SlackBridge` |
12
-
13
- ## Architecture
14
-
15
- ```
16
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────┐
17
- │ Platform │────▶│ Bridge │────▶│ Runtime │────▶│ pi │
18
- │ │ │ (normalize) │ │ (store/pass) │ │ (view) │
19
- └──────────────┘ └──────────────┘ └──────────────┘ └─────────┘
20
- │ │
21
- ▼ ▼
22
- ┌──────────────┐ ┌──────────────┐
23
- │ Workspace │ │ Workspace │
24
- │ /inbox/ │ │ /outbox/ │
25
- └──────────────┘ └──────────────┘
26
- ```
27
-
28
- ## Media Types
29
-
30
- All platforms map to these generic types defined in `src/types.ts`:
31
-
32
- ```typescript
33
- type MediaType = "image" | "video" | "audio" | "voice" | "document";
34
-
35
- interface MessageAttachment {
36
- path: string; // Local file path
37
- type: MediaType; // Generic type
38
- mimeType: string; // MIME type (e.g., "image/jpeg")
39
- filename?: string; // Original filename if available
40
- sizeBytes?: number; // File size in bytes
41
- }
42
- ```
43
-
44
- ## Configuration
45
-
46
- | Env Variable | Default | Description |
47
- |--------------|---------|-------------|
48
- | `MERCURY_MEDIA_ENABLED` | `true` | Enable/disable media downloads |
49
- | `MERCURY_MEDIA_MAX_SIZE_MB` | `10` | Max file size to download (MB) |
50
-
51
- ## Storage
52
-
53
- ### Ingress (inbox/)
54
-
55
- Incoming media files are saved to the space workspace:
56
-
57
- ```
58
- .mercury/spaces/<space_id>/inbox/<timestamp>-<type>.<ext>
59
- ```
60
-
61
- Example:
62
- ```
63
- .mercury/spaces/whatsapp_123456_g_us/inbox/
64
- ├── 1709012345-image.jpg
65
- ├── 1709012400-voice.ogg
66
- └── 1709012500-document.pdf
67
- ```
68
-
69
- ### Egress (outbox/)
70
-
71
- The model writes files to `outbox/` during a container run. After exit, the runtime scans for files with `mtime >= startTime` and attaches them to the reply:
72
-
73
- ```
74
- .mercury/spaces/<space_id>/outbox/
75
- ├── chart.png
76
- └── summary.pdf
77
- ```
78
-
79
- Previous outbox files are not deleted — only new or modified files are sent. See [pipeline.md](../pipeline.md) for details.
80
-
81
- ## Database Schema
82
-
83
- Attachments are stored as JSON in the `messages.attachments` column:
84
-
85
- ```sql
86
- ALTER TABLE messages ADD COLUMN attachments TEXT;
87
- ```
88
-
89
- ```json
90
- [
91
- {
92
- "path": "/Users/.../media/1709012345-image.jpg",
93
- "type": "image",
94
- "mimeType": "image/jpeg",
95
- "sizeBytes": 12345
96
- }
97
- ]
98
- ```
99
-
100
- ## Prompt Format
101
-
102
- Attachments are passed to pi as XML:
103
-
104
- ```xml
105
- <attachments>
106
- <attachment type="image" path="/spaces/xxx/media/123-image.jpg" mime="image/jpeg" size="12345" />
107
- </attachments>
108
-
109
- @mercury what's in this image?
110
- ```
111
-
112
- Reply context includes media info:
113
-
114
- ```xml
115
- <reply_to name="John" jid="123@wa" message_id="ABC" media_type="image" media_mime="image/jpeg">
116
- Check out this sunset!
117
- </reply_to>
118
- ```
119
-
120
- ## pi Capabilities
121
-
122
- | Media Type | pi Support |
123
- |------------|------------|
124
- | Images (jpg, png, gif, webp) | ✅ Can view via `read` tool |
125
- | Voice/Audio | ❌ Cannot play — needs transcription |
126
- | Video | ❌ Cannot play — could extract frames |
127
- | Documents (txt, code) | ✅ Can read text-based files |
128
- | Documents (pdf, docx) | ❌ Cannot read binary formats |
129
-
130
- ## Voice transcription
131
-
132
- Voice and audio attachments are not playable inside pi. Install the **`voice-transcribe`** extension (see dashboard catalog or `examples/extensions/voice-transcribe/`) to append a text transcript before the agent runs.
133
-
134
- - **Default (`voice-transcribe.provider=local`)**: runs Python on the Mercury host; install deps from the extension’s `requirements.txt`. Set `voice-transcribe.local_engine` to `transformers` (default, e.g. [mike249/whisper-tiny-he-2](https://huggingface.co/mike249/whisper-tiny-he-2)) or `faster_whisper` for [CTranslate2](https://github.com/OpenNMT/CTranslate2) models on the Hub (e.g. [ivrit-ai](https://huggingface.co/ivrit-ai) `*-ct2` repos). See the extension skill for `MERCURY_VOICE_FW_COMPUTE_TYPE` and `MERCURY_VOICE_LANGUAGE`.
135
- - **API (`voice-transcribe.provider=api`)**: uses the [Hugging Face Inference API](https://huggingface.co/docs/api-inference) with `MERCURY_HF_TOKEN` — choose a model that has a Hub Inference Provider.
136
-
137
- ## Future Enhancements
138
-
139
- - [ ] Video frame extraction
140
- - [ ] PDF text extraction
1
+ # Media Handling
2
+
3
+ Mercury downloads and processes media attachments from chat platforms, saving them to space workspaces and passing them to pi for processing. Models can also produce files via the `outbox/` directory.
4
+
5
+ ## Supported Platforms
6
+
7
+ | Platform | Ingress | Egress | Details |
8
+ |----------|---------|--------|---------|
9
+ | WhatsApp | ✅ Baileys socket | ✅ image/video/audio/document | [whatsapp.md](./whatsapp.md) |
10
+ | Discord | ✅ CDN URL download | ✅ channel.send() with files | Via `DiscordBridge` |
11
+ | Slack | ✅ URL download (auth'd) | ✅ files.uploadV2 | Via `SlackBridge` |
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────┐
17
+ │ Platform │────▶│ Bridge │────▶│ Runtime │────▶│ pi │
18
+ │ │ │ (normalize) │ │ (store/pass) │ │ (view) │
19
+ └──────────────┘ └──────────────┘ └──────────────┘ └─────────┘
20
+ │ │
21
+ ▼ ▼
22
+ ┌──────────────┐ ┌──────────────┐
23
+ │ Workspace │ │ Workspace │
24
+ │ /inbox/ │ │ /outbox/ │
25
+ └──────────────┘ └──────────────┘
26
+ ```
27
+
28
+ ## Media Types
29
+
30
+ All platforms map to these generic types defined in `src/types.ts`:
31
+
32
+ ```typescript
33
+ type MediaType = "image" | "video" | "audio" | "voice" | "document";
34
+
35
+ interface MessageAttachment {
36
+ path: string; // Local file path
37
+ type: MediaType; // Generic type
38
+ mimeType: string; // MIME type (e.g., "image/jpeg")
39
+ filename?: string; // Original filename if available
40
+ sizeBytes?: number; // File size in bytes
41
+ }
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ | Env Variable | Default | Description |
47
+ |--------------|---------|-------------|
48
+ | `MERCURY_MEDIA_ENABLED` | `true` | Enable/disable media downloads |
49
+ | `MERCURY_MEDIA_MAX_SIZE_MB` | `10` | Max file size to download (MB) |
50
+
51
+ ## Storage
52
+
53
+ ### Ingress (inbox/)
54
+
55
+ Incoming media files are saved to the space workspace:
56
+
57
+ ```
58
+ .mercury/spaces/<space_id>/inbox/<timestamp>-<type>.<ext>
59
+ ```
60
+
61
+ Example:
62
+ ```
63
+ .mercury/spaces/whatsapp_123456_g_us/inbox/
64
+ ├── 1709012345-image.jpg
65
+ ├── 1709012400-voice.ogg
66
+ └── 1709012500-document.pdf
67
+ ```
68
+
69
+ ### Egress (outbox/)
70
+
71
+ The model writes files to `outbox/` during a container run. After exit, the runtime scans for files with `mtime >= startTime` and attaches them to the reply:
72
+
73
+ ```
74
+ .mercury/spaces/<space_id>/outbox/
75
+ ├── chart.png
76
+ └── summary.pdf
77
+ ```
78
+
79
+ Previous outbox files are not deleted — only new or modified files are sent. See [pipeline.md](../pipeline.md) for details.
80
+
81
+ ## Database Schema
82
+
83
+ Attachments are stored as JSON in the `messages.attachments` column:
84
+
85
+ ```sql
86
+ ALTER TABLE messages ADD COLUMN attachments TEXT;
87
+ ```
88
+
89
+ ```json
90
+ [
91
+ {
92
+ "path": "/Users/.../media/1709012345-image.jpg",
93
+ "type": "image",
94
+ "mimeType": "image/jpeg",
95
+ "sizeBytes": 12345
96
+ }
97
+ ]
98
+ ```
99
+
100
+ ## Prompt Format
101
+
102
+ Attachments are passed to pi as XML:
103
+
104
+ ```xml
105
+ <attachments>
106
+ <attachment type="image" path="/spaces/xxx/media/123-image.jpg" mime="image/jpeg" size="12345" />
107
+ </attachments>
108
+
109
+ @mercury what's in this image?
110
+ ```
111
+
112
+ Reply context includes media info:
113
+
114
+ ```xml
115
+ <reply_to name="John" jid="123@wa" message_id="ABC" media_type="image" media_mime="image/jpeg">
116
+ Check out this sunset!
117
+ </reply_to>
118
+ ```
119
+
120
+ ## pi Capabilities
121
+
122
+ | Media Type | pi Support |
123
+ |------------|------------|
124
+ | Images (jpg, png, gif, webp) | ✅ Can view via `read` tool |
125
+ | Voice/Audio | ❌ Cannot play — needs transcription |
126
+ | Video | ❌ Cannot play — could extract frames |
127
+ | Documents (txt, code) | ✅ Can read text-based files |
128
+ | Documents (pdf, docx) | ❌ Cannot read binary formats |
129
+
130
+ ## Voice transcription
131
+
132
+ Voice and audio attachments are not playable inside pi. Install the **`voice-transcribe`** extension (see dashboard catalog or `examples/extensions/voice-transcribe/`) to append a text transcript before the agent runs.
133
+
134
+ - **Default (`voice-transcribe.provider=local`)**: runs Python on the Mercury host; install deps from the extension’s `requirements.txt`. Set `voice-transcribe.local_engine` to `transformers` (default, e.g. [mike249/whisper-tiny-he-2](https://huggingface.co/mike249/whisper-tiny-he-2)) or `faster_whisper` for [CTranslate2](https://github.com/OpenNMT/CTranslate2) models on the Hub (e.g. [ivrit-ai](https://huggingface.co/ivrit-ai) `*-ct2` repos). See the extension skill for `MERCURY_VOICE_FW_COMPUTE_TYPE` and `MERCURY_VOICE_LANGUAGE`.
135
+ - **API (`voice-transcribe.provider=api`)**: uses the [Hugging Face Inference API](https://huggingface.co/docs/api-inference) with `MERCURY_HF_TOKEN` — choose a model that has a Hub Inference Provider.
136
+
137
+ ## Future Enhancements
138
+
139
+ - [ ] Video frame extraction
140
+ - [ ] PDF text extraction