add tmux team observer + member lifecycle events (T-139, D-75)
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 24s

team_observer.dart is the single drift-containment point for Claude
Code's experimental tmux team mode. It discovers the active team for a
workspace (~/.claude/teams/<team>/config.json, matched by member cwd),
polls `tmux -L clide list-panes -a`, and correlates live panes with the
config's tmuxPaneId to emit TeamMemberBorn / TeamMemberDied — identity
(name, agentType, model, colour, pane) comes from the config, so it's
reliable regardless of transcript drift.

Each teammate's subagent transcript is resolved best-effort and streamed
on a per-agent MessageBus channel via TranscriptPublisher (TranscriptReader
gains an explicit `file:` for this). The config<->transcript join is the
fragile part: no shared key, so it uses a sibling .meta.json agentType
when present, else zips members-by-joinedAt against files-by-mtime. This
join needs validation against a live team run.

App wiring + visible surfacing land with the teammate tiles (T-140).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 01:13:46 +02:00
co-authored by Claude Opus 4.7
parent cda289b8b1
commit fc55f58e6b
7 changed files with 735 additions and 5 deletions
+71
View File
@@ -106,3 +106,74 @@ class DaemonEvent extends ClideEvent {
@override
Map<String, Object?> payload() => {'ts': ts.toIso8601String(), ...data};
}
/// A Claude Code tmux teammate appeared (its pane is live). Identity is
/// taken from the team config, so it is reliable regardless of transcript
/// drift (T-139, D-75). [transcriptPath] is the best-effort resolved
/// subagent transcript, or null if it could not be joined yet.
class TeamMemberBorn extends ClideEvent {
const TeamMemberBorn({
required this.team,
required this.agentId,
required this.name,
required this.agentType,
required this.paneId,
this.model,
this.color,
this.cwd,
this.transcriptPath,
});
/// Team name (the `~/.claude/teams/<team>` directory).
final String team;
/// Config agent id (`<name>@<team>`).
final String agentId;
final String name;
final String agentType;
/// tmux pane id (`%N`).
final String paneId;
final String? model;
final String? color;
final String? cwd;
final String? transcriptPath;
@override
String get subsystem => 'team';
@override
String get kind => 'member-born';
@override
Map<String, Object?> payload() => {
'team': team,
'agentId': agentId,
'name': name,
'agentType': agentType,
'paneId': paneId,
if (model != null) 'model': model,
if (color != null) 'color': color,
if (cwd != null) 'cwd': cwd,
if (transcriptPath != null) 'transcriptPath': transcriptPath,
};
}
/// A Claude Code tmux teammate's pane went away (it exited or the team
/// dissolved) — T-139.
class TeamMemberDied extends ClideEvent {
const TeamMemberDied({
required this.team,
required this.agentId,
required this.paneId,
});
final String team;
final String agentId;
final String paneId;
@override
String get subsystem => 'team';
@override
String get kind => 'member-died';
@override
Map<String, Object?> payload() => {'team': team, 'agentId': agentId, 'paneId': paneId};
}