mercury-agent 0.16.0 → 0.16.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.
- package/README.md +6 -0
- package/container/Dockerfile +6 -1
- package/container/Dockerfile.base +6 -1
- package/docs/README.md +50 -0
- package/docs/authoring-profiles.md +8 -0
- package/docs/behavior-layers.md +193 -0
- package/docs/extensions.md +2 -0
- package/docs/goals/football-reporter-profile/decisions.md +479 -0
- package/docs/goals/football-reporter-profile/goal.md +91 -0
- package/docs/goals/football-reporter-profile/roadmap.md +196 -0
- package/docs/goals/whatsapp-bot-hardening/README.md +43 -0
- package/docs/goals/whatsapp-bot-hardening/archive/ambient-group-context.md +211 -0
- package/docs/goals/whatsapp-bot-hardening/archive/command-routing-consistency.md +210 -0
- package/docs/goals/whatsapp-bot-hardening/archive/destructive-command-guards.md +218 -0
- package/docs/goals/whatsapp-bot-hardening/archive/docs-extension.md +182 -0
- package/docs/goals/whatsapp-bot-hardening/archive/handoff-2026-08-09.md +588 -0
- package/docs/goals/whatsapp-bot-hardening/archive/media-size-and-silent-drop.md +178 -0
- package/docs/goals/whatsapp-bot-hardening/archive/member-memory.md +199 -0
- package/docs/goals/whatsapp-bot-hardening/archive/message-author-attribution.md +211 -0
- package/docs/goals/whatsapp-bot-hardening/archive/run-from-source-switchover.md +212 -0
- package/docs/goals/whatsapp-bot-hardening/archive/setup-plan-2026-08-06.md +1117 -0
- package/docs/goals/whatsapp-bot-hardening/decisions.md +200 -0
- package/docs/goals/whatsapp-bot-hardening/goal.md +75 -0
- package/docs/goals/whatsapp-bot-hardening/open-threads.md +396 -0
- package/docs/goals/whatsapp-bot-hardening/roadmap.md +245 -0
- package/docs/memory.md +30 -0
- package/docs/profile-guide.md +483 -0
- package/docs/refactor/.gitkeep +0 -0
- package/docs/refactor/archive/.gitkeep +0 -0
- package/docs/refactor/audits/.gitkeep +0 -0
- package/docs/refactor/backlog/.gitkeep +0 -0
- package/docs/skills-guide.md +154 -0
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/feed-watch/config.ts +354 -0
- package/examples/extensions/feed-watch/digest.ts +344 -0
- package/examples/extensions/feed-watch/feeds.ts +341 -0
- package/examples/extensions/feed-watch/index.ts +302 -0
- package/examples/extensions/feed-watch/items.ts +171 -0
- package/examples/extensions/feed-watch/match.ts +107 -0
- package/examples/extensions/feed-watch/prompts/verify.md +35 -0
- package/examples/extensions/feed-watch/skill/SKILL.md +128 -0
- package/examples/extensions/feed-watch/watch.ts +548 -0
- package/examples/extensions/longview/hook.ts +179 -16
- package/examples/extensions/longview/index.ts +13 -2
- package/examples/extensions/longview/prompts/summarize.md +3 -2
- package/examples/extensions/longview/render/telegraph-nodes.ts +21 -1
- package/examples/extensions/longview/summarize.ts +57 -14
- package/examples/extensions/napkin/index.ts +334 -86
- package/examples/extensions/napkin/pi-spawn.ts +196 -0
- package/examples/extensions/pinchtab/index.ts +37 -4
- package/examples/extensions/pinchtab/lib/session-injector.ts +31 -9
- package/examples/extensions/pinchtab/skill/SKILL.md +29 -0
- package/examples/profiles/_template/AGENTS.md +138 -0
- package/examples/profiles/_template/README.md +65 -0
- package/examples/profiles/_template/config.yaml +57 -0
- package/examples/profiles/_template/tasks/daily.md +32 -0
- package/examples/profiles/football-reporter/AGENTS.md +219 -0
- package/examples/profiles/football-reporter/README.md +138 -0
- package/examples/profiles/football-reporter/config.yaml +174 -0
- package/examples/profiles/football-reporter/seed/MEMORY.md +42 -0
- package/examples/profiles/football-reporter/seed/episodes/barcelona-2026-27.md +25 -0
- package/examples/profiles/football-reporter/seed/episodes/beitar-jerusalem-2026-27.md +25 -0
- package/examples/profiles/football-reporter/seed/episodes/maccabi-haifa-2026-27.md +24 -0
- package/examples/profiles/football-reporter/seed/episodes/man-united-2026-27.md +26 -0
- package/examples/profiles/football-reporter/seed/episodes/real-madrid-2026-27.md +25 -0
- package/examples/profiles/football-reporter/seed/napkin-distill.md +55 -0
- package/examples/profiles/football-reporter/tasks/daily-article.md +142 -0
- package/package.json +8 -5
- package/src/adapters/whatsapp.ts +75 -13
- package/src/bridges/whatsapp.ts +6 -4
- package/src/cli/mrctl.ts +9 -1
- package/src/core/handler.ts +51 -6
- package/src/core/routes/dashboard.ts +106 -5
- package/src/core/routes/tasks.ts +28 -0
- package/src/core/runtime.ts +63 -2
- package/src/core/task-scheduler.ts +103 -12
- package/src/extensions/catalog.ts +9 -0
- package/src/profile/space-profile.ts +780 -0
- package/src/storage/db.ts +215 -0
- package/src/types.ts +56 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Destructive Command Guards
|
|
2
|
+
|
|
3
|
+
**Status**: Done
|
|
4
|
+
**Slug**: destructive-command-guards
|
|
5
|
+
**Goal**: whatsapp-bot-hardening
|
|
6
|
+
**Milestone**: 3
|
|
7
|
+
**Created**: 2026-08-09
|
|
8
|
+
**Last updated**: 2026-08-09
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
> Part of goal [whatsapp-bot-hardening](../goals/whatsapp-bot-hardening/goal.md).
|
|
15
|
+
> HANDOFF Bugs 5.1, 5.4–5.5, 6a, 6c — the defects that destroy data, lock you
|
|
16
|
+
> out, or disable the emergency brake.
|
|
17
|
+
|
|
18
|
+
Four small, high-severity fixes: (1) `mrctl spaces delete <id>` silently
|
|
19
|
+
ignores its argument and always deletes the **current** space — this is what
|
|
20
|
+
actually destroyed the baduk space (559 messages, recovered from the WAL);
|
|
21
|
+
(2) the chat delete-confirmation can never match a WhatsApp *reply* because the
|
|
22
|
+
adapter appends a `<reply_to>` block, and pending state is cleared before
|
|
23
|
+
matching; (3) `/stop`, `/compact`, `/clear` with a slash route to the model
|
|
24
|
+
instead of executing — so the abort command *queues another run*; (4)
|
|
25
|
+
`/spaces unlink` in a group is an unrecoverable self-lockout, also reachable
|
|
26
|
+
without any command via `onGroupRemoval` + re-add.
|
|
27
|
+
|
|
28
|
+
## User Stories
|
|
29
|
+
|
|
30
|
+
- As an admin, I want `mrctl spaces delete <id>` to refuse loudly rather than
|
|
31
|
+
delete a different space than the one I named.
|
|
32
|
+
- As an admin, I want to confirm a cross-space delete by *replying* "yes" —
|
|
33
|
+
the literal instruction the prompt gives — and have it delete the named space.
|
|
34
|
+
- As any user, I want `/stop` to actually abort a runaway agent.
|
|
35
|
+
- As an admin, I want a group that got unlinked (by command or by bot
|
|
36
|
+
removal/re-add) to be recoverable from chat.
|
|
37
|
+
|
|
38
|
+
## MVP Scope
|
|
39
|
+
|
|
40
|
+
**In scope:** mrctl argument rejection + usage text; shared
|
|
41
|
+
confirmation-text normalization (strip `<reply_to>` etc.); pending-state
|
|
42
|
+
lifecycle fix; `/`-tolerant `CHAT_COMMANDS` lookup + honest help wording;
|
|
43
|
+
unlinked conversations stay eligible for link-restoring commands; re-link (or
|
|
44
|
+
prompt) on group re-join; removal of `/spaces unlink` from chat (D-009).
|
|
45
|
+
|
|
46
|
+
**Out of scope:** a `DELETE /api/spaces/:id` route — explicitly forbidden
|
|
47
|
+
(D-006); requiring the confirmation to echo the target id — recommended,
|
|
48
|
+
tracked as an open question; the rest of the command-surface audit (M3.2).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Context for Claude
|
|
53
|
+
|
|
54
|
+
- `src/cli/mrctl.ts:384` (the argument-ignoring delete) and `:85` (usage line).
|
|
55
|
+
- `src/core/runtime.ts:911-933` (chat delete flow — correct),
|
|
56
|
+
`:953` (`handlePendingSpaceDelete`, the `text === "yes"` match),
|
|
57
|
+
`:981-982` (pending state cleared before matching).
|
|
58
|
+
- `src/adapters/whatsapp.ts:609` — where `<reply_to …>` is appended to text.
|
|
59
|
+
- `src/core/router.ts:39-43` (`CHAT_COMMANDS` whole-prompt match) and
|
|
60
|
+
`src/core/commands.ts:15-67` (`SLASH_COMMANDS`).
|
|
61
|
+
- `src/core/conversation.ts:127` (unlink lockout) and `src/core/handler.ts:190-197`
|
|
62
|
+
(drop gate upstream of routing); `main.ts` `onGroupRemoval`.
|
|
63
|
+
- `resources/skills/spaces/SKILL.md` — agent-facing docs to make emphatic.
|
|
64
|
+
- `HANDOFF.md` Bugs 5, 6a, 6c — full analysis; 6b's correction of the incident
|
|
65
|
+
account (the agent's `mrctl` call, not the chat command, killed baduk).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Architecture & Data
|
|
70
|
+
|
|
71
|
+
### Data Models
|
|
72
|
+
|
|
73
|
+
None.
|
|
74
|
+
|
|
75
|
+
### API Contracts
|
|
76
|
+
|
|
77
|
+
Unchanged by design — the `/current`-only API is a security boundary (D-006).
|
|
78
|
+
|
|
79
|
+
### File & Folder Structure
|
|
80
|
+
|
|
81
|
+
| Path | New / Modified | Purpose |
|
|
82
|
+
|------|---------------|---------|
|
|
83
|
+
| `src/cli/mrctl.ts` | Modified | Reject `spaces delete` argument with explanatory error; fix usage line |
|
|
84
|
+
| `src/core/runtime.ts` | Modified | Confirmation normalization + pending-state lifecycle reorder |
|
|
85
|
+
| `src/core/` (new helper) | New | Shared `normalizeConfirmationText()` — strip `<reply_to>` block/appended context before compare |
|
|
86
|
+
| `src/core/router.ts` | Modified | Strip leading `/` before `CHAT_COMMANDS` lookup |
|
|
87
|
+
| `src/core/commands.ts` | Modified | Remove `spaces unlink` from chat surface; help wording for bare/slash commands |
|
|
88
|
+
| `src/core/conversation.ts` + `src/core/handler.ts` | Modified | Unlinked conversations route link-restoring commands only |
|
|
89
|
+
| `main.ts` | Modified | Re-link or prompt on group re-join |
|
|
90
|
+
| `resources/skills/spaces/SKILL.md` | Modified | State mrctl is current-space-only, cross-space delete is chat-admin-only |
|
|
91
|
+
|
|
92
|
+
### Implementation Sequence
|
|
93
|
+
|
|
94
|
+
1. **mrctl argument rejection** (the load-bearing fix per HANDOFF 6b):
|
|
95
|
+
`mrctl spaces delete <arg>` → exit non-zero with
|
|
96
|
+
"only deletes the CURRENT space and takes no argument; use chat
|
|
97
|
+
`/spaces delete <id>` as an admin" — and issue **no** HTTP request. Fix the
|
|
98
|
+
usage line to `spaces list|name [<name>]|delete` with "(current space only)".
|
|
99
|
+
- Read first: `src/cli/mrctl.ts:80-90, 380-390`
|
|
100
|
+
- Verify: unit — with arg: non-zero, no request; without arg: still works.
|
|
101
|
+
2. **Confirmation normalization helper** — strip `<reply_to …>…</reply_to>`
|
|
102
|
+
(and any appended context) before the `yes`/`no` compare; use it in
|
|
103
|
+
`handlePendingSpaceDelete`. Build as a shared helper for future flows.
|
|
104
|
+
- Read first: `src/core/runtime.ts:940-990`, `src/adapters/whatsapp.ts:600-615`
|
|
105
|
+
- Verify: unit — "yes" with an appended `<reply_to>` block matches (**the
|
|
106
|
+
baduk regression test**).
|
|
107
|
+
3. **Pending-state lifecycle** — clear pending only on match, explicit `no`,
|
|
108
|
+
or expiry; an unrecognized message leaves it pending and falls through.
|
|
109
|
+
- Read first: `src/core/runtime.ts:953-990`
|
|
110
|
+
- Verify: unit — unrelated message does not clear pending; `no` and expiry do;
|
|
111
|
+
deleting space A from space B deletes A, not B.
|
|
112
|
+
4. **`/stop`/`/compact`/`/clear` routing** — strip a leading `/` before the
|
|
113
|
+
`CHAT_COMMANDS` lookup (or register leaf entries in `SLASH_COMMANDS`);
|
|
114
|
+
reword `/help`'s "Bare commands" section to say the slash form works.
|
|
115
|
+
- Read first: `src/core/router.ts:35-50`, `src/core/commands.ts:15-67`
|
|
116
|
+
- Verify: unit — `/stop`, `/compact`, `/clear` route to `type: "command"`
|
|
117
|
+
and never reach the assistant path.
|
|
118
|
+
5. **Unlink lockout** — unlinked conversations remain routable for
|
|
119
|
+
link-restoring commands only (`/spaces switch`, `/spaces list`); ordinary
|
|
120
|
+
messages still dropped. Remove `/spaces unlink` from the chat surface
|
|
121
|
+
(D-009). On group re-join (`onGroupRemoval` counterpart), re-link or prompt.
|
|
122
|
+
- Read first: `src/core/conversation.ts:110-135`, `src/core/handler.ts:185-200`,
|
|
123
|
+
`main.ts` (group membership handlers)
|
|
124
|
+
- Verify: unit — after `unlinkConversation`, `/spaces switch <id>` routes
|
|
125
|
+
and re-links; an ordinary message is still dropped.
|
|
126
|
+
6. **Docs** — make `resources/skills/spaces/SKILL.md` emphatic that `mrctl` is
|
|
127
|
+
current-space-only (so the *agent* never repeats the baduk mistake).
|
|
128
|
+
- Read first: that SKILL.md
|
|
129
|
+
- Verify: text review; no behaviour to test.
|
|
130
|
+
|
|
131
|
+
Steps are independent except 2→3 (same function).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Non-Negotiable Rules
|
|
136
|
+
|
|
137
|
+
- **Never** add a cross-space delete route to the API (D-006).
|
|
138
|
+
- The mrctl fix must fail **closed**: argument present → no request sent at all.
|
|
139
|
+
- A stray message must never silently cancel a pending delete.
|
|
140
|
+
- All manual destructive testing happens in **throwaway** spaces/groups only.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Edge Cases & Risks
|
|
145
|
+
|
|
146
|
+
| Scenario | Handling |
|
|
147
|
+
|----------|---------|
|
|
148
|
+
| Reply "yes" carries other appended context beyond `<reply_to>` | Normalization strips known appended blocks, then trims — covered by unit tests per adapter format |
|
|
149
|
+
| Pending delete never confirmed | Expiry clears it (existing timeout retained) |
|
|
150
|
+
| `/stop` while nothing is running | Command executes as today's bare `stop` does — no model call |
|
|
151
|
+
| Group re-added but user declines re-link | Conversation stays unlinked but commands still route — no lockout |
|
|
152
|
+
| Agent (not human) types the destructive mrctl call | Step 1 makes it error; step 6 teaches it not to try |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Implementation Checklist
|
|
157
|
+
|
|
158
|
+
### Phase 1 — Goal
|
|
159
|
+
- [x] Goal, User Stories, and MVP Scope written and reviewed
|
|
160
|
+
|
|
161
|
+
### Phase 2 — Architecture
|
|
162
|
+
- [x] Architecture & Data section complete
|
|
163
|
+
- [x] Non-Negotiable Rules defined
|
|
164
|
+
- [x] Edge Cases covered
|
|
165
|
+
- [x] Context for Claude pointers filled in
|
|
166
|
+
|
|
167
|
+
### Implementation (commit `c44720f`)
|
|
168
|
+
- [x] mrctl rejection + usage (step 1)
|
|
169
|
+
- [x] Confirmation normalization (step 2)
|
|
170
|
+
- [x] Pending-state lifecycle (step 3)
|
|
171
|
+
- [x] Slash-tolerant CHAT_COMMANDS + help wording (step 4)
|
|
172
|
+
- [x] Unlink recovery + removal from chat (step 5; re-join handling deferred —
|
|
173
|
+
see retrospective)
|
|
174
|
+
- [x] SKILL.md wording (step 6)
|
|
175
|
+
- [x] 21 unit tests green, incl. the baduk regression; `bun run check` passes
|
|
176
|
+
- [ ] **Manual (needs a phone, throwaway space):** `/spaces delete <other-id>`
|
|
177
|
+
→ **reply** "yes" → named space dies, current survives
|
|
178
|
+
- [ ] **Manual (needs a phone, throwaway group):** unlink then re-link from chat
|
|
179
|
+
- [x] No secrets or `.env` files committed
|
|
180
|
+
- [x] No unrelated files modified
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Open Questions
|
|
185
|
+
|
|
186
|
+
- [ ] Require the confirmation to echo the target id (`yes cmp-opus`) for
|
|
187
|
+
cross-space deletes? Recommended in HANDOFF — owner: user
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Retrospective
|
|
192
|
+
|
|
193
|
+
**Residual risks / follow-ups:**
|
|
194
|
+
- Re-linking on group re-join is **not** automatic. It was in the spec, but
|
|
195
|
+
chat recovery makes the lockout recoverable, which was the actual defect —
|
|
196
|
+
an admin types `/spaces switch <id>` and the group is back. Auto-relink on
|
|
197
|
+
rejoin would silently re-attach a group the bot was removed from, which is a
|
|
198
|
+
worse default. Accepted, deliberately.
|
|
199
|
+
|
|
200
|
+
**What changed from the plan:**
|
|
201
|
+
- The confirmation fix became a small shared module (`src/core/confirmation.ts`)
|
|
202
|
+
rather than an inline strip, because every future confirmation flow has the
|
|
203
|
+
same problem — the adapter appends context to *all* inbound text.
|
|
204
|
+
- Normalization **removes** the `<reply_to>` block instead of searching inside
|
|
205
|
+
it. Searching would have let a quoted message containing "yes" confirm a
|
|
206
|
+
delete on the user's behalf; a test pins that it cannot.
|
|
207
|
+
|
|
208
|
+
**Key decisions made during implementation:**
|
|
209
|
+
- The mrctl guard fails *before* the HTTP call, and the test asserts zero
|
|
210
|
+
requests reached a stub server. "Warns and then deletes anyway" would have
|
|
211
|
+
looked identical in a log-only test.
|
|
212
|
+
- `/spaces unlink` returns an explanation rather than disappearing, so someone
|
|
213
|
+
with the muscle memory learns why instead of getting "unknown verb".
|
|
214
|
+
- Recovery while unlinked is restricted to `list` and `switch`. Nothing that
|
|
215
|
+
mutates a space is reachable from a conversation that has no space.
|
|
216
|
+
|
|
217
|
+
**Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
|
|
218
|
+
- [ ] none expected
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# `docs` Extension — Real Document Creation
|
|
2
|
+
|
|
3
|
+
**Status**: Done
|
|
4
|
+
**Slug**: docs-extension
|
|
5
|
+
**Goal**: whatsapp-bot-hardening
|
|
6
|
+
**Milestone**: 4
|
|
7
|
+
**Created**: 2026-08-09
|
|
8
|
+
**Last updated**: 2026-08-09
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
> Part of goal [whatsapp-bot-hardening](../goals/whatsapp-bot-hardening/goal.md).
|
|
15
|
+
> HANDOFF Bug 4.
|
|
16
|
+
|
|
17
|
+
Asked to make a document, the bot attaches HTML + markdown and tells the user
|
|
18
|
+
to convert it themselves — because no document-writing capability exists: the
|
|
19
|
+
container has python3/node/bun but no `python-docx` and no `pandoc`. Egress
|
|
20
|
+
already works (the HTML attachment proves it); only *producing* office files is
|
|
21
|
+
missing. Ship a `docs` extension modeled directly on the existing `pdf`
|
|
22
|
+
extension (`index.ts` + skill dir; its `install` string is compiled into the
|
|
23
|
+
derived image by `extensions/image-builder.ts`) — D-005.
|
|
24
|
+
|
|
25
|
+
## User Stories
|
|
26
|
+
|
|
27
|
+
- As a group member (not just admin), I want to say "make me a doc with X" and
|
|
28
|
+
receive a real `.docx` in WhatsApp that opens in Word/Google Docs.
|
|
29
|
+
- As a user, I want spreadsheets (`openpyxl`) and ODF output available for the
|
|
30
|
+
same flow.
|
|
31
|
+
|
|
32
|
+
## MVP Scope
|
|
33
|
+
|
|
34
|
+
**In scope:** the extension (`index.ts` with install string + permission +
|
|
35
|
+
skill), `SKILL.md` teaching the agent to build documents and drop them in
|
|
36
|
+
`outbox/`, derived-image rebuild, member-role access.
|
|
37
|
+
|
|
38
|
+
**Out of scope:** Google Docs *links* (Workspace extension + OAuth — separate
|
|
39
|
+
project, per HANDOFF); templates/branding for generated documents.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Context for Claude
|
|
44
|
+
|
|
45
|
+
- `examples/extensions/pdf/index.ts` + `examples/extensions/pdf/skill/SKILL.md`
|
|
46
|
+
— the model to copy (structure, install-string idiom, outbox flow).
|
|
47
|
+
- `src/extensions/image-builder.ts` — how `install` strings are grouped and
|
|
48
|
+
compiled into the derived image.
|
|
49
|
+
- `container/Dockerfile` — base image contents (python3 + pip present).
|
|
50
|
+
- `PLAN.md` "Web browsing" section — hard-won install-string traps: **never put
|
|
51
|
+
a backtick inside the TS template literal**, and validate with
|
|
52
|
+
`mercury extensions validate <name>` before restarting.
|
|
53
|
+
- `HANDOFF.md` Bug 4 — the exact `install` string to use.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Architecture & Data
|
|
58
|
+
|
|
59
|
+
### Data Models
|
|
60
|
+
|
|
61
|
+
None.
|
|
62
|
+
|
|
63
|
+
### API Contracts
|
|
64
|
+
|
|
65
|
+
None (extension surface only).
|
|
66
|
+
|
|
67
|
+
### File & Folder Structure
|
|
68
|
+
|
|
69
|
+
| Path | New / Modified | Purpose |
|
|
70
|
+
|------|---------------|---------|
|
|
71
|
+
| `examples/extensions/docs/index.ts` | New | CLI registration + install string + permissions |
|
|
72
|
+
| `examples/extensions/docs/skill/SKILL.md` | New | Teach: compose → build `.docx`/`.xlsx`/`.odt` → write to `outbox/` |
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// index.ts core (from HANDOFF):
|
|
76
|
+
mercury.cli({
|
|
77
|
+
name: "docs",
|
|
78
|
+
install: "apt-get update && apt-get install -y --no-install-recommends pandoc && "
|
|
79
|
+
+ "python3 -m pip install --break-system-packages python-docx openpyxl odfpy && "
|
|
80
|
+
+ "rm -rf /var/lib/apt/lists/*",
|
|
81
|
+
});
|
|
82
|
+
mercury.permission({ defaultRoles: ["admin", "member"] });
|
|
83
|
+
mercury.skill("./skill");
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Implementation Sequence
|
|
87
|
+
|
|
88
|
+
1. Create the extension from the `pdf` template with the install string above.
|
|
89
|
+
No backticks anywhere inside the template literal.
|
|
90
|
+
- Read first: `examples/extensions/pdf/index.ts`, `pdf/skill/SKILL.md`
|
|
91
|
+
- Verify: `mercury extensions validate docs` passes.
|
|
92
|
+
2. Write `SKILL.md`: when asked for a document, build it with
|
|
93
|
+
python-docx/pandoc, save to `outbox/`, confirm delivery; prefer `.docx` for
|
|
94
|
+
"doc", `.xlsx` for tables, pandoc for markdown→docx conversion.
|
|
95
|
+
- Read first: `pdf/skill/SKILL.md` (tone + outbox conventions)
|
|
96
|
+
- Verify: skill loads; text review.
|
|
97
|
+
3. Install into the live project (path install, like pinchtab in PLAN.md) and
|
|
98
|
+
let the derived image rebuild (~3-4 min, background).
|
|
99
|
+
- Read first: PLAN.md extension-install trap notes
|
|
100
|
+
- Verify: image build succeeds; `docker run --rm --entrypoint python3
|
|
101
|
+
mercury-agent-ext:<hash> -c "import docx, openpyxl"` works and
|
|
102
|
+
`pandoc --version` runs in the derived image.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Non-Negotiable Rules
|
|
107
|
+
|
|
108
|
+
- No backticks inside the install template literal (breaks the whole extension
|
|
109
|
+
load — symptom: extension shows "uninstalled" in dashboard).
|
|
110
|
+
- Validate with `mercury extensions validate docs` before any restart.
|
|
111
|
+
- Generated files go through `outbox/` — no new egress path.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Edge Cases & Risks
|
|
116
|
+
|
|
117
|
+
| Scenario | Handling |
|
|
118
|
+
|----------|---------|
|
|
119
|
+
| Derived image rebuild slow first boot | Expected; background build, note in test plan |
|
|
120
|
+
| `pip --break-system-packages` conflicts | Pinned minimal set; same idiom other extensions use |
|
|
121
|
+
| Member permission not applied | Explicit manual test: member (not admin) triggers doc creation |
|
|
122
|
+
| `npm update -g` reverting extensions | Moot after M1.1 — we run from source |
|
|
123
|
+
| Hebrew/RTL content in docx | Include an RTL paragraph in the manual test (real user language) |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Implementation Checklist
|
|
128
|
+
|
|
129
|
+
### Phase 1 — Goal
|
|
130
|
+
- [x] Goal, User Stories, and MVP Scope written and reviewed
|
|
131
|
+
|
|
132
|
+
### Phase 2 — Architecture
|
|
133
|
+
- [x] Architecture & Data section complete
|
|
134
|
+
- [x] Non-Negotiable Rules defined
|
|
135
|
+
- [x] Edge Cases covered
|
|
136
|
+
- [x] Context for Claude pointers filled in
|
|
137
|
+
|
|
138
|
+
### Implementation (commit `e1208b8`)
|
|
139
|
+
- [x] Extension created + `mercury extensions validate docs` passes
|
|
140
|
+
- [x] SKILL.md written (step 2)
|
|
141
|
+
- [x] Installed; derived image builds; `import docx, openpyxl, odf` and
|
|
142
|
+
`pandoc 3.1.3` verified **inside** the image
|
|
143
|
+
- [x] `bun run check` passes
|
|
144
|
+
- [x] **Verified end to end:** asked the bot for a packing list → produced a
|
|
145
|
+
real "Microsoft Word 2007+" file, parsed back to a `Heading 1` and three
|
|
146
|
+
`List Bullet` paragraphs
|
|
147
|
+
- [ ] **Manual (needs a phone):** a member (not admin) triggers it over WhatsApp
|
|
148
|
+
- [x] No secrets or `.env` files committed
|
|
149
|
+
- [x] No unrelated files modified
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Open Questions
|
|
154
|
+
|
|
155
|
+
- [ ] none
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Retrospective
|
|
160
|
+
|
|
161
|
+
**Residual risks / follow-ups:**
|
|
162
|
+
- Google Workspace links extension — ideas/gws-docs-links.md (create if wanted)
|
|
163
|
+
|
|
164
|
+
**What changed from the plan:**
|
|
165
|
+
- Nothing structural — the `pdf` extension was a faithful model and the
|
|
166
|
+
install string from HANDOFF worked as written.
|
|
167
|
+
|
|
168
|
+
**Key decisions made during implementation:**
|
|
169
|
+
- The skill spends as much space on what *not* to do as on how: no markdown or
|
|
170
|
+
HTML as a substitute for a requested document, no claiming a file was saved
|
|
171
|
+
without writing it, no promising a Google Docs link. Those are the exact
|
|
172
|
+
behaviours this replaces, and a skill that only shows the happy path would
|
|
173
|
+
not have displaced them.
|
|
174
|
+
- RTL guidance included up front. The documents actually requested here are
|
|
175
|
+
frequently Hebrew, and Word mis-aligns them unless the paragraph is marked —
|
|
176
|
+
a "working" file that looks broken would have read as the feature failing.
|
|
177
|
+
- Verified by parsing the produced file, not by trusting the bot's report that
|
|
178
|
+
it had saved one. The whole class of bug in this goal is a confident report
|
|
179
|
+
that does not match what happened.
|
|
180
|
+
|
|
181
|
+
**Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
|
|
182
|
+
- [ ] New package/module added (`docs` extension)
|