superqode 0.1.5__py3-none-any.whl
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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Collaboration View - Multi-Agent Pipeline Visualization.
|
|
3
|
+
|
|
4
|
+
A SuperQode-original widget showing the flow of work between
|
|
5
|
+
multiple QE agents. Displays agent states, handoffs, and
|
|
6
|
+
current activities in a visual pipeline.
|
|
7
|
+
|
|
8
|
+
Design: Unique SuperQode visualization for multi-agent collaboration
|
|
9
|
+
that emphasizes the "team of agents" concept.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
from rich.console import RenderableType
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
from textual.reactive import reactive
|
|
22
|
+
from textual.widgets import Static
|
|
23
|
+
from textual.timer import Timer
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AgentState(Enum):
|
|
27
|
+
"""State of an agent in the pipeline."""
|
|
28
|
+
|
|
29
|
+
IDLE = "idle" # ○ Gray - waiting
|
|
30
|
+
ACTIVE = "active" # ● Blue - currently working
|
|
31
|
+
COMPLETE = "complete" # ✓ Green - finished successfully
|
|
32
|
+
ERROR = "error" # ✗ Red - encountered error
|
|
33
|
+
PENDING = "pending" # ◐ Yellow - about to start
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AgentNode:
|
|
38
|
+
"""An agent in the collaboration pipeline."""
|
|
39
|
+
|
|
40
|
+
id: str
|
|
41
|
+
name: str
|
|
42
|
+
role: str # e.g., "Scout", "Verifier", "Reviewer"
|
|
43
|
+
state: AgentState = AgentState.IDLE
|
|
44
|
+
current_task: str = ""
|
|
45
|
+
issues_found: int = 0
|
|
46
|
+
issues_verified: int = 0
|
|
47
|
+
started_at: Optional[datetime] = None
|
|
48
|
+
completed_at: Optional[datetime] = None
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def duration_seconds(self) -> Optional[float]:
|
|
52
|
+
"""Get duration in seconds if completed."""
|
|
53
|
+
if self.started_at:
|
|
54
|
+
end = self.completed_at or datetime.now()
|
|
55
|
+
return (end - self.started_at).total_seconds()
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class Handoff:
|
|
61
|
+
"""A handoff between agents."""
|
|
62
|
+
|
|
63
|
+
from_agent: str
|
|
64
|
+
to_agent: str
|
|
65
|
+
message: str = ""
|
|
66
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
67
|
+
issue_count: int = 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Agent role colors and icons - SuperQode branding
|
|
71
|
+
AGENT_STYLES = {
|
|
72
|
+
"Scout": {"color": "#f59e0b", "icon": "🔍"}, # Orange - discovery
|
|
73
|
+
"Verifier": {"color": "#3b82f6", "icon": "✓"}, # Blue - validation
|
|
74
|
+
"Reviewer": {"color": "#8b5cf6", "icon": "📝"}, # Purple - review
|
|
75
|
+
"Fixer": {"color": "#22c55e", "icon": "🔧"}, # Green - fix
|
|
76
|
+
"Tester": {"color": "#06b6d4", "icon": "🧪"}, # Cyan - test
|
|
77
|
+
"Guardian": {"color": "#ef4444", "icon": "🛡️"}, # Red - security
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
STATE_SYMBOLS = {
|
|
81
|
+
AgentState.IDLE: ("○", "#6b7280"),
|
|
82
|
+
AgentState.ACTIVE: ("●", "#3b82f6"),
|
|
83
|
+
AgentState.COMPLETE: ("✓", "#22c55e"),
|
|
84
|
+
AgentState.ERROR: ("✗", "#ef4444"),
|
|
85
|
+
AgentState.PENDING: ("◐", "#eab308"),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AgentCollabView(Static):
|
|
90
|
+
"""Agent Collaboration Pipeline Widget.
|
|
91
|
+
|
|
92
|
+
Displays the flow of work between multiple QE agents in a visual
|
|
93
|
+
pipeline format, showing states, handoffs, and current activities.
|
|
94
|
+
|
|
95
|
+
Usage:
|
|
96
|
+
collab = AgentCollabView()
|
|
97
|
+
collab.add_agent(AgentNode("scout", "Scout Agent", "Scout"))
|
|
98
|
+
collab.add_agent(AgentNode("verifier", "Verifier Agent", "Verifier"))
|
|
99
|
+
collab.set_agent_state("scout", AgentState.ACTIVE, "Scanning codebase...")
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
DEFAULT_CSS = """
|
|
103
|
+
AgentCollabView {
|
|
104
|
+
height: auto;
|
|
105
|
+
border: solid #3f3f46;
|
|
106
|
+
padding: 0 1;
|
|
107
|
+
margin: 0 0 1 0;
|
|
108
|
+
}
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Reactive state
|
|
112
|
+
current_handoff: reactive[Optional[Handoff]] = reactive(None)
|
|
113
|
+
status_message: reactive[str] = reactive("")
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
title: str = "Agent Orchestra",
|
|
118
|
+
show_details: bool = True,
|
|
119
|
+
**kwargs,
|
|
120
|
+
):
|
|
121
|
+
super().__init__(**kwargs)
|
|
122
|
+
self.title = title
|
|
123
|
+
self.show_details = show_details
|
|
124
|
+
self._agents: Dict[str, AgentNode] = {}
|
|
125
|
+
self._pipeline_order: List[str] = [] # Order of agents in pipeline
|
|
126
|
+
self._handoffs: List[Handoff] = []
|
|
127
|
+
self._animation_frame = 0
|
|
128
|
+
self._timer: Optional[Timer] = None
|
|
129
|
+
|
|
130
|
+
def on_mount(self) -> None:
|
|
131
|
+
"""Start animation timer when mounted."""
|
|
132
|
+
self._timer = self.set_interval(0.2, self._tick, pause=True)
|
|
133
|
+
|
|
134
|
+
def _tick(self) -> None:
|
|
135
|
+
"""Animation tick."""
|
|
136
|
+
self._animation_frame += 1
|
|
137
|
+
# Only refresh if we have an active agent
|
|
138
|
+
if any(a.state == AgentState.ACTIVE for a in self._agents.values()):
|
|
139
|
+
self.refresh()
|
|
140
|
+
|
|
141
|
+
def add_agent(self, agent: AgentNode) -> None:
|
|
142
|
+
"""Add an agent to the pipeline."""
|
|
143
|
+
self._agents[agent.id] = agent
|
|
144
|
+
if agent.id not in self._pipeline_order:
|
|
145
|
+
self._pipeline_order.append(agent.id)
|
|
146
|
+
self.refresh()
|
|
147
|
+
|
|
148
|
+
def set_pipeline_order(self, order: List[str]) -> None:
|
|
149
|
+
"""Set the order of agents in the pipeline."""
|
|
150
|
+
self._pipeline_order = order
|
|
151
|
+
self.refresh()
|
|
152
|
+
|
|
153
|
+
def set_agent_state(
|
|
154
|
+
self,
|
|
155
|
+
agent_id: str,
|
|
156
|
+
state: AgentState,
|
|
157
|
+
task: str = "",
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Update an agent's state."""
|
|
160
|
+
if agent_id in self._agents:
|
|
161
|
+
agent = self._agents[agent_id]
|
|
162
|
+
old_state = agent.state
|
|
163
|
+
agent.state = state
|
|
164
|
+
agent.current_task = task
|
|
165
|
+
|
|
166
|
+
if state == AgentState.ACTIVE and old_state != AgentState.ACTIVE:
|
|
167
|
+
agent.started_at = datetime.now()
|
|
168
|
+
if self._timer:
|
|
169
|
+
self._timer.resume()
|
|
170
|
+
elif state in (AgentState.COMPLETE, AgentState.ERROR):
|
|
171
|
+
agent.completed_at = datetime.now()
|
|
172
|
+
# Pause timer if no agents are active
|
|
173
|
+
if not any(a.state == AgentState.ACTIVE for a in self._agents.values()):
|
|
174
|
+
if self._timer:
|
|
175
|
+
self._timer.pause()
|
|
176
|
+
|
|
177
|
+
self.refresh()
|
|
178
|
+
|
|
179
|
+
def record_handoff(
|
|
180
|
+
self,
|
|
181
|
+
from_agent: str,
|
|
182
|
+
to_agent: str,
|
|
183
|
+
message: str = "",
|
|
184
|
+
issue_count: int = 0,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Record a handoff between agents."""
|
|
187
|
+
handoff = Handoff(
|
|
188
|
+
from_agent=from_agent,
|
|
189
|
+
to_agent=to_agent,
|
|
190
|
+
message=message,
|
|
191
|
+
issue_count=issue_count,
|
|
192
|
+
)
|
|
193
|
+
self._handoffs.append(handoff)
|
|
194
|
+
self.current_handoff = handoff
|
|
195
|
+
|
|
196
|
+
# Update issue counts
|
|
197
|
+
if from_agent in self._agents:
|
|
198
|
+
self._agents[from_agent].issues_found = issue_count
|
|
199
|
+
|
|
200
|
+
self.refresh()
|
|
201
|
+
|
|
202
|
+
def set_status(self, message: str) -> None:
|
|
203
|
+
"""Set the status message."""
|
|
204
|
+
self.status_message = message
|
|
205
|
+
self.refresh()
|
|
206
|
+
|
|
207
|
+
def update_issues(self, agent_id: str, found: int = 0, verified: int = 0) -> None:
|
|
208
|
+
"""Update issue counts for an agent."""
|
|
209
|
+
if agent_id in self._agents:
|
|
210
|
+
if found:
|
|
211
|
+
self._agents[agent_id].issues_found = found
|
|
212
|
+
if verified:
|
|
213
|
+
self._agents[agent_id].issues_verified = verified
|
|
214
|
+
self.refresh()
|
|
215
|
+
|
|
216
|
+
def clear(self) -> None:
|
|
217
|
+
"""Reset all agents to idle."""
|
|
218
|
+
for agent in self._agents.values():
|
|
219
|
+
agent.state = AgentState.IDLE
|
|
220
|
+
agent.current_task = ""
|
|
221
|
+
agent.started_at = None
|
|
222
|
+
agent.completed_at = None
|
|
223
|
+
self._handoffs.clear()
|
|
224
|
+
self.current_handoff = None
|
|
225
|
+
self.status_message = ""
|
|
226
|
+
if self._timer:
|
|
227
|
+
self._timer.pause()
|
|
228
|
+
self.refresh()
|
|
229
|
+
|
|
230
|
+
def _get_active_indicator(self) -> str:
|
|
231
|
+
"""Get animated indicator for active state."""
|
|
232
|
+
indicators = ["◐", "◓", "◑", "◒"]
|
|
233
|
+
return indicators[self._animation_frame % len(indicators)]
|
|
234
|
+
|
|
235
|
+
def _render_agent_box(self, agent: AgentNode, is_current: bool = False) -> Text:
|
|
236
|
+
"""Render a single agent box."""
|
|
237
|
+
style = AGENT_STYLES.get(agent.role, {"color": "#6b7280", "icon": "◆"})
|
|
238
|
+
color = style["color"]
|
|
239
|
+
symbol, symbol_color = STATE_SYMBOLS[agent.state]
|
|
240
|
+
|
|
241
|
+
# Use animated symbol for active state
|
|
242
|
+
if agent.state == AgentState.ACTIVE:
|
|
243
|
+
symbol = self._get_active_indicator()
|
|
244
|
+
|
|
245
|
+
result = Text()
|
|
246
|
+
|
|
247
|
+
# Box top
|
|
248
|
+
box_width = 10
|
|
249
|
+
result.append("┌" + "─" * box_width + "┐\n", style="#3f3f46")
|
|
250
|
+
|
|
251
|
+
# Agent name (centered)
|
|
252
|
+
name = agent.role[:box_width]
|
|
253
|
+
padding = (box_width - len(name)) // 2
|
|
254
|
+
result.append("│", style="#3f3f46")
|
|
255
|
+
result.append(
|
|
256
|
+
" " * padding + name + " " * (box_width - len(name) - padding), style=f"bold {color}"
|
|
257
|
+
)
|
|
258
|
+
result.append("│\n", style="#3f3f46")
|
|
259
|
+
|
|
260
|
+
# State symbol (centered)
|
|
261
|
+
result.append("│", style="#3f3f46")
|
|
262
|
+
result.append(
|
|
263
|
+
" " * (box_width // 2) + symbol + " " * (box_width - box_width // 2 - 1),
|
|
264
|
+
style=f"bold {symbol_color}",
|
|
265
|
+
)
|
|
266
|
+
result.append("│\n", style="#3f3f46")
|
|
267
|
+
|
|
268
|
+
# Box bottom
|
|
269
|
+
result.append("└" + "─" * box_width + "┘", style="#3f3f46")
|
|
270
|
+
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
def _render_arrow(self, active: bool = False) -> Text:
|
|
274
|
+
"""Render an arrow between agents."""
|
|
275
|
+
result = Text()
|
|
276
|
+
if active:
|
|
277
|
+
result.append("───▶", style="bold #3b82f6")
|
|
278
|
+
else:
|
|
279
|
+
result.append("───▶", style="#3f3f46")
|
|
280
|
+
return result
|
|
281
|
+
|
|
282
|
+
def render(self) -> RenderableType:
|
|
283
|
+
"""Render the collaboration view."""
|
|
284
|
+
content = Text()
|
|
285
|
+
|
|
286
|
+
if not self._agents:
|
|
287
|
+
content.append("No agents configured", style="#6b7280")
|
|
288
|
+
return Panel(
|
|
289
|
+
content,
|
|
290
|
+
title=f"[bold #8b5cf6]{self.title}[/]",
|
|
291
|
+
border_style="#3f3f46",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
content.append("\n")
|
|
295
|
+
|
|
296
|
+
# Get ordered agents
|
|
297
|
+
ordered_agents = [self._agents[aid] for aid in self._pipeline_order if aid in self._agents]
|
|
298
|
+
|
|
299
|
+
# Add any agents not in pipeline order
|
|
300
|
+
for aid, agent in self._agents.items():
|
|
301
|
+
if aid not in self._pipeline_order:
|
|
302
|
+
ordered_agents.append(agent)
|
|
303
|
+
|
|
304
|
+
# Build pipeline visualization (horizontal boxes with arrows)
|
|
305
|
+
# We'll render as text since Textual doesn't easily support side-by-side widgets
|
|
306
|
+
|
|
307
|
+
# Line 1: Box tops
|
|
308
|
+
for i, agent in enumerate(ordered_agents):
|
|
309
|
+
style = AGENT_STYLES.get(agent.role, {"color": "#6b7280"})
|
|
310
|
+
content.append(" ┌─────────┐", style="#3f3f46")
|
|
311
|
+
if i < len(ordered_agents) - 1:
|
|
312
|
+
content.append(" ")
|
|
313
|
+
content.append("\n")
|
|
314
|
+
|
|
315
|
+
# Line 2: Agent names
|
|
316
|
+
for i, agent in enumerate(ordered_agents):
|
|
317
|
+
style = AGENT_STYLES.get(agent.role, {"color": "#6b7280"})
|
|
318
|
+
color = style["color"]
|
|
319
|
+
name = agent.role[:9].center(9)
|
|
320
|
+
content.append(" │", style="#3f3f46")
|
|
321
|
+
content.append(name, style=f"bold {color}")
|
|
322
|
+
content.append("│", style="#3f3f46")
|
|
323
|
+
if i < len(ordered_agents) - 1:
|
|
324
|
+
# Arrow between agents
|
|
325
|
+
is_handoff = self.current_handoff and self.current_handoff.from_agent == agent.id
|
|
326
|
+
if is_handoff:
|
|
327
|
+
content.append("───▶", style="bold #3b82f6")
|
|
328
|
+
else:
|
|
329
|
+
content.append("───▶", style="#52525b")
|
|
330
|
+
content.append("\n")
|
|
331
|
+
|
|
332
|
+
# Line 3: State symbols
|
|
333
|
+
for i, agent in enumerate(ordered_agents):
|
|
334
|
+
symbol, symbol_color = STATE_SYMBOLS[agent.state]
|
|
335
|
+
if agent.state == AgentState.ACTIVE:
|
|
336
|
+
symbol = self._get_active_indicator()
|
|
337
|
+
content.append(" │", style="#3f3f46")
|
|
338
|
+
content.append(f" {symbol} ", style=f"bold {symbol_color}")
|
|
339
|
+
content.append("│", style="#3f3f46")
|
|
340
|
+
if i < len(ordered_agents) - 1:
|
|
341
|
+
content.append(" ")
|
|
342
|
+
content.append("\n")
|
|
343
|
+
|
|
344
|
+
# Line 4: Box bottoms
|
|
345
|
+
for i, agent in enumerate(ordered_agents):
|
|
346
|
+
content.append(" └─────────┘", style="#3f3f46")
|
|
347
|
+
if i < len(ordered_agents) - 1:
|
|
348
|
+
content.append(" ")
|
|
349
|
+
content.append("\n")
|
|
350
|
+
|
|
351
|
+
# Status section
|
|
352
|
+
if self.show_details:
|
|
353
|
+
content.append("\n")
|
|
354
|
+
|
|
355
|
+
# Current handoff info
|
|
356
|
+
if self.current_handoff:
|
|
357
|
+
from_name = self._agents.get(self.current_handoff.from_agent)
|
|
358
|
+
to_name = self._agents.get(self.current_handoff.to_agent)
|
|
359
|
+
|
|
360
|
+
if from_name and to_name:
|
|
361
|
+
content.append(f" {from_name.role}", style="bold #f59e0b")
|
|
362
|
+
if self.current_handoff.issue_count:
|
|
363
|
+
content.append(
|
|
364
|
+
f" found {self.current_handoff.issue_count} issues", style="#a1a1aa"
|
|
365
|
+
)
|
|
366
|
+
content.append(" → ", style="#6b7280")
|
|
367
|
+
content.append(f"{to_name.role}", style="bold #3b82f6")
|
|
368
|
+
if to_name.current_task:
|
|
369
|
+
content.append(f" {to_name.current_task}", style="#a1a1aa")
|
|
370
|
+
content.append("\n")
|
|
371
|
+
|
|
372
|
+
# Active agent task
|
|
373
|
+
active_agents = [a for a in ordered_agents if a.state == AgentState.ACTIVE]
|
|
374
|
+
if active_agents and not self.current_handoff:
|
|
375
|
+
agent = active_agents[0]
|
|
376
|
+
style = AGENT_STYLES.get(agent.role, {"color": "#6b7280", "icon": "◆"})
|
|
377
|
+
content.append(f" [{style['icon']} {agent.role}] ", style=f"bold {style['color']}")
|
|
378
|
+
content.append(agent.current_task or "Working...", style="#a1a1aa")
|
|
379
|
+
content.append("\n")
|
|
380
|
+
|
|
381
|
+
# Status message
|
|
382
|
+
if self.status_message:
|
|
383
|
+
content.append(f" {self.status_message}", style="#6b7280")
|
|
384
|
+
|
|
385
|
+
return Panel(
|
|
386
|
+
content,
|
|
387
|
+
title=f"[bold #8b5cf6]{self.title}[/]",
|
|
388
|
+
border_style="#3f3f46",
|
|
389
|
+
padding=(0, 0),
|
|
390
|
+
)
|