kollabor 0.4.9__py3-none-any.whl → 0.4.15__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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,181 @@
1
+ """Monitor agent panes for completion via MD5 hashing."""
2
+
3
+ import asyncio
4
+ import hashlib
5
+ import logging
6
+ from typing import Callable, Dict, Awaitable
7
+ from dataclasses import dataclass, field
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ @dataclass
13
+ class AgentState:
14
+ """Tracks activity state for a single agent."""
15
+
16
+ name: str
17
+ last_hash: str = ""
18
+ idle_count: int = 0
19
+
20
+
21
+ class ActivityMonitor:
22
+ """Monitor agent panes for completion via MD5 hashing.
23
+
24
+ Polls tmux panes at regular intervals and computes MD5 hash of content.
25
+ When content remains unchanged for a threshold number of checks,
26
+ the agent is considered complete.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ orchestrator,
32
+ on_agent_complete: Callable[[str, str, str], Awaitable[None]],
33
+ poll_interval: int = 2,
34
+ idle_threshold: int = 3,
35
+ capture_lines: int = 500,
36
+ ):
37
+ """Initialize activity monitor.
38
+
39
+ Args:
40
+ orchestrator: AgentOrchestrator instance for capturing output
41
+ on_agent_complete: Async callback(name, duration, output) when agent completes
42
+ poll_interval: Seconds between polls
43
+ idle_threshold: Number of unchanged polls before considering done
44
+ capture_lines: Lines to capture when agent completes
45
+ """
46
+ self.orchestrator = orchestrator
47
+ self.on_agent_complete = on_agent_complete
48
+ self.poll_interval = poll_interval
49
+ self.idle_threshold = idle_threshold
50
+ self.capture_lines = capture_lines
51
+
52
+ self.tracked: Dict[str, AgentState] = {}
53
+ self._running = False
54
+ self._task = None
55
+
56
+ def track(self, name: str) -> None:
57
+ """Start tracking an agent.
58
+
59
+ Args:
60
+ name: Agent name to track
61
+ """
62
+ if name not in self.tracked:
63
+ self.tracked[name] = AgentState(name=name)
64
+ logger.debug(f"Now tracking agent: {name}")
65
+
66
+ def untrack(self, name: str) -> None:
67
+ """Stop tracking an agent.
68
+
69
+ Args:
70
+ name: Agent name to stop tracking
71
+ """
72
+ if name in self.tracked:
73
+ del self.tracked[name]
74
+ logger.debug(f"Stopped tracking agent: {name}")
75
+
76
+ def is_tracking(self, name: str) -> bool:
77
+ """Check if agent is being tracked.
78
+
79
+ Args:
80
+ name: Agent name
81
+
82
+ Returns:
83
+ True if agent is tracked
84
+ """
85
+ return name in self.tracked
86
+
87
+ async def start(self) -> None:
88
+ """Start monitoring loop."""
89
+ self._running = True
90
+ logger.info(
91
+ f"Activity monitor started (poll={self.poll_interval}s, "
92
+ f"threshold={self.idle_threshold})"
93
+ )
94
+
95
+ while self._running:
96
+ try:
97
+ await self._check_agents()
98
+ except Exception as e:
99
+ logger.error(f"Error in activity monitor: {e}")
100
+
101
+ await asyncio.sleep(self.poll_interval)
102
+
103
+ async def stop(self) -> None:
104
+ """Stop monitoring loop."""
105
+ self._running = False
106
+ logger.info("Activity monitor stopped")
107
+
108
+ async def _check_agents(self) -> None:
109
+ """Check all tracked agents for completion."""
110
+ if not self.tracked:
111
+ return
112
+
113
+ completed = []
114
+
115
+ for name, state in list(self.tracked.items()):
116
+ try:
117
+ # Capture current pane content
118
+ content = self.orchestrator.capture_output(name, self.capture_lines)
119
+
120
+ if not content:
121
+ # Session might be gone
122
+ continue
123
+
124
+ current_hash = hashlib.md5(content.encode()).hexdigest()
125
+
126
+ if current_hash == state.last_hash:
127
+ # No change - increment idle count
128
+ state.idle_count += 1
129
+ logger.debug(
130
+ f"Agent {name} idle count: {state.idle_count}/{self.idle_threshold}"
131
+ )
132
+
133
+ if state.idle_count >= self.idle_threshold:
134
+ # Agent is done
135
+ completed.append((name, content))
136
+ else:
137
+ # Content changed - reset idle count
138
+ if state.idle_count > 0:
139
+ logger.debug(f"Agent {name} activity detected, resetting idle count")
140
+ state.idle_count = 0
141
+ state.last_hash = current_hash
142
+
143
+ except Exception as e:
144
+ logger.error(f"Error checking agent {name}: {e}")
145
+
146
+ # Notify completions
147
+ for name, content in completed:
148
+ agent = self.orchestrator.get_agent(name)
149
+ duration = agent.duration if agent else "?"
150
+
151
+ logger.info(f"Agent {name} completed @ {duration}")
152
+
153
+ # Remove from tracking before callback to prevent re-detection
154
+ self.untrack(name)
155
+
156
+ try:
157
+ await self.on_agent_complete(name, duration, content)
158
+ except Exception as e:
159
+ logger.error(f"Error in completion callback for {name}: {e}")
160
+
161
+ def get_tracked_agents(self) -> list:
162
+ """Get list of currently tracked agent names.
163
+
164
+ Returns:
165
+ List of agent names
166
+ """
167
+ return list(self.tracked.keys())
168
+
169
+ def reset_agent_state(self, name: str) -> None:
170
+ """Reset idle state for an agent.
171
+
172
+ Useful when sending a message to an agent that should restart
173
+ activity monitoring.
174
+
175
+ Args:
176
+ name: Agent name
177
+ """
178
+ if name in self.tracked:
179
+ self.tracked[name].idle_count = 0
180
+ self.tracked[name].last_hash = ""
181
+ logger.debug(f"Reset activity state for agent: {name}")
@@ -0,0 +1,77 @@
1
+ """Read and format files for attachment to agent tasks."""
2
+
3
+ from pathlib import Path
4
+ from typing import List, Optional
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class FileAttacher:
11
+ """Read and format files for attachment to agent tasks."""
12
+
13
+ def attach(self, file_paths: List[str]) -> str:
14
+ """Read files and format with delimiters.
15
+
16
+ Args:
17
+ file_paths: List of file paths to attach
18
+
19
+ Returns:
20
+ Formatted string with all file contents
21
+ """
22
+ parts = []
23
+
24
+ for file_path in file_paths:
25
+ content = self._read_file(file_path)
26
+ if content is not None:
27
+ parts.append(self._format_file(file_path, content))
28
+ else:
29
+ logger.warning(f"Could not read file: {file_path}")
30
+
31
+ return "\n\n".join(parts)
32
+
33
+ def _read_file(self, file_path: str) -> Optional[str]:
34
+ """Read file contents.
35
+
36
+ Args:
37
+ file_path: Path to file
38
+
39
+ Returns:
40
+ File contents or None if file cannot be read
41
+ """
42
+ path = Path(file_path)
43
+
44
+ if not path.exists():
45
+ logger.warning(f"File does not exist: {file_path}")
46
+ return None
47
+
48
+ if not path.is_file():
49
+ logger.warning(f"Path is not a file: {file_path}")
50
+ return None
51
+
52
+ try:
53
+ return path.read_text(encoding="utf-8")
54
+ except UnicodeDecodeError:
55
+ # Try with different encoding
56
+ try:
57
+ return path.read_text(encoding="latin-1")
58
+ except Exception as e:
59
+ logger.error(f"Failed to read file {file_path}: {e}")
60
+ return None
61
+ except Exception as e:
62
+ logger.error(f"Failed to read file {file_path}: {e}")
63
+ return None
64
+
65
+ def _format_file(self, file_path: str, content: str) -> str:
66
+ """Format file with delimiters.
67
+
68
+ Args:
69
+ file_path: Original file path
70
+ content: File contents
71
+
72
+ Returns:
73
+ Formatted file block
74
+ """
75
+ return f"""--file_start {file_path}--
76
+ {content}
77
+ --file_end--"""
@@ -0,0 +1,135 @@
1
+ """Inject messages back into conversation as user role."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class MessageInjector:
10
+ """Inject messages back into conversation as user role.
11
+
12
+ Formats messages with delimiters and adds them to the conversation
13
+ history. Can optionally trigger the LLM to continue generating.
14
+ """
15
+
16
+ def __init__(self, event_bus, conversation_manager):
17
+ """Initialize message injector.
18
+
19
+ Args:
20
+ event_bus: Event bus for emitting events
21
+ conversation_manager: Conversation manager for adding messages
22
+ """
23
+ self.event_bus = event_bus
24
+ self.conversation_manager = conversation_manager
25
+
26
+ async def inject(
27
+ self,
28
+ source: str,
29
+ content: str,
30
+ trigger_llm: bool = True,
31
+ metadata: Optional[dict] = None,
32
+ ) -> bool:
33
+ """Inject message into conversation.
34
+
35
+ Args:
36
+ source: Source identifier (e.g., agent name)
37
+ content: Message content
38
+ trigger_llm: Whether to trigger LLM to continue
39
+ metadata: Optional metadata dict
40
+
41
+ Returns:
42
+ True if injection was successful
43
+ """
44
+ # Format with XML tags (hidden from user display)
45
+ formatted = f"""<sys_msg>
46
+ {content}
47
+ </sys_msg>"""
48
+
49
+ # Emit pre-injection event (plugins can modify/block)
50
+ context = {
51
+ "source": source,
52
+ "content": formatted,
53
+ "original_content": content,
54
+ "trigger_llm": trigger_llm,
55
+ "metadata": metadata or {},
56
+ "blocked": False,
57
+ }
58
+
59
+ try:
60
+ # Try to emit pre-injection event
61
+ from core.events.types import EventType
62
+
63
+ if hasattr(EventType, "PRE_MESSAGE_INJECT"):
64
+ context = await self.event_bus.emit_with_hooks(
65
+ EventType.PRE_MESSAGE_INJECT, context, "message_injector"
66
+ )
67
+ except Exception as e:
68
+ logger.debug(f"Pre-injection event not available: {e}")
69
+
70
+ if context.get("blocked"):
71
+ logger.info(f"Message injection blocked for source: {source}")
72
+ return False
73
+
74
+ # Add to conversation as user message
75
+ try:
76
+ self.conversation_manager.add_message(
77
+ role="user", content=context["content"]
78
+ )
79
+ logger.info(f"Injected message from: {source}")
80
+ except Exception as e:
81
+ logger.error(f"Failed to inject message: {e}")
82
+ return False
83
+
84
+ # Emit post-injection event
85
+ try:
86
+ from core.events.types import EventType
87
+
88
+ if hasattr(EventType, "POST_MESSAGE_INJECT"):
89
+ await self.event_bus.emit_with_hooks(EventType.POST_MESSAGE_INJECT, context, "message_injector")
90
+ except Exception as e:
91
+ logger.debug(f"Post-injection event not available: {e}")
92
+
93
+ # Trigger LLM to continue
94
+ if context.get("trigger_llm", True):
95
+ try:
96
+ from core.events.types import EventType
97
+
98
+ if hasattr(EventType, "TRIGGER_LLM_CONTINUE"):
99
+ await self.event_bus.emit_with_hooks(EventType.TRIGGER_LLM_CONTINUE, {}, "message_injector")
100
+ except Exception as e:
101
+ logger.debug(f"Trigger LLM continue event not available: {e}")
102
+
103
+ return True
104
+
105
+ async def inject_raw(
106
+ self,
107
+ content: str,
108
+ trigger_llm: bool = False,
109
+ ) -> bool:
110
+ """Inject raw content without formatting.
111
+
112
+ Args:
113
+ content: Raw content to inject
114
+ trigger_llm: Whether to trigger LLM to continue
115
+
116
+ Returns:
117
+ True if injection was successful
118
+ """
119
+ try:
120
+ self.conversation_manager.add_message(role="user", content=content)
121
+ logger.info("Injected raw message")
122
+
123
+ if trigger_llm:
124
+ try:
125
+ from core.events.types import EventType
126
+
127
+ if hasattr(EventType, "TRIGGER_LLM_CONTINUE"):
128
+ await self.event_bus.emit_with_hooks(EventType.TRIGGER_LLM_CONTINUE, {}, "message_injector")
129
+ except Exception:
130
+ pass
131
+
132
+ return True
133
+ except Exception as e:
134
+ logger.error(f"Failed to inject raw message: {e}")
135
+ return False
@@ -0,0 +1,48 @@
1
+ """Data models for agent orchestration."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+ import time
6
+
7
+
8
+ @dataclass
9
+ class AgentTask:
10
+ """Parsed agent task from XML."""
11
+
12
+ name: str
13
+ task: str
14
+ files: List[str] = field(default_factory=list)
15
+
16
+
17
+ @dataclass
18
+ class AgentSession:
19
+ """Running agent session."""
20
+
21
+ name: str
22
+ full_name: str
23
+ status: str # running, idle
24
+ start_time: float
25
+
26
+ @property
27
+ def duration(self) -> str:
28
+ """Get formatted duration since start."""
29
+ elapsed = time.time() - self.start_time
30
+ minutes = int(elapsed // 60)
31
+ seconds = int(elapsed % 60)
32
+ return f"{minutes}m{seconds:02d}s"
33
+
34
+
35
+ @dataclass
36
+ class ParsedCommand:
37
+ """Parsed XML command from LLM response."""
38
+
39
+ type: str # agent, message, stop, status, capture, clone, team, broadcast
40
+ agents: List[AgentTask] = field(default_factory=list)
41
+ target: str = ""
42
+ targets: List[str] = field(default_factory=list)
43
+ content: str = ""
44
+ lines: int = 50
45
+ pattern: str = ""
46
+ lead: str = ""
47
+ workers: int = 3
48
+ conversation: bool = False