bind each Claude pane to its own session id (T-146)

A regression from T-137: every pane rendered the newest .jsonl in the
workspace dir, so concurrent sessions collided — a secondary tab showed
the primary's conversation. Each pane now spawns claude with its own
--session-id (a transcript is named <session-id>.jsonl), tails that
exact file via TranscriptReader's file: param, and uses a per-session
MessageBus channel so controllers don't cross-talk.

The primary's id is deterministic from its session name (stable → it
resumes across restarts, like /resume off the same history file);
secondaries get a fresh random id so a clean session is always available.
The reader now waits for the bound file to appear rather than throwing.

Migration: an existing tmux session created before this (no --session-id,
claude chose its own id) must be killed once (claude.kill-all-sessions)
so the next spawn binds the controlled id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 08:28:55 +02:00
co-authored by Claude Opus 4.7
parent 24f68e7aaa
commit e1cd4653c0
10 changed files with 222 additions and 4 deletions
@@ -22,6 +22,10 @@ abstract final class ClaudeConversation {
/// Channel for the lead (or single) Claude pane's conversation.
static const leadChannel = 'conversation';
/// Per-session channel, keyed by the Claude session id (T-146) so
/// concurrent panes in one workspace don't cross-talk.
static String sessionChannel(String sessionId) => 'conversation/$sessionId';
/// Channel for a teammate's conversation (team work, T-139/T-140).
static String teammateChannel(String agentId) => 'conversation/$agentId';