mercury-agent 0.24.3-beta.3 → 0.24.3-beta.6

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 (99) hide show
  1. package/README.md +1 -1
  2. package/container/Dockerfile +1 -0
  3. package/container/Dockerfile.base +1 -0
  4. package/container/Dockerfile.power +1 -1
  5. package/docs/behavior-layers.md +3 -1
  6. package/docs/configuration.md +3 -3
  7. package/docs/container-lifecycle.md +4 -4
  8. package/docs/deployment.md +1 -1
  9. package/docs/pending-verification.md +48 -3
  10. package/docs/permissions.md +1 -0
  11. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S7.md +2 -2
  12. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/decisions.md +2 -2
  13. package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/plan.md +12 -20
  14. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S12.md +47 -0
  15. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S7.md +75 -0
  16. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S8.md +66 -0
  17. package/docs/plans/archive/2026-10-12-monitoring-1005/sessions/S9.md +42 -0
  18. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/D1.md +64 -0
  19. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S1.md +72 -0
  20. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S2.md +63 -0
  21. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/S3.md +63 -0
  22. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/briefs/T1.md +33 -0
  23. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/decisions.md +7 -0
  24. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/plan.md +118 -0
  25. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/D1.md +59 -0
  26. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S1.md +46 -0
  27. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S2.md +56 -0
  28. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/S3.md +66 -0
  29. package/docs/plans/archive/2026-10-14-tagula-predeploy-1014/sessions/T1.md +7 -0
  30. package/docs/plans/general-0928/briefs/S3.md +1 -1
  31. package/docs/plans/general-0928/briefs/S4.md +1 -1
  32. package/docs/plans/general-0928/plan.md +5 -9
  33. package/docs/plans/watchdog-1011/briefs/S1.md +75 -0
  34. package/docs/plans/watchdog-1011/decisions.md +8 -0
  35. package/docs/plans/watchdog-1011/plan.md +114 -0
  36. package/docs/web-search.md +30 -7
  37. package/examples/extensions/gws/README.md +24 -4
  38. package/examples/extensions/gws/index.ts +171 -19
  39. package/examples/extensions/gws/skill/SKILL.md +33 -7
  40. package/examples/extensions/gws/skill/scripts/google-public-read.ts +384 -0
  41. package/examples/extensions/gws/token-mint.ts +206 -0
  42. package/examples/extensions/longview/summarize.ts +8 -4
  43. package/examples/extensions/napkin/index.ts +4 -2
  44. package/examples/extensions/office-365-mail/README.md +18 -2
  45. package/examples/extensions/office-365-mail/index.ts +7 -8
  46. package/examples/extensions/office-365-mail/lib/graph.ts +162 -17
  47. package/examples/extensions/office-365-mail/scripts/login.ts +5 -4
  48. package/examples/extensions/office-365-mail/skill/SKILL.md +18 -4
  49. package/examples/extensions/pinchtab/bin/pinchtab_ensure +52 -6
  50. package/examples/extensions/pinchtab/bin/pinchtab_nav +29 -3
  51. package/examples/extensions/web-search/capability.ts +630 -0
  52. package/examples/extensions/web-search/chain.ts +761 -0
  53. package/examples/extensions/web-search/index.ts +158 -49
  54. package/examples/extensions/web-search/ledger.ts +13 -0
  55. package/examples/extensions/web-search/providers/brave.ts +169 -0
  56. package/examples/extensions/web-search/providers/index.ts +91 -0
  57. package/examples/extensions/web-search/providers/tavily.ts +143 -0
  58. package/examples/extensions/web-search/providers/types.ts +191 -0
  59. package/examples/extensions/web-search/skill/SKILL.md +18 -13
  60. package/package.json +3 -3
  61. package/resources/pi-extensions/subagent/index.ts +8 -4
  62. package/resources/skills/permissions/SKILL.md +5 -1
  63. package/src/adapters/whatsapp-offline.ts +141 -0
  64. package/src/adapters/whatsapp.ts +333 -63
  65. package/src/agent/container-entry.ts +6 -4
  66. package/src/agent/container-runner.ts +5 -4
  67. package/src/agent/image-contract.ts +1 -0
  68. package/src/bridges/whatsapp.ts +48 -9
  69. package/src/cli/mercury.ts +14 -0
  70. package/src/cli/service-unit.ts +18 -1
  71. package/src/config.ts +1 -2
  72. package/src/core/data-dir-ownership.ts +53 -0
  73. package/src/core/exec.ts +5 -4
  74. package/src/core/permissions.ts +7 -0
  75. package/src/core/router.ts +6 -1
  76. package/src/core/routes/send.ts +10 -0
  77. package/src/core/systemd-watchdog.ts +381 -0
  78. package/src/core/task-scheduler.ts +32 -0
  79. package/src/core/task-sender.ts +86 -8
  80. package/src/extensions/image-builder.ts +3 -2
  81. package/src/main.ts +18 -2
  82. package/src/preflight/binary-version.ts +3 -2
  83. package/src/server.ts +33 -4
  84. package/src/text/utf8-stream.ts +48 -0
  85. package/src/types.ts +22 -0
  86. package/docs/plans/general-0928/briefs/S5.md +0 -58
  87. package/docs/plans/monitoring-1005/briefs/S5.md +0 -62
  88. package/docs/plans/monitoring-1005/briefs/S6.md +0 -67
  89. package/examples/extensions/web-search/brave.ts +0 -751
  90. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S1.md +0 -0
  91. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S12.md +0 -0
  92. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S2.md +0 -0
  93. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S3.md +0 -0
  94. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S4.md +0 -0
  95. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S8.md +0 -0
  96. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/briefs/S9.md +0 -0
  97. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S2.md +0 -0
  98. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S3.md +0 -0
  99. /package/docs/plans/{monitoring-1005 → archive/2026-10-12-monitoring-1005}/sessions/S4.md +0 -0
