feat(arena): Add agent collaboration arena feature

Introduces a new Arena system for running multiple AI agents in parallel
terminal sessions with support for iTerm and Tmux backends.

Core:
- Add ArenaManager and ArenaAgentClient for orchestrating multi-agent sessions
- Add terminal backends (ITermBackend, TmuxBackend) with feature detection
- Add git worktree service for isolated agent workspaces
- Add arena event system for real-time status updates

CLI:
- Add /arena command with start, stop, status, and select subcommands
- Add Arena dialogs (Select, Start, Status, Stop)
- Add ArenaCards component for displaying parallel agent outputs
- Consolidate message components into StatusMessages and ConversationMessages
- Add MultiSelect component for agent selection

Config:
- Add arena-related settings to schema and config

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-02-18 10:51:35 +08:00
parent 6bc37c6c23
commit 6b55c8161f
73 changed files with 11225 additions and 417 deletions

View file

@ -8,19 +8,24 @@ import type React from 'react';
import { useMemo } from 'react';
import { escapeAnsiCtrlCodes } from '../utils/textUtils.js';
import type { HistoryItem } from '../types.js';
import { UserMessage } from './messages/UserMessage.js';
import { UserShellMessage } from './messages/UserShellMessage.js';
import { GeminiMessage } from './messages/GeminiMessage.js';
import { InfoMessage } from './messages/InfoMessage.js';
import { ErrorMessage } from './messages/ErrorMessage.js';
import {
UserMessage,
UserShellMessage,
AssistantMessage,
AssistantMessageContent,
ThinkMessage,
ThinkMessageContent,
} from './messages/ConversationMessages.js';
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
import { GeminiThoughtMessage } from './messages/GeminiThoughtMessage.js';
import { GeminiThoughtMessageContent } from './messages/GeminiThoughtMessageContent.js';
import { CompressionMessage } from './messages/CompressionMessage.js';
import { SummaryMessage } from './messages/SummaryMessage.js';
import { WarningMessage } from './messages/WarningMessage.js';
import { RetryCountdownMessage } from './messages/RetryCountdownMessage.js';
import {
InfoMessage,
WarningMessage,
ErrorMessage,
RetryCountdownMessage,
SuccessMessage,
} from './messages/StatusMessages.js';
import { Box } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
@ -34,6 +39,7 @@ import { getMCPServerStatus } from '@qwen-code/qwen-code-core';
import { SkillsList } from './views/SkillsList.js';
import { ToolsList } from './views/ToolsList.js';
import { McpStatus } from './views/McpStatus.js';
import { ArenaAgentCard, ArenaSessionCard } from './messages/ArenaCards.js';
interface HistoryItemDisplayProps {
item: HistoryItem;
@ -60,6 +66,11 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
embeddedShellFocused,
availableTerminalHeightGemini,
}) => {
const marginTop =
item.type === 'gemini_content' || item.type === 'gemini_thought_content'
? 0
: 1;
const itemForDisplay = useMemo(() => escapeAnsiCtrlCodes(item), [item]);
const contentWidth = terminalWidth - 4;
const boxWidth = mainAreaWidth || contentWidth;
@ -68,6 +79,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
<Box
flexDirection="column"
key={itemForDisplay.id}
marginTop={marginTop}
marginLeft={2}
marginRight={2}
>
@ -79,7 +91,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
<UserShellMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'gemini' && (
<GeminiMessage
<AssistantMessage
text={itemForDisplay.text}
isPending={isPending}
availableTerminalHeight={
@ -89,7 +101,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
/>
)}
{itemForDisplay.type === 'gemini_content' && (
<GeminiMessageContent
<AssistantMessageContent
text={itemForDisplay.text}
isPending={isPending}
availableTerminalHeight={
@ -99,7 +111,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
/>
)}
{itemForDisplay.type === 'gemini_thought' && (
<GeminiThoughtMessage
<ThinkMessage
text={itemForDisplay.text}
isPending={isPending}
availableTerminalHeight={
@ -109,7 +121,7 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
/>
)}
{itemForDisplay.type === 'gemini_thought_content' && (
<GeminiThoughtMessageContent
<ThinkMessageContent
text={itemForDisplay.text}
isPending={isPending}
availableTerminalHeight={
@ -121,6 +133,9 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'info' && (
<InfoMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'success' && (
<SuccessMessage text={itemForDisplay.text} />
)}
{itemForDisplay.type === 'warning' && (
<WarningMessage text={itemForDisplay.text} />
)}
@ -180,6 +195,18 @@ const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'mcp_status' && (
<McpStatus {...itemForDisplay} serverStatus={getMCPServerStatus} />
)}
{itemForDisplay.type === 'arena_agent_complete' && (
<ArenaAgentCard agent={itemForDisplay.agent} width={boxWidth} />
)}
{itemForDisplay.type === 'arena_session_complete' && (
<ArenaSessionCard
sessionStatus={itemForDisplay.sessionStatus}
task={itemForDisplay.task}
totalDurationMs={itemForDisplay.totalDurationMs}
agents={itemForDisplay.agents}
width={boxWidth}
/>
)}
</Box>
);
};