add the team chat inbox over the broker

Renders broker traffic as a chat timeline and makes the user a first-class
participant. The broker grows a Stream<TeamMessage> and a recipient field,
auto-registers a virtual `user` member, and gains sendAsUser. A Flutter-free
TeamChatModel (owned by the orchestrator) accumulates the feed and exposes
postAsUser with @-routing (a new at_commands helper mirroring slash) and an
optional interrupt that cancels the target's turn before delivery. One model
backs two surfaces: a compact cockpit widget that pops out into a full
workspace chat pane. CLI parity via clide.team-chat.open / .post.

T-180.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-31 10:49:04 +02:00
co-authored by Claude
parent e3c0b0146b
commit a0636c2e5a
17 changed files with 1656 additions and 31 deletions
+51
View File
@@ -9,6 +9,7 @@ import 'package:clide/builtin/claude/src/pane_context_status.dart';
import 'package:clide/builtin/claude/src/claude_meta_sidebar.dart';
import 'package:clide/builtin/claude/src/session_index.dart';
import 'package:clide/builtin/claude/src/session_storage.dart';
import 'package:clide/builtin/claude/src/team_chat_sidebar.dart' show TeamChatPane;
import 'package:clide/builtin/claude/src/team_panel_host.dart';
import 'package:clide/extension/extension.dart';
import 'package:clide/kernel/kernel.dart';
@@ -174,6 +175,56 @@ class ClaudeExtension extends ClideExtension {
return IpcResponse.ok(id: '', data: {'taskId': taskId, 'toId': toId, 'ok': ok});
},
),
// T-180: full team chat pane opened as a workspace tab.
// Shares the TeamChatModel with the sidebar widget.
TabContribution(
id: 'claude.team-chat',
slot: Slots.workspace,
title: 'Team Chat',
titleKey: 'tab.title',
i18nNamespace: id,
priority: 85,
build: (_) {
final orch = _orchestrator;
if (orch == null) return const SizedBox.shrink();
return TeamChatPane(model: orch.chatModel, broker: orch.broker);
},
),
// CLI parity: open the team chat pane from the shell.
// Usage: clide claude.team-chat.open
CommandContribution(
id: 'claude.team-chat.open',
command: 'claude.team-chat.open',
title: 'Claude: open the team chat pane',
run: (args) async {
_ctx?.panels.activateTab(Slots.workspace, 'claude.team-chat');
return IpcResponse.ok(id: '', data: const {'status': 'opened'});
},
),
// Usage: clide claude.team-chat.post [@name] <text...>
// Posts a message into the broker channel as the user.
// Leading @name tag selects the recipient; omit for broadcast.
CommandContribution(
id: 'claude.team-chat.post',
command: 'claude.team-chat.post',
title: 'Claude: post a message into the team channel as the user',
run: (args) async {
if (args.isEmpty) return IpcResponse.ok(id: '', data: const {'error': 'usage: [@name] <text>'});
final raw = args.join(' ');
String? recipient;
String body = raw;
if (raw.startsWith('@')) {
final ws = raw.indexOf(RegExp(r'\s'));
if (ws > 0) {
final tag = raw.substring(1, ws);
recipient = (tag == 'team' || tag.isEmpty) ? null : tag;
body = raw.substring(ws).trim();
}
}
_orchestrator?.chatModel.postAsUser(body, toName: recipient);
return IpcResponse.ok(id: '', data: {'status': 'posted', if (recipient != null) 'to': recipient});
},
),
// claude.agent.fork: branch a managed session into a new fork session
// (T-172, D-6 CLI/UI parity for the roster fork button).
// Usage: clide claude.agent.fork <sourceSessionId> [<cwd>]