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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. 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
+ )