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
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 status tracking.
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
- logger.debug("HookExecutor initialized")
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 timeout.
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
- # Track execution time
55
- start_time = time.time()
56
-
57
- try:
58
- # Update hook status to working
59
- hook.status = HookStatus.WORKING
60
-
61
- # Execute hook with timeout
62
- result = await asyncio.wait_for(
63
- hook.callback(event.data, event),
64
- timeout=hook.timeout
65
- )
66
-
67
- # Calculate execution time
68
- end_time = time.time()
69
- result_metadata["duration_ms"] = max(1, int((end_time - start_time) * 1000))
70
-
71
- # Mark as successful
72
- hook.status = HookStatus.COMPLETED
73
- result_metadata["success"] = True
74
- result_metadata["result"] = result
75
- # Handle data transformation if hook returns modified data
76
- if isinstance(result, dict) and "data" in result:
77
- self._apply_data_transformation(event, result["data"])
78
- logger.debug(f"Hook {hook_key} modified event data")
79
-
80
- except asyncio.TimeoutError:
81
- end_time = time.time()
82
- result_metadata["duration_ms"] = max(1, int((end_time - start_time) * 1000))
83
- result_metadata["error"] = "timeout"
84
-
85
- hook.status = HookStatus.TIMEOUT
86
- logger.warning(f"Hook {hook_key} timed out after {hook.timeout}s")
87
-
88
- # Handle timeout based on error action
89
- if hook.error_action == "stop":
90
- event.cancelled = True
91
- logger.info(f"Event cancelled due to hook timeout: {hook_key}")
92
-
93
- except Exception as e:
94
- end_time = time.time()
95
- result_metadata["duration_ms"] = max(1, int((end_time - start_time) * 1000))
96
- result_metadata["error"] = str(e)
97
-
98
- hook.status = HookStatus.FAILED
99
- log_and_continue(logger, f"executing hook {hook_key}", e)
100
-
101
- # Handle error based on error action
102
- if hook.error_action == "stop":
103
- event.cancelled = True
104
- logger.info(f"Event cancelled due to hook error: {hook_key}")
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 ("continue" or "stop").
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 = 30
141
- retry_attempts: int = 3
142
- error_action: str = "continue"
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": "[THINK]", "processing": "[PROC]", "complete": "[OK]", "error": "[ERR]"
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.SYSTEM,
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
- # Register alias commands if any
152
- for alias in (metadata.aliases or []):
153
- alias_command = CommandDefinition(
154
- name=alias,
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, None)
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 all commands for this plugin
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
- # Flush output
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."""