package/README.md CHANGED
@@ -420,7 +420,7 @@ Supported OAuth providers: Anthropic, GitHub Copilot, Google Gemini CLI, Antigra
420
420
 
421
421
  | Variable | Default | Description |
422
422
  |----------|---------|-------------|
423
- | `MERCURY_AGENT_IMAGE` | `ghcr.io/avishai-tsabari/mercury-agent:<version>` | Container image; pinned to the installed version, see `docs/configuration.md` § Agent image |
423
+ | `MERCURY_AGENT_IMAGE` | `ghcr.io/tagula-ai/mercury-agent:<version>` | Container image; pinned to the installed version, see `docs/configuration.md` § Agent image |
424
424
  | `MERCURY_CONTAINER_TIMEOUT_MS` | `300000` | Container timeout (5 min) |
425
425
  | `MERCURY_CONTAINER_RUNTIME` | `runc` | `runc` (default) or `runsc` ([gVisor](https://gvisor.dev)) |
426
426
  | `MERCURY_CONTAINER_BWRAP_DOCKER_COMPAT` | `false` | Set `true` on Linux Docker Engine (see note below) |
@@ -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/
@@ -3,7 +3,7 @@
3
3
  # Extends Base with: python3 + pip + venv, build-essential, jq, ffmpeg, bubblewrap
4
4
  # The patch script, mrctl, and entrypoint are all inherited from Base — not duplicated here.
5
5
  ARG BASE_TAG
6
- FROM ghcr.io/avishai-tsabari/mercury-agent:${BASE_TAG}-base
6
+ FROM ghcr.io/tagula-ai/mercury-agent:${BASE_TAG}-base
7
7
 
8
8
  USER root
9
9
  RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -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
 
@@ -834,7 +834,7 @@ agent:
834
834
  Env: `MERCURY_AGENT_IMAGE`.
835
835
 
836
836
  **The default is the registry image at this host's own version** —
837
- `ghcr.io/avishai-tsabari/mercury-agent:<version>`, where `<version>` is the
837
+ `ghcr.io/tagula-ai/mercury-agent:<version>`, where `<version>` is the
838
838
  installed package's `package.json` version (release-gate R0.6, since 0.22.0;
839
839
  before that the default floated on `:latest`). Nothing needs to be set for it,
840
840
  and a host upgrade changes the reference by itself, so the box asks for the
@@ -183,7 +183,7 @@ Message received
183
183
  export MERCURY_CONTAINER_TIMEOUT_MS=600000
184
184
 
185
185
  # Override the agent image. The default needs no setting: it is the registry
186
- # image at this host's own version, ghcr.io/avishai-tsabari/mercury-agent:<version>
186
+ # image at this host's own version, ghcr.io/tagula-ai/mercury-agent:<version>
187
187
  export MERCURY_AGENT_IMAGE=mercury-agent:latest # a local `mercury build` / container/build.sh
188
188
  ```
189
189
 
@@ -349,7 +349,7 @@ Mercury publishes an image preset to GitHub Container Registry:
349
349
 
350
350
  | Preset | Size | Contents |
351
351
  |--------|------|----------|
352
- | `ghcr.io/avishai-tsabari/mercury-agent:<version>` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
352
+ | `ghcr.io/tagula-ai/mercury-agent:<version>` | ~2.8GB | Full devcontainer: Bun, Node.js, Python, Go, git, build tools |
353
353
 
354
354
  Images are published on each release by `release.yml`'s `publish-image` job,
355
355
  which builds from the published npm tarball and pushes `:<version>` alongside
@@ -357,7 +357,7 @@ which builds from the published npm tarball and pushes `:<version>` alongside
357
357
 
358
358
  **The default is pinned to the host's version** (release-gate R0.6, since
359
359
  0.22.0): a host running mercury `X.Y.Z` with no `agent.image` override asks
360
- for `ghcr.io/avishai-tsabari/mercury-agent:X.Y.Z` — never `:latest`. So a
360
+ for `ghcr.io/tagula-ai/mercury-agent:X.Y.Z` — never `:latest`. So a
361
361
  host upgrade changes the reference, the new tag is absent locally, and boot
362
362
  pulls it (or fails loudly if it cannot); `mercury preflight`'s `docker.image`
363
363
  check names the pinned tag, the host version and the two ways out when the
@@ -399,7 +399,7 @@ image id, repo digest, version label and whether a refresh happened, so drift
399
399
  is visible without being asked for:
400
400
 
401
401
  ```
402
- Agent image resolved image=ghcr.io/avishai-tsabari/mercury-agent:0.22.0
402
+ Agent image resolved image=ghcr.io/tagula-ai/mercury-agent:0.22.0
403
403
  imageId=sha256:… digest=…@sha256:… imageVersion=0.22.0 hostVersion=0.22.0
404
404
  refresh=pulled reason=not-present
405
405
  ```
@@ -112,7 +112,7 @@ mercury upgrade --skip-image-pull # host only — for a box on a locally built
112
112
  you asked for to one exact version (`latest`, a dist-tag or a range go
113
113
  through `npm view`), then **pulls the agent image that version's host will
114
114
  ask for** — the default is pinned to the version, so that is
115
- `ghcr.io/avishai-tsabari/mercury-agent:<version>` — and refuses when the
115
+ `ghcr.io/tagula-ai/mercury-agent:<version>` — and refuses when the
116
116
  registry has no such tag or refuses the credential, **leaving the host
117
117
  untouched**. Only then, on macOS and Linux, does it stop the managed service,
118
118
  install the new version globally and restart the service. It installs the
@@ -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
+ ~~~