From 8c8be67a981ab53ac200d6e809482982d136430a Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 20 May 2026 16:42:52 +0200 Subject: [PATCH] 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) --- README.md | 31 +++++++++++++++++++++++++++++++ node/README.md | 10 ++++++++++ plugin/README.md | 2 ++ 3 files changed, 43 insertions(+) diff --git a/README.md b/README.md index a48ac32..d2827d3 100644 --- a/README.md +++ b/README.md @@ -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 [--timeout 25] [--url ] +``` + +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 ''` 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 --to --body claude-mailbox peek --name claude-mailbox check --name [--hook] +claude-mailbox watch --block --name [--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 ` to target a non-default daemon address. | `POST` | `/v1/send` | yes (sender) | body: `{ to, body }` | | `GET` | `/v1/peek?name=` | no | read-only status | | `POST` | `/v1/check-inbox?name=` | yes (must match `name`) | consume inbox | +| `GET` | `/v1/watch?name=&timeout=` | 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 | --- diff --git a/node/README.md b/node/README.md index 849d35f..82efe3f 100644 --- a/node/README.md +++ b/node/README.md @@ -39,6 +39,16 @@ Under the hood the hook runs `claude-mailbox check --name --hook`, whi 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 +``` + +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 `npm install` returns `401 Unauthorized` diff --git a/plugin/README.md b/plugin/README.md index f7f75e7..4f1bb3e 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -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). +The SessionStart announcement also instructs Claude to start `claude-mailbox watch --block --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 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.