oxtail 0.7.0 → 0.8.0

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.
package/AGENTS.md CHANGED
@@ -17,21 +17,21 @@ Scope is **project-root as the unit**. Sessions in one project root see each oth
17
17
  - **Registry (leaning):** `tmux list-sessions` filtered by project-derived names, rather than a custom JSON registry. Free dead-session detection, free naming, no daemon to maintain. Decision pending real-use signals.
18
18
  - **Project scoping:** project root inferred from session CWD at agent startup.
19
19
 
20
- ## Status: v0.7.0 shipped, dogfooding
20
+ ## Status: v0.8.0 shipped, dogfooding
21
21
 
22
- Nine MCP tools live: `list_project_sessions`, `read_session`, `claim_session`, `set_my_state`, `register_my_session`, `get_my_session`, the v0.5 messaging pair `send_message` and `read_my_messages`, and the v0.6 delegate-and-wait primitive `ask_peer`. Registered both project-locally (via `.mcp.json` using `tsx ./src/server.ts` for the dev loop) and globally (in `~/.claude.json` and `~/.codex/config.toml`, pointing at `dist/server.js`).
22
+ Nine MCP tools live: `list_project_sessions`, `read_session`, `claim_session`, `set_my_state`, `register_my_session`, `get_my_session`, the v0.5 messaging pair `send_message` and `read_my_messages`, and `ask_peer` (delegate-and-wait, introduced v0.6, per-client wake routing in v0.7). Registered both project-locally (via `.mcp.json` using `tsx ./src/server.ts` for the dev loop) and globally (in `~/.claude.json` and `~/.codex/config.toml`, pointing at `dist/server.js`).
23
23
 
24
24
  The v0.4.0 change: peer `client_session_id` and `transcript_path` now resolve reliably for Claude Code and Codex peers, even though Claude Code strips its session-id env var from MCP children. Detection layers in `src/detect/` — env, then birth-time fingerprint matching of transcript files, with a `claim_session` escape hatch (`register_my_session` is kept for debugging) — see `README.md` for details.
25
25
 
26
26
  The follow-on additions (`claim_session`, `set_my_state`) introduce a peer-awareness layer: `list_project_sessions` now surfaces each peer's `state` card so an agent can learn what its peers are doing without paying for `read_session`. Raw transcripts become the deep-dive fallback, not the default mode of peer awareness.
27
27
 
28
- Current phase remains **dogfooding**: use the tools in real parallel-agent work, log friction in `NOTES.md`. Each version (v0.1 list_project_sessions → v0.2 read_session → v0.3 reliable peer identity → v0.4 peer-awareness state cards → v0.5 peer-to-peer messaging → v0.6 delegate-and-wait) shipped only after observed friction named the next addition; the same gating applies to whatever comes next.
28
+ Current phase remains **dogfooding**: use the tools in real parallel-agent work, log friction in `NOTES.md`. Each version (v0.1 list_project_sessions → v0.2 read_session → v0.3 reliable peer identity → v0.4 peer-awareness state cards → v0.5 peer-to-peer messaging → v0.6 delegate-and-wait → v0.7 per-client wake routing → v0.8 symmetric Claude Code wake) shipped only after observed friction named the next addition; the same gating applies to whatever comes next.
29
29
 
30
30
  The v0.5 change: two new MCP tools (`send_message`, `read_my_messages`) plus an opt-in `PreToolUse` hook installable via `npx oxtail install-hook`. Friction observed while pairing on Terminator — two agents in the same project root can see each other's state cards and transcripts but couldn't say anything to each other. Now they can. Claude Code peers see messages mid-turn (via the hook); Codex peers (or unhooked Claude Code) see them next-turn (via polling `read_my_messages`).
31
31
 
32
32
  The v0.6 change: one new MCP tool (`ask_peer`) that turns v0.5's async pings into a blocking delegate-and-wait. Friction observed while dogfooding v0.5 — `send_message` lets agents say things to each other, but the sender doesn't stay in-turn waiting for a reply. `ask_peer` blocks server-side until a reply with a matching `from_session_id` lands (or a fixed timeout elapses) and fires a `tmux send-keys` wake against the peer's pane.
33
33
 
