mercury-agent 0.24.3-beta.3 → 0.24.3-beta.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 (94) hide show
  1. package/container/Dockerfile +1 -0
  2. package/container/Dockerfile.base +1 -0
  3. package/docs/behavior-layers.md +3 -1
  4. package/docs/configuration.md +2 -2
  5. package/docs/pending-verification.md +48 -3
  6. package/docs/permissions.md +1 -0
  7. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S7.md +2 -2
  8. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/decisions.md +2 -2
  9. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/plan.md +12 -20
  10. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S12.md +47 -0
  11. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S7.md +75 -0
  12. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S8.md +66 -0
  13. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S9.md +42 -0
  14. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/D1.md +64 -0
  15. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S1.md +72 -0
  16. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S2.md +63 -0
  17. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S3.md +63 -0
  18. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/T1.md +33 -0
  19. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/decisions.md +7 -0
  20. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/plan.md +118 -0
  21. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/D1.md +59 -0
  22. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S1.md +46 -0
  23. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S2.md +56 -0
  24. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S3.md +66 -0
  25. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/T1.md +7 -0
  26. package/docs/plans/general-0928/briefs/S3.md +1 -1
  27. package/docs/plans/general-0928/briefs/S4.md +1 -1
  28. package/docs/plans/general-0928/plan.md +5 -9
  29. package/docs/plans/watchdog-1011/briefs/S1.md +75 -0
  30. package/docs/plans/watchdog-1011/decisions.md +8 -0
  31. package/docs/plans/watchdog-1011/plan.md +114 -0
  32. package/docs/web-search.md +30 -7
  33. package/examples/extensions/gws/README.md +24 -4
  34. package/examples/extensions/gws/index.ts +171 -19
  35. package/examples/extensions/gws/skill/SKILL.md +33 -7
  36. package/examples/extensions/gws/skill/scripts/google-public-read.ts +384 -0
  37. package/examples/extensions/gws/token-mint.ts +206 -0
  38. package/examples/extensions/longview/summarize.ts +8 -4
  39. package/examples/extensions/napkin/index.ts +4 -2
  40. package/examples/extensions/office-365-mail/README.md +18 -2
  41. package/examples/extensions/office-365-mail/index.ts +7 -8
  42. package/examples/extensions/office-365-mail/lib/graph.ts +162 -17
  43. package/examples/extensions/office-365-mail/scripts/login.ts +5 -4
  44. package/examples/extensions/office-365-mail/skill/SKILL.md +18 -4
  45. package/examples/extensions/pinchtab/bin/pinchtab_ensure +52 -6
  46. package/examples/extensions/pinchtab/bin/pinchtab_nav +29 -3
  47. package/examples/extensions/web-search/capability.ts +630 -0
  48. package/examples/extensions/web-search/chain.ts +761 -0
  49. package/examples/extensions/web-search/index.ts +158 -49
  50. package/examples/extensions/web-search/ledger.ts +13 -0
  51. package/examples/extensions/web-search/providers/brave.ts +169 -0
  52. package/examples/extensions/web-search/providers/index.ts +91 -0
  53. package/examples/extensions/web-search/providers/tavily.ts +143 -0
  54. package/examples/extensions/web-search/providers/types.ts +191 -0
  55. package/examples/extensions/web-search/skill/SKILL.md +18 -13
  56. package/package.json +3 -3
  57. package/resources/pi-extensions/subagent/index.ts +8 -4
  58. package/resources/skills/permissions/SKILL.md +5 -1
  59. package/src/adapters/whatsapp-offline.ts +141 -0
  60. package/src/adapters/whatsapp.ts +333 -63
  61. package/src/agent/container-entry.ts +6 -4
  62. package/src/agent/container-runner.ts +5 -4
  63. package/src/agent/image-contract.ts +1 -0
  64. package/src/bridges/whatsapp.ts +48 -9
  65. package/src/cli/mercury.ts +14 -0
  66. package/src/cli/service-unit.ts +18 -1
  67. package/src/core/data-dir-ownership.ts +53 -0
  68. package/src/core/exec.ts +5 -4
  69. package/src/core/permissions.ts +7 -0
  70. package/src/core/router.ts +6 -1
  71. package/src/core/routes/send.ts +10 -0
  72. package/src/core/systemd-watchdog.ts +381 -0
  73. package/src/core/task-scheduler.ts +32 -0
  74. package/src/core/task-sender.ts +86 -8
  75. package/src/extensions/image-builder.ts +3 -2
  76. package/src/main.ts +18 -2
  77. package/src/preflight/binary-version.ts +3 -2
  78. package/src/server.ts +33 -4
  79. package/src/text/utf8-stream.ts +48 -0
  80. package/src/types.ts +22 -0
  81. package/docs/plans/general-0928/briefs/S5.md +0 -58
  82. package/docs/plans/monitoring-1005/briefs/S5.md +0 -62
  83. package/docs/plans/monitoring-1005/briefs/S6.md +0 -67
  84. package/examples/extensions/web-search/brave.ts +0 -751
  85. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S1.md +0 -0
  86. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S12.md +0 -0
  87. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S2.md +0 -0
  88. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S3.md +0 -0
  89. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S4.md +0 -0
  90. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S8.md +0 -0
  91. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S9.md +0 -0
  92. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S2.md +0 -0
  93. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S3.md +0 -0
  94. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S4.md +0 -0
@@ -113,6 +113,7 @@ COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
113
113
  COPY src/extensions/permission-guard.ts /app/src/extensions/permission-guard.ts
114
114
  COPY src/extensions/bash-timeout.ts /app/src/extensions/bash-timeout.ts
115
115
  COPY src/text/sanitize-text.ts /app/src/text/sanitize-text.ts
116
+ COPY src/text/utf8-stream.ts /app/src/text/utf8-stream.ts
116
117
  COPY src/types.ts /app/src/types.ts
117
118
  COPY resources/ /app/resources/
118
119
  COPY examples/extensions/ /tmp/examples-extensions/
@@ -82,6 +82,7 @@ COPY src/extensions/reserved.ts /app/src/extensions/reserved.ts
82
82
  COPY src/extensions/permission-guard.ts /app/src/extensions/permission-guard.ts
83
83
  COPY src/extensions/bash-timeout.ts /app/src/extensions/bash-timeout.ts
84
84
  COPY src/text/sanitize-text.ts /app/src/text/sanitize-text.ts
85
+ COPY src/text/utf8-stream.ts /app/src/text/utf8-stream.ts
85
86
  COPY src/types.ts /app/src/types.ts
86
87
  COPY resources/ /app/resources/
87
88
  COPY examples/extensions/ /tmp/examples-extensions/
@@ -32,7 +32,8 @@ the repo, after a pi upgrade.
32
32
  ## Memory (MEMORY.md, <active_episodes>, <members>, member notes)
33
33
  ## Run budget (tools-capable models only)
34
34
  ## Destructive Operations — Confirmation Required
35
- ## Presenting tool results ("simple lists", "never show JSON/commands")
35
+ ## Presenting tool results ("simple lists", "never show JSON/commands",
36
+ "a bot-check page is a block, never a pass")
36
37
  ## Delivery ("[reply] … [/reply] — only the span inside is sent";
37
38
  pi-jsonl-parser.ts extractReplyEnvelope honours it,
38
39
  a reply with no markers is sent whole)
@@ -204,6 +205,7 @@ Everything else is opt-in by instruction.
204
205
  | Voice that must hold across all spaces of one deployment | Bot Character | every space's `AGENTS.md` (drifts) |
205
206
  | Deterministic business logic, credentials, anything with numbers or dates | a capability / engine / extension, returning structured facts | prompt prose ("compute the day name yourself") |
206
207
  | What members may do | `role.member.permissions` / applicative `member_permissions` | prompt prose ("only admins may…") — the model is not the enforcement |
208
+ | A Google Docs/Sheets/Slides link someone sent: read it through its public export first (`google-public-read.ts`, no credentials, every caller), `gws` only for a private doc and only for a caller holding it; a private doc is "not shared publicly", never "no permission" | the `gws` extension skill (top section of `examples/extensions/gws/skill/SKILL.md`; the reader prints the fact — content, `NOT-PUBLIC`, and whether this caller holds `gws`) | the permission-guard text or the platform block (they stay a hard boundary for `gws`: the export read is not an "alternative means", because it reads only what Google serves without a login); granting `gws` to members (on a shared box it is one box-wide Google credential) |
207
209
  | Knobs an extension reads (`feed-watch.*`, `longview.*`) | `extension_config` in the space profile (repo-owned, drift-checked) | hand edits on the box |
