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
core/events/executor.py
CHANGED
|
@@ -3,32 +3,74 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
|
-
from typing import Any, Dict, List
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
7
|
|
|
8
8
|
from .models import Event, Hook, HookStatus
|
|
9
9
|
from ..utils.error_utils import log_and_continue
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
|
+
# Configuration constants with safe limits
|
|
14
|
+
DEFAULT_HOOK_TIMEOUT = 30
|
|
15
|
+
DEFAULT_HOOK_RETRIES = 3
|
|
16
|
+
DEFAULT_ERROR_ACTION = "continue"
|
|
17
|
+
ABSOLUTE_MAX_RETRIES = 10
|
|
18
|
+
MIN_TIMEOUT = 1
|
|
19
|
+
MAX_TIMEOUT = 300 # 5 minutes
|
|
20
|
+
VALID_ERROR_ACTIONS = {"continue", "stop"}
|
|
21
|
+
|
|
22
|
+
# Absolute maximum time for all retry attempts combined (5 minutes)
|
|
23
|
+
MAX_TOTAL_RETRY_DURATION = 300
|
|
24
|
+
|
|
13
25
|
|
|
14
26
|
class HookExecutor:
|
|
15
27
|
"""Executes individual hooks with timeout and error handling.
|
|
16
|
-
|
|
28
|
+
|
|
17
29
|
This class is responsible for the safe execution of a single hook,
|
|
18
|
-
including timeout management, error handling, and
|
|
30
|
+
including timeout management, error handling, status tracking, and retry logic.
|
|
31
|
+
|
|
32
|
+
Thread Safety:
|
|
33
|
+
Uses per-hook locks to prevent concurrent execution of the same hook,
|
|
34
|
+
avoiding race conditions on hook status mutations.
|
|
19
35
|
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self):
|
|
22
|
-
"""Initialize the hook executor.
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
38
|
+
"""Initialize the hook executor.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config: Configuration dictionary for hook execution settings.
|
|
42
|
+
"""
|
|
43
|
+
self.config = config or {}
|
|
44
|
+
self._hook_locks: Dict[str, asyncio.Lock] = {}
|
|
45
|
+
self._locks_lock = asyncio.Lock() # Lock for creating locks
|
|
46
|
+
logger.debug("HookExecutor initialized with config")
|
|
47
|
+
|
|
48
|
+
async def _get_hook_lock(self, hook_key: str) -> asyncio.Lock:
|
|
49
|
+
"""Get or create a lock for a specific hook.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
hook_key: The hook identifier (plugin_name.hook_name)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
asyncio.Lock for the specified hook
|
|
56
|
+
"""
|
|
57
|
+
if hook_key not in self._hook_locks:
|
|
58
|
+
async with self._locks_lock:
|
|
59
|
+
# Double-check after acquiring lock
|
|
60
|
+
if hook_key not in self._hook_locks:
|
|
61
|
+
self._hook_locks[hook_key] = asyncio.Lock()
|
|
62
|
+
return self._hook_locks[hook_key]
|
|
63
|
+
|
|
25
64
|
async def execute_hook(self, hook: Hook, event: Event) -> Dict[str, Any]:
|
|
26
|
-
"""Execute a single hook with error handling and
|
|
27
|
-
|
|
65
|
+
"""Execute a single hook with error handling, timeout, and retry logic.
|
|
66
|
+
|
|
67
|
+
Thread-safe: Uses per-hook locks to prevent concurrent execution of the
|
|
68
|
+
same hook, avoiding race conditions on hook status mutations.
|
|
69
|
+
|
|
28
70
|
Args:
|
|
29
71
|
hook: The hook to execute.
|
|
30
72
|
event: The event being processed.
|
|
31
|
-
|
|
73
|
+
|
|
32
74
|
Returns:
|
|
33
75
|
Dictionary with execution result and metadata.
|
|
34
76
|
"""
|
|
@@ -38,76 +80,164 @@ class HookExecutor:
|
|
|
38
80
|
"success": False,
|
|
39
81
|
"result": None,
|
|
40
82
|
"error": None,
|
|
41
|
-
"duration_ms": 0
|
|
83
|
+
"duration_ms": 0,
|
|
84
|
+
"retry_count": 0,
|
|
85
|
+
"attempts": []
|
|
42
86
|
}
|
|
43
|
-
|
|
87
|
+
|
|
44
88
|
if not hook.enabled:
|
|
45
89
|
result_metadata["error"] = "hook_disabled"
|
|
46
90
|
logger.debug(f"Skipping disabled hook: {hook_key}")
|
|
47
91
|
return result_metadata
|
|
48
|
-
|
|
92
|
+
|
|
49
93
|
if event.cancelled:
|
|
50
94
|
result_metadata["error"] = "event_cancelled"
|
|
51
95
|
logger.debug(f"Skipping hook due to cancelled event: {hook_key}")
|
|
52
96
|
return result_metadata
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
hook.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
|
|
98
|
+
# Acquire lock to prevent concurrent execution of the same hook
|
|
99
|
+
hook_lock = await self._get_hook_lock(hook_key)
|
|
100
|
+
async with hook_lock:
|
|
101
|
+
# Apply fallback defaults if values are None (for hooks created without registration)
|
|
102
|
+
hooks_config = self.config.get("hooks", {})
|
|
103
|
+
timeout = hook.timeout if hook.timeout is not None else hooks_config.get("default_timeout", DEFAULT_HOOK_TIMEOUT)
|
|
104
|
+
retry_attempts = hook.retry_attempts if hook.retry_attempts is not None else hooks_config.get("default_retries", DEFAULT_HOOK_RETRIES)
|
|
105
|
+
error_action = hook.error_action if hook.error_action is not None else hooks_config.get("default_error_action", DEFAULT_ERROR_ACTION)
|
|
106
|
+
|
|
107
|
+
# Validate and clamp timeout
|
|
108
|
+
if not isinstance(timeout, (int, float)) or timeout < MIN_TIMEOUT:
|
|
109
|
+
logger.warning(f"Invalid timeout {timeout} for {hook_key}, using minimum: {MIN_TIMEOUT}")
|
|
110
|
+
timeout = MIN_TIMEOUT
|
|
111
|
+
elif timeout > MAX_TIMEOUT:
|
|
112
|
+
logger.warning(f"Timeout {timeout} exceeds maximum for {hook_key}, capping at: {MAX_TIMEOUT}")
|
|
113
|
+
timeout = MAX_TIMEOUT
|
|
114
|
+
|
|
115
|
+
# Validate and cap retry attempts
|
|
116
|
+
if not isinstance(retry_attempts, int) or retry_attempts < 0:
|
|
117
|
+
logger.warning(f"Invalid retry_attempts {retry_attempts} for {hook_key}, using default: {DEFAULT_HOOK_RETRIES}")
|
|
118
|
+
retry_attempts = DEFAULT_HOOK_RETRIES
|
|
119
|
+
elif retry_attempts > ABSOLUTE_MAX_RETRIES:
|
|
120
|
+
logger.warning(
|
|
121
|
+
f"Retry attempts {retry_attempts} exceeds absolute maximum for {hook_key}, "
|
|
122
|
+
f"capping at: {ABSOLUTE_MAX_RETRIES}"
|
|
123
|
+
)
|
|
124
|
+
retry_attempts = ABSOLUTE_MAX_RETRIES
|
|
125
|
+
|
|
126
|
+
# Validate error_action
|
|
127
|
+
if error_action not in VALID_ERROR_ACTIONS:
|
|
128
|
+
logger.error(
|
|
129
|
+
f"Invalid error_action '{error_action}' for {hook_key}, "
|
|
130
|
+
f"must be one of {VALID_ERROR_ACTIONS}. Using default: '{DEFAULT_ERROR_ACTION}'"
|
|
131
|
+
)
|
|
132
|
+
error_action = DEFAULT_ERROR_ACTION
|
|
133
|
+
|
|
134
|
+
# Track overall execution time
|
|
135
|
+
overall_start = time.time()
|
|
136
|
+
|
|
137
|
+
# Retry loop with exponential backoff and absolute timeout
|
|
138
|
+
max_attempts = retry_attempts + 1 # Initial attempt + retries
|
|
139
|
+
for attempt in range(max_attempts):
|
|
140
|
+
# Check if we've exceeded the absolute maximum retry duration
|
|
141
|
+
elapsed_time = time.time() - overall_start
|
|
142
|
+
if elapsed_time > MAX_TOTAL_RETRY_DURATION:
|
|
143
|
+
logger.error(
|
|
144
|
+
f"Hook {hook_key} exceeded maximum total retry duration "
|
|
145
|
+
f"({MAX_TOTAL_RETRY_DURATION}s). Aborting after {attempt} attempts."
|
|
146
|
+
)
|
|
147
|
+
result_metadata["error"] = "max_retry_duration_exceeded"
|
|
148
|
+
result_metadata["retry_count"] = attempt
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
attempt_start = time.time()
|
|
152
|
+
attempt_info = {"attempt": attempt + 1, "duration_ms": 0, "success": False, "error": None}
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Update hook status to working
|
|
156
|
+
hook.status = HookStatus.WORKING
|
|
157
|
+
|
|
158
|
+
# Execute hook with timeout
|
|
159
|
+
result = await asyncio.wait_for(
|
|
160
|
+
hook.callback(event.data, event),
|
|
161
|
+
timeout=timeout
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Calculate attempt execution time
|
|
165
|
+
attempt_end = time.time()
|
|
166
|
+
attempt_info["duration_ms"] = max(1, int((attempt_end - attempt_start) * 1000))
|
|
167
|
+
attempt_info["success"] = True
|
|
168
|
+
result_metadata["attempts"].append(attempt_info)
|
|
169
|
+
|
|
170
|
+
# Mark as successful
|
|
171
|
+
hook.status = HookStatus.COMPLETED
|
|
172
|
+
result_metadata["success"] = True
|
|
173
|
+
result_metadata["result"] = result
|
|
174
|
+
result_metadata["retry_count"] = attempt
|
|
175
|
+
|
|
176
|
+
# Handle data transformation if hook returns modified data
|
|
177
|
+
if isinstance(result, dict) and "data" in result:
|
|
178
|
+
self._apply_data_transformation(event, result["data"])
|
|
179
|
+
logger.debug(f"Hook {hook_key} modified event data")
|
|
180
|
+
|
|
181
|
+
# Success - break out of retry loop
|
|
182
|
+
break
|
|
183
|
+
|
|
184
|
+
except asyncio.TimeoutError:
|
|
185
|
+
attempt_end = time.time()
|
|
186
|
+
attempt_info["duration_ms"] = max(1, int((attempt_end - attempt_start) * 1000))
|
|
187
|
+
attempt_info["error"] = "timeout"
|
|
188
|
+
result_metadata["attempts"].append(attempt_info)
|
|
189
|
+
|
|
190
|
+
hook.status = HookStatus.TIMEOUT
|
|
191
|
+
logger.warning(f"Hook {hook_key} timed out after {timeout}s (attempt {attempt + 1}/{max_attempts})")
|
|
192
|
+
|
|
193
|
+
# On final attempt, mark as error
|
|
194
|
+
if attempt == max_attempts - 1:
|
|
195
|
+
result_metadata["error"] = "timeout"
|
|
196
|
+
result_metadata["retry_count"] = attempt
|
|
197
|
+
|
|
198
|
+
# Handle timeout based on error action
|
|
199
|
+
if error_action == "stop":
|
|
200
|
+
event.cancelled = True
|
|
201
|
+
logger.info(f"Event cancelled due to hook timeout: {hook_key}")
|
|
202
|
+
else:
|
|
203
|
+
# Wait before retry with exponential backoff
|
|
204
|
+
backoff_delay = min(2 ** attempt, 30) # Max 30 seconds
|
|
205
|
+
logger.debug(f"Retrying hook {hook_key} in {backoff_delay}s")
|
|
206
|
+
await asyncio.sleep(backoff_delay)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
attempt_end = time.time()
|
|
210
|
+
attempt_info["duration_ms"] = max(1, int((attempt_end - attempt_start) * 1000))
|
|
211
|
+
attempt_info["error"] = str(e)
|
|
212
|
+
result_metadata["attempts"].append(attempt_info)
|
|
213
|
+
|
|
214
|
+
hook.status = HookStatus.FAILED
|
|
215
|
+
log_and_continue(logger, f"executing hook {hook_key} (attempt {attempt + 1}/{max_attempts})", e)
|
|
216
|
+
|
|
217
|
+
# On final attempt, mark as error
|
|
218
|
+
if attempt == max_attempts - 1:
|
|
219
|
+
result_metadata["error"] = str(e)
|
|
220
|
+
result_metadata["retry_count"] = attempt
|
|
221
|
+
|
|
222
|
+
# Handle error based on error action
|
|
223
|
+
if error_action == "stop":
|
|
224
|
+
event.cancelled = True
|
|
225
|
+
logger.info(f"Event cancelled due to hook error: {hook_key}")
|
|
226
|
+
else:
|
|
227
|
+
# Wait before retry with exponential backoff
|
|
228
|
+
backoff_delay = min(2 ** attempt, 30) # Max 30 seconds
|
|
229
|
+
logger.debug(f"Retrying hook {hook_key} in {backoff_delay}s after error: {e}")
|
|
230
|
+
await asyncio.sleep(backoff_delay)
|
|
231
|
+
|
|
232
|
+
# Calculate total execution time including retries
|
|
233
|
+
overall_end = time.time()
|
|
234
|
+
result_metadata["duration_ms"] = max(1, int((overall_end - overall_start) * 1000))
|
|
235
|
+
|
|
106
236
|
return result_metadata
|
|
107
|
-
|
|
237
|
+
|
|
108
238
|
def _apply_data_transformation(self, event: Event, hook_data: Dict[str, Any]) -> None:
|
|
109
239
|
"""Apply data transformation from hook result to event.
|
|
110
|
-
|
|
240
|
+
|
|
111
241
|
Args:
|
|
112
242
|
event: The event to modify.
|
|
113
243
|
hook_data: Data transformation from hook.
|
|
@@ -119,13 +249,13 @@ class HookExecutor:
|
|
|
119
249
|
logger.warning(f"Hook returned non-dict data transformation: {type(hook_data)}")
|
|
120
250
|
except Exception as e:
|
|
121
251
|
log_and_continue(logger, "applying hook data transformation", e)
|
|
122
|
-
|
|
252
|
+
|
|
123
253
|
def get_execution_stats(self, results: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
124
254
|
"""Get execution statistics from a list of hook results.
|
|
125
|
-
|
|
255
|
+
|
|
126
256
|
Args:
|
|
127
257
|
results: List of hook execution results.
|
|
128
|
-
|
|
258
|
+
|
|
129
259
|
Returns:
|
|
130
260
|
Dictionary with execution statistics.
|
|
131
261
|
"""
|
|
@@ -138,12 +268,12 @@ class HookExecutor:
|
|
|
138
268
|
"total_duration_ms": 0,
|
|
139
269
|
"avg_duration_ms": 0
|
|
140
270
|
}
|
|
141
|
-
|
|
271
|
+
|
|
142
272
|
successful = sum(1 for r in results if r.get("success", False))
|
|
143
273
|
failed = sum(1 for r in results if r.get("error") and r["error"] not in ["timeout", "hook_disabled", "event_cancelled"])
|
|
144
274
|
timed_out = sum(1 for r in results if r.get("error") == "timeout")
|
|
145
275
|
total_duration = sum(r.get("duration_ms", 0) for r in results)
|
|
146
|
-
|
|
276
|
+
|
|
147
277
|
return {
|
|
148
278
|
"total_hooks": len(results),
|
|
149
279
|
"successful": successful,
|
|
@@ -151,4 +281,4 @@ class HookExecutor:
|
|
|
151
281
|
"timed_out": timed_out,
|
|
152
282
|
"total_duration_ms": total_duration,
|
|
153
283
|
"avg_duration_ms": int(total_duration / len(results)) if results else 0
|
|
154
|
-
}
|
|
284
|
+
}
|
core/events/models.py
CHANGED
|
@@ -88,6 +88,14 @@ class EventType(Enum):
|
|
|
88
88
|
# Command output display events
|
|
89
89
|
COMMAND_OUTPUT_DISPLAY = "command_output_display"
|
|
90
90
|
|
|
91
|
+
# Message injection events (SDK extension points)
|
|
92
|
+
ADD_MESSAGE = "add_message"
|
|
93
|
+
PRE_MESSAGE_INJECT = "pre_message_inject"
|
|
94
|
+
POST_MESSAGE_INJECT = "post_message_inject"
|
|
95
|
+
|
|
96
|
+
# LLM continuation control (SDK extension point)
|
|
97
|
+
TRIGGER_LLM_CONTINUE = "trigger_llm_continue"
|
|
98
|
+
|
|
91
99
|
# Command menu events (enhanced)
|
|
92
100
|
COMMAND_MENU_FILTER = "command_menu_filter"
|
|
93
101
|
|
|
@@ -96,6 +104,7 @@ class EventType(Enum):
|
|
|
96
104
|
STATUS_MODAL_TRIGGER = "status_modal_trigger"
|
|
97
105
|
STATUS_MODAL_RENDER = "status_modal_render"
|
|
98
106
|
LIVE_MODAL_TRIGGER = "live_modal_trigger"
|
|
107
|
+
MODAL_COMMAND_SELECTED = "modal_command_selected"
|
|
99
108
|
|
|
100
109
|
# Rendering control events
|
|
101
110
|
PAUSE_RENDERING = "pause_rendering"
|
|
@@ -113,10 +122,11 @@ class EventType(Enum):
|
|
|
113
122
|
STATUS_TAKEOVER_END = "status_takeover_end"
|
|
114
123
|
|
|
115
124
|
|
|
125
|
+
|
|
116
126
|
@dataclass
|
|
117
127
|
class Hook:
|
|
118
128
|
"""Hook definition for the event system.
|
|
119
|
-
|
|
129
|
+
|
|
120
130
|
Attributes:
|
|
121
131
|
name: Unique name for the hook.
|
|
122
132
|
plugin_name: Name of the plugin that owns this hook.
|
|
@@ -124,9 +134,9 @@ class Hook:
|
|
|
124
134
|
priority: Execution priority (higher numbers execute first).
|
|
125
135
|
callback: Async function to call when event occurs.
|
|
126
136
|
enabled: Whether the hook is currently enabled.
|
|
127
|
-
timeout: Maximum execution time in seconds.
|
|
128
|
-
retry_attempts: Number of retry attempts on failure.
|
|
129
|
-
error_action: Action to take on error (
|
|
137
|
+
timeout: Maximum execution time in seconds (None = use config default, then 30).
|
|
138
|
+
retry_attempts: Number of retry attempts on failure (None = use config default, then 3).
|
|
139
|
+
error_action: Action to take on error (None = use config default, then "continue").
|
|
130
140
|
status: Current execution status.
|
|
131
141
|
status_area: Status area identifier.
|
|
132
142
|
icon_set: Icons for different states.
|
|
@@ -137,13 +147,13 @@ class Hook:
|
|
|
137
147
|
priority: int
|
|
138
148
|
callback: Callable
|
|
139
149
|
enabled: bool = True
|
|
140
|
-
timeout: int =
|
|
141
|
-
retry_attempts: int =
|
|
142
|
-
error_action: str =
|
|
150
|
+
timeout: Optional[int] = None
|
|
151
|
+
retry_attempts: Optional[int] = None
|
|
152
|
+
error_action: Optional[str] = None
|
|
143
153
|
status: HookStatus = HookStatus.PENDING
|
|
144
154
|
status_area: str = "A"
|
|
145
155
|
icon_set: Dict[str, str] = field(default_factory=lambda: {
|
|
146
|
-
"thinking": "[
|
|
156
|
+
"thinking": "[think]", "processing": "[proc]", "complete": "[ok]", "error": "[err]"
|
|
147
157
|
})
|
|
148
158
|
|
|
149
159
|
|
|
@@ -219,6 +229,14 @@ class ParameterDefinition:
|
|
|
219
229
|
validation: Optional[str] = None
|
|
220
230
|
|
|
221
231
|
|
|
232
|
+
@dataclass
|
|
233
|
+
class SubcommandInfo:
|
|
234
|
+
"""Info about a subcommand for menu display."""
|
|
235
|
+
name: str
|
|
236
|
+
args: str # e.g., "<name> <command>" or "[name]"
|
|
237
|
+
description: str
|
|
238
|
+
|
|
239
|
+
|
|
222
240
|
@dataclass
|
|
223
241
|
class CommandDefinition:
|
|
224
242
|
"""Complete definition of a slash command."""
|
|
@@ -234,6 +252,7 @@ class CommandDefinition:
|
|
|
234
252
|
icon: str = ""
|
|
235
253
|
hidden: bool = False
|
|
236
254
|
enabled: bool = True
|
|
255
|
+
subcommands: List[SubcommandInfo] = field(default_factory=list)
|
|
237
256
|
|
|
238
257
|
|
|
239
258
|
@dataclass
|
|
@@ -27,15 +27,22 @@ class FullScreenCommandIntegrator:
|
|
|
27
27
|
- Maps commands to plugin execution
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
def __init__(self, command_registry: SlashCommandRegistry, event_bus
|
|
30
|
+
def __init__(self, command_registry: SlashCommandRegistry, event_bus,
|
|
31
|
+
config=None, profile_manager=None, terminal_renderer=None):
|
|
31
32
|
"""Initialize the fullscreen command integrator.
|
|
32
33
|
|
|
33
34
|
Args:
|
|
34
35
|
command_registry: Slash command registry for registration
|
|
35
36
|
event_bus: Event bus for communication
|
|
37
|
+
config: Optional config service for plugins that need it
|
|
38
|
+
profile_manager: Optional profile manager for plugins that need it
|
|
39
|
+
terminal_renderer: Optional terminal renderer for fullscreen manager
|
|
36
40
|
"""
|
|
37
41
|
self.command_registry = command_registry
|
|
38
42
|
self.event_bus = event_bus
|
|
43
|
+
self.config = config
|
|
44
|
+
self.profile_manager = profile_manager
|
|
45
|
+
self.terminal_renderer = terminal_renderer
|
|
39
46
|
self.registered_plugins: Dict[str, Type[FullScreenPlugin]] = {}
|
|
40
47
|
self.plugin_instances: Dict[str, FullScreenPlugin] = {}
|
|
41
48
|
self._fullscreen_manager = None
|
|
@@ -136,7 +143,7 @@ class FullScreenCommandIntegrator:
|
|
|
136
143
|
name=metadata.name,
|
|
137
144
|
aliases=metadata.aliases or [],
|
|
138
145
|
description=metadata.description,
|
|
139
|
-
category=CommandCategory.
|
|
146
|
+
category=CommandCategory.CUSTOM, # Fullscreen plugins are custom/plugins
|
|
140
147
|
mode=CommandMode.INSTANT,
|
|
141
148
|
handler=self._create_plugin_handler(metadata.name),
|
|
142
149
|
icon=metadata.icon,
|
|
@@ -148,24 +155,10 @@ class FullScreenCommandIntegrator:
|
|
|
148
155
|
logger.error(f"Failed to register primary command for {metadata.name}")
|
|
149
156
|
return False
|
|
150
157
|
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
aliases=[],
|
|
156
|
-
description=f"{metadata.description} (alias)",
|
|
157
|
-
category=CommandCategory.SYSTEM,
|
|
158
|
-
mode=CommandMode.INSTANT,
|
|
159
|
-
handler=self._create_plugin_handler(metadata.name),
|
|
160
|
-
icon=metadata.icon,
|
|
161
|
-
plugin_name="fullscreen_integrator"
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
alias_success = self.command_registry.register_command(alias_command)
|
|
165
|
-
if alias_success:
|
|
166
|
-
logger.debug(f"Registered alias command: {alias} -> {metadata.name}")
|
|
167
|
-
else:
|
|
168
|
-
logger.warning(f"Failed to register alias command: {alias}")
|
|
158
|
+
# Aliases are stored in the primary command's aliases field
|
|
159
|
+
# No need to register them separately - registry handles alias lookups
|
|
160
|
+
if metadata.aliases:
|
|
161
|
+
logger.debug(f"Command {metadata.name} has aliases: {metadata.aliases}")
|
|
169
162
|
|
|
170
163
|
return True
|
|
171
164
|
|
|
@@ -188,7 +181,7 @@ class FullScreenCommandIntegrator:
|
|
|
188
181
|
# Get or create fullscreen manager
|
|
189
182
|
if not self._fullscreen_manager:
|
|
190
183
|
from . import FullScreenManager
|
|
191
|
-
self._fullscreen_manager = FullScreenManager(self.event_bus,
|
|
184
|
+
self._fullscreen_manager = FullScreenManager(self.event_bus, self.terminal_renderer)
|
|
192
185
|
|
|
193
186
|
# Get or create plugin instance
|
|
194
187
|
if plugin_name not in self.plugin_instances:
|
|
@@ -197,6 +190,11 @@ class FullScreenCommandIntegrator:
|
|
|
197
190
|
raise ValueError(f"Plugin class not found: {plugin_name}")
|
|
198
191
|
|
|
199
192
|
plugin_instance = plugin_class()
|
|
193
|
+
|
|
194
|
+
# Pass managers to plugins that need them (e.g., setup wizard)
|
|
195
|
+
if hasattr(plugin_instance, 'set_managers'):
|
|
196
|
+
plugin_instance.set_managers(self.config, self.profile_manager)
|
|
197
|
+
|
|
200
198
|
self.plugin_instances[plugin_name] = plugin_instance
|
|
201
199
|
self._fullscreen_manager.register_plugin(plugin_instance)
|
|
202
200
|
logger.debug(f"Created and registered plugin instance: {plugin_name}")
|
|
@@ -246,10 +244,8 @@ class FullScreenCommandIntegrator:
|
|
|
246
244
|
temp_instance = plugin_class()
|
|
247
245
|
metadata = temp_instance.metadata
|
|
248
246
|
|
|
249
|
-
# Unregister
|
|
247
|
+
# Unregister the primary command (aliases are handled by registry)
|
|
250
248
|
self.command_registry.unregister_command(metadata.name)
|
|
251
|
-
for alias in (metadata.aliases or []):
|
|
252
|
-
self.command_registry.unregister_command(alias)
|
|
253
249
|
|
|
254
250
|
del self.registered_plugins[plugin_name]
|
|
255
251
|
|
|
@@ -3,10 +3,19 @@
|
|
|
3
3
|
from .drawing import DrawingPrimitives
|
|
4
4
|
from .animation import AnimationFramework
|
|
5
5
|
from .matrix_components import MatrixColumn, MatrixRenderer
|
|
6
|
+
from .space_shooter_components import (
|
|
7
|
+
Star, Ship, Enemy, Laser, Explosion, SpaceShooterRenderer
|
|
8
|
+
)
|
|
6
9
|
|
|
7
10
|
__all__ = [
|
|
8
11
|
"DrawingPrimitives",
|
|
9
12
|
"AnimationFramework",
|
|
10
13
|
"MatrixColumn",
|
|
11
|
-
"MatrixRenderer"
|
|
14
|
+
"MatrixRenderer",
|
|
15
|
+
"Star",
|
|
16
|
+
"Ship",
|
|
17
|
+
"Enemy",
|
|
18
|
+
"Laser",
|
|
19
|
+
"Explosion",
|
|
20
|
+
"SpaceShooterRenderer"
|
|
12
21
|
]
|
|
@@ -168,8 +168,7 @@ class MatrixRenderer:
|
|
|
168
168
|
for column in self.columns:
|
|
169
169
|
column.render(renderer)
|
|
170
170
|
|
|
171
|
-
#
|
|
172
|
-
renderer.flush()
|
|
171
|
+
# Note: Flushing is handled by session's end_frame() for flicker-free rendering
|
|
173
172
|
|
|
174
173
|
def reset(self):
|
|
175
174
|
"""Reset the Matrix renderer."""
|