34
- The v0.7 change: per-client wake routing after the v0.6 wake was found to be broken against idle TUI peers. Spike investigation (issue #3) revealed two distinct constraints, fixed differently. For **Codex**: the root cause was not `\r`-as-newline as initially suspected, but Codex's paste-burst heuristic (`codex-rs/tui/src/bottom_pane/paste_burst.rs`) suppressing Enter for ~120ms after a fast typed burst — `tmux send-keys -l text` + immediate `send-keys Enter` looked like a paste, so the trailing Enter was forcibly converted to newline. Fix: a 500ms gap between the text and the Enter. Verified live 2026-05-13 against the live `oxtail-codex` peer in this repo. For **Claude Code**: idle peers are architecturally unwakeable from outside the process the documented Claude Code hook surface has no idle event, no polling, no external "start a turn" mechanism (`Notification` is outbound-only; `FileChanged` only fires inside an in-flight turn). v0.7 ask_peer fail-fasts for Claude Code targets with `wake_status: "skipped_unsupported"` rather than burning the 45s timeout. The outbound is still enqueued and delivered next time the peer enters a turn. Wake strategy is overridable via `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` as a rollback.
34
+ The v0.7 change: per-client wake routing after the v0.6 wake was found to be broken against idle TUI peers. Spike investigation (issue #3) revealed Codex's paste-burst heuristic (`codex-rs/tui/src/bottom_pane/paste_burst.rs`) was suppressing Enter for ~120ms after a fast typed burst — `tmux send-keys -l text` + immediate `send-keys Enter` looked like a paste, so the trailing Enter was forcibly converted to newline. Fix: a 500ms gap between the text and the Enter for Codex peers. Verified live 2026-05-13 against the live `oxtail-codex` peer in this repo. v0.7 also fail-fasted Claude Code targets with `wake_status: "skipped_unsupported"` based on a reading of the Claude Code hook catalog (no idle hook surface → "architecturally unwakeable") but that reasoning conflated *hook events* (which Claude Code doesn't expose for idle) with *TUI input* (which works fine via `tmux send-keys`, the same mechanism that wakes Codex). A falsifying experiment 2026-05-13 against the live `oxtail-claudejr` peer confirmed the full round-trip works: ask_peer enqueue → manual send-keys → peer entered a turn → PreToolUse hook drained mailbox → peer replied via send_message. The fail-fast was a self-inflicted regression against oxtail's symmetric-matrix vision (Claude↔Claude, Claude↔Codex, both directions), so the short-circuit was removed in the follow-up. Claude Code peers now wake via the same send-keys mechanism, just without the Codex paste-burst gap. Wake strategy is overridable via `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` as a rollback.
35
35
 
36
36
  ## How to collaborate on this project
37
37
 
@@ -53,7 +53,7 @@ The v0.7 change: per-client wake routing after the v0.6 wake was found to be bro
53
53
 
54
54
  ## Recently shipped
55
55
 
56
- - **Per-client wake routing (v0.7).** `ask_peer` now routes its wake mechanism per `client_type`. Codex: paste-burst-aware send-keys (500ms gap between text and Enter) — verified to actually submit. Claude Code: fail-fast with `wake_status: "skipped_unsupported"` since the hook surface has no idle event. Response gains a `wake_status` field for caller diagnostics. Pre-wake pane re-resolution closes the stale-pane-ID race from v0.6. `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` env override for rollback. Issue #3 has the spike findings.
56
+ - **Per-client wake routing (v0.7, refined).** `ask_peer` routes its wake mechanism per `client_type`. **Codex**: paste-burst-aware send-keys (500ms gap between text and Enter) — verified to submit. **Claude Code**: same send-keys mechanism without the gap (no paste-burst in its TUI) — verified end-to-end 2026-05-13 against `oxtail-claudejr`. v0.7 originally fail-fasted Claude Code targets under a hook-catalog argument; the follow-up restored symmetric wake after falsifying that conclusion empirically. Response includes a `wake_status` field for caller diagnostics. Pre-wake pane re-resolution closes the stale-pane-ID race from v0.6. `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` env override for rollback. Issue #3 has the spike findings.
57
57
  - **Delegate-and-wait (v0.6).** `ask_peer({ target, body })` blocks server-side until the peer replies (filtered by `from_session_id`) or a fixed timeout elapses. Late replies fall back to the v0.5 hook / poll delivery path. Target must have a registered `client.session_id`.
58
58
  - **Cross-session messaging (v0.5).** `send_message({ target, body })` + `read_my_messages()`. Mailbox lives at `~/.oxtail/mailboxes/<server_pid>.jsonl`, drained under an `mkdir`-based advisory lock. Opt-in PreToolUse hook (`npx oxtail install-hook`) for mid-turn delivery to Claude Code.
59
59
 
package/README.md CHANGED
@@ -21,7 +21,7 @@ End users — paste into your MCP config and oxtail is fetched from npm on first
21
21
  **Claude Code** — add to `~/.claude.json` (global) or any project's `.mcp.json`:
22
22
 
23
23
  ```jsonc
24
- { "mcpServers": { "oxtail": { "command": "npx", "args": ["-y", "oxtail@0.6.0"] } } }
24
+ { "mcpServers": { "oxtail": { "command": "npx", "args": ["-y", "oxtail@0.8.0"] } } }
25
25
  ```
26
26
 
27
27
  **Codex CLI** — add to `~/.codex/config.toml`:
@@ -29,14 +29,14 @@ End users — paste into your MCP config and oxtail is fetched from npm on first
29
29
  ```toml
30
30
  [mcp_servers.oxtail]
31
31
  command = "npx"
32
- args = ["-y", "oxtail@0.6.0"]
32
+ args = ["-y", "oxtail@0.8.0"]
33
33
  ```
34
34
 
35
35
  **Claude slash command** (`/oxtail-join`):
36
36
 
37
37
  ```sh
38
38
  mkdir -p ~/.claude/commands
39
- curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.6.0/.claude/commands/oxtail-join.md \
39
+ curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.8.0/.claude/commands/oxtail-join.md \
40
40
  -o ~/.claude/commands/oxtail-join.md
41
41
  ```
42
42
 
@@ -44,9 +44,9 @@ curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.6.0/.claude/commands
44
44
 
45
45
  ```sh
46
46
  mkdir -p ~/.codex/skills/oxtail-join/agents
47
- curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.6.1/integrations/codex/oxtail-join/SKILL.md \
47
+ curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.8.0/integrations/codex/oxtail-join/SKILL.md \
48
48
  -o ~/.codex/skills/oxtail-join/SKILL.md
49
- curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.6.1/integrations/codex/oxtail-join/agents/openai.yaml \
49
+ curl -L https://raw.githubusercontent.com/d4j3y2k/oxtail/v0.8.0/integrations/codex/oxtail-join/agents/openai.yaml \
50
50
  -o ~/.codex/skills/oxtail-join/agents/openai.yaml
51
51
  ```
52
52
 
@@ -67,7 +67,7 @@ Contributing? `git clone https://github.com/d4j3y2k/oxtail && cd oxtail && npm i
67
67
  - `set_my_state` — write a small "state card" onto this session's registry entry so peers can see what we're doing without reading our transcript. v1 surfaces a single field, `purpose` (≤200 chars).
68
68
  - `send_message` — **fire-and-forget** message to a peer. **Does NOT wake an idle peer.** Target is a tmux session name or a raw `client_session_id` UUID. Body ≤ 8KB. Delivery is async via the peer's mailbox file. (v0.5+)
69
69
  - `read_my_messages` — drain this session's mailbox and return any queued messages. Codex peers (and unhooked Claude Code) poll this; Claude Code peers with the PreToolUse hook installed see messages mid-turn instead. (v0.5+)
70
- - `ask_peer` — **delegate-and-wait**. Enqueues a message and blocks server-side until the peer replies (or the fixed timeout elapses, default 45s, tunable via `OXTAIL_ASK_PEER_TIMEOUT_MS`). v0.7 routes the wake per `client_type`: Codex gets a paste-burst-aware `tmux send-keys` wake that actually submits; Claude Code targets fail-fast (no wake surface for idle Claude see "Delegate-and-wait" below). Response includes `wake_status` so the caller can distinguish "we polled and got nothing" from "this peer can't be woken." Use `send_message` for fire-and-forget. (v0.7+; v0.6 caveats below)
70
+ - `ask_peer` — **delegate-and-wait**. Enqueues a message and blocks server-side until the peer replies (or the fixed timeout elapses, default 45s, tunable via `OXTAIL_ASK_PEER_TIMEOUT_MS`). Routes the wake per `client_type`: Codex gets a paste-burst-aware `tmux send-keys` wake (500ms gap before Enter to defeat the paste-burst heuristic); Claude Code gets the same send-keys mechanism without the gap (its TUI has no paste-burst). Response includes `wake_status` so the caller can distinguish "we polled and got nothing" from "no tmux pane resolved." Use `send_message` for fire-and-forget. (v0.7+)
71
71
  - `register_my_session` — pin this MCP server's `session_id` directly. Kept for debugging; prefer `claim_session`.
72
72
  - `get_my_session` — return this MCP server's own registry entry plus a per-strategy detection diagnosis. Useful for debugging.
73
73
 
@@ -151,17 +151,17 @@ ask_peer({ target, body })
151
151
  }
152
152
  ```
153
153
 
154
- `wake_status` distinguishes the four outcomes a caller may need to handle differently. `fired` means the wake was attempted (or the reply arrived during the grace window, so no wake was needed). `skipped_unsupported` means the target's `client_type` cannot be woken externally currently Claude Code, see "Per-client wake routing" below. `skipped_no_target` means no tmux pane/session resolved for the target. `disabled` means `OXTAIL_ASK_PEER_WAKE_STRATEGY=off` is in effect.
154
+ `wake_status` distinguishes the four outcomes a caller may need to handle differently. `fired` means the wake was attempted (or the reply arrived during the grace window, so no wake was needed). `skipped_unsupported` is reserved no client currently returns this in auto mode (both Codex and Claude Code wake via send-keys). `skipped_no_target` means no tmux pane/session resolved for the target. `disabled` means `OXTAIL_ASK_PEER_WAKE_STRATEGY=off` is in effect.
155
155
 
156
- `timed_out` is `true` only when the poll loop ran to its deadline without a reply. Fail-fast for `skipped_unsupported` returns `timed_out: false` because no polling was attempted — the message is still enqueued and will be delivered when the peer next enters a turn.
156
+ `timed_out` is `true` only when the poll loop ran to its deadline without a reply.
157
157
 
158
158
  ### Per-client wake routing
159
159
 
160
- v0.7 routes the wake mechanism per `client_type`. Verified 2026-05-13 via spike investigation and falsifying experiment:
160
+ `ask_peer` routes the wake mechanism per `client_type`. Verified 2026-05-13 via spike investigations and end-to-end falsifying experiments against the live `oxtail-codex` and `oxtail-claudejr` peers in this repo:
161
161
 
162
162
  - **Codex** — `tmux send-keys -l <text>` followed by `send-keys Enter` is the wake. The keystrokes are split by 500ms because Codex's TUI has a paste-burst heuristic in `codex-rs/tui/src/bottom_pane/paste_burst.rs` (`PASTE_BURST_MIN_CHARS=3`, `PASTE_ENTER_SUPPRESS_WINDOW=120ms`) that converts Enter→newline for ~120ms after a fast typed burst. Without the gap, the wake text accumulates in the composer and Enter is suppressed. With the gap, Codex submits and enters a turn. 500ms is a deliberately generous multiple of the documented window for upstream-drift safety.
163
163
 
164
- - **Claude Code** — fail-fast. Claude Code's hook surface (per [docs](https://code.claude.com/docs/en/hooks)) has no event that fires while the agent is idle: no polling hook, no external "start a turn" mechanism, `Notification` is outbound-only, `FileChanged` only fires inside an in-flight turn. An external process cannot rouse an idle Claude Code peer. ask_peer returns immediately with `wake_status: "skipped_unsupported"` rather than burning the timeout. The message is enqueued and delivered to the peer's PreToolUse hook on their next tool call (or via explicit `read_my_messages`).
164
+ - **Claude Code** — `tmux send-keys -l <text>` + immediate `send-keys Enter`, no inter-keystroke gap. The Claude Code TUI has no paste-burst suppression, so back-to-back text+Enter submits cleanly. Once the peer is in a turn, the oxtail PreToolUse hook drains queued messages as `additionalContext` on the peer's first tool call (or the peer reads them explicitly via `read_my_messages`). v0.7 originally shipped a fail-fast here, reasoning from the [hook catalog](https://code.claude.com/docs/en/hooks) that "no idle hook" meant "unwakeable" but send-keys is a TUI-input mechanism, not a hook event, and it submits the same way a human keypress would. The fail-fast was a self-inflicted gap against oxtail's symmetric-matrix vision (Claude↔Claude, Claude↔Codex, both directions); restored to symmetric wake in the v0.7 follow-up after an end-to-end falsifying experiment confirmed the full round-trip works.
165
165
 
166
166
  - **Unknown** — legacy v0.6 wake (text + Enter, no gap). No implied promise; if a new TUI lands, treat it as unknown until verified.
167
167
 
@@ -195,7 +195,7 @@ Pane targeting can go stale: `tmux_pane` is cached at server startup, but tmux c
195
195
  If `ask_peer` returns an abort error before its built-in 45s timeout fires, your MCP client's tool-call ceiling is lower than 45s. Override the bound at server startup:
196
196
 
197
197
  ```sh
198
- OXTAIL_ASK_PEER_TIMEOUT_MS=30000 npx -y oxtail@0.6.0
198
+ OXTAIL_ASK_PEER_TIMEOUT_MS=30000 npx -y oxtail@0.8.0
199
199
  ```
200
200
 
201
201
  The server reads the env var once at boot and uses it as the fixed timeout for all `ask_peer` calls in that session. Values must be positive numbers; anything else falls back to the 45000ms default.
@@ -242,4 +242,4 @@ If `MCP_TRACE_FILE` is set in the environment, every detection run appends an ND
242
242
 
243
243
  ## Status
244
244
 
245
- v0.7.0. Replaces v0.6's wake mechanism with per-client routing after a spike investigation found the root cause was Codex's paste-burst heuristic suppressing Enter, not `\r`-as-newline. Codex idle peers now wake reliably via a 500ms-gap send-keys sequence (verified live 2026-05-13). Claude Code idle peers fail-fast with `wake_status: "skipped_unsupported"` Claude Code's hook surface has no idle event so they're architecturally unwakeable from outside; the message is still enqueued and delivered next turn. ask_peer's response gains a `wake_status` field for caller diagnostics. Wake strategy is overridable via `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` as a rollback. See [issue #3](https://github.com/d4j3y2k/oxtail/issues/3) for the spike findings.
245
+ v0.8.0. Builds on v0.7's per-client wake routing Codex peers wake via a 500ms-gap send-keys sequence that defeats their TUI's paste-burst heuristic (verified live 2026-05-13). Claude Code peers, originally fail-fasted under a misread of the Claude Code hook catalog, now wake via the same send-keys mechanism without the gap (no paste-burst in Claude Code's TUI). An end-to-end falsifying experiment 2026-05-13 against the live `oxtail-claudejr` peer in this repo confirmed the full round-trip works: ask_peer enqueue send-keys → peer entered a turn → PreToolUse hook drained mailbox peer replied via send_message. The symmetric-matrix vision (Claude↔Claude, Claude↔Codex, both directions, no human relay) is intact. ask_peer's response gains a `wake_status` field for caller diagnostics; `skipped_unsupported` is now reserved for forward compat with hypothetical future unwakeable client_types rather than firing on any current client. Wake strategy is overridable via `OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off` as a rollback. See [issue #3](https://github.com/d4j3y2k/oxtail/issues/3) for the v0.7 spike findings.
package/dist/server.js CHANGED
@@ -715,7 +715,7 @@ server.registerTool("send_message", {
715
715
  "Delivery is asynchronous: the message lands in the target's mailbox and is delivered mid-turn via the oxtail PreToolUse hook (Claude Code) or next-turn via read_my_messages (Codex, or any client without the hook installed). If the peer is idle (no in-flight turn, no polling), the message waits until they next call a tool or poll explicitly — there is no nudge.",
716
716
  "Sender-side wrapping: if you want the message to appear as a system-reminder, include the <system-reminder>...</system-reminder> tags in `body`. The mailbox is a dumb transport.",
717
717
  "Cross-project targets are rejected, never silently dropped.",
718
- "For a blocking send-and-wait variant that pauses your turn until the peer replies, use ask_peer instead. (Note: ask_peer's idle-peer wake is best-effort in v0.6 and does not reliably rouse fully-idle TUI peers see ask_peer's tool description.)",
718
+ "For a blocking send-and-wait variant that pauses your turn until the peer replies, use ask_peer instead. ask_peer routes the wake per client_type (v0.7+): Codex peers are woken via paste-burst-aware send-keys; Claude Code peers fail-fast since their hook surface has no idle event. See ask_peer's tool description for the full contract.",
719
719
  ].join(" "),
720
720
  inputSchema: {
721
721
  target: z
@@ -841,8 +841,8 @@ function askPeerDelay(ms, signal) {
841
841
  signal.addEventListener("abort", onAbort, { once: true });
842
842
  });
843
843
  }
844
- // Wake routing (v0.7). The wake's job is to nudge an idle peer into a turn so
845
- // it drains its mailbox. Mechanics differ per client:
844
+ // Wake routing. The wake's job is to nudge an idle peer into a turn so it
845
+ // drains its mailbox. Mechanics differ per client:
846
846
  //
847
847
  // Codex — `tmux send-keys -l <text>` followed by `send-keys Enter` would
848
848
  // work, EXCEPT Codex's paste-burst heuristic suppresses Enter for 120ms
@@ -850,11 +850,17 @@ function askPeerDelay(ms, signal) {
850
850
  // We insert ASK_PEER_CODEX_SUBMIT_DELAY_MS between the text and the Enter
851
851
  // so the suppression window expires. Verified live 2026-05-13.
852
852
  //
853
- // Claude Code — has no hook event that fires while idle (verified via
854
- // Claude Code's documented hook catalog at code.claude.com/docs/en/hooks;
855
- // Notification is outbound-only; FileChanged cannot start a turn).
856
- // No external surface can rouse an idle Claude Code peer. wakePeer()
857
- // short-circuits with skipped_unsupported for this client_type.
853
+ // Claude Code — `tmux send-keys -l <text>` + immediate `send-keys Enter`,
854
+ // no inter-keystroke gap. The Claude Code TUI has no paste-burst heuristic
855
+ // that suppresses Enter, so the legacy v0.6 sequence works as-is. v0.7
856
+ // originally shipped a fail-fast here, reasoning from the hook catalog
857
+ // ("no idle hook" "unwakeable") — but send-keys is a TUI-input
858
+ // mechanism, not a hook, and it submits to the prompt the same way a
859
+ // human keypress would. Restored to symmetric wake 2026-05-13 after an
860
+ // end-to-end falsifying experiment against the live `oxtail-claudejr`
861
+ // peer in this repo (ask_peer enqueue → manual send-keys → claudejr
862
+ // entered a turn, drained mailbox via PreToolUse hook, replied via
863
+ // send_message; round-trip confirmed).
858
864
  //
859
865
  // Unknown — legacy v0.6 behavior (text + Enter, no gap). No implied
860
866
  // promise; if a new TUI lands and breaks, we treat it as unknown until
@@ -921,10 +927,6 @@ async function wakePeer(peer) {
921
927
  return "disabled";
922
928
  }
923
929
  const clientType = peer.client.type;
924
- if (ASK_PEER_WAKE_STRATEGY === "auto" && clientType === "claude-code") {
925
- trace("ask_peer_wake_skipped", { reason: "client-unsupported", client_type: clientType });
926
- return "skipped_unsupported";
927
- }
928
930
  if (!peer.tmux_pane && !peer.tmux_session) {
929
931
  return "skipped_no_target";
930
932
  }
@@ -996,10 +998,10 @@ server.registerTool("ask_peer", {
996
998
  description: [
997
999
  "Enqueue a message to a peer and block until they reply (or timeout).",
998
1000
  "Use this when you want a back-and-forth with another agent in the same project root, rather than fire-and-forget like send_message.",
999
- "Wake behavior (v0.7) varies per client_type. Codex peers are woken via paste-burst-aware tmux send-keys (literal text + 500ms gap + Enter) so the composer submits. Claude Code peers cannot be woken externally Claude Code's hook surface has no idle event (verified against the documented hook catalog), so ask_peer fails fast for Claude Code targets and returns wake_status: \"skipped_unsupported\" rather than burning the timeout. Unknown clients use legacy send-keys wake.",
1000
- "Response includes a wake_status field: \"fired\" (wake attempted or reply received during grace window), \"skipped_unsupported\" (target client cannot be woken fail-fast, no poll), \"skipped_no_target\" (no tmux pane or session resolved for target), \"disabled\" (OXTAIL_ASK_PEER_WAKE_STRATEGY=off).",
1001
- "Behavior: enqueues the body to the target's mailbox, waits ~500ms for a hook-delivered reply (rare: peer was mid-turn, hook delivered as additionalContext), fires the per-client wake, then polls this session's mailbox at 200ms for a reply from the target. Fail-fast for skipped_unsupported skips polling entirely; the message is still enqueued and will be delivered the next time the peer enters a turn.",
1002
- "Returns when the target sends a message back (via send_message) whose from_session_id matches them, or when the timeout elapses (returns reply: null, timed_out: true). timed_out is false on fail-fast (we didn't actually poll). Timeout defaults to 45000ms; user-tunable via OXTAIL_ASK_PEER_TIMEOUT_MS env var.",
1001
+ "Wake behavior varies per client_type. Codex peers are woken via paste-burst-aware tmux send-keys (literal text + 500ms gap + Enter) the gap defeats Codex's paste-burst heuristic which would otherwise suppress Enter. Claude Code peers are woken via the same send-keys mechanism without the gap (Claude Code's TUI has no paste-burst, so back-to-back text+Enter submits immediately). Unknown clients use legacy send-keys wake.",
1002
+ "Response includes a wake_status field: \"fired\" (wake attempted or reply received during grace window), \"skipped_unsupported\" (reserved — no client currently returns this in auto mode), \"skipped_no_target\" (no tmux pane or session resolved for target), \"disabled\" (OXTAIL_ASK_PEER_WAKE_STRATEGY=off).",
1003
+ "Behavior: enqueues the body to the target's mailbox, waits ~500ms for a hook-delivered reply (rare: peer was mid-turn, hook delivered as additionalContext), fires the per-client wake, then polls this session's mailbox at 200ms for a reply from the target.",
1004
+ "Returns when the target sends a message back (via send_message) whose from_session_id matches them, or when the timeout elapses (returns reply: null, timed_out: true). Timeout defaults to 45000ms; user-tunable via OXTAIL_ASK_PEER_TIMEOUT_MS env var.",
1003
1005
  "Wake strategy can be overridden via OXTAIL_ASK_PEER_WAKE_STRATEGY=auto|legacy|off (default auto). legacy = v0.6 behavior for every client (no gap, no per-client routing). off = no wake fired; ask_peer becomes a pure blocking poll until the peer naturally enters a turn or timeout.",
1004
1006
  "Target must have a registered client.session_id (Codex peers must call register_my_session first).",
1005
1007
  "Late replies that arrive after timeout are delivered normally via read_my_messages / the PreToolUse hook.",
@@ -1086,12 +1088,11 @@ server.registerTool("ask_peer", {
1086
1088
  // Common path: peer was idle. Route the wake per client_type.
1087
1089
  wakeStatus = await wakePeer(peer);
1088
1090
  if (wakeStatus === "skipped_unsupported") {
1089
- // Claude Code idle has no external wake surface — polling would just
1090
- // burn the caller's wall-clock budget for no reason. Return fast so
1091
- // the caller can fall back to send_message + read_my_messages, or
1092
- // wait until the peer is observed mid-turn via list_project_sessions.
1093
- // The outbound has been enqueued; it'll be delivered next time the
1094
- // peer enters a turn (via PreToolUse hook or explicit read_my_messages).
1091
+ // Reserved branch. No client currently returns skipped_unsupported
1092
+ // in auto mode (Codex and Claude Code both wake via send-keys).
1093
+ // Kept in the type for forward compat: if a future client_type
1094
+ // lands that genuinely cannot be woken externally, wakePeer() can
1095
+ // return this and the caller fail-fasts instead of polling.
1095
1096
  }
1096
1097
  else {
1097
1098
  reply = await askPeerPoll(entry.server_pid, expectedSessionId, deadlineMs, extra.signal);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxtail",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Coordination layer for parallel AI coding agent sessions, exposed over MCP.",