208
210
  | A fact the bot keeps getting wrong | a file the bot reads each turn (`MEMORY.md`, a notebook under `knowledge/`), written with source and date | a longer rule |
209
211
 
@@ -24,7 +24,7 @@ These must be supplied via environment variables only; they are **not** read fro
24
24
  - `MERCURY_DISCORD_GATEWAY_SECRET`
25
25
  - `MERCURY_HEARTBEAT_URL` (see [Heartbeat](#heartbeat-mercury_heartbeat_url-mercury_heartbeat_tasks))
26
26
 
27
- Platform tokens, provider API keys, and extension keys (e.g. `MERCURY_TELEGRAM_BOT_TOKEN`, `MERCURY_BRAVE_API_KEY`) are also **env-only** today—they are not part of the YAML schema.
27
+ Platform tokens, provider API keys, and extension keys (e.g. `MERCURY_TELEGRAM_BOT_TOKEN`, `MERCURY_BRAVE_API_KEY`, `MERCURY_TAVILY_API_KEY`) are also **env-only** today—they are not part of the YAML schema.
28
28
 
29
29
  ## YAML layout
30
30
 
@@ -521,7 +521,7 @@ agent:
521
521
  ```
522
522
 
523
523
  - **`claimed`** (default) — only variables an extension declared via `mercury.env()` are passed, and only when the triggering caller holds that extension's permission. Undeclared variables stay on the host.
524
- - **`all`** — every `MERCURY_*` var except a fixed blocklist is passed into the container with the prefix stripped (`MERCURY_BILLING_API_KEY` → `BILLING_API_KEY`). Convenient, but blunt: a secret added to `.env` for one purpose reaches **every space's container**, regardless of who triggered the turn or whether that space has anything to do with it. Vars an extension declares `hostOnly` are excluded even here — `MERCURY_BRAVE_API_KEY` stays on the host once `web-search` is installed, which brokers Brave queries through `mrctl capability web-search`.
524
+ - **`all`** — every `MERCURY_*` var except a fixed blocklist is passed into the container with the prefix stripped (`MERCURY_BILLING_API_KEY` → `BILLING_API_KEY`). Convenient, but blunt: a secret added to `.env` for one purpose reaches **every space's container**, regardless of who triggered the turn or whether that space has anything to do with it. Vars an extension declares `hostOnly` are excluded even here — `MERCURY_BRAVE_API_KEY` and `MERCURY_TAVILY_API_KEY` stay on the host once `web-search` is installed, which brokers search queries through `mrctl capability web-search` (see `docs/web-search.md` for the provider chain and its `web-search.providers`, `monthly_budget` and `tavily_monthly_budget` keys).
525
525
 
526
526
  **Model-provider credentials are exempt** and pass in both modes (`MERCURY_ANTHROPIC_API_KEY`, `MERCURY_ANTHROPIC_OAUTH_TOKEN`, `MERCURY_GEMINI_API_KEY`, `MERCURY_GROQ_API_KEY`, and the rest of the provider list). pi reads them inside the container, and no extension declares them — without the exemption, `claimed` would leave the agent unable to reach any model. They remain subject to the blocklist.
527
527
 
@@ -2402,9 +2402,9 @@ Image rebuild (`src/agent/container-entry.ts` is baked), then restart; verify th
2402
2402
 
2403
2403
  Image rebuild (`src/agent/container-entry.ts` is baked), then restart; the host hint and the drift metric are in `src/core/runtime.ts` and go live with the restart.
2404
2404
 
2405
- - [ ] MANUAL-VERIFY(2026-10-10): `docker run --rm --entrypoint grep <derived ext image> -c "## Reply language" /app/src/agent/container-entry.ts` prints 1 (fix `reply-language-drifts-to-english-on-hebrew-input`).
2406
- - [ ] MANUAL-VERIFY(2026-10-10): Send the bot a Hebrew question that takes many tool calls (for example a lookup across several sites). The reply is in Hebrew, and the run's trace shows the prompt ending in `<reply_language>he</reply_language>`.
2407
- - [ ] MANUAL-VERIFY(2026-10-10): In `main`, write one message in English: the reply is in English and the journal has no `Reply language drifted` line for it.
2405
+ - [x] MANUAL-VERIFY(2026-10-10): `docker run --rm --entrypoint grep <derived ext image> -c "## Reply language" /app/src/agent/container-entry.ts` prints 1 (fix `reply-language-drifts-to-english-on-hebrew-input`). **PASSED 2026-10-10**: `mercury-agent-ext:9b669aa35969` printed 1.
2406
+ - [x] MANUAL-VERIFY(2026-10-10): Send the bot a Hebrew question that takes many tool calls (for example a lookup across several sites). The reply is in Hebrew, and the run's trace shows the prompt ending in `<reply_language>he</reply_language>`. **PASSED 2026-10-10**: weather + USD/ILS question in `main`, Hebrew reply with tool calls, trace `mercury-1791648092438-1.jsonl` carries the tag.
2407
+ - [x] MANUAL-VERIFY(2026-10-10): In `main`, write one message in English: the reply is in English and the journal has no `Reply language drifted` line for it. **FAILED 2026-10-10** (1 of 2 English messages answered in Hebrew; filed and fixed as `reply-language-english-drifts-to-hebrew`, `9597274`). **PASSED 2026-10-11** after that deploy: 4 of 4 English messages answered in English, each trace carries `<reply_language>en</reply_language>`, a Hebrew control answered in Hebrew, 0 drift lines.
2408
2408
  - [ ] MANUAL-VERIFY(2026-10-10): A week after the deploy, count `Reply language drifted` lines in the journal against the Hebrew-input runs. Close or act on `docs/bugs/reply-language-drift-gets-no-rewrite.md` from that number.
2409
2409
  - [ ] MANUAL-VERIFY(2026-10-10): On the Tagula box, after its next deploy: the same count, by space (aggregate only). Before the fix it was 25 of 168.
2410
2410
 
@@ -2422,3 +2422,48 @@ Image rebuild (`src/agent/container-entry.ts` is baked), then restart; the host
2422
2422
 
2423
2423
  - [ ] MANUAL-VERIFY(2026-10-10): On a console box (Tagula), in a space whose Anthropic grant was revoked: send one message. The user should get the "session expired / reconnect" reply, not "Something went wrong", and the operator should get one 🚨 DM naming `MERCURY_ANTHROPIC_OAUTH_TOKEN (console credential blob)`. A second message within the cooldown should send no second DM.
2424
2424
  - [ ] MANUAL-VERIFY(2026-10-10): After reconnecting in the console, the next message should get a normal answer, and a later revoke should alert again without waiting for the cooldown.
2425
+
2426
+ ## Plan monitoring-1005 / S12
2427
+
2428
+ - [ ] MANUAL-VERIFY(2026-10-11): Before re-rendering the unit, copy `~/.config/systemd/user/mercury.service` aside. Re-run `service install` from a script file with the old unit's `PATH` exported. `diff` old and new should show exactly two added lines, `NotifyAccess=all` and `WatchdogSec=120`, and the `PATH` line should be unchanged.
2429
+ - [ ] MANUAL-VERIFY(2026-10-11): After the restart (in a gap), `systemctl --user show mercury -p WatchdogUSec -p NotifyAccess` should print `2min` and `all`. The journal should have one `systemd watchdog: pinging … watchdogMs=120000 intervalMs=30000` line. Ten minutes later the unit should still be `active` with the same `MainPID` (pings are being accepted).
2430
+ - [ ] MANUAL-VERIFY(2026-10-11): Over the next days, `journalctl --user -u mercury | grep 'systemd watchdog'` should show no `ping failed` lines. Any `event loop was blocked` line names a stall worth a bug report.
2431
+
2432
+ ## Plan monitoring-1005 / S7 — a queued WhatsApp send is no longer reported as delivered (added 2026-10-11, deploy-gated: restart)
2433
+
2434
+ - [ ] MANUAL-VERIFY(2026-10-11): After restart, with WhatsApp disconnected, call `POST /api/send` to the owner's DM. It should answer **202** with `{"queued":true}` (not `{"delivered":true}`), and the journal should show `Direct send queued, not delivered`. When WhatsApp reconnects, the message should arrive once.
2435
+ - [ ] MANUAL-VERIFY(2026-10-11): The next time WhatsApp is down while a task in `MERCURY_HEARTBEAT_TASKS` runs: the journal should show `Scheduled task output queued, not delivered` and **no** `Heartbeat pinged` line, the run is `error` in `task_runs`, there is no `Scheduled task will be retried` line, and healthchecks.io goes late for that task.
2436
+
2437
+ ## Bug fix pi-stdout-chunks-decoded-separately-split-utf8-in-replies (added 2026-10-11, deploy-gated: image rebuild)
2438
+
2439
+ - [ ] MANUAL-VERIFY(2026-10-11): After `container/build.sh` and a restart in a gap, run `docker run --rm --entrypoint grep mercury-agent:latest -c onUtf8Data /app/src/agent/container-entry.ts` (should print 2 or more) and `… -c setEncoding /app/resources/pi-extensions/subagent/index.ts` (should print 2). On Tagula, check the same thing in the image tag the box runs.
2440
+ - [ ] MANUAL-VERIFY(2026-10-11): About two weeks after the deploy, `SELECT count(*) FROM messages WHERE role='assistant' AND created_at > '<deploy time>' AND content LIKE '%' || char(65533) || '%'` should be 0 on both boxes. Before the fix it was about 1 in 110 replies. A hit whose `��` shows up in pi's own `message_end` line, not on a chunk boundary, is the separate model-emitted case named in the post-mortem.
2441
+
2442
+ ## Plan monitoring-1005 / S8 — a logged-out WhatsApp device is loud, not mute (added 2026-10-11, deploy-gated: restart)
2443
+
2444
+ - [x] MANUAL-VERIFY(2026-10-11): After the restart (in a gap), `curl -s http://127.0.0.1:8787/health` should contain `"whatsappNeedsRepair":false` and `"whatsapp":true` under `adapters`. **PASSED 2026-10-12**: box at `a454651` (restarted 2026-10-11 21:16), `/health` returned `"adapters":{"whatsapp":true},"whatsappNeedsRepair":false`.
2445
+ - [ ] MANUAL-VERIFY(2026-10-11): Do not unlink the live device to test this. The next time WhatsApp really logs the bot out, the journal should show exactly one `ERROR` line `WhatsApp logged out … mercury auth whatsapp …` and no `WhatsApp reconnecting` after it. `/health` should show `"whatsappNeedsRepair":true`, and `GET /auth/whatsapp` (with the API secret) should answer `{"status":"needs-repair","reason":"logged-out"}`.
2446
+
2447
+ ## search-provider-fallback-chain: keys and Tagula deploy (2026-10-11)
2448
+
2449
+ - [ ] MANUAL-VERIFY(2026-10-11): Tagula (`tagula-assistant`) still runs 0.24.3-beta.3 without the Tavily provider; its `.env` already holds its own `MERCURY_TAVILY_API_KEY` (backup `.env.bak-2026-10-11-tavily` beside it). After it is deployed with `cdf7d09` or later and restarted in a gap, `ls /opt/tagula/assistants/tagula-assistant/.mercury/extensions/web-search/providers/` lists `tavily.ts` and one search in the management space logs `provider=tavily` (Tagula's Brave credit is gone until 2026-11-01).
2450
+ - [ ] MANUAL-VERIFY(2026-10-11): The local bot's Tavily key was only called directly, never through the chain. When Brave next runs out or is skipped, one search's journal line shows `provider=tavily`.
2451
+
2452
+ ## Plan monitoring-1005 / S9 — messages sent while the bot was offline are answered or noticed (added 2026-10-12, deploy-gated: restart)
2453
+
2454
+ - [ ] MANUAL-VERIFY(2026-10-12): After the restart (in a gap), send the bot a DM from another phone within a minute of the restart. It should be answered, and the journal should show `WhatsApp offline message admitted` (or `WhatsApp inbound` if it arrived live).
2455
+ - [ ] MANUAL-VERIFY(2026-10-12): Next real outage longer than 15 minutes (do not cause one): the journal should show one `WhatsApp offline message dropped: too old to answer` line per missed message and one `WhatsApp offline notice sent` per DM chat, and that chat should get one "I was offline…" (or Hebrew) message with the right time range.
2456
+
2457
+ ## Plan tagula-predeploy-1014 / S1 — public Google Docs links are read without gws (added 2026-10-14, deploy-gated: image rebuild)
2458
+
2459
+ - [ ] MANUAL-VERIFY(2026-10-14): As a member without gws, send the bot a public Google Docs link, then a private one. The first should come back with the document's content, the second with "this document isn't shared publicly — set it to Anyone with the link, or paste it", and no permission message either time.
2460
+
2461
+ ## Plan tagula-predeploy-1014 / S2 — a bot-check page is reported as a block, never a pass (added 2026-10-14, deploy-gated: image rebuild)
2462
+
2463
+ - [ ] MANUAL-VERIFY(2026-10-14): After the beta.4 image is built on the Tagula box (plan D1), `docker run --rm --entrypoint grep <the mercury-agent image> -c "Blocked pages" /app/src/agent/container-entry.ts` prints 1 (grep the image, not the repo).
2464
+ - [ ] MANUAL-VERIFY(2026-10-14): The next time a site blocks a fetch (for example passportnews.co.il from the Tagula box), the reply says it was blocked and quotes the page title ("Just a moment…" or "Performing security verification"). It must not say the check was passed or that the site "didn't respond after".
2465
+
2466
+ ## Plan tagula-predeploy-1014 / S3 — the first navigation waits for pinchtab's browser (added 2026-10-14, deploy-gated: image rebuild, rides beta.4)
2467
+
2468
+ - [ ] MANUAL-VERIFY(2026-10-14): After beta.4 is deployed to the Tagula box, `docker run --rm --entrypoint grep <the derived mercury-agent-ext image> -c _pinchtab_await_instance /usr/local/bin/pinchtab_ensure` prints a number above 0. Grep the image, not the repo.
2469
+ - [ ] MANUAL-VERIFY(2026-10-14): Over the next week of Tagula traces, no `Error 409` `no_current_tab` right after a run's first `pinchtab_ensure`. If a navigation does fail, `pinchtab_nav` prints `the navigation to … FAILED`, not a silent exit 0.
@@ -56,6 +56,7 @@ Custom roles can be created by assigning permissions to any role name.
56
56
  | `spaces.list` | View all spaces |
57
57
  | `spaces.rename` | Rename the current space |
58
58
  | `spaces.delete` | Delete current space and all related DB data |
59
+ | `spaces.pause` | Run chat `/pause` and `/resume` in a group, where slash commands are otherwise admin-only. Not a member default — grant it with `mrctl permissions set member <current list>,spaces.pause`. In a DM, `/pause` needs only `prompt` |
59
60
  | `model.list` | List the configured model chain (`mrctl model list`, chat `/model list`) |
60
61
  | `model.switch` | Pin the space to one model leg (`mrctl model switch`, chat `/model switch`) or clear that pin (`mrctl model reset`, chat `/model reset`) — takes effect on the next agent run. A pinned space has no fallback; reset is refused while the space has an active scheduled task |
61
62
  | `links.list` | See the space links touching this space, which spaces reach it and at what level, and their audit (`mrctl links list/inspect/audit`). Admin-only by default: it names every space that reaches this one. Creating, changing or revoking a link (`mrctl links create/update/revoke`) is **not** a permission — those verbs are global-admin-only, and no role grant enables them |
@@ -24,8 +24,8 @@
24
24
  Apply CLAUDE.md "a flag sampled before an `await` is a guard": queue on the throw as well as on `!connected`. Ratchet B (bare-delivery-await) must not go up.
25
25
 
26
26
  **Siblings must know** (write these into your log):
27
- - the new outcome type and where it is exported, because S13 maps the scheduler outcome after you;
28
- - the exact `/api/send` 202 body, because the relay (S6) treats it as not yet delivered;
27
+ - the new outcome type and where it is exported, because plan `tagula-client-1008` S9 (formerly S13 here) maps the scheduler outcome after you;
28
+ - the exact `/api/send` 202 body, because the WhatsApp relay (`docs/backlog/healthchecks-whatsapp-relay.md`, plan `watchdog-1011`) treats it as not yet delivered;
29
29
  - any rename in `whatsapp.ts`, because S8 and S9 edit that file after you.
30
30
 
31
31
  **Related, not yours:** `general-0928`'s `whatsapp-outbound-queue-survives-restart` will *persist* the queue. Do not persist it here. Make "queued" visible, and leave the residual pointing at that idea.
@@ -4,7 +4,7 @@
4
4
 
5
5
  | ID | Blocks | Question | Options | Answer | Answered |
6
6
  |----|--------|----------|---------|--------|----------|
7
- | Q1 | S1 | Do you approve the Goal of `docs/backlog/healthchecks-whatsapp-relay.md` (Phase 1: one DM per real up/down transition of a Tagula healthchecks.io check, sent through the home bot's `/api/send`; no reminders; the home project is not covered)? `/f-feature-planning` on a backlog doc goes straight to Architecture. | approve / feedback: … | | |
8
- | Q2 | S5 | Which Telegram bot does the daily digest send through: the one healthchecks.io already uses, or a new dedicated bot? A new token is a secret, and it must be mirrored into a gitignored local file in the same sitting (global secrets rule). | reuse / new | | |
7
+ | Q1 | S1 | Do you approve the Goal of `docs/backlog/healthchecks-whatsapp-relay.md` (Phase 1: one DM per real up/down transition of a Tagula healthchecks.io check, sent through the home bot's `/api/send`; no reminders; the home project is not covered)? `/f-feature-planning` on a backlog doc goes straight to Architecture. | approve / feedback: … | approve — the rewritten Goal of 2026-10-07 (fleet-monitor Worker relay feed). Owner also wants every alert on all three channels: email, Telegram and WhatsApp, WhatsApp being the one most watched **Moot 2026-10-11:** this approval was for the Worker-shape Goal, which was reverted the same day to healthchecks.io; S6 dropped, re-approval tracked in plan `watchdog-1011` | 2026-10-11 (owner; written by Claude) |
8
+ | Q2 | S5 | Which Telegram bot does the daily digest send through: the one healthchecks.io already uses, or a new dedicated bot? A new token is a secret, and it must be mirrored into a gitignored local file in the same sitting (global secrets rule). | reuse / new | moot — S5 dropped 2026-10-11 (the digest waits for phase 3; its transport is re-decided then, `notes/2026-10-11-monitoring-rollout-order.md`) | 2026-10-11 (Claude, on the owner's instruction) |
9
9
  | Q3 | S13 | Lane: does Avishai Tsabari own `src/core/runtime.ts` (landed commits in the last 14 d, none in flight)? | ours / theirs / split | moot — S13 moved to plan tagula-client-1008 with `--drop`; the lane is measured there (no open PR, no live branch in the file; Avishai's last edits landed) | 2026-10-08 (Claude, on the owner's instruction to decide) |
10
10
  | Q4 | S11 | Lane: does Avishai Tsabari own `src/agent/container-entry.ts` (landed commits in the last 14 d, none in flight)? | ours / theirs / split | moot — S11 moved to plan tagula-client-1008 with `--drop`; the lane is measured there (no open PR, no live branch in the file; Avishai's last edits landed) | 2026-10-08 (Claude, on the owner's instruction to decide) |
@@ -1,10 +1,10 @@
1
1
  # Plan: monitoring-1005
2
2
 
3
- **Status**: Active
4
- **Created**: 2026-10-05 **Last updated**: 2026-10-08
3
+ **Status**: Archived
4
+ **Created**: 2026-10-05 **Last updated**: 2026-10-12
5
5
  **Landing mode**: pr
6
6
  **Max width**: 4
7
- **Inputs**: build monitoring — live-bot failure monitoring: healthchecks-whatsapp-relay, box-watchdog, run-outcome-counters, operator-alert-ping-mirror, daily-ops-digest-via-telegram, space-probe-harness addendum, and bugs whatsapp-messages-sent-while-offline-are-dropped-forever, queued-whatsapp-send-is-reported-as-delivered, health-reports-every-adapter-as-up, whatsapp-logged-out-socket-leaves-the-process-up-and-mute, console-oauth-invalid-grant-reaches-no-operator, third-party-apps-400-is-retried-and-shown-as-try-again, service-unit-never-restarts-a-hung-process, scheduled-run-over-cloud-quota-posts-the-quota-text-as-output
7
+ **Inputs**: build monitoring — live-bot failure monitoring: healthchecks-whatsapp-relay, box-watchdog, run-outcome-counters, operator-alert-ping-mirror, daily-ops-digest-via-telegram, space-probe-harness addendum, and bugs whatsapp-messages-sent-while-offline-are-dropped-forever, queued-whatsapp-send-is-reported-as-delivered, health-reports-every-adapter-as-up, whatsapp-logged-out-socket-leaves-the-process-up-and-mute, console-oauth-invalid-grant-reaches-no-operator, third-party-apps-400-is-retried-and-shown-as-try-again, service-unit-never-restarts-a-hung-process, scheduled-run-over-cloud-quota-posts-the-quota-text-as-output; `--rebuild --drop S5 S6` (owner, 2026-10-11)
8
8
  **Me**: saharw01, Saharw01@gmail.com
9
9
 
10
10
  > Written by `/f-ops-plan` build (and `--rebuild`) and `archive` only. Nothing perishable lives here — no base sha, no other author's file list, no story counts; `next` measures those every time it runs. `plan-state.sh` parses the tables below by their header row, so keep the column order.
@@ -13,6 +13,10 @@
13
13
 
14
14
  > **Session rules.** The owner is present, but no session deploys. Do not run `~/sync-mercury.sh`, restart the service, rebuild the image, or read `~/whatsapp-bot/.env`. Each session stops after its local commits and the check gate (CLAUDE.local.md "Push access"). The only push allowed is the docs-only claim push. Live checks go to `docs/pending-verification.md` under the session's heading.
15
15
 
16
+ > **Rebuild 2026-10-11 (owner).** Monitoring stays on healthchecks.io and the fleet-monitor Worker is deferred (`docs/notes/2026-10-06-round0-plans-adversarial-review.md` § Handled so far). S5 (the digest) and S6 (the relay build) are dropped: the digest waits for phase 3, and the relay's Goal changed again, so it is re-planned in plan `watchdog-1011` together with box-watchdog. Q1 and Q2 are moot. This plan now carries the live bot's WhatsApp reliability fixes only.
17
+
18
+ > **Cross-repo.** The order across mercury, tagula-agents and fleet-monitor is `docs/notes/2026-10-11-monitoring-rollout-order.md`. This plan runs in parallel with phases 1–2 there. It has no dependency on another repo, and none waits on it.
19
+
16
20
  ## Items
17
21
 
18
22
  | ID | Kind | Slug | Doc | Command | Round | Needs |
@@ -21,8 +25,6 @@
21
25
  | S2 | plan | box-watchdog | docs/ideas/box-watchdog.md | /f-feature-planning box-watchdog | 0 | — |
22
26
  | S3 | plan | run-outcome-counters | docs/ideas/run-outcome-counters.md | /f-feature-planning run-outcome-counters | 0 | — |
23
27
  | S4 | plan | operator-alert-ping-mirror | docs/ideas/operator-alert-ping-mirror.md | /f-feature-planning operator-alert-ping-mirror | 0 | — |
24
- | S5 | plan | daily-ops-digest-via-telegram | docs/ideas/daily-ops-digest-via-telegram.md | /f-feature-planning daily-ops-digest-via-telegram | 0 | Q2 |
25
- | S6 | feature | healthchecks-whatsapp-relay | docs/backlog/healthchecks-whatsapp-relay.md | /f-feature-dev healthchecks-whatsapp-relay | 4 | — |
26
28
  | S7 | bug | queued-whatsapp-send-is-reported-as-delivered | docs/bugs/queued-whatsapp-send-is-reported-as-delivered.md | /f-bug-fix queued-whatsapp-send-is-reported-as-delivered | 1 | — |
27
29
  | S8 | bug | whatsapp-logged-out-socket-leaves-the-process-up-and-mute | docs/bugs/whatsapp-logged-out-socket-leaves-the-process-up-and-mute.md | /f-bug-fix whatsapp-logged-out-socket-leaves-the-process-up-and-mute | 2 | — |
28
30
  | S9 | bug | whatsapp-messages-sent-while-offline-are-dropped-forever | docs/bugs/whatsapp-messages-sent-while-offline-are-dropped-forever.md | /f-bug-fix whatsapp-messages-sent-while-offline-are-dropped-forever | 3 | — |
@@ -38,11 +40,10 @@
38
40
  |------|----|------|-------|
39
41
  | S7 | S8 | start-after | file: src/adapters/whatsapp.ts (both predicted) |
40
42
  | S8 | S9 | start-after | file: src/adapters/whatsapp.ts (both predicted) |
41
- | S7 | S6 | merge-after | semantic: the relay treats a 2xx from /api/send as delivered, which holds only once a queued send answers 202 queued (user) |
42
43
 
43
44
  > Kind ∈ `start-after` (To may not launch until From is landed) | `merge-after` (To may launch now, may not land until From is landed).
44
45
  > Basis starts with `file:` (inferred from the collision map) or `semantic:` (asked) — never mixed in one row.
45
- > Semantic edges: asked 2026-10-05, answer `S6 after S7`.
46
+ > Semantic edges: asked 2026-10-05, answer `S6 after S7`. S6 was dropped on 2026-10-11, so the edge went with it; the relay in plan `watchdog-1011` lands after this plan's S7 by its own edge.
46
47
  > Rebuild 2026-10-08 (`--drop S10 S11 S13`): the three moved to plan `tagula-client-1008`, which owns the files they share with its higher-priority sessions. That plan's S9 (formerly S13 here) now carries a cross-plan `start-after` edge onto this plan's S7 for `src/core/task-scheduler.ts`.
47
48
  > S7 → S9 is implied by the chain through S8 and is not written separately.
48
49
 
@@ -52,12 +53,11 @@
52
53
 
53
54
  | Session | Action | Clears |
54
55
  |---------|--------|--------|
55
- | S1 | plan healthchecks-whatsapp-relay: Architecture (Phase 2), after Q1 | S6 (via `--rebuild`) |
56
+ | S1 | plan healthchecks-whatsapp-relay (Goal only; Architecture moved to plan `watchdog-1011`) | — |
56
57
  | S2 | plan box-watchdog | — |
57
58
  | S3 | plan run-outcome-counters | — |
58
59
  | S4 | plan operator-alert-ping-mirror | — |
59
- | S5 | plan daily-ops-digest-via-telegram, after Q2 | — |
60
- | — | owner answers decisions.md Q1–Q2 (Q3, Q4 moot since 2026-10-08) | S1, S5 |
60
+ | — | decisions.md Q1–Q4 all closed (Q3, Q4 moot since 2026-10-08; Q1, Q2 moot since 2026-10-11) | — |
61
61
 
62
62
  > S2–S5 are ideas, so they get planning sessions only. Their implementation sessions are planned once their Architecture exists: `--add` them in a `--rebuild`, or put them in a follow-up plan.
63
63
 
@@ -80,12 +80,6 @@
80
80
  |---------|------|----------------------------|
81
81
  | S9 | whatsapp-messages-sent-while-offline-are-dropped-forever | 1 |
82
82
 
83
- ### Round 4
84
-
85
- | Session | Item | Predicted surfaces (count) |
86
- |---------|------|----------------------------|
87
- | S6 | healthchecks-whatsapp-relay | 0 (stale until S1 lands — then `--rebuild`) |
88
-
89
83
  ## Collision map
90
84
 
91
85
  > Whole files, predicted. `docs/` and infrastructure paths are excluded exactly as `overlap-check.sh` excludes them. Nine such paths were ignored: the research note, cited once by each of the seven bugs, plus `ideas/run-outcome-counters.md` and `ideas/whatsapp-outbound-queue-survives-restart.md`. Line-range variants were collapsed to their file: `src/adapters/whatsapp.ts:380`, `:653-661` and `:358-376`, `src/agent/container-runner.ts:740-843`, and `src/core/runtime.ts:3285-3337`.
@@ -104,10 +98,6 @@
104
98
 
105
99
  ### S4
106
100
 
107
- ### S5
108
-
109
- ### S6
110
-
111
101
  ### S7
112
102
 
113
103
  - src/adapters/whatsapp.ts
@@ -157,6 +147,8 @@
157
147
 
158
148
  | Item | Reason | Home |
159
149
  |------|--------|------|
150
+ | S5 daily-ops-digest-via-telegram | dropped by owner 2026-10-11: on hold until phase 3, transport to be re-decided (`notes/2026-10-11-monitoring-rollout-order.md`) | docs/ideas/daily-ops-digest-via-telegram.md |
151
+ | S6 healthchecks-whatsapp-relay | dropped by owner 2026-10-11: the Goal reverted to healthchecks.io; re-planned in plan `watchdog-1011` | docs/backlog/healthchecks-whatsapp-relay.md |
160
152
  | health-reports-every-adapter-as-up | the WhatsApp half is fixed on local `dev` as `6cf4ea7` (`/health` reads `getQrStatus()`), but the bug doc is still `Open` and was never archived. The rest (`queuedOutbound`, `lastCloseReason`) needs its own decision: close the doc with those fields as residuals, or narrow it. The idea `dashboard-adapter-status-is-frozen-true` (`d426fb7`) is the sibling surface. | docs/bugs/health-reports-every-adapter-as-up.md |
161
153
  | space-probe-harness | the idea says "deliberately not now". It waits on rehearsal-bench M2.2 (`simulateIngress`) and on tagula-agents settling its probe shape. The 2026-10-05 cross-bot WhatsApp probe addendum also needs S2 (box-watchdog) to exist as the reader. | docs/ideas/space-probe-harness.md |
162
154
  | S10 console-oauth-invalid-grant-reaches-no-operator | dropped by user — moved to plan tagula-client-1008 (2026-10-08) | docs/bugs/console-oauth-invalid-grant-reaches-no-operator.md |
@@ -0,0 +1,47 @@
1
+ ## Launched — 2026-10-11T11:14Z
2
+ Branch: worktree-fix-service-unit-never-restarts-a-hung-process
3
+ Session: 3e65697f-325c-4c1e-9d4f-1a70538134fb
4
+
5
+ ## Report — 2026-10-11T12:34Z
6
+ Outcome: delivered
7
+ Delivered: local
8
+ Blocked-on: —
9
+ Deploy needs: unit re-render (PATH-preserving, diff before/after) + restart in a gap
10
+ Source: command
11
+ Files changed:
12
+ - docs/debug/moderate/2026-10-11-service-unit-never-restarts-a-hung-process.md
13
+ - docs/debug/service-unit-never-restarts-a-hung-process.md
14
+ - docs/pending-verification.md
15
+ - src/cli/mercury.ts
16
+ - src/cli/service-unit.ts
17
+ - src/core/systemd-watchdog.ts
18
+ - src/main.ts
19
+ - tests/fixtures/release-gate/regression-map.json
20
+ - tests/service-unit.test.ts
21
+ - tests/systemd-watchdog.test.ts
22
+ Siblings must know:
23
+ - watchdog-1011/S1 (box-watchdog): the in-process watchdog catches a frozen event loop only. A wedged socket or a deadlocked await keeps the timer running and is still the external /health probe's job.
24
+ - The unit now has NotifyAccess=all: any process in the unit's cgroup can send sd_notify messages. Nothing else uses it today.
25
+ --- final report (verbatim) ---
26
+ ~~~
27
+ Fixed service-unit-never-restarts-a-hung-process (moderate).
28
+ Squash commit a80b7dd on local dev (on top of 65a260f, a parallel session's local docs(verify) tick that was replayed onto origin/dev to reconcile the divergence). NOT pushed: push needs owner approval.
29
+ Files: src/cli/service-unit.ts (NotifyAccess=all, WatchdogSec=120), src/core/systemd-watchdog.ts (new: runtime pinger, wrapper startup raise + SIGABRT handler), src/main.ts, src/cli/mercury.ts, tests/systemd-watchdog.test.ts, tests/service-unit.test.ts, tests/fixtures/release-gate/regression-map.json, docs/pending-verification.md.
30
+ Post-mortem: docs/debug/moderate/2026-10-11-service-unit-never-restarts-a-hung-process.md
31
+ Proved on real systemd (transient user units): healthy stays active; frozen child -> Result=watchdog with the wrapper's log line; NotifyAccess=main negative control fails silently; an 8 s blocking pull survives only with the raise.
32
+ Gate: ~/check-mercury.sh check — 16/16 links, 6928 pass / 0 fail.
33
+ DELIVERED: local
34
+ ~~~
35
+
36
+ ## Exit — 2026-10-11T12:37Z
37
+ Verdict: unattributed (2 files)
38
+ Source: session-end-hook
39
+ ~~~
40
+ EXIT-RECEIPT: unattributed (2 files)
41
+ SHARED: /home/guysaha/projects/mercury with live session 17b2b227-e433-4924-b157-bc8594f320ea (since 2026-10-11T10:49:02Z)
42
+ SHARED: /home/guysaha/projects/mercury with live session b85f6792-899d-4e1d-95ba-4917fe8fbb88 (since 2026-10-11T08:46:30Z)
43
+ SHARED: /home/guysaha/projects/mercury with live session c9491da9-e77d-48a0-984a-ed8a7a779e97 (since 2026-10-11T11:14:08Z)
44
+ UNATTRIBUTED: /home/guysaha/projects/mercury/docs/plans/monitoring-1005/sessions/ — 2 files (S12.md, S7.md): changed during this session, but live session 17b2b227-e433-4924-b157-bc8594f320ea b85f6792-899d-4e1d-95ba-4917fe8fbb88 c9491da9-e77d-48a0-984a-ed8a7a779e97 shares this checkout -> nothing records whose this is: ask the owner; never discard it
45
+ DEFERRED: push 3 commit(s) on dev -> git (/f-status reports ahead=3)
46
+ NOTE: remote state as of the last fetch (--no-fetch)
47
+ ~~~
@@ -0,0 +1,75 @@
1
+ ## Launched — 2026-10-11T11:14Z
2
+ Branch: worktree-fix-queued-whatsapp-send-is-reported-as-delivered
3
+ Session: c9491da9-e77d-48a0-984a-ed8a7a779e97
4
+
5
+ ## Report — 2026-10-11T13:14Z
6
+ Outcome: delivered
7
+ Delivered: local
8
+ Blocked-on: —
9
+ Deploy needs: restart
10
+ Source: command
11
+ Files changed:
12
+ - docs/backlog/box-watchdog.md
13
+ - docs/backlog/healthchecks-whatsapp-relay.md
14
+ - docs/backlog/operator-alert-ping-mirror.md
15
+ - docs/backlog/run-outcome-counters.md
16
+ - docs/bugs/queued-whatsapp-send-is-reported-as-delivered.md
17
+ - docs/debug/major/2026-10-11-queued-whatsapp-send-is-reported-as-delivered.md
18
+ - docs/debug/moderate/2026-10-11-service-unit-never-restarts-a-hung-process.md
19
+ - docs/debug/service-unit-never-restarts-a-hung-process.md
20
+ - docs/ideas/daily-ops-digest-via-telegram.md
21
+ - docs/notes/2026-10-06-round0-plans-adversarial-review.md
22
+ - docs/notes/2026-10-11-monitoring-rollout-order.md
23
+ - docs/pending-verification.md
24
+ - docs/plans/monitoring-1005/briefs/S5.md
25
+ - docs/plans/monitoring-1005/briefs/S6.md
26
+ - docs/plans/monitoring-1005/briefs/S7.md
27
+ - docs/plans/monitoring-1005/decisions.md
28
+ - docs/plans/monitoring-1005/plan.md
29
+ - docs/plans/watchdog-1011/briefs/S1.md
30
+ - docs/plans/watchdog-1011/decisions.md
31
+ - docs/plans/watchdog-1011/plan.md
32
+ - src/adapters/whatsapp.ts
33
+ - src/bridges/whatsapp.ts
34
+ - src/cli/mercury.ts
35
+ - src/cli/service-unit.ts
36
+ - src/core/routes/send.ts
37
+ - src/core/systemd-watchdog.ts
38
+ - src/core/task-scheduler.ts
39
+ - src/core/task-sender.ts
40
+ - src/main.ts
41
+ - src/types.ts
42
+ - tests/direct-send.test.ts
43
+ - tests/fixtures/release-gate/regression-map.json
44
+ - tests/scheduler-heartbeat.test.ts
45
+ - tests/service-unit.test.ts
46
+ - tests/systemd-watchdog.test.ts
47
+ - tests/task-sender.test.ts
48
+ - tests/whatsapp-outbound-queue.test.ts
49
+ Siblings must know:
50
+ - New outcome types: `OutboundDelivery` + optional `PlatformBridge.deliver` in src/types.ts; `OutboundQueuedError` / `QueuedDelivery` exported from src/core/task-sender.ts; `QueuedPostResult` / `WhatsAppPostResult` from src/adapters/whatsapp.ts. `TaskDeliveryError.queued` (src/core/task-scheduler.ts) is true when the cause is an OutboundQueuedError — tagula-client-1008 S9 maps the scheduler outcome after this: a queued run is ledgered `error`, no heartbeat, no retry, no failure report. Sent-to-one/queued-to-another is still a PartialDeliveryError, which runtime.ts counts as `posted` (pings) — S9's call.
51
+ - POST /api/send answers exactly `202` with body `{"queued": true}` when the send was parked (no spaceId); 200 `{delivered: true, spaceId}` is now only a real send. The WhatsApp relay must treat 202 as not delivered.
52
+ - whatsapp.ts: no renames. `postMessage` return type is now `WhatsAppPostResult` (adds optional `queued`); `queueOutbound` returns `QueuedPostResult`. Bridge `sendReply` behaviour unchanged; new `deliver` and private `send` share one path.
53
+ - Other sendDirect callers (routes/capability.ts, extension ctx.send) now get an OutboundQueuedError throw on a queued send, and runtime.sendDirect logs "Direct send failed" before /api/send answers 202 — runtime.ts was out of this lane.
54
+ --- final report (verbatim) ---
55
+ ~~~
56
+ Fixed: a queued WhatsApp send is reported as delivered (major).
57
+ Squash commit 92615e7 on local dev (not pushed). Files: src/adapters/whatsapp.ts, src/bridges/whatsapp.ts, src/types.ts, src/core/task-sender.ts, src/core/task-scheduler.ts, src/core/routes/send.ts, + 4 test files and tests/fixtures/release-gate/regression-map.json.
58
+ Post-mortem: docs/debug/major/2026-10-11-queued-whatsapp-send-is-reported-as-delivered.md
59
+ Gate: ~/check-mercury.sh check — all 16 links passed, 6943 pass / 0 fail.
60
+ Live checks: docs/pending-verification.md "## Plan monitoring-1005 / S7".
61
+ DELIVERED: local (push not approved in this session). The claim push of the promotion commit was refused by the auto-mode classifier twice; not pushed.
62
+ Rebase note: local dev had diverged from origin/dev (3 local, 2 origin docs-only commits); the branch was replayed onto local dev, so dev must be reconciled with origin/dev before the push.
63
+ ~~~
64
+
65
+ ## Exit — 2026-10-11T13:25Z
66
+ Verdict: unattributed (2 files)
67
+ Source: session-end-hook
68
+ ~~~
69
+ EXIT-RECEIPT: unattributed (2 files)
70
+ SHARED: /home/guysaha/projects/mercury with live session 17b2b227-e433-4924-b157-bc8594f320ea (since 2026-10-11T10:49:02Z)
71
+ SHARED: /home/guysaha/projects/mercury with live session b85f6792-899d-4e1d-95ba-4917fe8fbb88 (since 2026-10-11T08:46:30Z)
72
+ UNATTRIBUTED: /home/guysaha/projects/mercury/docs/plans/monitoring-1005/sessions/ — 2 files (S12.md, S7.md): changed during this session, but live session 17b2b227-e433-4924-b157-bc8594f320ea b85f6792-899d-4e1d-95ba-4917fe8fbb88 shares this checkout -> nothing records whose this is: ask the owner; never discard it
73
+ DEFERRED: push 5 commit(s) on dev, which is also 2 behind origin/dev -> git pull --rebase first, then push (/f-status reports ahead=5)
74
+ NOTE: remote state as of the last fetch (--no-fetch)
75
+ ~~~
@@ -0,0 +1,66 @@
1
+ ## Launched — 2026-10-11T15:39Z
2
+ Branch: worktree-fix-whatsapp-logged-out-socket-leaves-the-process-up-and-mute
3
+ Session: a4d3146c-a1f3-445e-b709-1ee7fed3c4bf
4
+
5
+ ## Report — 2026-10-11T17:37Z
6
+ Outcome: delivered
7
+ Delivered: local
8
+ Blocked-on: —
9
+ Deploy needs: restart
10
+ Source: command
11
+ Files changed:
12
+ - container/Dockerfile
13
+ - container/Dockerfile.base
14
+ - docs/bugs/mercury-auth-whatsapp-reports-already-authenticated-on-logged-out-creds.md
15
+ - docs/debug/major/2026-10-11-whatsapp-logged-out-socket-leaves-the-process-up-and-mute.md
16
+ - docs/debug/moderate/2026-10-11-pi-stdout-chunks-decoded-separately-split-utf8-in-replies.md
17
+ - docs/debug/whatsapp-logged-out-socket-leaves-the-process-up-and-mute.md
18
+ - docs/pending-verification.md
19
+ - examples/extensions/longview/summarize.ts
20
+ - examples/extensions/napkin/index.ts
21
+ - resources/pi-extensions/subagent/index.ts
22
+ - src/adapters/whatsapp.ts
23
+ - src/agent/container-entry.ts
24
+ - src/agent/container-runner.ts
25
+ - src/agent/image-contract.ts
26
+ - src/core/exec.ts
27
+ - src/extensions/image-builder.ts
28
+ - src/preflight/binary-version.ts
29
+ - src/server.ts
30
+ - src/text/utf8-stream.ts
31
+ - tests/fixtures/release-gate/regression-map.json
32
+ - tests/health.test.ts
33
+ - tests/longview-summarize.test.ts
34
+ - tests/utf8-stream.test.ts
35
+ - tests/whatsapp-logged-out.test.ts
36
+ Siblings must know:
37
+ - S9: while WhatsApp is logged out, postMessage keeps appending to the in-memory outgoingQueue with no bound and nothing will ever flush it. getQrStatus().status === "needs-repair" is the signal to stop queueing (or to persist instead).
38
+ - operator-alert-ping-mirror: alert source is /health `whatsappNeedsRepair: true` or getQrStatus().status === "needs-repair".
39
+ - The connection.update listener body is now the private method handleConnectionUpdate(update) in src/adapters/whatsapp.ts.
40
+ --- final report (verbatim) ---
41
+ ~~~
42
+ Fixed whatsapp-logged-out-socket-leaves-the-process-up-and-mute (major).
43
+ - src/adapters/whatsapp.ts: connection.update body is now handleConnectionUpdate(). A 401 loggedOut close sets needs-repair on getQrStatus() ({status:"needs-repair",reason:"logged-out"}), logs one error naming the repair (stop, move auth dir aside, `mercury auth whatsapp`, start), no reconnect, process stays up. `open` clears it.
44
+ - src/server.ts: one readWhatsAppState() feeds isAdapterUp() and new isWhatsAppNeedsRepair(); /health gains always-emitted boolean `whatsappNeedsRepair` (only booleans leave /health).
45
+ - tests/whatsapp-logged-out.test.ts (6, negative-tested), tests/health.test.ts (+2), regression-map row.
46
+ - Follow-up bug filed: docs/bugs/mercury-auth-whatsapp-reports-already-authenticated-on-logged-out-creds.md
47
+ - Post-mortem: docs/debug/major/2026-10-11-whatsapp-logged-out-socket-leaves-the-process-up-and-mute.md
48
+ - Live checks: docs/pending-verification.md "## Plan monitoring-1005 / S8".
49
+ - Gate on the rebased tree: 15/16 links pass; test 6964 pass / 1 fail. The one fail is release-gate-map on moderate/2026-10-11-pi-stdout-chunks-decoded-separately-split-utf8-in-replies.md (another session's post-mortem, already on local dev at 4d00843/a062eab, missing its regression-map row) - not this fix.
50
+ DELIVERED: local (squash 0623fc9 on local dev; push not approved yet)
51
+ ~~~
52
+
53
+ ## Note — 2026-10-11
54
+ The Report block's "Files changed" list was derived against origin/dev and also lists the three unpushed split-utf8 commits (7add681, 4d00843, a062eab) that sat under this branch on local dev. This fix's own files are exactly the 9 in `git show --stat 0623fc9`: src/adapters/whatsapp.ts, src/server.ts, tests/whatsapp-logged-out.test.ts, tests/health.test.ts, tests/fixtures/release-gate/regression-map.json, docs/pending-verification.md, the post-mortem rename, and the new follow-up bug doc.
55
+
56
+ ## Exit — 2026-10-11T17:45Z
57
+ Verdict: unattributed (1 file)
58
+ Source: session-end-hook
59
+ ~~~
60
+ EXIT-RECEIPT: unattributed (1 file)
61
+ SHARED: /home/guysaha/projects/mercury with live session 181f2d41-4ebb-4b25-a5f4-a95bc6f4bde2 (since 2026-10-11T14:20:13Z)
62
+ SHARED: /home/guysaha/projects/mercury with live session bb84df0a-4857-479f-9250-3f332e211581 (since 2026-10-11T16:18:18Z)
63
+ UNATTRIBUTED: /home/guysaha/projects/mercury/docs/plans/monitoring-1005/sessions/S8.md — changed during this session, but live session 181f2d41-4ebb-4b25-a5f4-a95bc6f4bde2 bb84df0a-4857-479f-9250-3f332e211581 shares this checkout -> nothing records whose this is: ask the owner; never discard it
64
+ DEFERRED: push 4 commit(s) on dev -> git (/f-status reports ahead=4)
65
+ NOTE: remote state as of the last fetch (--no-fetch)
66
+ ~~~
@@ -0,0 +1,42 @@
1
+ ## Launched — 2026-10-12T09:50Z
2
+ Branch: worktree-fix-whatsapp-messages-sent-while-offline-are-dropped-forever
3
+ Session: 863a3268-442a-4142-9d6f-909bb250947c
4
+
5
+ ## Report — 2026-10-12T13:39Z
6
+ Outcome: delivered
7
+ Delivered: local
8
+ Blocked-on: —
9
+ Deploy needs: restart
10
+ Source: command
11
+ Files changed:
12
+ - docs/debug/major/2026-10-12-whatsapp-messages-sent-while-offline-are-dropped-forever.md
13
+ - docs/debug/whatsapp-messages-sent-while-offline-are-dropped-forever.md
14
+ - docs/pending-verification.md
15
+ - src/adapters/whatsapp-offline.ts
16
+ - src/adapters/whatsapp.ts
17
+ - tests/fixtures/release-gate/regression-map.json
18
+ - tests/whatsapp-offline-backlog.test.ts
19
+ Siblings must know:
20
+ - New module src/adapters/whatsapp-offline.ts (pure: offlineVerdict, offlineNoticeAddressed, offlineNoticeLanguage, offlineNoticeText, OFFLINE_ANSWER_WINDOW_MS).
21
+ - WhatsAppAdapter.offlineIngressStats() returns { admitted, dropped, noticesSent, noticesFailed } (in memory, reset on restart) for run-outcome-counters / the watchdog to read; no route was built.
22
+ - The upsert listener is now the method handleMessagesUpsert(type, messages); handleIncomingMessage takes an upsertType argument.
23
+ --- final report (verbatim) ---
24
+ ~~~
25
+ Fixed: WhatsApp messages sent while the bot was offline were dropped silently (messages.upsert "append" was discarded, and a 10 s backlog cutoff logged at debug).
26
+ Squash commit b22597f5 `fix(whatsapp): answer or notice messages sent while the bot was offline` on local dev (7 files, +1114/-97): src/adapters/whatsapp.ts, src/adapters/whatsapp-offline.ts (new), tests/whatsapp-offline-backlog.test.ts (27 tests), tests/fixtures/release-gate/regression-map.json, docs/pending-verification.md.
27
+ Policy (owner, 2026-10-12): backlog ≤ 15 min is answered; older is counted, logged at info, and gets one resend notice per chat (every DM; groups only on @-mention/reply), in the majority detected language of the dropped messages, English by default.
28
+ Post-mortem: docs/debug/major/2026-10-12-whatsapp-messages-sent-while-offline-are-dropped-forever.md
29
+ Gate: ~/check-mercury.sh check on the rebased tree, all 16 links passed, 7048 tests.
30
+ DELIVERED: local — not pushed; the push waits for the owner's approval (CLAUDE.local.md).
31
+ Deploy: restart (no baked file changed). Manual checks under "## Plan monitoring-1005 / S9" in docs/pending-verification.md.
32
+ ~~~
33
+
34
+ ## Exit — 2026-10-12T13:42Z
35
+ Verdict: nothing-lost (measured-only)
36
+ Source: session-end-hook
37
+ ~~~
38
+ EXIT-RECEIPT: nothing-lost (measured-only)
39
+ SHARED: /home/guysaha/projects/mercury with live session 181f2d41-4ebb-4b25-a5f4-a95bc6f4bde2 (since 2026-10-11T14:20:13Z)
40
+ DEFERRED: push 2 commit(s) on dev -> git (/f-status reports ahead=2)
41
+ NOTE: remote state as of the last fetch (--no-fetch)
42
+ ~~~
@@ -0,0 +1,64 @@
1
+ # Brief D1 — Cut 0.24.3-beta.4 and deploy it to the Tagula box (final step, not an item)
2
+
3
+ **Plan**: docs/plans/tagula-predeploy-1014/plan.md **Task**: D1 **Kind**: release + deploy (no branch, no workflow command)
4
+
5
+ > The plan's last step. It is not in the Items table: `plan-state.sh` derives "landed" from a merged branch, and a release plus a box deploy has none. Launch it by opening a fresh session in `~/projects/mercury` and saying: *"Run docs/plans/tagula-predeploy-1014/briefs/D1.md"*. Two steps below need the **owner's explicit yes in that session**: the push of the bump and tag, and the restart of the client box. Approval given in an earlier session does not carry over.
6
+
7
+ ## Why (decided 2026-10-14)
8
+
9
+ - The Tagula box runs `0.24.3-beta.3`, pinned by hand. Its nightly `tagula-box-upgrade` logs `holding` every night (the box is ahead of `:latest` = `0.24.2`), so it **never** takes a beta by itself. A beta reaches the box only through this manual deploy.
10
+ - **The reason for the deploy:** Brave ran out of credit on the box on 2026-10-11 (402 after 574 searches this month). There has been no web search on the box since, for every space: `guy-pishkin` told Avishai so on 10-14 08:03Z. The provider chain on `dev` (`cdf7d09`, Brave → Tavily, plus `f896e0e`) fails over on out-of-credit. `beta.3` was cut before it (`ed33f99`, 10-11 11:25), and no published version has it.
11
+ - The box's `.env` already has `MERCURY_TAVILY_API_KEY` (name checked, value not). **Whether that key is valid and has credit is unverified.**
12
+ - The WSL bot already runs everything on `origin/dev` as of 10-14 (`~/mercury-src` @ `8407fdb6`). Its 6 searches in 3 days all went to Brave, so failover to Tavily has **not** been seen live anywhere yet.
13
+ - Besides web-search, `beta.4` carries everything on `dev` since `beta.3`:
14
+ - the WhatsApp fixes (offline messages, logged-out socket, queued send, media reset);
15
+ - stream decoding of child output;
16
+ - the reply-language tag;
17
+ - office-365 meeting invites;
18
+ - **the systemd watchdog** (`3231203`);
19
+ - whatever S1–S3 of this plan landed.
20
+
21
+ ## Preconditions
22
+
23
+ 1. S1, S2 and S3 are landed on `origin/dev` (`/f-ops-plan next tagula-predeploy-1014`), or the owner says to deploy without the ones that are not. T1 is not a precondition.
24
+ 2. `git fetch origin` and `git log v0.24.3-beta.3..origin/dev --oneline` shows what will ship. Read it to the owner in one paragraph.
25
+ 3. Nobody else cut a `beta.4` meanwhile: `git ls-remote --tags origin 'v0.24.3-beta.*'` and `npm view mercury-agent versions --json | tail`. **Tell Avishai first: he cut `beta.3`.**
26
+
27
+ ## A — Cut the release (owner yes #1)
28
+
29
+ `docs/runbooks/publish-checklist.md` is the procedure. A prerelease is tagged on `dev`, and `release.yml` refuses one whose commit is not on `dev`.
30
+
31
+ - Work in a scratch worktree from `origin/dev`, **not** local `dev`. Local `dev` carries unpushed docs commits (Petel/egress notes) that must not ride along. Use literal absolute paths (see memory `worktree-remove-absolute-paths`).
32
+ - `npm version 0.24.3-beta.4 --no-git-tag-version` → commit `chore: bump version to 0.24.3-beta.4` → tag `v0.24.3-beta.4` → push the commit to `dev` and the tag.
33
+ - Watch `release.yml`. It must produce npm `mercury-agent@0.24.3-beta.4` (dist-tag `beta`) and `ghcr.io/avishai-tsabari/mercury-agent:0.24.3-beta.4` with label `com.mercury.version=0.24.3-beta.4`, and `:latest` must **not** move. If `publish-image` fails, npm is ahead of GHCR, which is the safe direction: stop, nothing on the box moves.
34
+
35
+ ## B — Deploy to the box (owner yes #2, for the restart)
36
+
37
+ Box: `ssh -i ~/.ssh/tagula_fleet_ed25519 root@46.225.180.66`, assistant dir `/opt/tagula/assistants/tagula-assistant`, port 8787, `export XDG_RUNTIME_DIR=/run/user/0`. Procedure: tagula-agents `docs/runbooks/run-box-upgrade.md` §4, read **from `origin/dev`** (`git -C ~/projects/tagula-agents fetch && git show origin/dev:docs/runbooks/run-box-upgrade.md`; the local checkout lags). There is no `sqlite3` on the box: use the runbook's `bun -e` one-liners.
38
+
39
+ 1. **Disk first.** It needs **at least 8 GB free** (`df -h /`): the base pull is about 3.6 GB and the derived ext build about 4.1 GB. On 2026-10-08 a pull into a full disk broke Kulmus (every article `submit` → HTTP 500, 09:51–10:40 IDT), and that is the failure this step prevents. On 2026-10-14 the box was freed to 12 GB (the `beta.2` image and 847 MB of `type=regular` build cache removed). **The disk tooling belongs to tagula-agents `box-disk-management`** (step 8 retrofits this box). If `tagula-box-disk` exists by then, use `tagula-box-disk status` and `room`. If not, and space is short, ask the owner; never delete on your own beyond the runbook.
40
+ 2. **Idle gap.** `/health` must show `queue.active + queue.pending + containers.active == 0`, and the last `messages` row must not be an unanswered user turn. Check the cron of active tasks (`SELECT id, cron, timezone, next_run_at FROM tasks WHERE active=1`). In `guy-pishkin`, 216 and 320 run `0 5 * * 0` (no timezone) and 406 runs `30 8 * * 0` Asia/Jerusalem. Guy works through the day, so prefer a night or early-morning gap. The updater timer (around 04:30–05:30 IDT) only logs `holding`, which is harmless.
41
+ 3. Unit file: copy `/root/.config/systemd/user/mercury.service` aside **before** anything. On 10-14 it was `Type=simple`, `Restart=on-failure`, `RestartSec=10`, with no watchdog.
42
+ 4. §4 in order:
43
+ - snapshot `state.db` (`VACUUM INTO`);
44
+ - `docker login` with `/etc/tagula/registry-token` → pull `:0.24.3-beta.4` → check its label and `image-manifest.json`;
45
+ - pin `MERCURY_AGENT_IMAGE` and `MERCURY_VERSION` in `.env` (atomic `sed`; read `.env` key names only, never print values);
46
+ - `mercury upgrade 0.24.3-beta.4` (the npm prerelease exists, so no tarball);
47
+ - journal: `Building derived` then `Built derived agent image`;
48
+ - `/health`: version `0.24.3-beta.4`, `status: ok`.
49
+ 5. **Unit diff.** `beta.4`'s watchdog adds `NotifyAccess=all` + `WatchdogSec=120` to the unit that `mercury service install` renders. Check whether `mercury upgrade` re-rendered it (diff against the saved copy). If not, ask the owner whether to re-render now. Re-rendering bakes the invoking shell's `PATH` into the unit (CLAUDE.local.md Gotchas), so carry the old unit's `PATH` over and diff again.
50
+
51
+ ## C — Verify (content inside the running artifact, never the repo)
52
+
53
+ - **Web search:** one real search in a test space (`tagula-management`, never a client space). The journal must show `web-search: query … provider=tavily` (Brave is still out of credit until its monthly reset or a top-up). A rejected Tavily key shows as failover plus an operator alert: report it to the owner, who then has to fix the key.
54
+ - **S1 (if landed):** in a space where you hold only the `member` role, a public Google Doc **you own** (never the client's) reads with no `gws`. A private one gives the "not shared publicly" wording.
55
+ - **S2/S3 (if landed):** grep the **derived** image for the new pinchtab skill rule and the helper change: `docker run --rm --entrypoint grep mercury-agent-ext-tagula:<new> …`.
56
+ - A round trip per §4 step 4 (`mercury chat` with a probe caller in a non-client space).
57
+ - Paste the `docs/release-gate.md` §4 evidence block into `sessions/D1.md`. An empty cell is a gate not passed.
58
+
59
+ ## D — After
60
+
61
+ - Old derived image `mercury-agent-ext-tagula:c5463f8de0fc` (4.08 GB): the derived build normally prunes it (`Pruned stale ext image`). Confirm it's gone with `docker images`.
62
+ - Keep the `beta.3` base as the rollback (§4 Rollback). Leave `0.24.2` alone. Image retention is `box-disk-management`'s policy, not this task's.
63
+ - Rollback, if needed: `mercury upgrade 0.24.3-beta.3`, re-pin `.env` to `:0.24.3-beta.3`, restore the saved unit, and restart. Restore `state.db` only if a migration ran (read the journal).
64
+ - Append the report to `docs/plans/tagula-predeploy-1014/sessions/D1.md`: the version live, the evidence block, the unit diff, the Tavily verdict and disk free after. Then `/f-ops-plan archive tagula-predeploy-1014` once S1–S3 are landed.