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 ## CLI
Any external process — scripts, UIs, manual debugging — can talk to a running daemon directly: 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 send --from <mailbox> --to <mailbox> --body <text>
claude-mailbox peek --name <mailbox> claude-mailbox peek --name <mailbox>
claude-mailbox check --name <mailbox> [--hook] claude-mailbox check --name <mailbox> [--hook]
claude-mailbox watch --block --name <mailbox> [--timeout 25]
claude-mailbox list claude-mailbox list
claude-mailbox status claude-mailbox status
claude-mailbox session-announce # hook helper, reads stdin JSON 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 }` | | `POST` | `/v1/send` | yes (sender) | body: `{ to, body }` |
| `GET` | `/v1/peek?name=<mailbox>` | no | read-only status | | `GET` | `/v1/peek?name=<mailbox>` | no | read-only status |
| `POST` | `/v1/check-inbox?name=<mailbox>` | yes (must match `name`) | consume inbox | | `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 | | `GET` | `/v1/list` | optional (presence registers caller) | list all mailboxes |
--- ---

View File

@@ -39,6 +39,16 @@ Under the hood the hook runs `claude-mailbox check --name <mailbox> --hook`, whi
Cost: one local HTTP round-trip plus Node coldstart per prompt (~100ms on Windows). Cost: one local HTTP round-trip plus Node coldstart per prompt (~100ms on Windows).
## Push delivery (watch)
For long-running autonomous sessions, run the watcher as a background bash task so peer messages surface immediately via `BashOutput`:
```sh
claude-mailbox watch --block --name <mailbox>
```
Exit codes: `0` delivered or renamed, `1` error, `2` daemon unreachable, `3` timeout. See the [repository README](https://git.kuns.dev/releases/ClaudeMailbox/src/branch/main/README.md#push-delivery-watch) for the full contract.
## Troubleshooting ## Troubleshooting
`npm install` returns `401 Unauthorized` `npm install` returns `401 Unauthorized`

View File

@@ -45,6 +45,8 @@ The `SessionStart` hook announces the current session's mailbox name in the conv
Cost: one local HTTP round-trip per prompt and per subagent stop + Node coldstart (~100ms on Windows). Cost: one local HTTP round-trip per prompt and per subagent stop + Node coldstart (~100ms on Windows).
The SessionStart announcement also instructs Claude to start `claude-mailbox watch --block --name <derived-name>` as a background bash task on its first turn. While that watcher is alive, peers can `mcp__mailbox__send(...)` and Claude reacts mid-turn — no user prompt needed. After processing each completion (delivery, timeout, rename, or daemon-down), Claude relaunches the watcher in the background. The pull hook (`UserPromptSubmit`) remains as a fallback for any messages that arrive while no watcher is running.
## MCP tools ## MCP tools
The plugin ships a `.mcp.json` that spawns a **stdio MCP wrapper** (`claude-mailbox mcp-stdio`) so the daemon URL is configurable per machine via the `CLAUDE_MAILBOX_URL` env var (Claude Code doesn't yet support env substitution in HTTP MCP URLs — see issue #46889). The wrapper proxies tool calls to the daemon's REST API. The plugin ships a `.mcp.json` that spawns a **stdio MCP wrapper** (`claude-mailbox mcp-stdio`) so the daemon URL is configurable per machine via the `CLAUDE_MAILBOX_URL` env var (Claude Code doesn't yet support env substitution in HTTP MCP URLs — see issue #46889). The wrapper proxies tool calls to the daemon's REST API.