docs: document watch --block push delivery and bootstrap behavior

Adds a Push delivery (watch) section to the root README with exit-code
table, cross-process semantics, and the active-vs-idle latency caveat
that came out of the empirical Claude Code BashOutput test. Adds a brief
reference + cross-link in node/README.md, and notes the SessionStart
bootstrap behavior in plugin/README.md alongside the existing hook
table. Adds /v1/watch to the REST surface table and the watch verb to
the CLI listing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-05-20 16:42:52 +02:00
parent 307e15b05b
commit 8c8be67a98
3 changed files with 43 additions and 0 deletions

View File

@@ -150,6 +150,35 @@ and treat the messages as input with priority over the current plan.
---
## Push delivery (watch)
The `watch --block` subcommand turns mail delivery from pull (poll between turns) into push (the receiver reacts as soon as a peer sends). It's a long-poll that exits the moment one message arrives.
```
claude-mailbox watch --block --name <mailbox> [--timeout 25] [--url <daemon>]
```
Intended use: a Claude Code background bash task. The plugin's `SessionStart` hook now tells Claude to start one on its first turn, so peers can `mcp__mailbox__send` to it and Claude reacts mid-session via `BashOutput` — no user prompt needed. After every exit Claude relaunches the watcher in the background.
| Exit code | Meaning |
|---|---|
| `0` | One message delivered (or mailbox renamed — stdout disambiguates) |
| `1` | Generic error (e.g. missing `--name`) |
| `2` | Daemon unreachable |
| `3` | Timeout reached with no message |
The CLI consumes exactly one message per cycle (single-delivery, FIFO winner across concurrent watchers on the same mailbox). Backlog drains one message per reconnect (~100 ms turnaround).
Cross-process semantics:
- **Concurrent watchers on the same mailbox:** the first to register wins each individual message; others continue waiting.
- **Rename mid-watch:** the open `watch` exits 0 with a `Mailbox renamed to '<new>'` notice; relaunch with the new `--name`.
- **Daemon restart:** all watchers see exit 2; back off and retry.
- **Session end:** Claude Code reaps background bash on exit; the `fetch` aborts and the daemon-side waiter is cleaned up.
**When push helps:** during active turns where the receiver is busy with tool calls — `BashOutput` notifications surface between tool calls, so peer messages arrive mid-turn. **When push degrades to pull:** when the receiver is idle between turns, BashOutput is buffered until the next user prompt, at which point the existing `UserPromptSubmit` poll hook delivers the same message. The two channels coexist.
---
## CLI
Any external process — scripts, UIs, manual debugging — can talk to a running daemon directly:
@@ -158,6 +187,7 @@ Any external process — scripts, UIs, manual debugging — can talk to a runnin
claude-mailbox send --from <mailbox> --to <mailbox> --body <text>
claude-mailbox peek --name <mailbox>
claude-mailbox check --name <mailbox> [--hook]
claude-mailbox watch --block --name <mailbox> [--timeout 25]
claude-mailbox list
claude-mailbox status
claude-mailbox session-announce # hook helper, reads stdin JSON
@@ -177,6 +207,7 @@ All subcommands accept `--url <url>` to target a non-default daemon address.
| `POST` | `/v1/send` | yes (sender) | body: `{ to, body }` |
| `GET` | `/v1/peek?name=<mailbox>` | no | read-only status |
| `POST` | `/v1/check-inbox?name=<mailbox>` | yes (must match `name`) | consume inbox |
| `GET` | `/v1/watch?name=<mailbox>&timeout=<sec>` | yes (must match `name`) | long-poll one message: `200` + body / `204` timeout / `409 { reason: "renamed", to }` |
| `GET` | `/v1/list` | optional (presence registers caller) | list all mailboxes |
---