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/io/key_parser.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""Keyboard input parsing for terminal applications.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive keyboard input parsing, including
|
|
4
|
+
single character input, control key detection, and escape sequence
|
|
5
|
+
handling for terminal applications.
|
|
6
|
+
"""
|
|
7
|
+
|
|
1
8
|
import logging
|
|
2
9
|
from dataclasses import dataclass
|
|
3
10
|
from enum import Enum
|
core/io/layout.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Layout management system for terminal rendering.
|
|
1
|
+
"""Layout management system for terminal rendering.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive layout management for terminal rendering,
|
|
4
|
+
including area management, adaptive sizing, and thinking animation support.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
import re
|
|
4
8
|
from collections import deque
|
|
@@ -166,16 +170,24 @@ class ThinkingAnimationManager:
|
|
|
166
170
|
Returns:
|
|
167
171
|
List of formatted display lines.
|
|
168
172
|
"""
|
|
173
|
+
import time
|
|
174
|
+
|
|
169
175
|
if not self.is_active or not self.messages:
|
|
170
176
|
return []
|
|
171
177
|
|
|
172
178
|
lines = []
|
|
173
179
|
spinner = self.get_next_frame()
|
|
174
180
|
|
|
181
|
+
# Calculate elapsed time
|
|
182
|
+
elapsed = ""
|
|
183
|
+
if self.start_time:
|
|
184
|
+
duration = time.time() - self.start_time
|
|
185
|
+
elapsed = f" ({duration:.0f}s - esc to cancel)"
|
|
186
|
+
|
|
175
187
|
for i, msg in enumerate(self.messages):
|
|
176
188
|
if i == len(self.messages) - 1:
|
|
177
|
-
# Main thinking line with spinner
|
|
178
|
-
formatted_text = apply_effect_func(f"{spinner} Thinking: {msg}")
|
|
189
|
+
# Main thinking line with spinner and elapsed time
|
|
190
|
+
formatted_text = apply_effect_func(f"{spinner} Thinking{elapsed}: {msg}")
|
|
179
191
|
lines.append(formatted_text)
|
|
180
192
|
else:
|
|
181
193
|
# Secondary thinking line
|
core/io/message_coordinator.py
CHANGED
|
@@ -3,22 +3,30 @@
|
|
|
3
3
|
This coordinator solves the fundamental race condition where multiple
|
|
4
4
|
message writing systems interfere with each other, causing messages
|
|
5
5
|
to be overwritten or cleared unexpectedly.
|
|
6
|
+
|
|
7
|
+
IMPORTANT: All terminal state changes (input rendering, clearing, buffer
|
|
8
|
+
transitions) should go through this coordinator to prevent state bugs.
|
|
9
|
+
|
|
10
|
+
This module provides atomic message display coordination and unified
|
|
11
|
+
state management to prevent interference between different message
|
|
12
|
+
writing systems.
|
|
6
13
|
"""
|
|
7
14
|
|
|
8
15
|
import logging
|
|
9
|
-
from typing import List, Tuple, Dict, Any
|
|
16
|
+
from typing import List, Tuple, Dict, Any, Optional
|
|
10
17
|
|
|
11
18
|
logger = logging.getLogger(__name__)
|
|
12
19
|
|
|
13
20
|
|
|
14
21
|
class MessageDisplayCoordinator:
|
|
15
|
-
"""Coordinates message display to prevent interference
|
|
22
|
+
"""Coordinates message display AND render state to prevent interference.
|
|
16
23
|
|
|
17
24
|
Key Features:
|
|
18
25
|
- Atomic message sequences (all messages display together)
|
|
19
26
|
- Unified state management (prevents clearing conflicts)
|
|
20
27
|
- Proper ordering (system messages before responses)
|
|
21
28
|
- Protection from interference (no race conditions)
|
|
29
|
+
- Buffer transition management (modal open/close state preservation)
|
|
22
30
|
"""
|
|
23
31
|
|
|
24
32
|
def __init__(self, terminal_renderer):
|
|
@@ -31,8 +39,26 @@ class MessageDisplayCoordinator:
|
|
|
31
39
|
self.message_queue: List[Tuple[str, str, Dict[str, Any]]] = []
|
|
32
40
|
self.is_displaying = False
|
|
33
41
|
|
|
42
|
+
# Saved state for buffer transitions (modal, fullscreen, etc.)
|
|
43
|
+
self._saved_main_buffer_state: Optional[Dict[str, Any]] = None
|
|
44
|
+
self._in_alternate_buffer = False
|
|
45
|
+
|
|
34
46
|
logger.debug("MessageDisplayCoordinator initialized")
|
|
35
47
|
|
|
48
|
+
def _capture_render_state(self) -> Dict[str, Any]:
|
|
49
|
+
"""Capture current render state for later restoration.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Dictionary containing render state snapshot.
|
|
53
|
+
"""
|
|
54
|
+
return {
|
|
55
|
+
"writing_messages": self.terminal_renderer.writing_messages,
|
|
56
|
+
"input_line_written": self.terminal_renderer.input_line_written,
|
|
57
|
+
"last_line_count": self.terminal_renderer.last_line_count,
|
|
58
|
+
"conversation_active": self.terminal_renderer.conversation_active,
|
|
59
|
+
"thinking_active": self.terminal_renderer.thinking_active,
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
def queue_message(self, message_type: str, content: str, **kwargs) -> None:
|
|
37
63
|
"""Queue a message for coordinated display.
|
|
38
64
|
|
|
@@ -92,6 +118,11 @@ class MessageDisplayCoordinator:
|
|
|
92
118
|
self.terminal_renderer.writing_messages = False
|
|
93
119
|
self.message_queue.clear()
|
|
94
120
|
self.is_displaying = False
|
|
121
|
+
# Reset render state for clean input box rendering
|
|
122
|
+
# This prevents duplicate input boxes when render loop resumes
|
|
123
|
+
self.terminal_renderer.input_line_written = False
|
|
124
|
+
self.terminal_renderer.last_line_count = 0
|
|
125
|
+
self.terminal_renderer.invalidate_render_cache()
|
|
95
126
|
logger.debug("Completed atomic message display")
|
|
96
127
|
|
|
97
128
|
def display_message_sequence(
|
|
@@ -169,6 +200,16 @@ class MessageDisplayCoordinator:
|
|
|
169
200
|
format_style=MessageFormat.HIGHLIGHTED, # Uses red color from _format_highlighted
|
|
170
201
|
**kwargs,
|
|
171
202
|
)
|
|
203
|
+
elif message_type == "info":
|
|
204
|
+
# For info messages, use MessageType.SYSTEM with plain format
|
|
205
|
+
from .message_renderer import MessageType, MessageFormat
|
|
206
|
+
|
|
207
|
+
self.terminal_renderer.message_renderer.conversation_renderer.write_message(
|
|
208
|
+
content,
|
|
209
|
+
message_type=MessageType.SYSTEM,
|
|
210
|
+
format_style=MessageFormat.PLAIN,
|
|
211
|
+
**kwargs,
|
|
212
|
+
)
|
|
172
213
|
else:
|
|
173
214
|
logger.warning(f"Unknown message type: {message_type}")
|
|
174
215
|
# Fallback to regular message
|
|
@@ -202,3 +243,71 @@ class MessageDisplayCoordinator:
|
|
|
202
243
|
"is_displaying": self.is_displaying,
|
|
203
244
|
"queued_types": [msg[0] for msg in self.message_queue],
|
|
204
245
|
}
|
|
246
|
+
|
|
247
|
+
# === Buffer Transition Management ===
|
|
248
|
+
# These methods handle state preservation during modal/fullscreen transitions
|
|
249
|
+
|
|
250
|
+
def enter_alternate_buffer(self) -> None:
|
|
251
|
+
"""Mark entering alternate buffer and pause render loop.
|
|
252
|
+
|
|
253
|
+
Call this BEFORE opening a modal or entering fullscreen mode.
|
|
254
|
+
Captures current render state for potential restoration.
|
|
255
|
+
"""
|
|
256
|
+
if self._in_alternate_buffer:
|
|
257
|
+
logger.warning("Already in alternate buffer")
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# Capture state BEFORE modifying anything
|
|
261
|
+
self._saved_main_buffer_state = self._capture_render_state()
|
|
262
|
+
logger.debug(f"Captured render state: {self._saved_main_buffer_state}")
|
|
263
|
+
|
|
264
|
+
self._in_alternate_buffer = True
|
|
265
|
+
# Prevent render loop interference during modal
|
|
266
|
+
self.terminal_renderer.writing_messages = True
|
|
267
|
+
logger.debug("Entered alternate buffer mode")
|
|
268
|
+
|
|
269
|
+
def exit_alternate_buffer(self, restore_state: bool = False) -> None:
|
|
270
|
+
"""Exit alternate buffer mode and reset render state.
|
|
271
|
+
|
|
272
|
+
Call this AFTER closing a modal or exiting fullscreen mode.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
restore_state: If True, restore captured state. If False (default),
|
|
276
|
+
reset to clean state for fresh input rendering.
|
|
277
|
+
"""
|
|
278
|
+
if not self._in_alternate_buffer:
|
|
279
|
+
logger.warning("Not in alternate buffer")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
self._in_alternate_buffer = False
|
|
283
|
+
|
|
284
|
+
if restore_state and self._saved_main_buffer_state:
|
|
285
|
+
# Restore previously captured state
|
|
286
|
+
self.terminal_renderer.writing_messages = self._saved_main_buffer_state[
|
|
287
|
+
"writing_messages"
|
|
288
|
+
]
|
|
289
|
+
self.terminal_renderer.input_line_written = self._saved_main_buffer_state[
|
|
290
|
+
"input_line_written"
|
|
291
|
+
]
|
|
292
|
+
self.terminal_renderer.last_line_count = self._saved_main_buffer_state[
|
|
293
|
+
"last_line_count"
|
|
294
|
+
]
|
|
295
|
+
logger.debug(f"Restored render state: {self._saved_main_buffer_state}")
|
|
296
|
+
else:
|
|
297
|
+
# Reset to clean state (default - prevents duplicate input boxes)
|
|
298
|
+
self.terminal_renderer.writing_messages = False
|
|
299
|
+
self.terminal_renderer.input_line_written = False
|
|
300
|
+
self.terminal_renderer.last_line_count = 0
|
|
301
|
+
logger.debug("Reset to clean render state")
|
|
302
|
+
|
|
303
|
+
# Always invalidate cache after buffer transition
|
|
304
|
+
self.terminal_renderer.invalidate_render_cache()
|
|
305
|
+
self._saved_main_buffer_state = None
|
|
306
|
+
|
|
307
|
+
def get_saved_state(self) -> Optional[Dict[str, Any]]:
|
|
308
|
+
"""Get the saved render state (for debugging).
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Saved state dict if in alternate buffer, None otherwise.
|
|
312
|
+
"""
|
|
313
|
+
return self._saved_main_buffer_state
|
core/io/message_renderer.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
"""Message rendering system for conversation display.
|
|
1
|
+
"""Message rendering system for conversation display.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive message rendering for conversation display,
|
|
4
|
+
including message formatting, conversation buffer management, and streaming
|
|
5
|
+
response support.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
import logging
|
|
9
|
+
import re
|
|
4
10
|
from collections import deque
|
|
5
11
|
from dataclasses import dataclass
|
|
6
12
|
from enum import Enum
|
|
@@ -10,6 +16,102 @@ from typing import List, Optional, Dict, Any, Callable
|
|
|
10
16
|
logger = logging.getLogger(__name__)
|
|
11
17
|
|
|
12
18
|
|
|
19
|
+
class DisplayFilterRegistry:
|
|
20
|
+
"""Registry for message display filters.
|
|
21
|
+
|
|
22
|
+
Plugins can register filter functions that transform message content
|
|
23
|
+
before display. This allows plugins to strip/transform their own
|
|
24
|
+
XML commands without hardcoding patterns in core.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
# In plugin initialize():
|
|
28
|
+
DisplayFilterRegistry.register(
|
|
29
|
+
"my_plugin",
|
|
30
|
+
self._strip_my_xml,
|
|
31
|
+
message_types=[MessageType.ASSISTANT]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Filter function signature:
|
|
35
|
+
def _strip_my_xml(content: str, message_type: 'MessageType') -> str:
|
|
36
|
+
return re.sub(r"<my-tag>.*?</my-tag>", "", content)
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
_filters: Dict[str, Dict[str, Any]] = {}
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def register(
|
|
43
|
+
cls,
|
|
44
|
+
name: str,
|
|
45
|
+
filter_fn: Callable[[str, "MessageType"], str],
|
|
46
|
+
message_types: Optional[List["MessageType"]] = None,
|
|
47
|
+
priority: int = 100,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Register a display filter.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
name: Unique name for this filter (usually plugin name).
|
|
53
|
+
filter_fn: Function that takes (content, message_type) and returns transformed content.
|
|
54
|
+
message_types: List of message types to apply filter to (None = all types).
|
|
55
|
+
priority: Execution priority (higher = runs first).
|
|
56
|
+
"""
|
|
57
|
+
cls._filters[name] = {
|
|
58
|
+
"fn": filter_fn,
|
|
59
|
+
"message_types": message_types,
|
|
60
|
+
"priority": priority,
|
|
61
|
+
}
|
|
62
|
+
logger.debug(f"Registered display filter: {name}")
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def unregister(cls, name: str) -> None:
|
|
66
|
+
"""Unregister a display filter.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
name: Name of the filter to remove.
|
|
70
|
+
"""
|
|
71
|
+
if name in cls._filters:
|
|
72
|
+
del cls._filters[name]
|
|
73
|
+
logger.debug(f"Unregistered display filter: {name}")
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def apply_filters(cls, content: str, message_type: "MessageType") -> str:
|
|
77
|
+
"""Apply all registered filters to content.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
content: Original message content.
|
|
81
|
+
message_type: Type of message being displayed.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Transformed content after all applicable filters.
|
|
85
|
+
"""
|
|
86
|
+
if not cls._filters:
|
|
87
|
+
return content
|
|
88
|
+
|
|
89
|
+
# Sort by priority (higher first)
|
|
90
|
+
sorted_filters = sorted(
|
|
91
|
+
cls._filters.items(),
|
|
92
|
+
key=lambda x: x[1]["priority"],
|
|
93
|
+
reverse=True,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for name, filter_info in sorted_filters:
|
|
97
|
+
# Check if filter applies to this message type
|
|
98
|
+
allowed_types = filter_info["message_types"]
|
|
99
|
+
if allowed_types is not None and message_type not in allowed_types:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
content = filter_info["fn"](content, message_type)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Display filter '{name}' failed: {e}")
|
|
106
|
+
|
|
107
|
+
return content
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def clear(cls) -> None:
|
|
111
|
+
"""Clear all registered filters (useful for testing)."""
|
|
112
|
+
cls._filters.clear()
|
|
113
|
+
|
|
114
|
+
|
|
13
115
|
class MessageType(Enum):
|
|
14
116
|
"""Types of messages in conversation."""
|
|
15
117
|
|
|
@@ -281,12 +383,19 @@ class ConversationRenderer:
|
|
|
281
383
|
immediate_display: Whether to display immediately.
|
|
282
384
|
**metadata: Additional message metadata.
|
|
283
385
|
"""
|
|
386
|
+
# Apply registered display filters (plugins can strip their XML commands)
|
|
387
|
+
display_content = DisplayFilterRegistry.apply_filters(content, message_type)
|
|
388
|
+
display_content = display_content.strip()
|
|
389
|
+
|
|
390
|
+
if not display_content:
|
|
391
|
+
return # Nothing to display after filtering
|
|
392
|
+
|
|
284
393
|
# Add to buffer
|
|
285
|
-
self.buffer.add_message(
|
|
394
|
+
self.buffer.add_message(display_content, message_type, format_style, **metadata)
|
|
286
395
|
|
|
287
396
|
# Display immediately if requested
|
|
288
397
|
if immediate_display:
|
|
289
|
-
self._display_message_immediately(
|
|
398
|
+
self._display_message_immediately(display_content, message_type, format_style)
|
|
290
399
|
|
|
291
400
|
def start_streaming_response(self) -> None:
|
|
292
401
|
"""Start a streaming response by setting up the display area."""
|
|
@@ -308,8 +417,19 @@ class ConversationRenderer:
|
|
|
308
417
|
content: User message content.
|
|
309
418
|
**metadata: Additional metadata.
|
|
310
419
|
"""
|
|
420
|
+
# Strip hidden system message blocks (used for plugin instruction injection)
|
|
421
|
+
display_content = re.sub(
|
|
422
|
+
r"<sys_msg>.*?</sys_msg>\s*",
|
|
423
|
+
"",
|
|
424
|
+
content,
|
|
425
|
+
flags=re.DOTALL,
|
|
426
|
+
).strip()
|
|
427
|
+
|
|
428
|
+
if not display_content:
|
|
429
|
+
return # Nothing to display after stripping
|
|
430
|
+
|
|
311
431
|
self.write_message(
|
|
312
|
-
|
|
432
|
+
display_content, MessageType.USER, MessageFormat.GRADIENT, **metadata
|
|
313
433
|
)
|
|
314
434
|
|
|
315
435
|
def write_system_message(self, content: str, **metadata) -> None:
|
|
@@ -391,6 +511,11 @@ class ConversationRenderer:
|
|
|
391
511
|
|
|
392
512
|
try:
|
|
393
513
|
# Write to terminal
|
|
514
|
+
line_count = formatted_content.count('\n') + 1
|
|
515
|
+
logger.info(
|
|
516
|
+
f"DISPLAY: Printing {message_type.value} message: "
|
|
517
|
+
f"{len(content)} chars, {line_count} lines"
|
|
518
|
+
)
|
|
394
519
|
print(formatted_content, flush=self.flush_immediately)
|
|
395
520
|
# Add blank line for visual separation between messages
|
|
396
521
|
print("", flush=self.flush_immediately)
|