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,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
|