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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Manages tmux sessions for agent sub-processes."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import asyncio
|
|
6
|
+
import fnmatch
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional, Tuple, Dict
|
|
10
|
+
|
|
11
|
+
from .models import AgentSession, AgentTask
|
|
12
|
+
from .file_attacher import FileAttacher
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentOrchestrator:
|
|
18
|
+
"""Manages tmux sessions for agent sub-processes."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, project_name: str = None):
|
|
21
|
+
"""Initialize orchestrator.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
project_name: Project name for session naming. Defaults to cwd name.
|
|
25
|
+
"""
|
|
26
|
+
self.project_name = project_name or Path.cwd().name
|
|
27
|
+
self.agents: Dict[str, AgentSession] = {}
|
|
28
|
+
self.file_attacher = FileAttacher()
|
|
29
|
+
|
|
30
|
+
# Timing configuration
|
|
31
|
+
self.session_init_delay = 1
|
|
32
|
+
self.kollab_init_delay = 7 # Allow time for kollab to fully initialize
|
|
33
|
+
self.message_delay = 2
|
|
34
|
+
|
|
35
|
+
# -------------------------------------------------------------------------
|
|
36
|
+
# Spawn
|
|
37
|
+
# -------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
async def spawn(self, name: str, task: str, files: List[str] = None) -> bool:
|
|
40
|
+
"""Spawn a new agent session.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: Agent name
|
|
44
|
+
task: Task description
|
|
45
|
+
files: Optional list of files to attach
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if successful
|
|
49
|
+
"""
|
|
50
|
+
full_name = f"{self.project_name}-{name}"
|
|
51
|
+
|
|
52
|
+
# Check if already exists
|
|
53
|
+
if self._session_exists(full_name):
|
|
54
|
+
logger.warning(f"Session already exists: {full_name}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
# Create session
|
|
58
|
+
if not self._create_session(full_name):
|
|
59
|
+
logger.error(f"Failed to create session: {full_name}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
# Start kollab
|
|
63
|
+
logger.info(f"[spawn] Sending 'kollab' command to {full_name}")
|
|
64
|
+
self._send_keys(full_name, "kollab")
|
|
65
|
+
logger.info(f"[spawn] Waiting {self.kollab_init_delay}s for kollab to initialize")
|
|
66
|
+
await asyncio.sleep(self.kollab_init_delay)
|
|
67
|
+
|
|
68
|
+
# Prepare message with attached files
|
|
69
|
+
message = ""
|
|
70
|
+
if files:
|
|
71
|
+
message = self.file_attacher.attach(files)
|
|
72
|
+
message += "\n\n"
|
|
73
|
+
message += task
|
|
74
|
+
|
|
75
|
+
logger.info(f"[spawn] Sending task to {full_name}: {message[:100]}...")
|
|
76
|
+
|
|
77
|
+
# Send task
|
|
78
|
+
result = self._send_keys(full_name, message)
|
|
79
|
+
logger.info(f"[spawn] Task send result: {result}")
|
|
80
|
+
|
|
81
|
+
# Track agent
|
|
82
|
+
self.agents[name] = AgentSession(
|
|
83
|
+
name=name,
|
|
84
|
+
full_name=full_name,
|
|
85
|
+
status="running",
|
|
86
|
+
start_time=time.time(),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
logger.info(f"Spawned agent: {name}")
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
async def spawn_clone(
|
|
93
|
+
self,
|
|
94
|
+
name: str,
|
|
95
|
+
task: str,
|
|
96
|
+
files: List[str],
|
|
97
|
+
conversation_file: str,
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Spawn agent with conversation context.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
name: Agent name
|
|
103
|
+
task: Task description
|
|
104
|
+
files: Files to attach
|
|
105
|
+
conversation_file: Path to exported conversation JSON
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if successful
|
|
109
|
+
"""
|
|
110
|
+
full_name = f"{self.project_name}-{name}"
|
|
111
|
+
|
|
112
|
+
if self._session_exists(full_name):
|
|
113
|
+
logger.warning(f"Session already exists: {full_name}")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
if not self._create_session(full_name):
|
|
117
|
+
logger.error(f"Failed to create session: {full_name}")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
# Start kollab with resume
|
|
121
|
+
self._send_keys(full_name, f"kollab --resume {conversation_file}")
|
|
122
|
+
await asyncio.sleep(self.kollab_init_delay)
|
|
123
|
+
|
|
124
|
+
# Send task with files
|
|
125
|
+
message = ""
|
|
126
|
+
if files:
|
|
127
|
+
message = self.file_attacher.attach(files) + "\n\n"
|
|
128
|
+
message += task
|
|
129
|
+
|
|
130
|
+
self._send_keys(full_name, message)
|
|
131
|
+
|
|
132
|
+
self.agents[name] = AgentSession(
|
|
133
|
+
name=name,
|
|
134
|
+
full_name=full_name,
|
|
135
|
+
status="running",
|
|
136
|
+
start_time=time.time(),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
logger.info(f"Spawned clone agent: {name} with conversation context")
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
async def spawn_team_lead(
|
|
143
|
+
self,
|
|
144
|
+
lead_name: str,
|
|
145
|
+
max_workers: int,
|
|
146
|
+
task: AgentTask,
|
|
147
|
+
) -> bool:
|
|
148
|
+
"""Spawn a team lead agent that can spawn workers.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
lead_name: Lead agent name
|
|
152
|
+
max_workers: Maximum number of workers the lead can spawn
|
|
153
|
+
task: Task for the lead
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True if successful
|
|
157
|
+
"""
|
|
158
|
+
full_name = f"{self.project_name}-{lead_name}"
|
|
159
|
+
|
|
160
|
+
if self._session_exists(full_name):
|
|
161
|
+
logger.warning(f"Session already exists: {full_name}")
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
if not self._create_session(full_name):
|
|
165
|
+
logger.error(f"Failed to create session: {full_name}")
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
self._send_keys(full_name, "kollab")
|
|
169
|
+
await asyncio.sleep(self.kollab_init_delay)
|
|
170
|
+
|
|
171
|
+
# Inject team lead prompt
|
|
172
|
+
lead_prompt = f"""You are a team lead agent.
|
|
173
|
+
You can spawn up to {max_workers} worker agents using <agent> tags.
|
|
174
|
+
Coordinate their work and integrate results.
|
|
175
|
+
Use <status /> to check on workers.
|
|
176
|
+
Use <capture>worker-name 100</capture> to see their progress.
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
message = lead_prompt
|
|
180
|
+
if task.files:
|
|
181
|
+
message += self.file_attacher.attach(task.files) + "\n\n"
|
|
182
|
+
message += task.task
|
|
183
|
+
|
|
184
|
+
self._send_keys(full_name, message)
|
|
185
|
+
|
|
186
|
+
self.agents[lead_name] = AgentSession(
|
|
187
|
+
name=lead_name,
|
|
188
|
+
full_name=full_name,
|
|
189
|
+
status="running",
|
|
190
|
+
start_time=time.time(),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
logger.info(f"Spawned team lead: {lead_name} with {max_workers} max workers")
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
# -------------------------------------------------------------------------
|
|
197
|
+
# Message
|
|
198
|
+
# -------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
async def message(self, name: str, content: str) -> bool:
|
|
201
|
+
"""Send message to agent.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
name: Agent name
|
|
205
|
+
content: Message content
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
True if successful
|
|
209
|
+
"""
|
|
210
|
+
full_name = f"{self.project_name}-{name}"
|
|
211
|
+
|
|
212
|
+
if not self._session_exists(full_name):
|
|
213
|
+
logger.warning(f"Session does not exist: {full_name}")
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
self._send_keys(full_name, content)
|
|
217
|
+
logger.info(f"Sent message to agent: {name}")
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
# -------------------------------------------------------------------------
|
|
221
|
+
# Stop
|
|
222
|
+
# -------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
async def stop(self, name: str) -> Tuple[str, str]:
|
|
225
|
+
"""Stop agent and return final output + duration.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
name: Agent name
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Tuple of (output, duration)
|
|
232
|
+
"""
|
|
233
|
+
full_name = f"{self.project_name}-{name}"
|
|
234
|
+
|
|
235
|
+
# Capture final output
|
|
236
|
+
output = self.capture_output(name, 100)
|
|
237
|
+
|
|
238
|
+
# Get duration
|
|
239
|
+
agent = self.agents.get(name)
|
|
240
|
+
duration = agent.duration if agent else "?"
|
|
241
|
+
|
|
242
|
+
# Kill session
|
|
243
|
+
self._kill_session(full_name)
|
|
244
|
+
|
|
245
|
+
# Remove from tracking
|
|
246
|
+
if name in self.agents:
|
|
247
|
+
del self.agents[name]
|
|
248
|
+
|
|
249
|
+
logger.info(f"Stopped agent: {name} @ {duration}")
|
|
250
|
+
return output, duration
|
|
251
|
+
|
|
252
|
+
# -------------------------------------------------------------------------
|
|
253
|
+
# Status / Capture
|
|
254
|
+
# -------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
def list_agents(self) -> List[AgentSession]:
|
|
257
|
+
"""List all active agents.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
List of agent sessions
|
|
261
|
+
"""
|
|
262
|
+
# Refresh status from tmux
|
|
263
|
+
self._refresh_agents()
|
|
264
|
+
return list(self.agents.values())
|
|
265
|
+
|
|
266
|
+
def get_agent(self, name: str) -> Optional[AgentSession]:
|
|
267
|
+
"""Get specific agent.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
name: Agent name
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Agent session or None
|
|
274
|
+
"""
|
|
275
|
+
return self.agents.get(name)
|
|
276
|
+
|
|
277
|
+
def find_agents(self, pattern: str) -> List[str]:
|
|
278
|
+
"""Find agents matching glob pattern.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
pattern: Glob pattern (e.g., "lint-*")
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
List of matching agent names
|
|
285
|
+
"""
|
|
286
|
+
return [
|
|
287
|
+
name for name in self.agents.keys() if fnmatch.fnmatch(name, pattern)
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
def capture_output(self, name: str, lines: int = 50) -> str:
|
|
291
|
+
"""Capture last N lines from agent.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
name: Agent name
|
|
295
|
+
lines: Number of lines to capture
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Captured output
|
|
299
|
+
"""
|
|
300
|
+
full_name = f"{self.project_name}-{name}"
|
|
301
|
+
|
|
302
|
+
result = subprocess.run(
|
|
303
|
+
["tmux", "capture-pane", "-t", full_name, "-p", "-S", f"-{lines}"],
|
|
304
|
+
capture_output=True,
|
|
305
|
+
text=True,
|
|
306
|
+
)
|
|
307
|
+
return result.stdout
|
|
308
|
+
|
|
309
|
+
# -------------------------------------------------------------------------
|
|
310
|
+
# Private Helpers
|
|
311
|
+
# -------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
def _session_exists(self, full_name: str) -> bool:
|
|
314
|
+
"""Check if tmux session exists."""
|
|
315
|
+
result = subprocess.run(
|
|
316
|
+
["tmux", "has-session", "-t", full_name], capture_output=True
|
|
317
|
+
)
|
|
318
|
+
return result.returncode == 0
|
|
319
|
+
|
|
320
|
+
def _create_session(self, full_name: str) -> bool:
|
|
321
|
+
"""Create new tmux session in current working directory."""
|
|
322
|
+
cwd = str(Path.cwd())
|
|
323
|
+
result = subprocess.run(
|
|
324
|
+
["tmux", "new-session", "-d", "-s", full_name, "-c", cwd], capture_output=True
|
|
325
|
+
)
|
|
326
|
+
if result.returncode != 0:
|
|
327
|
+
logger.error(f"Failed to create tmux session: {result.stderr}")
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
time.sleep(self.session_init_delay)
|
|
331
|
+
return True
|
|
332
|
+
|
|
333
|
+
def _send_keys(self, full_name: str, content: str) -> bool:
|
|
334
|
+
"""Send keys to tmux session."""
|
|
335
|
+
logger.debug(f"[send_keys] Sending to {full_name}: {len(content)} chars")
|
|
336
|
+
|
|
337
|
+
# Send content first
|
|
338
|
+
result = subprocess.run(
|
|
339
|
+
["tmux", "send-keys", "-t", full_name, content],
|
|
340
|
+
capture_output=True,
|
|
341
|
+
)
|
|
342
|
+
if result.returncode != 0:
|
|
343
|
+
logger.error(f"[send_keys] Content send failed: {result.stderr}")
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
logger.debug(f"[send_keys] Content sent, waiting 1s before Enter")
|
|
347
|
+
# Wait for paste to be processed
|
|
348
|
+
time.sleep(1)
|
|
349
|
+
|
|
350
|
+
# Then send Enter to submit
|
|
351
|
+
logger.debug(f"[send_keys] Sending Enter")
|
|
352
|
+
result = subprocess.run(
|
|
353
|
+
["tmux", "send-keys", "-t", full_name, "Enter"],
|
|
354
|
+
capture_output=True,
|
|
355
|
+
)
|
|
356
|
+
if result.returncode != 0:
|
|
357
|
+
logger.error(f"[send_keys] Enter send failed: {result.stderr}")
|
|
358
|
+
|
|
359
|
+
logger.debug(f"[send_keys] Waiting {self.message_delay}s message delay")
|
|
360
|
+
time.sleep(self.message_delay)
|
|
361
|
+
return result.returncode == 0
|
|
362
|
+
|
|
363
|
+
def _kill_session(self, full_name: str) -> bool:
|
|
364
|
+
"""Kill tmux session."""
|
|
365
|
+
result = subprocess.run(
|
|
366
|
+
["tmux", "kill-session", "-t", full_name], capture_output=True
|
|
367
|
+
)
|
|
368
|
+
return result.returncode == 0
|
|
369
|
+
|
|
370
|
+
def _refresh_agents(self) -> None:
|
|
371
|
+
"""Refresh agent status from tmux."""
|
|
372
|
+
result = subprocess.run(
|
|
373
|
+
["tmux", "list-sessions", "-F", "#{session_name}"],
|
|
374
|
+
capture_output=True,
|
|
375
|
+
text=True,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if result.returncode != 0:
|
|
379
|
+
return
|
|
380
|
+
|
|
381
|
+
active_sessions = set(result.stdout.strip().split("\n"))
|
|
382
|
+
prefix = f"{self.project_name}-"
|
|
383
|
+
|
|
384
|
+
# Remove dead agents
|
|
385
|
+
dead = [
|
|
386
|
+
name
|
|
387
|
+
for name in self.agents
|
|
388
|
+
if f"{prefix}{name}" not in active_sessions
|
|
389
|
+
]
|
|
390
|
+
for name in dead:
|
|
391
|
+
del self.agents[name]
|
|
392
|
+
|
|
393
|
+
# Discover new agents (created externally)
|
|
394
|
+
for session in active_sessions:
|
|
395
|
+
if session.startswith(prefix):
|
|
396
|
+
agent_name = session[len(prefix):]
|
|
397
|
+
if agent_name and agent_name not in self.agents:
|
|
398
|
+
self.agents[agent_name] = AgentSession(
|
|
399
|
+
name=agent_name,
|
|
400
|
+
full_name=session,
|
|
401
|
+
status="running",
|
|
402
|
+
start_time=time.time(), # approximate
|
|
403
|
+
)
|