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/commands/system_commands.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Core system commands for Kollabor CLI."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Dict, Any, List
|
|
4
|
+
from typing import Dict, Any, List, Optional
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
7
|
from ..events.models import (
|
|
@@ -10,7 +10,11 @@ from ..events.models import (
|
|
|
10
10
|
CommandCategory,
|
|
11
11
|
CommandResult,
|
|
12
12
|
SlashCommand,
|
|
13
|
-
UIConfig
|
|
13
|
+
UIConfig,
|
|
14
|
+
EventType,
|
|
15
|
+
Hook,
|
|
16
|
+
Event,
|
|
17
|
+
SubcommandInfo,
|
|
14
18
|
)
|
|
15
19
|
|
|
16
20
|
logger = logging.getLogger(__name__)
|
|
@@ -23,18 +27,32 @@ class SystemCommandsPlugin:
|
|
|
23
27
|
These commands are automatically registered at application startup.
|
|
24
28
|
"""
|
|
25
29
|
|
|
26
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
command_registry,
|
|
33
|
+
event_bus,
|
|
34
|
+
config_manager,
|
|
35
|
+
llm_service=None,
|
|
36
|
+
profile_manager=None,
|
|
37
|
+
agent_manager=None,
|
|
38
|
+
) -> None:
|
|
27
39
|
"""Initialize system commands plugin.
|
|
28
40
|
|
|
29
41
|
Args:
|
|
30
42
|
command_registry: Command registry for registration.
|
|
31
43
|
event_bus: Event bus for system events.
|
|
32
44
|
config_manager: Configuration manager for system settings.
|
|
45
|
+
llm_service: LLM service for conversation management.
|
|
46
|
+
profile_manager: LLM profile manager.
|
|
47
|
+
agent_manager: Agent/skill manager.
|
|
33
48
|
"""
|
|
34
49
|
self.name = "system"
|
|
35
50
|
self.command_registry = command_registry
|
|
36
51
|
self.event_bus = event_bus
|
|
37
52
|
self.config_manager = config_manager
|
|
53
|
+
self.llm_service = llm_service
|
|
54
|
+
self.profile_manager = profile_manager
|
|
55
|
+
self.agent_manager = agent_manager
|
|
38
56
|
self.logger = logger
|
|
39
57
|
|
|
40
58
|
def register_commands(self) -> None:
|
|
@@ -106,12 +124,1692 @@ class SystemCommandsPlugin:
|
|
|
106
124
|
)
|
|
107
125
|
self.command_registry.register_command(version_command)
|
|
108
126
|
|
|
127
|
+
# Note: /resume command is handled by resume_conversation_plugin.py
|
|
109
128
|
|
|
129
|
+
# Register /profile command
|
|
130
|
+
profile_command = CommandDefinition(
|
|
131
|
+
name="profile",
|
|
132
|
+
description="Manage LLM API profiles",
|
|
133
|
+
handler=self.handle_profile,
|
|
134
|
+
plugin_name=self.name,
|
|
135
|
+
category=CommandCategory.SYSTEM,
|
|
136
|
+
mode=CommandMode.STATUS_TAKEOVER,
|
|
137
|
+
aliases=["prof", "llm"],
|
|
138
|
+
icon="[PROF]",
|
|
139
|
+
ui_config=UIConfig(
|
|
140
|
+
type="modal",
|
|
141
|
+
navigation=["↑↓", "Enter", "Esc"],
|
|
142
|
+
height=15,
|
|
143
|
+
title="LLM Profiles",
|
|
144
|
+
footer="↑↓ navigate • Enter select • Esc exit"
|
|
145
|
+
),
|
|
146
|
+
subcommands=[
|
|
147
|
+
SubcommandInfo("list", "", "Show profile selection modal"),
|
|
148
|
+
SubcommandInfo("set", "<name>", "Switch to specified profile"),
|
|
149
|
+
SubcommandInfo("create", "", "Open create profile form"),
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
self.command_registry.register_command(profile_command)
|
|
153
|
+
|
|
154
|
+
# Register /agent command
|
|
155
|
+
agent_command = CommandDefinition(
|
|
156
|
+
name="agent",
|
|
157
|
+
description="Manage agents and their configurations",
|
|
158
|
+
handler=self.handle_agent,
|
|
159
|
+
plugin_name=self.name,
|
|
160
|
+
category=CommandCategory.AGENT,
|
|
161
|
+
mode=CommandMode.STATUS_TAKEOVER,
|
|
162
|
+
aliases=["ag"],
|
|
163
|
+
icon="[AGENT]",
|
|
164
|
+
ui_config=UIConfig(
|
|
165
|
+
type="modal",
|
|
166
|
+
navigation=["↑↓", "Enter", "Esc"],
|
|
167
|
+
height=15,
|
|
168
|
+
title="Agents",
|
|
169
|
+
footer="↑↓ navigate • Enter select • Esc exit"
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
self.command_registry.register_command(agent_command)
|
|
173
|
+
|
|
174
|
+
# Register /skill command
|
|
175
|
+
skill_command = CommandDefinition(
|
|
176
|
+
name="skill",
|
|
177
|
+
description="Load or unload agent skills",
|
|
178
|
+
handler=self.handle_skill,
|
|
179
|
+
plugin_name=self.name,
|
|
180
|
+
category=CommandCategory.AGENT,
|
|
181
|
+
mode=CommandMode.STATUS_TAKEOVER,
|
|
182
|
+
aliases=["sk"],
|
|
183
|
+
icon="[SKILL]",
|
|
184
|
+
ui_config=UIConfig(
|
|
185
|
+
type="modal",
|
|
186
|
+
navigation=["↑↓", "Enter", "Esc"],
|
|
187
|
+
height=15,
|
|
188
|
+
title="Agent Skills",
|
|
189
|
+
footer="↑↓ navigate • Enter select • Esc exit"
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
self.command_registry.register_command(skill_command)
|
|
193
|
+
|
|
194
|
+
self.logger.info("System commands registered successfully")
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
self.logger.error(f"Error registering system commands: {e}")
|
|
198
|
+
|
|
199
|
+
async def register_hooks(self) -> None:
|
|
200
|
+
"""Register event hooks for modal command handling."""
|
|
201
|
+
try:
|
|
202
|
+
hook = Hook(
|
|
203
|
+
name="system_modal_command",
|
|
204
|
+
plugin_name="system",
|
|
205
|
+
event_type=EventType.MODAL_COMMAND_SELECTED,
|
|
206
|
+
priority=10,
|
|
207
|
+
callback=self._handle_modal_command
|
|
208
|
+
)
|
|
209
|
+
await self.event_bus.register_hook(hook)
|
|
210
|
+
self.logger.info("System modal command hook registered")
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.logger.error(f"Error registering system hooks: {e}")
|
|
213
|
+
|
|
214
|
+
async def _handle_modal_command(
|
|
215
|
+
self, data: Dict[str, Any], event: Event
|
|
216
|
+
) -> Dict[str, Any]:
|
|
217
|
+
"""Handle modal command selection events for profile/agent/skill.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
data: Event data containing command info.
|
|
221
|
+
event: Event object.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Modified data dict with display_messages key.
|
|
225
|
+
"""
|
|
226
|
+
command = data.get("command", {})
|
|
227
|
+
action = command.get("action")
|
|
228
|
+
|
|
229
|
+
self.logger.info(f"System modal command received: action={action}")
|
|
230
|
+
|
|
231
|
+
# Handle profile selection
|
|
232
|
+
if action == "select_profile":
|
|
233
|
+
profile_name = command.get("profile_name")
|
|
234
|
+
if profile_name and self.profile_manager:
|
|
235
|
+
if self.profile_manager.set_active_profile(profile_name):
|
|
236
|
+
profile = self.profile_manager.get_active_profile()
|
|
237
|
+
# Update the API service with new profile settings
|
|
238
|
+
if self.llm_service and hasattr(self.llm_service, 'api_service'):
|
|
239
|
+
self.llm_service.api_service.update_from_profile(profile)
|
|
240
|
+
# Reload native tools (profile may have different native_tool_calling setting)
|
|
241
|
+
import asyncio
|
|
242
|
+
asyncio.create_task(self.llm_service._load_native_tools())
|
|
243
|
+
tool_mode = "native" if profile.get_native_tool_calling() else "xml"
|
|
244
|
+
data["display_messages"] = [
|
|
245
|
+
("system", f"[ok] Switched to profile: {profile_name}\n Model: {profile.model}\n API: {profile.api_url}\n Tool format: {profile.tool_format}\n Tool calling: {tool_mode}", {}),
|
|
246
|
+
]
|
|
247
|
+
else:
|
|
248
|
+
data["display_messages"] = [
|
|
249
|
+
("error", f"[err] Profile not found: {profile_name}", {}),
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
# Handle agent selection
|
|
253
|
+
elif action == "select_agent":
|
|
254
|
+
agent_name = command.get("agent_name")
|
|
255
|
+
if agent_name and self.agent_manager:
|
|
256
|
+
if self.agent_manager.set_active_agent(agent_name):
|
|
257
|
+
# Rebuild system prompt for the new agent
|
|
258
|
+
if self.llm_service:
|
|
259
|
+
self.llm_service.rebuild_system_prompt()
|
|
260
|
+
agent = self.agent_manager.get_active_agent()
|
|
261
|
+
skills = agent.list_skills() if agent else []
|
|
262
|
+
skill_info = f" ({len(skills)} skills)" if skills else ""
|
|
263
|
+
msg = f"[ok] Switched to agent: {agent_name}{skill_info}"
|
|
264
|
+
if agent and agent.profile:
|
|
265
|
+
msg += f"\n Preferred profile: {agent.profile}"
|
|
266
|
+
data["display_messages"] = [("system", msg, {})]
|
|
267
|
+
else:
|
|
268
|
+
data["display_messages"] = [
|
|
269
|
+
("error", f"[err] Agent not found: {agent_name}", {}),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
# Handle agent clear
|
|
273
|
+
elif action == "clear_agent":
|
|
274
|
+
if self.agent_manager:
|
|
275
|
+
self.agent_manager.clear_active_agent()
|
|
276
|
+
# Rebuild system prompt without agent
|
|
277
|
+
if self.llm_service:
|
|
278
|
+
self.llm_service.rebuild_system_prompt()
|
|
279
|
+
data["display_messages"] = [
|
|
280
|
+
("system", "[ok] Cleared active agent", {}),
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
# Handle skill load
|
|
284
|
+
elif action == "load_skill":
|
|
285
|
+
skill_name = command.get("skill_name")
|
|
286
|
+
if skill_name and self.agent_manager:
|
|
287
|
+
agent = self.agent_manager.get_active_agent()
|
|
288
|
+
skill = agent.get_skill(skill_name) if agent else None
|
|
289
|
+
if skill and self.agent_manager.load_skill(skill_name):
|
|
290
|
+
# Inject skill content as user message instead of system prompt
|
|
291
|
+
if self.llm_service:
|
|
292
|
+
skill_message = f"## Skill: {skill_name}\n\n{skill.content}"
|
|
293
|
+
self.llm_service._add_conversation_message("user", skill_message)
|
|
294
|
+
data["display_messages"] = [
|
|
295
|
+
("system", f"[ok] Loaded skill: {skill_name}", {}),
|
|
296
|
+
]
|
|
297
|
+
# Reopen the skills modal (skip reload since memory is fresh)
|
|
298
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
299
|
+
if modal_def:
|
|
300
|
+
data["show_modal"] = modal_def
|
|
301
|
+
else:
|
|
302
|
+
data["display_messages"] = [
|
|
303
|
+
("error", f"[err] Skill not found: {skill_name}", {}),
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
# Handle skill unload
|
|
307
|
+
elif action == "unload_skill":
|
|
308
|
+
skill_name = command.get("skill_name")
|
|
309
|
+
if skill_name and self.agent_manager:
|
|
310
|
+
if self.agent_manager.unload_skill(skill_name):
|
|
311
|
+
# Add message indicating skill was unloaded
|
|
312
|
+
if self.llm_service:
|
|
313
|
+
self.llm_service._add_conversation_message(
|
|
314
|
+
"user",
|
|
315
|
+
f"[Skill '{skill_name}' has been unloaded - please disregard its instructions]"
|
|
316
|
+
)
|
|
317
|
+
data["display_messages"] = [
|
|
318
|
+
("system", f"[ok] Unloaded skill: {skill_name}", {}),
|
|
319
|
+
]
|
|
320
|
+
# Reopen the skills modal (skip reload since memory is fresh)
|
|
321
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
322
|
+
if modal_def:
|
|
323
|
+
data["show_modal"] = modal_def
|
|
324
|
+
else:
|
|
325
|
+
data["display_messages"] = [
|
|
326
|
+
("error", f"[err] Skill not loaded: {skill_name}", {}),
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
# Handle toggle default skill (project scope)
|
|
330
|
+
elif action == "toggle_default_skill":
|
|
331
|
+
skill_name = command.get("skill_name")
|
|
332
|
+
if skill_name and self.agent_manager:
|
|
333
|
+
success, is_default = self.agent_manager.toggle_default_skill(
|
|
334
|
+
skill_name, scope="project"
|
|
335
|
+
)
|
|
336
|
+
if success:
|
|
337
|
+
status = "added to" if is_default else "removed from"
|
|
338
|
+
data["display_messages"] = [
|
|
339
|
+
("system", f"[ok] Skill '{skill_name}' {status} project defaults", {}),
|
|
340
|
+
]
|
|
341
|
+
# Reopen the skills modal
|
|
342
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
343
|
+
if modal_def:
|
|
344
|
+
data["show_modal"] = modal_def
|
|
345
|
+
else:
|
|
346
|
+
data["display_messages"] = [
|
|
347
|
+
("error", f"[err] Failed to toggle project default for: {skill_name}", {}),
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
# Handle toggle global default skill
|
|
351
|
+
elif action == "toggle_global_default_skill":
|
|
352
|
+
skill_name = command.get("skill_name")
|
|
353
|
+
if skill_name and self.agent_manager:
|
|
354
|
+
success, is_default = self.agent_manager.toggle_default_skill(
|
|
355
|
+
skill_name, scope="global"
|
|
356
|
+
)
|
|
357
|
+
if success:
|
|
358
|
+
status = "added to" if is_default else "removed from"
|
|
359
|
+
data["display_messages"] = [
|
|
360
|
+
("system", f"[ok] Skill '{skill_name}' {status} global defaults", {}),
|
|
361
|
+
]
|
|
362
|
+
# Reopen the skills modal
|
|
363
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
364
|
+
if modal_def:
|
|
365
|
+
data["show_modal"] = modal_def
|
|
366
|
+
else:
|
|
367
|
+
data["display_messages"] = [
|
|
368
|
+
("error", f"[err] Failed to toggle global default for: {skill_name}", {}),
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
# Handle create skill - show form modal
|
|
372
|
+
elif action == "create_skill_prompt":
|
|
373
|
+
if self.agent_manager:
|
|
374
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
375
|
+
if active_agent:
|
|
376
|
+
data["show_modal"] = self._get_create_skill_modal_definition(active_agent.name)
|
|
377
|
+
else:
|
|
378
|
+
data["display_messages"] = [
|
|
379
|
+
("error", "[err] No active agent", {}),
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
# Handle create skill form submission
|
|
383
|
+
elif action == "create_skill_submit":
|
|
384
|
+
form_data = command.get("form_data", {})
|
|
385
|
+
name = form_data.get("name", "").strip()
|
|
386
|
+
description = form_data.get("description", "").strip()
|
|
387
|
+
|
|
388
|
+
if not name:
|
|
389
|
+
data["display_messages"] = [
|
|
390
|
+
("error", "[err] Skill name is required", {}),
|
|
391
|
+
]
|
|
392
|
+
elif not description:
|
|
393
|
+
data["display_messages"] = [
|
|
394
|
+
("error", "[err] Description is required for AI generation", {}),
|
|
395
|
+
]
|
|
396
|
+
elif self.agent_manager:
|
|
397
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
398
|
+
if active_agent:
|
|
399
|
+
if self.llm_service:
|
|
400
|
+
# Build the generation prompt and send to LLM
|
|
401
|
+
generation_prompt = self._build_skill_generation_prompt(
|
|
402
|
+
agent_name=active_agent.name,
|
|
403
|
+
skill_name=name,
|
|
404
|
+
description=description,
|
|
405
|
+
)
|
|
406
|
+
# Send to LLM - it will use @@@FILE blocks to generate the file
|
|
407
|
+
await self.llm_service.process_user_input(generation_prompt)
|
|
408
|
+
# Close modal - LLM handles the rest with existing tool infrastructure
|
|
409
|
+
data["close_modal"] = True
|
|
410
|
+
else:
|
|
411
|
+
data["display_messages"] = [
|
|
412
|
+
("error", "[err] LLM service not available", {}),
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
# Handle edit skill - show form modal
|
|
416
|
+
elif action == "edit_skill_prompt":
|
|
417
|
+
skill_name = command.get("skill_name")
|
|
418
|
+
if skill_name and self.agent_manager:
|
|
419
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
420
|
+
if active_agent:
|
|
421
|
+
modal_def = self._get_edit_skill_modal_definition(active_agent.name, skill_name)
|
|
422
|
+
if modal_def:
|
|
423
|
+
data["show_modal"] = modal_def
|
|
424
|
+
else:
|
|
425
|
+
data["display_messages"] = [
|
|
426
|
+
("error", f"[err] Skill not found: {skill_name}", {}),
|
|
427
|
+
]
|
|
428
|
+
else:
|
|
429
|
+
data["display_messages"] = [
|
|
430
|
+
("error", "[err] Select a skill to edit", {}),
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
# Handle edit skill form submission (rename only)
|
|
434
|
+
elif action == "edit_skill_submit":
|
|
435
|
+
form_data = command.get("form_data", {})
|
|
436
|
+
original_name = command.get("edit_skill_name", "")
|
|
437
|
+
new_name = form_data.get("name", "").strip()
|
|
438
|
+
|
|
439
|
+
if not new_name:
|
|
440
|
+
data["display_messages"] = [
|
|
441
|
+
("error", "[err] Skill name is required", {}),
|
|
442
|
+
]
|
|
443
|
+
elif self.agent_manager:
|
|
444
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
445
|
+
if active_agent:
|
|
446
|
+
success = self._rename_skill_file(active_agent, original_name, new_name)
|
|
447
|
+
if success:
|
|
448
|
+
self.agent_manager.refresh()
|
|
449
|
+
msg = f"[ok] Updated skill: {new_name}"
|
|
450
|
+
if new_name != original_name:
|
|
451
|
+
msg += f"\n Renamed from: {original_name}"
|
|
452
|
+
data["display_messages"] = [("system", msg, {})]
|
|
453
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
454
|
+
if modal_def:
|
|
455
|
+
data["show_modal"] = modal_def
|
|
456
|
+
else:
|
|
457
|
+
data["display_messages"] = [
|
|
458
|
+
("error", f"[err] Failed to rename skill", {}),
|
|
459
|
+
]
|
|
460
|
+
|
|
461
|
+
# Handle delete skill - show confirmation modal
|
|
462
|
+
elif action == "delete_skill_prompt":
|
|
463
|
+
skill_name = command.get("skill_name")
|
|
464
|
+
if skill_name and self.agent_manager:
|
|
465
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
466
|
+
if active_agent:
|
|
467
|
+
modal_def = self._get_delete_skill_confirm_modal(active_agent.name, skill_name)
|
|
468
|
+
if modal_def:
|
|
469
|
+
data["show_modal"] = modal_def
|
|
470
|
+
else:
|
|
471
|
+
data["display_messages"] = [
|
|
472
|
+
("error", f"[err] Cannot delete skill: {skill_name}", {}),
|
|
473
|
+
]
|
|
474
|
+
else:
|
|
475
|
+
data["display_messages"] = [
|
|
476
|
+
("error", "[err] Select a skill to delete", {}),
|
|
477
|
+
]
|
|
478
|
+
|
|
479
|
+
# Handle delete skill confirmation
|
|
480
|
+
elif action == "delete_skill_confirm":
|
|
481
|
+
skill_name = command.get("skill_name")
|
|
482
|
+
if skill_name and self.agent_manager:
|
|
483
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
484
|
+
if active_agent:
|
|
485
|
+
success = self._delete_skill_file(active_agent, skill_name)
|
|
486
|
+
if success:
|
|
487
|
+
self.agent_manager.refresh()
|
|
488
|
+
data["display_messages"] = [
|
|
489
|
+
("system", f"[ok] Deleted skill: {skill_name}", {}),
|
|
490
|
+
]
|
|
491
|
+
modal_def = self._get_skills_modal_definition(skip_reload=True)
|
|
492
|
+
if modal_def:
|
|
493
|
+
data["show_modal"] = modal_def
|
|
494
|
+
else:
|
|
495
|
+
data["display_messages"] = [
|
|
496
|
+
("error", f"[err] Failed to delete skill: {skill_name}", {}),
|
|
497
|
+
]
|
|
498
|
+
|
|
499
|
+
# Handle save profile to config (profiles are global-only)
|
|
500
|
+
elif action == "save_profile_to_config":
|
|
501
|
+
if self.profile_manager:
|
|
502
|
+
profile = self.profile_manager.get_active_profile()
|
|
503
|
+
if profile:
|
|
504
|
+
result = self.profile_manager.save_profile_values_to_config(profile)
|
|
505
|
+
|
|
506
|
+
if result.get("global"):
|
|
507
|
+
# Reload profiles from config to pick up saved values
|
|
508
|
+
self.profile_manager.reload()
|
|
509
|
+
data["display_messages"] = [
|
|
510
|
+
("system", f"[ok] Saved '{profile.name}' profile to global config (~/.kollabor-cli/)", {}),
|
|
511
|
+
]
|
|
512
|
+
else:
|
|
513
|
+
data["display_messages"] = [
|
|
514
|
+
("error", f"[err] Failed to save profile '{profile.name}'.", {}),
|
|
515
|
+
]
|
|
516
|
+
# Reopen the profile modal (skip_reload since we just reloaded above)
|
|
517
|
+
data["show_modal"] = self._get_profiles_modal_definition(skip_reload=True)
|
|
518
|
+
else:
|
|
519
|
+
data["display_messages"] = [
|
|
520
|
+
("error", "[err] No active profile to save.", {}),
|
|
521
|
+
]
|
|
522
|
+
|
|
523
|
+
# Handle create profile - show form modal
|
|
524
|
+
elif action == "create_profile_prompt":
|
|
525
|
+
data["show_modal"] = self._get_create_profile_modal_definition()
|
|
526
|
+
|
|
527
|
+
# Handle create profile form submission
|
|
528
|
+
elif action == "create_profile_submit":
|
|
529
|
+
form_data = command.get("form_data", {})
|
|
530
|
+
name = form_data.get("name", "").strip()
|
|
531
|
+
api_url = form_data.get("api_url", "").strip()
|
|
532
|
+
model = form_data.get("model", "").strip()
|
|
533
|
+
api_token = form_data.get("api_token", "").strip() or None
|
|
534
|
+
temperature = float(form_data.get("temperature", 0.7))
|
|
535
|
+
tool_format = form_data.get("tool_format", "openai")
|
|
536
|
+
# Convert dropdown value to bool (native=True, xml=False)
|
|
537
|
+
native_tool_calling = form_data.get("native_tool_calling", "native") == "native"
|
|
538
|
+
description = form_data.get("description", "").strip()
|
|
539
|
+
|
|
540
|
+
if not name or not api_url or not model:
|
|
541
|
+
data["display_messages"] = [
|
|
542
|
+
("error", "[err] Name, API URL, and Model are required", {}),
|
|
543
|
+
]
|
|
544
|
+
elif self.profile_manager:
|
|
545
|
+
profile = self.profile_manager.create_profile(
|
|
546
|
+
name=name,
|
|
547
|
+
api_url=api_url,
|
|
548
|
+
model=model,
|
|
549
|
+
api_token=api_token,
|
|
550
|
+
temperature=temperature,
|
|
551
|
+
tool_format=tool_format,
|
|
552
|
+
native_tool_calling=native_tool_calling,
|
|
553
|
+
description=description or f"Created via /profile",
|
|
554
|
+
save_to_config=True
|
|
555
|
+
)
|
|
556
|
+
if profile:
|
|
557
|
+
data["display_messages"] = [
|
|
558
|
+
("system", f"[ok] Created profile: {name}\n API: {api_url}\n Model: {model}\n Saved to config.json", {}),
|
|
559
|
+
]
|
|
560
|
+
# Reopen the profile modal so user can see the new profile
|
|
561
|
+
data["show_modal"] = self._get_profiles_modal_definition(skip_reload=True)
|
|
562
|
+
else:
|
|
563
|
+
data["display_messages"] = [
|
|
564
|
+
("error", f"[err] Failed. Profile '{name}' may already exist.", {}),
|
|
565
|
+
]
|
|
566
|
+
|
|
567
|
+
# Handle create agent - show form modal
|
|
568
|
+
elif action == "create_agent_prompt":
|
|
569
|
+
data["show_modal"] = self._get_create_agent_modal_definition()
|
|
570
|
+
|
|
571
|
+
# Handle create agent form submission - AI generation
|
|
572
|
+
elif action == "create_agent_submit":
|
|
573
|
+
form_data = command.get("form_data", {})
|
|
574
|
+
name = form_data.get("name", "").strip()
|
|
575
|
+
description = form_data.get("description", "").strip()
|
|
576
|
+
profile = form_data.get("profile", "").strip()
|
|
577
|
+
source = form_data.get("source", "global").strip()
|
|
578
|
+
|
|
579
|
+
if not name:
|
|
580
|
+
data["display_messages"] = [
|
|
581
|
+
("error", "[err] Agent name is required", {}),
|
|
582
|
+
]
|
|
583
|
+
elif not description:
|
|
584
|
+
data["display_messages"] = [
|
|
585
|
+
("error", "[err] Description is required for AI generation", {}),
|
|
586
|
+
]
|
|
587
|
+
elif self.llm_service:
|
|
588
|
+
# Build the generation prompt and send to LLM
|
|
589
|
+
generation_prompt = self._build_agent_generation_prompt(
|
|
590
|
+
name=name,
|
|
591
|
+
description=description,
|
|
592
|
+
profile=profile if profile and profile != "(none)" else None,
|
|
593
|
+
source=source,
|
|
594
|
+
)
|
|
595
|
+
# Send to LLM - it will use @@@FILE blocks to generate files
|
|
596
|
+
await self.llm_service.process_user_input(generation_prompt)
|
|
597
|
+
# Close modal - LLM handles the rest with existing tool infrastructure
|
|
598
|
+
data["close_modal"] = True
|
|
599
|
+
else:
|
|
600
|
+
data["display_messages"] = [
|
|
601
|
+
("error", "[err] LLM service not available", {}),
|
|
602
|
+
]
|
|
603
|
+
|
|
604
|
+
# Handle edit profile - show form modal with profile data
|
|
605
|
+
elif action == "edit_profile_prompt":
|
|
606
|
+
profile_name = command.get("profile_name")
|
|
607
|
+
if profile_name and self.profile_manager:
|
|
608
|
+
modal_def = self._get_edit_profile_modal_definition(profile_name)
|
|
609
|
+
if modal_def:
|
|
610
|
+
data["show_modal"] = modal_def
|
|
611
|
+
else:
|
|
612
|
+
data["display_messages"] = [
|
|
613
|
+
("error", f"[err] Profile not found: {profile_name}", {}),
|
|
614
|
+
]
|
|
615
|
+
else:
|
|
616
|
+
data["display_messages"] = [
|
|
617
|
+
("error", "[err] Select a profile to edit", {}),
|
|
618
|
+
]
|
|
619
|
+
|
|
620
|
+
# Handle edit profile form submission
|
|
621
|
+
elif action == "edit_profile_submit":
|
|
622
|
+
form_data = command.get("form_data", {})
|
|
623
|
+
original_name = command.get("edit_profile_name", "")
|
|
624
|
+
new_name = form_data.get("name", "").strip()
|
|
625
|
+
api_url = form_data.get("api_url", "").strip()
|
|
626
|
+
model = form_data.get("model", "").strip()
|
|
627
|
+
api_token = form_data.get("api_token", "").strip() or None
|
|
628
|
+
temperature = float(form_data.get("temperature", 0.7))
|
|
629
|
+
tool_format = form_data.get("tool_format", "openai")
|
|
630
|
+
# Convert dropdown value to bool (native=True, xml=False)
|
|
631
|
+
native_tool_calling = form_data.get("native_tool_calling", "native") == "native"
|
|
632
|
+
description = form_data.get("description", "").strip()
|
|
633
|
+
|
|
634
|
+
if not new_name or not api_url or not model:
|
|
635
|
+
data["display_messages"] = [
|
|
636
|
+
("error", "[err] Name, API URL, and Model are required", {}),
|
|
637
|
+
]
|
|
638
|
+
elif self.profile_manager:
|
|
639
|
+
success = self.profile_manager.update_profile(
|
|
640
|
+
original_name=original_name,
|
|
641
|
+
new_name=new_name,
|
|
642
|
+
api_url=api_url,
|
|
643
|
+
model=model,
|
|
644
|
+
api_token=api_token,
|
|
645
|
+
temperature=temperature,
|
|
646
|
+
tool_format=tool_format,
|
|
647
|
+
native_tool_calling=native_tool_calling,
|
|
648
|
+
description=description,
|
|
649
|
+
save_to_config=True
|
|
650
|
+
)
|
|
651
|
+
if success:
|
|
652
|
+
# If this profile is active (check both original and new name), update the API service
|
|
653
|
+
is_active = (self.profile_manager.is_active(new_name) or
|
|
654
|
+
self.profile_manager.is_active(original_name))
|
|
655
|
+
if is_active and self.llm_service and hasattr(self.llm_service, 'api_service'):
|
|
656
|
+
profile = self.profile_manager.get_profile(new_name) or self.profile_manager.get_profile(original_name)
|
|
657
|
+
if profile:
|
|
658
|
+
self.llm_service.api_service.update_from_profile(profile)
|
|
659
|
+
# Reload native tools (tool calling mode may have changed)
|
|
660
|
+
import asyncio
|
|
661
|
+
asyncio.create_task(self.llm_service._load_native_tools())
|
|
662
|
+
tool_mode = "native" if native_tool_calling else "xml"
|
|
663
|
+
msg = f"[ok] Updated profile: {new_name}\n API: {api_url}\n Model: {model}\n Tool format: {tool_format}\n Tool calling: {tool_mode}"
|
|
664
|
+
if is_active:
|
|
665
|
+
msg += "\n [reloaded - changes applied]"
|
|
666
|
+
data["display_messages"] = [("system", msg, {})]
|
|
667
|
+
# Reopen the profile modal
|
|
668
|
+
data["show_modal"] = self._get_profiles_modal_definition(skip_reload=True)
|
|
669
|
+
else:
|
|
670
|
+
data["display_messages"] = [
|
|
671
|
+
("error", "[err] Failed to update profile", {}),
|
|
672
|
+
]
|
|
673
|
+
|
|
674
|
+
# Handle delete profile prompt - show confirmation modal
|
|
675
|
+
elif action == "delete_profile_prompt":
|
|
676
|
+
profile_name = command.get("profile_name")
|
|
677
|
+
if profile_name and self.profile_manager:
|
|
678
|
+
modal_def = self._get_delete_profile_confirm_modal(profile_name)
|
|
679
|
+
if modal_def:
|
|
680
|
+
data["show_modal"] = modal_def
|
|
681
|
+
else:
|
|
682
|
+
data["display_messages"] = [
|
|
683
|
+
("error", f"[err] Cannot delete profile: {profile_name}", {}),
|
|
684
|
+
]
|
|
685
|
+
else:
|
|
686
|
+
data["display_messages"] = [
|
|
687
|
+
("error", "[err] Select a profile to delete", {}),
|
|
688
|
+
]
|
|
689
|
+
|
|
690
|
+
# Handle delete profile confirmation
|
|
691
|
+
elif action == "delete_profile_confirm":
|
|
692
|
+
profile_name = command.get("profile_name")
|
|
693
|
+
if profile_name and self.profile_manager:
|
|
694
|
+
success = self.profile_manager.delete_profile(profile_name)
|
|
695
|
+
if success:
|
|
696
|
+
data["display_messages"] = [
|
|
697
|
+
("system", f"[ok] Deleted profile: {profile_name}", {}),
|
|
698
|
+
]
|
|
699
|
+
# Reopen the profile modal so user can continue managing
|
|
700
|
+
# Skip reload since memory state is already updated
|
|
701
|
+
data["show_modal"] = self._get_profiles_modal_definition(skip_reload=True)
|
|
702
|
+
else:
|
|
703
|
+
data["display_messages"] = [
|
|
704
|
+
("error", f"[err] Failed to delete profile: {profile_name}", {}),
|
|
705
|
+
]
|
|
706
|
+
|
|
707
|
+
# Handle delete agent prompt - show confirmation modal
|
|
708
|
+
elif action == "delete_agent_prompt":
|
|
709
|
+
agent_name = command.get("agent_name")
|
|
710
|
+
if agent_name and self.agent_manager:
|
|
711
|
+
modal_def = self._get_delete_agent_confirm_modal(agent_name)
|
|
712
|
+
if modal_def:
|
|
713
|
+
data["show_modal"] = modal_def
|
|
714
|
+
else:
|
|
715
|
+
data["display_messages"] = [
|
|
716
|
+
("error", f"[err] Cannot delete agent: {agent_name}", {}),
|
|
717
|
+
]
|
|
718
|
+
else:
|
|
719
|
+
data["display_messages"] = [
|
|
720
|
+
("error", "[err] Select an agent to delete", {}),
|
|
721
|
+
]
|
|
722
|
+
|
|
723
|
+
# Handle delete agent confirmation
|
|
724
|
+
elif action == "delete_agent_confirm":
|
|
725
|
+
agent_name = command.get("agent_name")
|
|
726
|
+
if agent_name and self.agent_manager:
|
|
727
|
+
success = self.agent_manager.delete_agent(agent_name)
|
|
728
|
+
if success:
|
|
729
|
+
data["display_messages"] = [
|
|
730
|
+
("system", f"[ok] Deleted agent: {agent_name}", {}),
|
|
731
|
+
]
|
|
732
|
+
# Reopen the agents modal so user can continue managing
|
|
733
|
+
# Skip reload since memory state is already updated
|
|
734
|
+
data["show_modal"] = self._get_agents_modal_definition(skip_reload=True)
|
|
735
|
+
else:
|
|
736
|
+
data["display_messages"] = [
|
|
737
|
+
("error", f"[err] Failed to delete agent: {agent_name}", {}),
|
|
738
|
+
]
|
|
739
|
+
|
|
740
|
+
# Handle edit agent - show form modal with agent data
|
|
741
|
+
elif action == "edit_agent_prompt":
|
|
742
|
+
agent_name = command.get("agent_name")
|
|
743
|
+
if agent_name and self.agent_manager:
|
|
744
|
+
modal_def = self._get_edit_agent_modal_definition(agent_name)
|
|
745
|
+
if modal_def:
|
|
746
|
+
data["show_modal"] = modal_def
|
|
747
|
+
else:
|
|
748
|
+
data["display_messages"] = [
|
|
749
|
+
("error", f"[err] Agent not found: {agent_name}", {}),
|
|
750
|
+
]
|
|
751
|
+
else:
|
|
752
|
+
data["display_messages"] = [
|
|
753
|
+
("error", "[err] Select an agent to edit", {}),
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
# Handle toggle project default
|
|
757
|
+
elif action == "toggle_project_default":
|
|
758
|
+
agent_name = command.get("agent_name")
|
|
759
|
+
if agent_name and self.agent_manager:
|
|
760
|
+
from ..utils.config_utils import get_all_default_agents, set_default_agent, clear_default_agent
|
|
761
|
+
|
|
762
|
+
# Check if this agent is already project default
|
|
763
|
+
defaults = get_all_default_agents()
|
|
764
|
+
current_project_default = defaults.get("project")
|
|
765
|
+
|
|
766
|
+
if current_project_default == agent_name:
|
|
767
|
+
# Clear it
|
|
768
|
+
if clear_default_agent("project"):
|
|
769
|
+
data["display_messages"] = [
|
|
770
|
+
("system", f"[ok] Cleared project default agent", {}),
|
|
771
|
+
]
|
|
772
|
+
else:
|
|
773
|
+
# Set it
|
|
774
|
+
if set_default_agent(agent_name, "project"):
|
|
775
|
+
data["display_messages"] = [
|
|
776
|
+
("system", f"[ok] Set '{agent_name}' as project default agent", {}),
|
|
777
|
+
]
|
|
778
|
+
else:
|
|
779
|
+
data["display_messages"] = [
|
|
780
|
+
("error", f"[err] Failed to set project default", {}),
|
|
781
|
+
]
|
|
782
|
+
|
|
783
|
+
# Reopen modal to show updated indicators
|
|
784
|
+
modal_def = self._get_agents_modal_definition(skip_reload=True)
|
|
785
|
+
if modal_def:
|
|
786
|
+
data["show_modal"] = modal_def
|
|
787
|
+
|
|
788
|
+
# Handle toggle global default
|
|
789
|
+
elif action == "toggle_global_default":
|
|
790
|
+
agent_name = command.get("agent_name")
|
|
791
|
+
if agent_name and self.agent_manager:
|
|
792
|
+
from ..utils.config_utils import get_all_default_agents, set_default_agent, clear_default_agent
|
|
793
|
+
|
|
794
|
+
# Check if this agent is already global default
|
|
795
|
+
defaults = get_all_default_agents()
|
|
796
|
+
current_global_default = defaults.get("global")
|
|
797
|
+
|
|
798
|
+
if current_global_default == agent_name:
|
|
799
|
+
# Clear it
|
|
800
|
+
if clear_default_agent("global"):
|
|
801
|
+
data["display_messages"] = [
|
|
802
|
+
("system", f"[ok] Cleared global default agent", {}),
|
|
803
|
+
]
|
|
804
|
+
else:
|
|
805
|
+
# Set it
|
|
806
|
+
if set_default_agent(agent_name, "global"):
|
|
807
|
+
data["display_messages"] = [
|
|
808
|
+
("system", f"[ok] Set '{agent_name}' as global default agent", {}),
|
|
809
|
+
]
|
|
810
|
+
else:
|
|
811
|
+
data["display_messages"] = [
|
|
812
|
+
("error", f"[err] Failed to set global default", {}),
|
|
813
|
+
]
|
|
814
|
+
|
|
815
|
+
# Reopen modal to show updated indicators
|
|
816
|
+
modal_def = self._get_agents_modal_definition(skip_reload=True)
|
|
817
|
+
if modal_def:
|
|
818
|
+
data["show_modal"] = modal_def
|
|
819
|
+
|
|
820
|
+
# Handle edit agent form submission
|
|
821
|
+
elif action == "edit_agent_submit":
|
|
822
|
+
form_data = command.get("form_data", {})
|
|
823
|
+
original_name = command.get("edit_agent_name", "")
|
|
824
|
+
new_name = form_data.get("name", "").strip()
|
|
825
|
+
description = form_data.get("description", "").strip()
|
|
826
|
+
profile = form_data.get("profile", "").strip()
|
|
827
|
+
|
|
828
|
+
if not new_name:
|
|
829
|
+
data["display_messages"] = [
|
|
830
|
+
("error", "[err] Agent name is required", {}),
|
|
831
|
+
]
|
|
832
|
+
elif self.agent_manager:
|
|
833
|
+
success = self.agent_manager.update_agent(
|
|
834
|
+
original_name=original_name,
|
|
835
|
+
new_name=new_name,
|
|
836
|
+
description=description,
|
|
837
|
+
profile=profile if profile and profile != "(none)" else None,
|
|
838
|
+
system_prompt=None, # Don't update system_prompt via modal
|
|
839
|
+
)
|
|
840
|
+
if success:
|
|
841
|
+
msg = f"[ok] Updated agent: {new_name}"
|
|
842
|
+
if new_name != original_name:
|
|
843
|
+
msg += f"\n Renamed from: {original_name}"
|
|
844
|
+
if description:
|
|
845
|
+
msg += f"\n Description: {description[:50]}..."
|
|
846
|
+
data["display_messages"] = [("system", msg, {})]
|
|
847
|
+
# Reopen the agents modal
|
|
848
|
+
data["show_modal"] = self._get_agents_modal_definition(skip_reload=True)
|
|
849
|
+
else:
|
|
850
|
+
data["display_messages"] = [
|
|
851
|
+
("error", f"[err] Failed to update agent", {}),
|
|
852
|
+
]
|
|
853
|
+
|
|
854
|
+
return data
|
|
855
|
+
|
|
856
|
+
def _get_create_profile_modal_definition(self) -> Dict[str, Any]:
|
|
857
|
+
"""Get modal definition for creating a new profile."""
|
|
858
|
+
return {
|
|
859
|
+
"title": "Create New Profile",
|
|
860
|
+
"footer": "Tab: next • Ctrl+S: create • Esc: cancel",
|
|
861
|
+
"width": 82,
|
|
862
|
+
"height": 26,
|
|
863
|
+
"form_action": "create_profile_submit",
|
|
864
|
+
"sections": [
|
|
865
|
+
{
|
|
866
|
+
"title": "Profile Name (required)",
|
|
867
|
+
"widgets": [
|
|
868
|
+
{
|
|
869
|
+
"type": "text_input",
|
|
870
|
+
"label": "Name *",
|
|
871
|
+
"field": "name",
|
|
872
|
+
"placeholder": "my-llm, claude-prod, openai-dev, etc.",
|
|
873
|
+
"help": "Used for env vars: KOLLABOR_{NAME}_TOKEN"
|
|
874
|
+
},
|
|
875
|
+
]
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"title": "Connection (required)",
|
|
879
|
+
"widgets": [
|
|
880
|
+
{
|
|
881
|
+
"type": "text_input",
|
|
882
|
+
"label": "Endpoint *",
|
|
883
|
+
"field": "api_url",
|
|
884
|
+
"placeholder": "https://api.openai.com/v1/chat/completions",
|
|
885
|
+
"help": "API endpoint URL"
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"type": "dropdown",
|
|
889
|
+
"label": "Provider",
|
|
890
|
+
"field": "tool_format",
|
|
891
|
+
"options": ["openai", "anthropic"],
|
|
892
|
+
"current_value": "openai",
|
|
893
|
+
"help": "API format (most use openai)"
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"type": "dropdown",
|
|
897
|
+
"label": "Tool Calling",
|
|
898
|
+
"field": "native_tool_calling",
|
|
899
|
+
"options": ["native", "xml"],
|
|
900
|
+
"current_value": "native",
|
|
901
|
+
"help": "native=API tools, xml=XML tags only"
|
|
902
|
+
},
|
|
903
|
+
]
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"title": "Authentication (required)",
|
|
907
|
+
"widgets": [
|
|
908
|
+
{
|
|
909
|
+
"type": "text_input",
|
|
910
|
+
"label": "Token *",
|
|
911
|
+
"field": "api_token",
|
|
912
|
+
"placeholder": "sk-... or set env var KOLLABOR_{NAME}_TOKEN",
|
|
913
|
+
"password": True,
|
|
914
|
+
"help": "API key (or leave empty and set env var)"
|
|
915
|
+
},
|
|
916
|
+
]
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
"title": "Model (required)",
|
|
920
|
+
"widgets": [
|
|
921
|
+
{
|
|
922
|
+
"type": "text_input",
|
|
923
|
+
"label": "Model *",
|
|
924
|
+
"field": "model",
|
|
925
|
+
"placeholder": "gpt-4-turbo, claude-sonnet-4-20250514, qwen/qwen3-4b",
|
|
926
|
+
"help": "Model identifier"
|
|
927
|
+
},
|
|
928
|
+
]
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
"title": "Advanced (optional)",
|
|
932
|
+
"widgets": [
|
|
933
|
+
{
|
|
934
|
+
"type": "slider",
|
|
935
|
+
"label": "Temperature",
|
|
936
|
+
"field": "temperature",
|
|
937
|
+
"min_value": 0.0,
|
|
938
|
+
"max_value": 2.0,
|
|
939
|
+
"step": 0.1,
|
|
940
|
+
"current_value": 0.7,
|
|
941
|
+
"help": "0.0 = precise, 2.0 = creative"
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"type": "text_input",
|
|
945
|
+
"label": "Description",
|
|
946
|
+
"field": "description",
|
|
947
|
+
"placeholder": "Optional description"
|
|
948
|
+
},
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
],
|
|
952
|
+
"actions": [
|
|
953
|
+
{"key": "Ctrl+S", "label": "[ Create ]", "action": "submit", "style": "primary"},
|
|
954
|
+
{"key": "Escape", "label": "[ Cancel ]", "action": "cancel", "style": "secondary"}
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
def _get_edit_profile_modal_definition(self, profile_name: str) -> Dict[str, Any]:
|
|
959
|
+
"""Get modal definition for editing an existing profile.
|
|
960
|
+
|
|
961
|
+
Args:
|
|
962
|
+
profile_name: Name of the profile to edit.
|
|
963
|
+
|
|
964
|
+
Returns:
|
|
965
|
+
Modal definition dict with pre-populated values.
|
|
966
|
+
"""
|
|
967
|
+
if not self.profile_manager:
|
|
968
|
+
return {}
|
|
969
|
+
|
|
970
|
+
profile = self.profile_manager.get_profile(profile_name)
|
|
971
|
+
if not profile:
|
|
972
|
+
return {}
|
|
973
|
+
|
|
974
|
+
# Get env var hints for this profile
|
|
975
|
+
env_hints = profile.get_env_var_hints()
|
|
976
|
+
|
|
977
|
+
# Determine token status
|
|
978
|
+
token_from_env = env_hints['token'].is_set
|
|
979
|
+
token_in_config = bool(profile.api_token)
|
|
980
|
+
if token_from_env:
|
|
981
|
+
token_status = f"(using env: {env_hints['token'].name})"
|
|
982
|
+
token_placeholder = "Leave empty to use env var"
|
|
983
|
+
elif token_in_config:
|
|
984
|
+
token_status = "(set in config)"
|
|
985
|
+
token_placeholder = ""
|
|
986
|
+
else:
|
|
987
|
+
token_status = "[REQUIRED - not set]"
|
|
988
|
+
token_placeholder = "Enter API key or set env var"
|
|
989
|
+
|
|
990
|
+
# Determine overall status
|
|
991
|
+
issues = []
|
|
992
|
+
if not profile.api_url:
|
|
993
|
+
issues.append("endpoint missing")
|
|
994
|
+
if not profile.model:
|
|
995
|
+
issues.append("model missing")
|
|
996
|
+
if not token_from_env and not token_in_config:
|
|
997
|
+
issues.append("token missing")
|
|
998
|
+
|
|
999
|
+
if issues:
|
|
1000
|
+
status_line = f"[!] Fix {len(issues)} issue(s): {', '.join(issues)}"
|
|
1001
|
+
else:
|
|
1002
|
+
status_line = "[ok] Ready to use"
|
|
1003
|
+
|
|
1004
|
+
return {
|
|
1005
|
+
"title": f"Edit Profile: {profile_name}",
|
|
1006
|
+
"footer": "Tab: next • Ctrl+S: save • Ctrl+T: test • Esc: cancel",
|
|
1007
|
+
"width": 82,
|
|
1008
|
+
"height": 26,
|
|
1009
|
+
"form_action": "edit_profile_submit",
|
|
1010
|
+
"edit_profile_name": profile_name,
|
|
1011
|
+
"sections": [
|
|
1012
|
+
{
|
|
1013
|
+
"title": "Connection (required)",
|
|
1014
|
+
"widgets": [
|
|
1015
|
+
{
|
|
1016
|
+
"type": "text_input",
|
|
1017
|
+
"label": "Endpoint *",
|
|
1018
|
+
"field": "api_url",
|
|
1019
|
+
"value": profile.api_url,
|
|
1020
|
+
"placeholder": "https://api.openai.com/v1/chat/completions",
|
|
1021
|
+
"help": "API endpoint URL"
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
"type": "dropdown",
|
|
1025
|
+
"label": "Provider",
|
|
1026
|
+
"field": "tool_format",
|
|
1027
|
+
"options": ["openai", "anthropic"],
|
|
1028
|
+
"current_value": profile.tool_format,
|
|
1029
|
+
"help": "API format (most use openai)"
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
"type": "dropdown",
|
|
1033
|
+
"label": "Tool Calling",
|
|
1034
|
+
"field": "native_tool_calling",
|
|
1035
|
+
"options": ["native", "xml"],
|
|
1036
|
+
"current_value": "native" if profile.native_tool_calling else "xml",
|
|
1037
|
+
"help": "native=API tools, xml=XML tags only"
|
|
1038
|
+
},
|
|
1039
|
+
]
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"title": "Authentication (required)",
|
|
1043
|
+
"widgets": [
|
|
1044
|
+
{
|
|
1045
|
+
"type": "text_input",
|
|
1046
|
+
"label": f"Token * {token_status}",
|
|
1047
|
+
"field": "api_token",
|
|
1048
|
+
"value": profile.api_token or "",
|
|
1049
|
+
"placeholder": token_placeholder,
|
|
1050
|
+
"password": True
|
|
1051
|
+
},
|
|
1052
|
+
]
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
"title": "Model (required)",
|
|
1056
|
+
"widgets": [
|
|
1057
|
+
{
|
|
1058
|
+
"type": "text_input",
|
|
1059
|
+
"label": "Model *",
|
|
1060
|
+
"field": "model",
|
|
1061
|
+
"value": profile.model,
|
|
1062
|
+
"placeholder": "gpt-4-turbo, claude-sonnet-4-20250514, etc.",
|
|
1063
|
+
"help": "Model identifier"
|
|
1064
|
+
},
|
|
1065
|
+
]
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
"title": "Advanced (optional)",
|
|
1069
|
+
"widgets": [
|
|
1070
|
+
{
|
|
1071
|
+
"type": "text_input",
|
|
1072
|
+
"label": "Profile Name",
|
|
1073
|
+
"field": "name",
|
|
1074
|
+
"value": profile.name,
|
|
1075
|
+
"placeholder": "my-profile",
|
|
1076
|
+
"help": "Determines env var prefix: KOLLABOR_{NAME}_*"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
"type": "slider",
|
|
1080
|
+
"label": "Temperature",
|
|
1081
|
+
"field": "temperature",
|
|
1082
|
+
"min_value": 0.0,
|
|
1083
|
+
"max_value": 2.0,
|
|
1084
|
+
"step": 0.1,
|
|
1085
|
+
"current_value": profile.temperature,
|
|
1086
|
+
"help": "0.0 = precise, 2.0 = creative"
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
"type": "text_input",
|
|
1090
|
+
"label": "Description",
|
|
1091
|
+
"field": "description",
|
|
1092
|
+
"value": profile.description or "",
|
|
1093
|
+
"placeholder": "Optional description"
|
|
1094
|
+
},
|
|
1095
|
+
]
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
"title": f"Status: {status_line}",
|
|
1099
|
+
"widgets": [
|
|
1100
|
+
{
|
|
1101
|
+
"type": "label",
|
|
1102
|
+
"label": "Env vars",
|
|
1103
|
+
"value": f"{env_hints['token'].name}={'[set]' if token_from_env else '[not set]'}"
|
|
1104
|
+
},
|
|
1105
|
+
]
|
|
1106
|
+
}
|
|
1107
|
+
],
|
|
1108
|
+
"actions": [
|
|
1109
|
+
{"key": "Ctrl+S", "label": "[ Save ]", "action": "submit", "style": "primary"},
|
|
1110
|
+
{"key": "Ctrl+T", "label": "[ Test ]", "action": "test_connection", "style": "secondary"},
|
|
1111
|
+
{"key": "Escape", "label": "[ Cancel ]", "action": "cancel", "style": "secondary"}
|
|
1112
|
+
]
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
def _get_delete_profile_confirm_modal(self, profile_name: str) -> Dict[str, Any]:
|
|
1116
|
+
"""Get modal definition for delete profile confirmation.
|
|
1117
|
+
|
|
1118
|
+
Args:
|
|
1119
|
+
profile_name: Name of the profile to delete.
|
|
1120
|
+
|
|
1121
|
+
Returns:
|
|
1122
|
+
Modal definition dict for confirmation, or empty dict if cannot delete.
|
|
1123
|
+
"""
|
|
1124
|
+
if not self.profile_manager:
|
|
1125
|
+
return {}
|
|
1126
|
+
|
|
1127
|
+
profile = self.profile_manager.get_profile(profile_name)
|
|
1128
|
+
if not profile:
|
|
1129
|
+
return {}
|
|
1130
|
+
|
|
1131
|
+
# Check if profile can be deleted
|
|
1132
|
+
if profile_name in self.profile_manager.DEFAULT_PROFILES:
|
|
1133
|
+
# Cannot delete built-in profiles
|
|
1134
|
+
return {}
|
|
1135
|
+
|
|
1136
|
+
if self.profile_manager.is_active(profile_name):
|
|
1137
|
+
# Cannot delete active profile - but we can show a warning
|
|
1138
|
+
pass
|
|
1139
|
+
|
|
1140
|
+
is_active = self.profile_manager.is_active(profile_name)
|
|
1141
|
+
warning_msg = ""
|
|
1142
|
+
if is_active:
|
|
1143
|
+
warning_msg = "\n\n[!] This is the currently active profile.\n You must switch to another profile first."
|
|
1144
|
+
can_delete = False
|
|
1145
|
+
else:
|
|
1146
|
+
can_delete = True
|
|
1147
|
+
|
|
1148
|
+
return {
|
|
1149
|
+
"title": f"Delete Profile: {profile_name}?",
|
|
1150
|
+
"footer": "Enter confirm • Esc cancel",
|
|
1151
|
+
"width": 60,
|
|
1152
|
+
"height": 12,
|
|
1153
|
+
"sections": [
|
|
1154
|
+
{
|
|
1155
|
+
"title": "Confirm Deletion",
|
|
1156
|
+
"commands": [
|
|
1157
|
+
{
|
|
1158
|
+
"name": f"Delete '{profile_name}'",
|
|
1159
|
+
"description": f"Model: {profile.model} @ {profile.api_url}{warning_msg}",
|
|
1160
|
+
"profile_name": profile_name,
|
|
1161
|
+
"action": "delete_profile_confirm" if can_delete else "cancel"
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
"name": "Cancel",
|
|
1165
|
+
"description": "Keep the profile",
|
|
1166
|
+
"action": "cancel"
|
|
1167
|
+
}
|
|
1168
|
+
]
|
|
1169
|
+
}
|
|
1170
|
+
],
|
|
1171
|
+
"actions": [
|
|
1172
|
+
{"key": "Enter", "label": "Confirm", "action": "select"},
|
|
1173
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel"}
|
|
1174
|
+
]
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
def _get_create_agent_modal_definition(self) -> Dict[str, Any]:
|
|
1178
|
+
"""Get modal definition for creating a new agent."""
|
|
1179
|
+
# Get available profiles for dropdown
|
|
1180
|
+
profile_options = ["(none)"]
|
|
1181
|
+
if self.profile_manager:
|
|
1182
|
+
profile_options.extend(self.profile_manager.get_profile_names())
|
|
1183
|
+
|
|
1184
|
+
return {
|
|
1185
|
+
"title": "Create Agent",
|
|
1186
|
+
"footer": "Tab navigate • Enter confirm • Ctrl+S save • Esc cancel",
|
|
1187
|
+
"width": 70,
|
|
1188
|
+
"height": 20,
|
|
1189
|
+
"form_action": "create_agent_submit",
|
|
1190
|
+
"sections": [
|
|
1191
|
+
{
|
|
1192
|
+
"title": "Agent Settings",
|
|
1193
|
+
"widgets": [
|
|
1194
|
+
{
|
|
1195
|
+
"type": "text_input",
|
|
1196
|
+
"label": "Agent Name",
|
|
1197
|
+
"field": "name",
|
|
1198
|
+
"placeholder": "my-agent",
|
|
1199
|
+
"help": "Unique identifier (creates agents/<name>/ directory)"
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
"type": "text_input",
|
|
1203
|
+
"label": "Description",
|
|
1204
|
+
"field": "description",
|
|
1205
|
+
"placeholder": "A Python web development specialist...",
|
|
1206
|
+
"help": "Describe what this agent specializes in (AI generates from this)"
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
"type": "dropdown",
|
|
1210
|
+
"label": "Source",
|
|
1211
|
+
"field": "source",
|
|
1212
|
+
"options": ["global", "local"],
|
|
1213
|
+
"current_value": "global",
|
|
1214
|
+
"help": "global=~/shared, local=project-specific"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
"type": "dropdown",
|
|
1218
|
+
"label": "Preferred Profile",
|
|
1219
|
+
"field": "profile",
|
|
1220
|
+
"options": profile_options,
|
|
1221
|
+
"current_value": "(none)",
|
|
1222
|
+
"help": "LLM profile to use with this agent"
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
"type": "label",
|
|
1226
|
+
"label": "Generation",
|
|
1227
|
+
"value": "AI will generate system prompt and 5-6 skills based on description"
|
|
1228
|
+
},
|
|
1229
|
+
]
|
|
1230
|
+
}
|
|
1231
|
+
],
|
|
1232
|
+
"actions": [
|
|
1233
|
+
{"key": "Ctrl+S", "label": "Generate", "action": "submit", "style": "primary"},
|
|
1234
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel", "style": "secondary"}
|
|
1235
|
+
]
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
def _build_agent_creation_system_prompt(self) -> str:
|
|
1239
|
+
"""Build custom system prompt for agent creation background task.
|
|
1240
|
+
|
|
1241
|
+
This prompt focuses the LLM on agent file generation without
|
|
1242
|
+
the usual conversational context.
|
|
1243
|
+
|
|
1244
|
+
Returns:
|
|
1245
|
+
System prompt string for agent creation
|
|
1246
|
+
"""
|
|
1247
|
+
return """You are an expert AI agent creator for the Kollabor CLI system.
|
|
1248
|
+
|
|
1249
|
+
Your ONLY job is to generate comprehensive, high-quality agent files when requested.
|
|
1250
|
+
|
|
1251
|
+
# Core Mission
|
|
1252
|
+
|
|
1253
|
+
When asked to create an agent, you will:
|
|
1254
|
+
1. Read the default agent templates to understand the format
|
|
1255
|
+
2. Generate a complete agent with system_prompt.md, agent.json, and 5-6 skill files
|
|
1256
|
+
3. Match or exceed the quality and depth of the default templates
|
|
1257
|
+
|
|
1258
|
+
# Critical Rules
|
|
1259
|
+
|
|
1260
|
+
1. **Use @@@FILE blocks**: ALL file generation MUST use @@@FILE/@@@END blocks (NOT XML tags)
|
|
1261
|
+
2. **Be comprehensive**: System prompts should be 500+ lines, skills 300+ lines minimum
|
|
1262
|
+
3. **Include examples**: Skills should have concrete examples, not abstract guidance
|
|
1263
|
+
4. **Follow structure**: Match the structure of default agent templates exactly
|
|
1264
|
+
5. **No shortcuts**: Do not generate placeholder content or "TODO" sections
|
|
1265
|
+
|
|
1266
|
+
# File Generation Format
|
|
1267
|
+
|
|
1268
|
+
ALWAYS use this exact format (one file per block):
|
|
1269
|
+
|
|
1270
|
+
@@@FILE ~/.kollabor-cli/agents/name/system_prompt.md
|
|
1271
|
+
<!-- Agent Name -->
|
|
1272
|
+
# System Prompt Content Here
|
|
1273
|
+
... (500+ lines of comprehensive content) ...
|
|
1274
|
+
... can include <create>, <edit>, <read> XML examples safely ...
|
|
1275
|
+
@@@END
|
|
1276
|
+
|
|
1277
|
+
@@@FILE ~/.kollabor-cli/agents/name/agent.json
|
|
1278
|
+
{"description": "...", "profile": null}
|
|
1279
|
+
@@@END
|
|
1280
|
+
|
|
1281
|
+
@@@FILE ~/.kollabor-cli/agents/name/skill1.md
|
|
1282
|
+
<!-- Skill Description -->
|
|
1283
|
+
# Skill Name
|
|
1284
|
+
... (300+ lines of actionable content) ...
|
|
1285
|
+
@@@END
|
|
1286
|
+
|
|
1287
|
+
[repeat @@@FILE blocks for 5-6 skills total]
|
|
1288
|
+
|
|
1289
|
+
# Quality Standards
|
|
1290
|
+
|
|
1291
|
+
- System prompts: 500+ lines with clear sections, examples, and guidelines
|
|
1292
|
+
- Skills: 300+ lines with phases, examples, and mandatory rules
|
|
1293
|
+
- Content: Real, actionable guidance (not "you should" but "do this:")
|
|
1294
|
+
- Structure: Match default templates exactly
|
|
1295
|
+
- Examples: Include concrete code/command examples where relevant
|
|
1296
|
+
|
|
1297
|
+
You will receive requests to create agents. Respond ONLY with @@@FILE blocks containing all generated files.
|
|
1298
|
+
Do not engage in conversation. Do not ask questions. Just generate the files.
|
|
1299
|
+
"""
|
|
1300
|
+
|
|
1301
|
+
def _build_agent_generation_prompt(
|
|
1302
|
+
self, name: str, description: str, profile: Optional[str] = None, source: str = "global"
|
|
1303
|
+
) -> str:
|
|
1304
|
+
"""Build prompt for LLM-powered agent generation.
|
|
1305
|
+
|
|
1306
|
+
Args:
|
|
1307
|
+
name: Agent name (directory name).
|
|
1308
|
+
description: What the agent specializes in.
|
|
1309
|
+
profile: Optional preferred LLM profile.
|
|
1310
|
+
source: Agent source - "global" or "local".
|
|
1311
|
+
|
|
1312
|
+
Returns:
|
|
1313
|
+
Prompt string for the LLM to generate agent files.
|
|
1314
|
+
"""
|
|
1315
|
+
profile_value = f'"{profile}"' if profile else "null"
|
|
1316
|
+
|
|
1317
|
+
# Determine the base path for the agent
|
|
1318
|
+
if source == "local":
|
|
1319
|
+
agents_path = ".kollabor-cli/agents"
|
|
1320
|
+
else:
|
|
1321
|
+
agents_path = "~/.kollabor-cli/agents"
|
|
1322
|
+
|
|
1323
|
+
return f'''Create a new agent called "{name}" that specializes in: {description}
|
|
1324
|
+
|
|
1325
|
+
IMPORTANT: First, review the structure of the default agent to understand the format:
|
|
1326
|
+
- Read ~/.kollabor-cli/agents/default/system_prompt.md (the main system prompt template)
|
|
1327
|
+
- Read ~/.kollabor-cli/agents/default/agent.json (the configuration format)
|
|
1328
|
+
- Read ~/.kollabor-cli/agents/default/debugging.md (an example skill file format)
|
|
1329
|
+
|
|
1330
|
+
After reviewing the templates, create the new agent with the SAME level of detail and quality.
|
|
1331
|
+
|
|
1332
|
+
Create these files using <create> tags:
|
|
1333
|
+
|
|
1334
|
+
1. system_prompt.md - Comprehensive system prompt (500+ lines) following the default template structure:
|
|
1335
|
+
- Header with agent name
|
|
1336
|
+
- Core philosophy and mission
|
|
1337
|
+
- Session context with <trender> tags for dynamic content
|
|
1338
|
+
- Tool execution guidelines
|
|
1339
|
+
- Response patterns and examples
|
|
1340
|
+
- Quality assurance checklist
|
|
1341
|
+
- Error handling guidance
|
|
1342
|
+
|
|
1343
|
+
2. agent.json - Configuration file:
|
|
1344
|
+
{{"description": "{description}", "profile": {profile_value}}}
|
|
1345
|
+
|
|
1346
|
+
3. Create 5-6 skill files (.md) relevant to this agent's specialty. Each skill should:
|
|
1347
|
+
- Start with HTML comment description: <!-- Skill name - brief purpose -->
|
|
1348
|
+
- Include PHASE 0: Environment verification
|
|
1349
|
+
- Include multiple phases with detailed guidance
|
|
1350
|
+
- End with Mandatory rules section
|
|
1351
|
+
- Be 500+ lines with comprehensive, actionable content
|
|
1352
|
+
|
|
1353
|
+
CRITICAL: Use @@@FILE/@@@END blocks to generate all files. This protects your content
|
|
1354
|
+
from being parsed as actual tool calls. The format is:
|
|
1355
|
+
|
|
1356
|
+
@@@FILE {agents_path}/{name}/system_prompt.md
|
|
1357
|
+
... full system prompt content here (500+ lines) ...
|
|
1358
|
+
... can include XML command examples like <create>, <read>, etc ...
|
|
1359
|
+
@@@END
|
|
1360
|
+
|
|
1361
|
+
@@@FILE {agents_path}/{name}/agent.json
|
|
1362
|
+
{{"description": "{description}", "profile": {profile_value}}}
|
|
1363
|
+
@@@END
|
|
1364
|
+
|
|
1365
|
+
@@@FILE {agents_path}/{name}/skill_name.md
|
|
1366
|
+
... full skill content here (500+ lines) ...
|
|
1367
|
+
... can include command examples without breaking ...
|
|
1368
|
+
@@@END
|
|
1369
|
+
|
|
1370
|
+
[repeat @@@FILE blocks for each of the 5-6 skill files]
|
|
1371
|
+
|
|
1372
|
+
Generate ONE file at a time using @@@FILE blocks. Match the quality and depth of the default agent templates.'''
|
|
1373
|
+
|
|
1374
|
+
def _build_skill_generation_prompt(
|
|
1375
|
+
self, agent_name: str, skill_name: str, description: str
|
|
1376
|
+
) -> str:
|
|
1377
|
+
"""Build prompt for LLM-powered skill generation.
|
|
1378
|
+
|
|
1379
|
+
Args:
|
|
1380
|
+
agent_name: Name of the agent this skill belongs to.
|
|
1381
|
+
skill_name: Skill name (filename without .md).
|
|
1382
|
+
description: What the skill helps with.
|
|
1383
|
+
|
|
1384
|
+
Returns:
|
|
1385
|
+
Prompt string for the LLM to generate the skill file.
|
|
1386
|
+
"""
|
|
1387
|
+
# Get the agent's actual directory (could be local or global)
|
|
1388
|
+
agent_dir = f".kollabor-cli/agents/{agent_name}" # default fallback
|
|
1389
|
+
if self.agent_manager:
|
|
1390
|
+
agent = self.agent_manager.get_agent(agent_name)
|
|
1391
|
+
if agent:
|
|
1392
|
+
agent_dir = str(agent.directory)
|
|
1393
|
+
|
|
1394
|
+
return f'''Create a new skill called "{skill_name}" for the "{agent_name}" agent.
|
|
1395
|
+
|
|
1396
|
+
The skill should help with: {description}
|
|
1397
|
+
|
|
1398
|
+
IMPORTANT: First, review existing skills to understand the format and style:
|
|
1399
|
+
- Read {agent_dir}/system_prompt.md (to understand the agent's purpose)
|
|
1400
|
+
- Read ~/.kollabor-cli/agents/default/debugging.md (an example skill file format)
|
|
1401
|
+
- Read any existing .md skill files in {agent_dir}/ for format reference
|
|
1402
|
+
|
|
1403
|
+
After reviewing, create a comprehensive skill file that:
|
|
1404
|
+
1. Starts with HTML comment description: <!-- {skill_name} - {description} -->
|
|
1405
|
+
2. Has a clear header with skill name
|
|
1406
|
+
3. Includes PHASE 0: Environment/context verification
|
|
1407
|
+
4. Has multiple phases with detailed, actionable guidance
|
|
1408
|
+
5. Includes examples and code snippets where relevant
|
|
1409
|
+
6. Ends with a "Mandatory Rules" or "Quality Checklist" section
|
|
1410
|
+
7. Is comprehensive (500+ lines) with real, actionable content
|
|
1411
|
+
|
|
1412
|
+
CRITICAL: Use @@@FILE/@@@END blocks to generate the file. This protects your content
|
|
1413
|
+
from being parsed as actual tool calls. The format is:
|
|
1414
|
+
|
|
1415
|
+
@@@FILE {agent_dir}/{skill_name}.md
|
|
1416
|
+
<!-- {skill_name} - {description} -->
|
|
1417
|
+
|
|
1418
|
+
# {skill_name.replace('-', ' ').title()}
|
|
1419
|
+
|
|
1420
|
+
... comprehensive skill content here (500+ lines) ...
|
|
1421
|
+
... include PHASE 0, PHASE 1, etc with detailed guidance ...
|
|
1422
|
+
... can include XML command examples like <create>, <read>, <terminal>, etc ...
|
|
1423
|
+
... these examples will be preserved as literal text, not executed ...
|
|
1424
|
+
|
|
1425
|
+
## Mandatory Rules
|
|
1426
|
+
- Rule 1
|
|
1427
|
+
- Rule 2
|
|
1428
|
+
@@@END
|
|
1429
|
+
|
|
1430
|
+
Generate ONE comprehensive skill file using the @@@FILE block. Match the quality and depth of existing default agent skills.'''
|
|
1431
|
+
|
|
1432
|
+
def _get_delete_agent_confirm_modal(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
1433
|
+
"""Get modal definition for delete agent confirmation.
|
|
1434
|
+
|
|
1435
|
+
Args:
|
|
1436
|
+
agent_name: Name of the agent to delete.
|
|
1437
|
+
|
|
1438
|
+
Returns:
|
|
1439
|
+
Modal definition dict for confirmation, or empty dict if cannot delete.
|
|
1440
|
+
"""
|
|
1441
|
+
if not self.agent_manager:
|
|
1442
|
+
return {}
|
|
1443
|
+
|
|
1444
|
+
agents = self.agent_manager.list_agents()
|
|
1445
|
+
agent = next((a for a in agents if a.name == agent_name), None)
|
|
1446
|
+
if not agent:
|
|
1447
|
+
return {}
|
|
1448
|
+
|
|
1449
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
1450
|
+
active_name = active_agent.name if active_agent else None
|
|
1451
|
+
is_active = agent_name == active_name
|
|
1452
|
+
|
|
1453
|
+
warning_msg = ""
|
|
1454
|
+
if is_active:
|
|
1455
|
+
warning_msg = "\n\n[!] This is the currently active agent.\n You must clear or switch to another agent first."
|
|
1456
|
+
can_delete = False
|
|
1457
|
+
else:
|
|
1458
|
+
can_delete = True
|
|
1459
|
+
|
|
1460
|
+
skills = agent.list_skills()
|
|
1461
|
+
skill_info = f", {len(skills)} skills" if skills else ""
|
|
1462
|
+
|
|
1463
|
+
return {
|
|
1464
|
+
"title": f"Delete Agent: {agent_name}?",
|
|
1465
|
+
"footer": "Enter confirm • Esc cancel",
|
|
1466
|
+
"width": 60,
|
|
1467
|
+
"height": 12,
|
|
1468
|
+
"sections": [
|
|
1469
|
+
{
|
|
1470
|
+
"title": "Confirm Deletion",
|
|
1471
|
+
"commands": [
|
|
1472
|
+
{
|
|
1473
|
+
"name": f"Delete '{agent_name}'",
|
|
1474
|
+
"description": f"{agent.description or 'No description'}{skill_info}{warning_msg}",
|
|
1475
|
+
"agent_name": agent_name,
|
|
1476
|
+
"action": "delete_agent_confirm" if can_delete else "cancel"
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
"name": "Cancel",
|
|
1480
|
+
"description": "Keep the agent",
|
|
1481
|
+
"action": "cancel"
|
|
1482
|
+
}
|
|
1483
|
+
]
|
|
1484
|
+
}
|
|
1485
|
+
],
|
|
1486
|
+
"actions": [
|
|
1487
|
+
{"key": "Enter", "label": "Confirm", "action": "select"},
|
|
1488
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel"}
|
|
1489
|
+
]
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
def _get_edit_agent_modal_definition(self, agent_name: str) -> Optional[Dict[str, Any]]:
|
|
1493
|
+
"""Get modal definition for editing an existing agent.
|
|
1494
|
+
|
|
1495
|
+
Args:
|
|
1496
|
+
agent_name: Name of the agent to edit.
|
|
1497
|
+
|
|
1498
|
+
Returns:
|
|
1499
|
+
Modal definition dict with pre-populated values, or None if not found.
|
|
1500
|
+
"""
|
|
1501
|
+
if not self.agent_manager:
|
|
1502
|
+
return None
|
|
1503
|
+
|
|
1504
|
+
agent = self.agent_manager.get_agent(agent_name)
|
|
1505
|
+
if not agent:
|
|
1506
|
+
return None
|
|
1507
|
+
|
|
1508
|
+
# Get available profiles for dropdown
|
|
1509
|
+
profile_options = ["(none)"]
|
|
1510
|
+
if self.profile_manager:
|
|
1511
|
+
profile_options.extend(self.profile_manager.get_profile_names())
|
|
1512
|
+
|
|
1513
|
+
# Determine current profile value
|
|
1514
|
+
current_profile = agent.profile if agent.profile else "(none)"
|
|
1515
|
+
|
|
1516
|
+
# Read system prompt from file
|
|
1517
|
+
system_prompt = agent.system_prompt
|
|
1518
|
+
|
|
1519
|
+
# Get skill info for display
|
|
1520
|
+
skills = agent.list_skills()
|
|
1521
|
+
skill_info = f", {len(skills)} skills" if skills else ""
|
|
1522
|
+
|
|
1523
|
+
# Determine if agent is protected (cannot be renamed to default name)
|
|
1524
|
+
is_protected = agent_name in self.agent_manager.list_agents() and agent_name == "default"
|
|
1525
|
+
|
|
1526
|
+
# Show short path for system_prompt file
|
|
1527
|
+
short_path = f"agents/{agent_name}/system_prompt.md"
|
|
1528
|
+
|
|
1529
|
+
return {
|
|
1530
|
+
"title": f"Edit Agent: {agent_name}",
|
|
1531
|
+
"footer": "Tab navigate • Ctrl+S save • Esc cancel",
|
|
1532
|
+
"width": 70,
|
|
1533
|
+
"height": 16,
|
|
1534
|
+
"form_action": "edit_agent_submit",
|
|
1535
|
+
"edit_agent_name": agent_name, # Track original name for rename
|
|
1536
|
+
"sections": [
|
|
1537
|
+
{
|
|
1538
|
+
"title": "Agent Settings",
|
|
1539
|
+
"widgets": [
|
|
1540
|
+
{
|
|
1541
|
+
"type": "text_input",
|
|
1542
|
+
"label": "Name",
|
|
1543
|
+
"field": "name",
|
|
1544
|
+
"value": agent.name,
|
|
1545
|
+
"placeholder": "my-agent",
|
|
1546
|
+
"help": "Renames agent directory"
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
"type": "text_input",
|
|
1550
|
+
"label": "Desc",
|
|
1551
|
+
"field": "description",
|
|
1552
|
+
"value": agent.description or "",
|
|
1553
|
+
"placeholder": "What this agent does",
|
|
1554
|
+
"help": "Agent description"
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
"type": "dropdown",
|
|
1558
|
+
"label": "Profile",
|
|
1559
|
+
"field": "profile",
|
|
1560
|
+
"options": profile_options,
|
|
1561
|
+
"current_value": current_profile,
|
|
1562
|
+
"help": "Preferred LLM profile"
|
|
1563
|
+
},
|
|
1564
|
+
]
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
"title": f"Files{skill_info}",
|
|
1568
|
+
"widgets": [
|
|
1569
|
+
{
|
|
1570
|
+
"type": "label",
|
|
1571
|
+
"label": "Prompt",
|
|
1572
|
+
"value": short_path,
|
|
1573
|
+
"help": "nano or vim to edit"
|
|
1574
|
+
},
|
|
1575
|
+
]
|
|
1576
|
+
}
|
|
1577
|
+
],
|
|
1578
|
+
"actions": [
|
|
1579
|
+
{"key": "Ctrl+S", "label": "Save", "action": "submit", "style": "primary"},
|
|
1580
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel", "style": "secondary"}
|
|
1581
|
+
]
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
def _get_create_skill_modal_definition(self, agent_name: str) -> Dict[str, Any]:
|
|
1585
|
+
"""Get modal definition for creating a new skill."""
|
|
1586
|
+
short_path = f"agents/{agent_name}/<name>.md"
|
|
1587
|
+
|
|
1588
|
+
return {
|
|
1589
|
+
"title": f"Create Skill - {agent_name}",
|
|
1590
|
+
"footer": "Ctrl+S: create • Esc: cancel",
|
|
1591
|
+
"width": 70,
|
|
1592
|
+
"height": 18,
|
|
1593
|
+
"form_action": "create_skill_submit",
|
|
1594
|
+
"sections": [
|
|
1595
|
+
{
|
|
1596
|
+
"title": "New Skill",
|
|
1597
|
+
"widgets": [
|
|
1598
|
+
{
|
|
1599
|
+
"type": "text_input",
|
|
1600
|
+
"label": "Name",
|
|
1601
|
+
"field": "name",
|
|
1602
|
+
"placeholder": "my-skill",
|
|
1603
|
+
"help": "Creates <name>.md in agent directory"
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
"type": "text_input",
|
|
1607
|
+
"label": "Description",
|
|
1608
|
+
"field": "description",
|
|
1609
|
+
"placeholder": "What this skill helps with...",
|
|
1610
|
+
"help": "AI generates comprehensive skill from this"
|
|
1611
|
+
},
|
|
1612
|
+
]
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
"title": "Info",
|
|
1616
|
+
"widgets": [
|
|
1617
|
+
{
|
|
1618
|
+
"type": "label",
|
|
1619
|
+
"label": "Location",
|
|
1620
|
+
"value": short_path,
|
|
1621
|
+
"help": "AI generates detailed skill content"
|
|
1622
|
+
},
|
|
1623
|
+
]
|
|
1624
|
+
}
|
|
1625
|
+
],
|
|
1626
|
+
"actions": [
|
|
1627
|
+
{"key": "Ctrl+S", "label": "Create", "action": "submit", "style": "primary"},
|
|
1628
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel", "style": "secondary"}
|
|
1629
|
+
]
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
def _get_edit_skill_modal_definition(self, agent_name: str, skill_name: str) -> Optional[Dict[str, Any]]:
|
|
1633
|
+
"""Get modal definition for editing an existing skill."""
|
|
1634
|
+
if not self.agent_manager:
|
|
1635
|
+
return None
|
|
1636
|
+
|
|
1637
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
1638
|
+
if not active_agent or active_agent.name != agent_name:
|
|
1639
|
+
return None
|
|
1640
|
+
|
|
1641
|
+
# Find the skill
|
|
1642
|
+
skill = None
|
|
1643
|
+
for s in active_agent.list_skills():
|
|
1644
|
+
if s.name == skill_name:
|
|
1645
|
+
skill = s
|
|
1646
|
+
break
|
|
1647
|
+
|
|
1648
|
+
if not skill:
|
|
1649
|
+
return None
|
|
1650
|
+
|
|
1651
|
+
# Short path for display
|
|
1652
|
+
short_path = f"agents/{agent_name}/{skill_name}.md"
|
|
1653
|
+
|
|
1654
|
+
return {
|
|
1655
|
+
"title": f"Edit Skill: {skill_name}",
|
|
1656
|
+
"footer": "Tab navigate • Ctrl+S save • Esc cancel",
|
|
1657
|
+
"width": 70,
|
|
1658
|
+
"height": 14,
|
|
1659
|
+
"form_action": "edit_skill_submit",
|
|
1660
|
+
"edit_skill_name": skill_name,
|
|
1661
|
+
"sections": [
|
|
1662
|
+
{
|
|
1663
|
+
"title": "Skill Settings",
|
|
1664
|
+
"widgets": [
|
|
1665
|
+
{
|
|
1666
|
+
"type": "text_input",
|
|
1667
|
+
"label": "Name",
|
|
1668
|
+
"field": "name",
|
|
1669
|
+
"value": skill_name,
|
|
1670
|
+
"placeholder": "my-skill",
|
|
1671
|
+
"help": "Rename the skill file"
|
|
1672
|
+
},
|
|
1673
|
+
]
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
"title": "File",
|
|
1677
|
+
"widgets": [
|
|
1678
|
+
{
|
|
1679
|
+
"type": "label",
|
|
1680
|
+
"label": "Path",
|
|
1681
|
+
"value": short_path,
|
|
1682
|
+
"help": "nano or vim to edit"
|
|
1683
|
+
},
|
|
1684
|
+
]
|
|
1685
|
+
}
|
|
1686
|
+
],
|
|
1687
|
+
"actions": [
|
|
1688
|
+
{"key": "Ctrl+S", "label": "Save", "action": "submit", "style": "primary"},
|
|
1689
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel", "style": "secondary"}
|
|
1690
|
+
]
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
def _get_delete_skill_confirm_modal(self, agent_name: str, skill_name: str) -> Optional[Dict[str, Any]]:
|
|
1694
|
+
"""Get modal definition for delete skill confirmation."""
|
|
1695
|
+
if not self.agent_manager:
|
|
1696
|
+
return None
|
|
1697
|
+
|
|
1698
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
1699
|
+
if not active_agent or active_agent.name != agent_name:
|
|
1700
|
+
return None
|
|
1701
|
+
|
|
1702
|
+
# Find the skill
|
|
1703
|
+
skill = None
|
|
1704
|
+
for s in active_agent.list_skills():
|
|
1705
|
+
if s.name == skill_name:
|
|
1706
|
+
skill = s
|
|
1707
|
+
break
|
|
1708
|
+
|
|
1709
|
+
if not skill:
|
|
1710
|
+
return None
|
|
1711
|
+
|
|
1712
|
+
is_loaded = skill_name in active_agent.active_skills
|
|
1713
|
+
warning_msg = ""
|
|
1714
|
+
if is_loaded:
|
|
1715
|
+
warning_msg = "\n\n[!] This skill is currently loaded."
|
|
1716
|
+
|
|
1717
|
+
return {
|
|
1718
|
+
"title": f"Delete Skill: {skill_name}?",
|
|
1719
|
+
"footer": "Enter confirm • Esc cancel",
|
|
1720
|
+
"width": 60,
|
|
1721
|
+
"height": 12,
|
|
1722
|
+
"sections": [
|
|
1723
|
+
{
|
|
1724
|
+
"title": "Confirm Deletion",
|
|
1725
|
+
"commands": [
|
|
1726
|
+
{
|
|
1727
|
+
"name": f"Delete '{skill_name}'",
|
|
1728
|
+
"description": f"{skill.description or skill.file_path.name}{warning_msg}",
|
|
1729
|
+
"skill_name": skill_name,
|
|
1730
|
+
"action": "delete_skill_confirm"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
"name": "Cancel",
|
|
1734
|
+
"description": "Keep the skill",
|
|
1735
|
+
"action": "cancel"
|
|
1736
|
+
}
|
|
1737
|
+
]
|
|
1738
|
+
}
|
|
1739
|
+
],
|
|
1740
|
+
"actions": [
|
|
1741
|
+
{"key": "Enter", "label": "Confirm", "action": "select"},
|
|
1742
|
+
{"key": "Escape", "label": "Cancel", "action": "cancel"}
|
|
1743
|
+
]
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
def _create_skill_file(self, agent, name: str, content: str) -> bool:
|
|
1747
|
+
"""Create a new skill file in the agent directory."""
|
|
1748
|
+
try:
|
|
1749
|
+
# Sanitize name - remove .md extension if present
|
|
1750
|
+
if name.endswith(".md"):
|
|
1751
|
+
name = name[:-3]
|
|
1752
|
+
|
|
1753
|
+
skill_path = agent.directory / f"{name}.md"
|
|
110
1754
|
|
|
111
|
-
|
|
1755
|
+
# Don't overwrite existing files
|
|
1756
|
+
if skill_path.exists():
|
|
1757
|
+
return False
|
|
112
1758
|
|
|
113
|
-
|
|
114
|
-
|
|
1759
|
+
skill_path.write_text(content, encoding="utf-8")
|
|
1760
|
+
return True
|
|
1761
|
+
except Exception:
|
|
1762
|
+
return False
|
|
1763
|
+
|
|
1764
|
+
def _rename_skill_file(self, agent, original_name: str, new_name: str) -> bool:
|
|
1765
|
+
"""Rename a skill file."""
|
|
1766
|
+
try:
|
|
1767
|
+
# Sanitize names
|
|
1768
|
+
if original_name.endswith(".md"):
|
|
1769
|
+
original_name = original_name[:-3]
|
|
1770
|
+
if new_name.endswith(".md"):
|
|
1771
|
+
new_name = new_name[:-3]
|
|
1772
|
+
|
|
1773
|
+
# Same name = no-op success
|
|
1774
|
+
if original_name == new_name:
|
|
1775
|
+
return True
|
|
1776
|
+
|
|
1777
|
+
original_path = agent.directory / f"{original_name}.md"
|
|
1778
|
+
new_path = agent.directory / f"{new_name}.md"
|
|
1779
|
+
|
|
1780
|
+
if not original_path.exists():
|
|
1781
|
+
return False
|
|
1782
|
+
|
|
1783
|
+
# Check new name doesn't exist
|
|
1784
|
+
if new_path.exists():
|
|
1785
|
+
return False
|
|
1786
|
+
|
|
1787
|
+
# Rename file
|
|
1788
|
+
original_path.rename(new_path)
|
|
1789
|
+
return True
|
|
1790
|
+
except Exception:
|
|
1791
|
+
return False
|
|
1792
|
+
|
|
1793
|
+
def _delete_skill_file(self, agent, skill_name: str) -> bool:
|
|
1794
|
+
"""Delete a skill file from the agent directory."""
|
|
1795
|
+
try:
|
|
1796
|
+
# Sanitize name
|
|
1797
|
+
if skill_name.endswith(".md"):
|
|
1798
|
+
skill_name = skill_name[:-3]
|
|
1799
|
+
|
|
1800
|
+
# Don't delete system_prompt.md
|
|
1801
|
+
if skill_name == "system_prompt":
|
|
1802
|
+
return False
|
|
1803
|
+
|
|
1804
|
+
skill_path = agent.directory / f"{skill_name}.md"
|
|
1805
|
+
|
|
1806
|
+
if not skill_path.exists():
|
|
1807
|
+
return False
|
|
1808
|
+
|
|
1809
|
+
skill_path.unlink()
|
|
1810
|
+
return True
|
|
1811
|
+
except Exception:
|
|
1812
|
+
return False
|
|
115
1813
|
|
|
116
1814
|
async def handle_help(self, command: SlashCommand) -> CommandResult:
|
|
117
1815
|
"""Handle /help command.
|
|
@@ -251,9 +1949,9 @@ class SystemCommandsPlugin:
|
|
|
251
1949
|
{
|
|
252
1950
|
"title": "Services",
|
|
253
1951
|
"widgets": [
|
|
254
|
-
{"type": "label", "label": "Event Bus", "value": "[
|
|
255
|
-
{"type": "label", "label": "Input Handler", "value": "[
|
|
256
|
-
{"type": "label", "label": "Terminal Renderer", "value": "[
|
|
1952
|
+
{"type": "label", "label": "Event Bus", "value": "[ok] Active"},
|
|
1953
|
+
{"type": "label", "label": "Input Handler", "value": "[ok] Running"},
|
|
1954
|
+
{"type": "label", "label": "Terminal Renderer", "value": "[ok] Active"},
|
|
257
1955
|
]
|
|
258
1956
|
}
|
|
259
1957
|
],
|
|
@@ -295,7 +1993,756 @@ Platform: {version_info['platform']}"""
|
|
|
295
1993
|
display_type="error"
|
|
296
1994
|
)
|
|
297
1995
|
|
|
1996
|
+
async def handle_profile(self, command: SlashCommand) -> CommandResult:
|
|
1997
|
+
"""Handle /profile command.
|
|
1998
|
+
|
|
1999
|
+
Args:
|
|
2000
|
+
command: Parsed slash command.
|
|
2001
|
+
|
|
2002
|
+
Returns:
|
|
2003
|
+
Command execution result.
|
|
2004
|
+
"""
|
|
2005
|
+
try:
|
|
2006
|
+
if not self.profile_manager:
|
|
2007
|
+
return CommandResult(
|
|
2008
|
+
success=False,
|
|
2009
|
+
message="Profile manager not available",
|
|
2010
|
+
display_type="error"
|
|
2011
|
+
)
|
|
2012
|
+
|
|
2013
|
+
args = command.args or []
|
|
2014
|
+
|
|
2015
|
+
if not args or args[0] in ("list", "ls"):
|
|
2016
|
+
# Show profile selection modal
|
|
2017
|
+
return await self._show_profiles_modal()
|
|
2018
|
+
elif args[0] == "set" and len(args) >= 2:
|
|
2019
|
+
# Switch to profile: /profile set <name>
|
|
2020
|
+
profile_name = args[1]
|
|
2021
|
+
return await self._switch_profile(profile_name)
|
|
2022
|
+
elif args[0] == "create":
|
|
2023
|
+
# Show create profile form: /profile create
|
|
2024
|
+
return await self._show_create_profile_modal()
|
|
2025
|
+
else:
|
|
2026
|
+
# Switch to specified profile (direct command)
|
|
2027
|
+
profile_name = args[0]
|
|
2028
|
+
return await self._switch_profile(profile_name)
|
|
2029
|
+
|
|
2030
|
+
except Exception as e:
|
|
2031
|
+
self.logger.error(f"Error in profile command: {e}")
|
|
2032
|
+
return CommandResult(
|
|
2033
|
+
success=False,
|
|
2034
|
+
message=f"Error managing profiles: {str(e)}",
|
|
2035
|
+
display_type="error"
|
|
2036
|
+
)
|
|
2037
|
+
|
|
2038
|
+
def _get_profiles_modal_definition(self, skip_reload: bool = False) -> Dict[str, Any]:
|
|
2039
|
+
"""Get modal definition for profile selection.
|
|
2040
|
+
|
|
2041
|
+
Args:
|
|
2042
|
+
skip_reload: If True, don't reload from config (use current state).
|
|
2043
|
+
|
|
2044
|
+
Returns:
|
|
2045
|
+
Modal definition dictionary.
|
|
2046
|
+
"""
|
|
2047
|
+
# Reload profiles from config to pick up any changes
|
|
2048
|
+
# Skip reload when called immediately after delete (memory state is fresher)
|
|
2049
|
+
if not skip_reload:
|
|
2050
|
+
self.profile_manager.reload()
|
|
2051
|
+
|
|
2052
|
+
profiles = self.profile_manager.list_profiles()
|
|
2053
|
+
active_name = self.profile_manager.active_profile_name
|
|
2054
|
+
|
|
2055
|
+
# Build profile list for modal
|
|
2056
|
+
profile_items = []
|
|
2057
|
+
for profile in profiles:
|
|
2058
|
+
is_active = profile.name == active_name
|
|
2059
|
+
# Use getter methods to show resolved values (respects env vars)
|
|
2060
|
+
model = profile.get_model() or "unknown"
|
|
2061
|
+
api_url = profile.get_endpoint() or "unknown"
|
|
2062
|
+
profile_items.append({
|
|
2063
|
+
"name": f"{'[*] ' if is_active else ' '}{profile.name}",
|
|
2064
|
+
"description": f"{model} @ {api_url}",
|
|
2065
|
+
"profile_name": profile.name,
|
|
2066
|
+
"action": "select_profile"
|
|
2067
|
+
})
|
|
2068
|
+
|
|
2069
|
+
# Add management options
|
|
2070
|
+
management_items = [
|
|
2071
|
+
{
|
|
2072
|
+
"name": " [+] Save to Config",
|
|
2073
|
+
"description": "Save current profile settings (from env vars) to config.json",
|
|
2074
|
+
"action": "save_profile_to_config"
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
"name": " [+] Create New Profile",
|
|
2078
|
+
"description": "Create a new profile from scratch",
|
|
2079
|
+
"action": "create_profile_prompt"
|
|
2080
|
+
},
|
|
2081
|
+
]
|
|
2082
|
+
|
|
2083
|
+
# Env var help section (non-selectable info items)
|
|
2084
|
+
# Short label on left (name), env var on right (description)
|
|
2085
|
+
env_help_items = [
|
|
2086
|
+
{
|
|
2087
|
+
"name": "auto-create from env vars",
|
|
2088
|
+
"description": "python main.py --profile NAME --save",
|
|
2089
|
+
"action": "noop",
|
|
2090
|
+
"selectable": False
|
|
2091
|
+
},
|
|
2092
|
+
{
|
|
2093
|
+
"name": "API URL (required)",
|
|
2094
|
+
"description": "KOLLABOR_{NAME}_ENDPOINT",
|
|
2095
|
+
"action": "noop",
|
|
2096
|
+
"selectable": False
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
"name": "API key",
|
|
2100
|
+
"description": "KOLLABOR_{NAME}_TOKEN",
|
|
2101
|
+
"action": "noop",
|
|
2102
|
+
"selectable": False
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
"name": "model name",
|
|
2106
|
+
"description": "KOLLABOR_{NAME}_MODEL",
|
|
2107
|
+
"action": "noop",
|
|
2108
|
+
"selectable": False
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
"name": "tool format",
|
|
2112
|
+
"description": "KOLLABOR_{NAME}_TOOL_FORMAT",
|
|
2113
|
+
"action": "noop",
|
|
2114
|
+
"selectable": False
|
|
2115
|
+
},
|
|
2116
|
+
{
|
|
2117
|
+
"name": "max tokens",
|
|
2118
|
+
"description": "KOLLABOR_{NAME}_MAX_TOKENS",
|
|
2119
|
+
"action": "noop",
|
|
2120
|
+
"selectable": False
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
"name": "temperature",
|
|
2124
|
+
"description": "KOLLABOR_{NAME}_TEMPERATURE",
|
|
2125
|
+
"action": "noop",
|
|
2126
|
+
"selectable": False
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
"name": "timeout (ms)",
|
|
2130
|
+
"description": "KOLLABOR_{NAME}_TIMEOUT",
|
|
2131
|
+
"action": "noop",
|
|
2132
|
+
"selectable": False
|
|
2133
|
+
},
|
|
2134
|
+
]
|
|
2135
|
+
|
|
2136
|
+
return {
|
|
2137
|
+
"title": "LLM Profiles",
|
|
2138
|
+
"footer": "↑↓ navigate • Enter select • e edit • d delete • Esc exit",
|
|
2139
|
+
"width": 75,
|
|
2140
|
+
"height": 28,
|
|
2141
|
+
"sections": [
|
|
2142
|
+
{
|
|
2143
|
+
"title": f"Available Profiles (active: {active_name})",
|
|
2144
|
+
"commands": profile_items
|
|
2145
|
+
},
|
|
2146
|
+
{
|
|
2147
|
+
"title": "Management",
|
|
2148
|
+
"commands": management_items
|
|
2149
|
+
},
|
|
2150
|
+
{
|
|
2151
|
+
"title": "Create via Environment Variables",
|
|
2152
|
+
"commands": env_help_items
|
|
2153
|
+
}
|
|
2154
|
+
],
|
|
2155
|
+
"actions": [
|
|
2156
|
+
{"key": "Enter", "label": "Select", "action": "select"},
|
|
2157
|
+
{"key": "e", "label": "Edit", "action": "edit_profile_prompt"},
|
|
2158
|
+
{"key": "d", "label": "Delete", "action": "delete_profile_prompt"},
|
|
2159
|
+
{"key": "Escape", "label": "Close", "action": "cancel"}
|
|
2160
|
+
]
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
async def _show_profiles_modal(self) -> CommandResult:
|
|
2164
|
+
"""Show profile selection modal.
|
|
2165
|
+
|
|
2166
|
+
Returns:
|
|
2167
|
+
Command result with modal UI.
|
|
2168
|
+
"""
|
|
2169
|
+
modal_definition = self._get_profiles_modal_definition()
|
|
2170
|
+
|
|
2171
|
+
return CommandResult(
|
|
2172
|
+
success=True,
|
|
2173
|
+
message="Select a profile",
|
|
2174
|
+
ui_config=UIConfig(
|
|
2175
|
+
type="modal",
|
|
2176
|
+
title=modal_definition["title"],
|
|
2177
|
+
width=modal_definition["width"],
|
|
2178
|
+
height=modal_definition["height"],
|
|
2179
|
+
modal_config=modal_definition
|
|
2180
|
+
),
|
|
2181
|
+
display_type="modal"
|
|
2182
|
+
)
|
|
2183
|
+
|
|
2184
|
+
async def _show_create_profile_modal(self) -> CommandResult:
|
|
2185
|
+
"""Show create profile form modal.
|
|
2186
|
+
|
|
2187
|
+
Returns:
|
|
2188
|
+
Command result with create profile form modal.
|
|
2189
|
+
"""
|
|
2190
|
+
modal_definition = self._get_create_profile_modal_definition()
|
|
2191
|
+
|
|
2192
|
+
return CommandResult(
|
|
2193
|
+
success=True,
|
|
2194
|
+
message="Create new profile",
|
|
2195
|
+
ui_config=UIConfig(
|
|
2196
|
+
type="modal",
|
|
2197
|
+
title=modal_definition["title"],
|
|
2198
|
+
width=modal_definition["width"],
|
|
2199
|
+
height=modal_definition["height"],
|
|
2200
|
+
modal_config=modal_definition
|
|
2201
|
+
),
|
|
2202
|
+
display_type="modal"
|
|
2203
|
+
)
|
|
2204
|
+
|
|
2205
|
+
async def _switch_profile(self, profile_name: str) -> CommandResult:
|
|
2206
|
+
"""Switch to a different profile.
|
|
2207
|
+
|
|
2208
|
+
Args:
|
|
2209
|
+
profile_name: Name of profile to switch to.
|
|
2210
|
+
|
|
2211
|
+
Returns:
|
|
2212
|
+
Command result.
|
|
2213
|
+
"""
|
|
2214
|
+
if self.profile_manager.set_active_profile(profile_name):
|
|
2215
|
+
profile = self.profile_manager.get_active_profile()
|
|
2216
|
+
# Update the API service with new profile settings
|
|
2217
|
+
if self.llm_service and hasattr(self.llm_service, 'api_service'):
|
|
2218
|
+
self.llm_service.api_service.update_from_profile(profile)
|
|
2219
|
+
return CommandResult(
|
|
2220
|
+
success=True,
|
|
2221
|
+
message=f"Switched to profile: {profile_name}\n API: {profile.api_url}\n Model: {profile.model}",
|
|
2222
|
+
display_type="success"
|
|
2223
|
+
)
|
|
2224
|
+
else:
|
|
2225
|
+
available = ", ".join(self.profile_manager.get_profile_names())
|
|
2226
|
+
return CommandResult(
|
|
2227
|
+
success=False,
|
|
2228
|
+
message=f"Profile not found: {profile_name}\nAvailable: {available}",
|
|
2229
|
+
display_type="error"
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
async def _create_profile(
|
|
2233
|
+
self, name: str, api_url: str, model: str, temperature: float = 0.7
|
|
2234
|
+
) -> CommandResult:
|
|
2235
|
+
"""Create a new profile.
|
|
2236
|
+
|
|
2237
|
+
Args:
|
|
2238
|
+
name: Profile name.
|
|
2239
|
+
api_url: API endpoint URL.
|
|
2240
|
+
model: Model identifier.
|
|
2241
|
+
temperature: Sampling temperature.
|
|
2242
|
+
|
|
2243
|
+
Returns:
|
|
2244
|
+
Command result.
|
|
2245
|
+
"""
|
|
2246
|
+
profile = self.profile_manager.create_profile(
|
|
2247
|
+
name=name,
|
|
2248
|
+
api_url=api_url,
|
|
2249
|
+
model=model,
|
|
2250
|
+
temperature=temperature,
|
|
2251
|
+
description=f"Created via /profile create",
|
|
2252
|
+
save_to_config=True
|
|
2253
|
+
)
|
|
2254
|
+
if profile:
|
|
2255
|
+
return CommandResult(
|
|
2256
|
+
success=True,
|
|
2257
|
+
message=f"[ok] Created profile: {name}\n API: {api_url}\n Model: {model}\n Saved to config.json",
|
|
2258
|
+
display_type="success"
|
|
2259
|
+
)
|
|
2260
|
+
else:
|
|
2261
|
+
return CommandResult(
|
|
2262
|
+
success=False,
|
|
2263
|
+
message=f"[err] Failed to create profile. '{name}' may already exist.",
|
|
2264
|
+
display_type="error"
|
|
2265
|
+
)
|
|
2266
|
+
|
|
2267
|
+
async def handle_agent(self, command: SlashCommand) -> CommandResult:
|
|
2268
|
+
"""Handle /agent command.
|
|
2269
|
+
|
|
2270
|
+
Args:
|
|
2271
|
+
command: Parsed slash command.
|
|
2272
|
+
|
|
2273
|
+
Returns:
|
|
2274
|
+
Command execution result.
|
|
2275
|
+
"""
|
|
2276
|
+
try:
|
|
2277
|
+
if not self.agent_manager:
|
|
2278
|
+
return CommandResult(
|
|
2279
|
+
success=False,
|
|
2280
|
+
message="Agent manager not available",
|
|
2281
|
+
display_type="error"
|
|
2282
|
+
)
|
|
2283
|
+
|
|
2284
|
+
args = command.args or []
|
|
2285
|
+
|
|
2286
|
+
if not args or args[0] in ("list", "ls"):
|
|
2287
|
+
# Show agent selection modal
|
|
2288
|
+
return await self._show_agents_modal()
|
|
2289
|
+
elif args[0] == "clear":
|
|
2290
|
+
# Clear active agent
|
|
2291
|
+
self.agent_manager.clear_active_agent()
|
|
2292
|
+
return CommandResult(
|
|
2293
|
+
success=True,
|
|
2294
|
+
message="Cleared active agent, using default behavior",
|
|
2295
|
+
display_type="success"
|
|
2296
|
+
)
|
|
2297
|
+
else:
|
|
2298
|
+
# Switch to specified agent (direct command)
|
|
2299
|
+
agent_name = args[0]
|
|
2300
|
+
return await self._switch_agent(agent_name)
|
|
2301
|
+
|
|
2302
|
+
except Exception as e:
|
|
2303
|
+
self.logger.error(f"Error in agent command: {e}")
|
|
2304
|
+
return CommandResult(
|
|
2305
|
+
success=False,
|
|
2306
|
+
message=f"Error managing agents: {str(e)}",
|
|
2307
|
+
display_type="error"
|
|
2308
|
+
)
|
|
2309
|
+
|
|
2310
|
+
def _get_agents_modal_definition(self, skip_reload: bool = False) -> Optional[Dict[str, Any]]:
|
|
2311
|
+
"""Get modal definition for agent selection with default indicators.
|
|
2312
|
+
|
|
2313
|
+
Args:
|
|
2314
|
+
skip_reload: If True, don't reload from disk (use current state).
|
|
2315
|
+
|
|
2316
|
+
Returns:
|
|
2317
|
+
Modal definition dictionary, or None if no agents found.
|
|
2318
|
+
"""
|
|
2319
|
+
from ..utils.config_utils import get_all_default_agents
|
|
2320
|
+
|
|
2321
|
+
# Get all default agents
|
|
2322
|
+
default_agents = get_all_default_agents() # {"project": "coder", "global": "research"}
|
|
2323
|
+
project_default = default_agents.get("project")
|
|
2324
|
+
global_default = default_agents.get("global")
|
|
2325
|
+
|
|
2326
|
+
# Refresh agents from directories to pick up any changes
|
|
2327
|
+
if not skip_reload:
|
|
2328
|
+
self.agent_manager.refresh()
|
|
2329
|
+
|
|
2330
|
+
agents = self.agent_manager.list_agents()
|
|
2331
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2332
|
+
active_name = active_agent.name if active_agent else None
|
|
2333
|
+
|
|
2334
|
+
if not agents:
|
|
2335
|
+
return None
|
|
2336
|
+
|
|
2337
|
+
# Build agent list with indicators
|
|
2338
|
+
agent_items = []
|
|
2339
|
+
for agent in agents:
|
|
2340
|
+
is_active = agent.name == active_name
|
|
2341
|
+
is_project_default = agent.name == project_default
|
|
2342
|
+
is_global_default = agent.name == global_default
|
|
2343
|
+
|
|
2344
|
+
# Build source indicator (L=local only, G=global only, *=both)
|
|
2345
|
+
if agent.source == "local" and agent.overrides_global:
|
|
2346
|
+
source_char = "*"
|
|
2347
|
+
elif agent.source == "local":
|
|
2348
|
+
source_char = "L"
|
|
2349
|
+
else: # global
|
|
2350
|
+
source_char = "G"
|
|
2351
|
+
|
|
2352
|
+
# Build default indicator
|
|
2353
|
+
default_parts = []
|
|
2354
|
+
if is_project_default:
|
|
2355
|
+
default_parts.append("D")
|
|
2356
|
+
if is_global_default:
|
|
2357
|
+
default_parts.append("g")
|
|
2358
|
+
default_str = "".join(default_parts) if default_parts else " "
|
|
2359
|
+
|
|
2360
|
+
# Format: [active] source default - examples: [*G ] [ L] [ Gd]
|
|
2361
|
+
active_char = "*" if is_active else " "
|
|
2362
|
+
indicator = f"{active_char}{source_char}{default_str}"
|
|
2363
|
+
|
|
2364
|
+
skills = agent.list_skills()
|
|
2365
|
+
skill_count = f" ({len(skills)} skills)" if skills else ""
|
|
2366
|
+
description = agent.description or "No description"
|
|
2367
|
+
|
|
2368
|
+
agent_items.append({
|
|
2369
|
+
"name": f"[{indicator}] {agent.name}{skill_count}",
|
|
2370
|
+
"description": description,
|
|
2371
|
+
"agent_name": agent.name,
|
|
2372
|
+
"action": "select_agent",
|
|
2373
|
+
"is_active": is_active,
|
|
2374
|
+
"is_project_default": is_project_default,
|
|
2375
|
+
"is_global_default": is_global_default
|
|
2376
|
+
})
|
|
2377
|
+
|
|
2378
|
+
# Add clear option
|
|
2379
|
+
agent_items.append({
|
|
2380
|
+
"name": " [Clear Agent]",
|
|
2381
|
+
"description": "Use default system prompt behavior",
|
|
2382
|
+
"agent_name": None,
|
|
2383
|
+
"action": "clear_agent"
|
|
2384
|
+
})
|
|
2385
|
+
|
|
2386
|
+
# Management options
|
|
2387
|
+
management_items = [
|
|
2388
|
+
{
|
|
2389
|
+
"name": " [+] Create New Agent",
|
|
2390
|
+
"description": "Create a new agent with system prompt",
|
|
2391
|
+
"action": "create_agent_prompt"
|
|
2392
|
+
}
|
|
2393
|
+
]
|
|
2394
|
+
|
|
2395
|
+
return {
|
|
2396
|
+
"title": "Agents",
|
|
2397
|
+
"footer": "L=local G=global *=both | D=proj g=global | ↑↓ Enter",
|
|
2398
|
+
"width": 70,
|
|
2399
|
+
"height": 18,
|
|
2400
|
+
"sections": [
|
|
2401
|
+
{
|
|
2402
|
+
"title": f"Available Agents (active: {active_name or 'none'})",
|
|
2403
|
+
"commands": agent_items
|
|
2404
|
+
},
|
|
2405
|
+
{
|
|
2406
|
+
"title": "Management",
|
|
2407
|
+
"commands": management_items
|
|
2408
|
+
}
|
|
2409
|
+
],
|
|
2410
|
+
"actions": [
|
|
2411
|
+
{"key": "Enter", "label": "Select", "action": "select"},
|
|
2412
|
+
{"key": "d", "label": "Project Default", "action": "toggle_project_default"},
|
|
2413
|
+
{"key": "g", "label": "Global Default", "action": "toggle_global_default"},
|
|
2414
|
+
{"key": "e", "label": "Edit", "action": "edit_agent_prompt"},
|
|
2415
|
+
{"key": "r", "label": "Delete", "action": "delete_agent_prompt"},
|
|
2416
|
+
{"key": "Escape", "label": "Close", "action": "cancel"}
|
|
2417
|
+
]
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
async def _show_agents_modal(self) -> CommandResult:
|
|
2421
|
+
"""Show agent selection modal.
|
|
2422
|
+
|
|
2423
|
+
Returns:
|
|
2424
|
+
Command result with modal UI.
|
|
2425
|
+
"""
|
|
2426
|
+
modal_definition = self._get_agents_modal_definition()
|
|
2427
|
+
|
|
2428
|
+
if not modal_definition:
|
|
2429
|
+
return CommandResult(
|
|
2430
|
+
success=True,
|
|
2431
|
+
message="No agents found.\nCreate agents in .kollabor-cli/agents/<name>/system_prompt.md",
|
|
2432
|
+
display_type="info"
|
|
2433
|
+
)
|
|
2434
|
+
|
|
2435
|
+
return CommandResult(
|
|
2436
|
+
success=True,
|
|
2437
|
+
message="Select an agent",
|
|
2438
|
+
ui_config=UIConfig(
|
|
2439
|
+
type="modal",
|
|
2440
|
+
title=modal_definition["title"],
|
|
2441
|
+
width=modal_definition["width"],
|
|
2442
|
+
height=modal_definition["height"],
|
|
2443
|
+
modal_config=modal_definition
|
|
2444
|
+
),
|
|
2445
|
+
display_type="modal"
|
|
2446
|
+
)
|
|
2447
|
+
|
|
2448
|
+
async def _switch_agent(self, agent_name: str) -> CommandResult:
|
|
2449
|
+
"""Switch to a different agent.
|
|
2450
|
+
|
|
2451
|
+
Args:
|
|
2452
|
+
agent_name: Name of agent to switch to.
|
|
2453
|
+
|
|
2454
|
+
Returns:
|
|
2455
|
+
Command result.
|
|
2456
|
+
"""
|
|
2457
|
+
if self.agent_manager.set_active_agent(agent_name):
|
|
2458
|
+
# Rebuild system prompt for the new agent
|
|
2459
|
+
if self.llm_service:
|
|
2460
|
+
self.llm_service.rebuild_system_prompt()
|
|
2461
|
+
|
|
2462
|
+
agent = self.agent_manager.get_active_agent()
|
|
2463
|
+
skills = agent.list_skills()
|
|
2464
|
+
skill_info = f", {len(skills)} skills available" if skills else ""
|
|
2465
|
+
|
|
2466
|
+
# If agent has a preferred profile, mention it
|
|
2467
|
+
profile_info = ""
|
|
2468
|
+
if agent.profile:
|
|
2469
|
+
profile_info = f"\n Preferred profile: {agent.profile}"
|
|
2470
|
+
|
|
2471
|
+
return CommandResult(
|
|
2472
|
+
success=True,
|
|
2473
|
+
message=f"Switched to agent: {agent_name}{skill_info}{profile_info}",
|
|
2474
|
+
display_type="success"
|
|
2475
|
+
)
|
|
2476
|
+
else:
|
|
2477
|
+
available = ", ".join(self.agent_manager.get_agent_names())
|
|
2478
|
+
return CommandResult(
|
|
2479
|
+
success=False,
|
|
2480
|
+
message=f"Agent not found: {agent_name}\nAvailable: {available}",
|
|
2481
|
+
display_type="error"
|
|
2482
|
+
)
|
|
2483
|
+
|
|
2484
|
+
async def handle_skill(self, command: SlashCommand) -> CommandResult:
|
|
2485
|
+
"""Handle /skill command.
|
|
2486
|
+
|
|
2487
|
+
Args:
|
|
2488
|
+
command: Parsed slash command.
|
|
2489
|
+
|
|
2490
|
+
Returns:
|
|
2491
|
+
Command execution result.
|
|
2492
|
+
"""
|
|
2493
|
+
try:
|
|
2494
|
+
if not self.agent_manager:
|
|
2495
|
+
return CommandResult(
|
|
2496
|
+
success=False,
|
|
2497
|
+
message="Agent manager not available",
|
|
2498
|
+
display_type="error"
|
|
2499
|
+
)
|
|
2500
|
+
|
|
2501
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2502
|
+
if not active_agent:
|
|
2503
|
+
return CommandResult(
|
|
2504
|
+
success=False,
|
|
2505
|
+
message="No active agent. Use /agent <name> first.",
|
|
2506
|
+
display_type="error"
|
|
2507
|
+
)
|
|
2508
|
+
|
|
2509
|
+
args = command.args or []
|
|
2510
|
+
|
|
2511
|
+
if not args:
|
|
2512
|
+
# Show skill selection modal
|
|
2513
|
+
return await self._show_skills_modal()
|
|
2514
|
+
elif args[0] in ("list", "ls"):
|
|
2515
|
+
# Show skill selection modal
|
|
2516
|
+
return await self._show_skills_modal()
|
|
2517
|
+
elif args[0] == "load" and len(args) > 1:
|
|
2518
|
+
# Load skill
|
|
2519
|
+
skill_name = args[1]
|
|
2520
|
+
return await self._load_skill(skill_name)
|
|
2521
|
+
elif args[0] == "unload" and len(args) > 1:
|
|
2522
|
+
# Unload skill
|
|
2523
|
+
skill_name = args[1]
|
|
2524
|
+
return await self._unload_skill(skill_name)
|
|
2525
|
+
else:
|
|
2526
|
+
# Try to load skill by name directly
|
|
2527
|
+
skill_name = args[0]
|
|
2528
|
+
return await self._load_skill(skill_name)
|
|
2529
|
+
|
|
2530
|
+
except Exception as e:
|
|
2531
|
+
self.logger.error(f"Error in skill command: {e}")
|
|
2532
|
+
return CommandResult(
|
|
2533
|
+
success=False,
|
|
2534
|
+
message=f"Error managing skills: {str(e)}",
|
|
2535
|
+
display_type="error"
|
|
2536
|
+
)
|
|
2537
|
+
|
|
2538
|
+
def _get_skills_modal_definition(self, skip_reload: bool = False) -> Optional[Dict[str, Any]]:
|
|
2539
|
+
"""Get modal definition for skill selection.
|
|
2540
|
+
|
|
2541
|
+
Args:
|
|
2542
|
+
skip_reload: If True, don't reload from disk (use current state).
|
|
2543
|
+
|
|
2544
|
+
Returns:
|
|
2545
|
+
Modal definition dictionary, or None if no skills available.
|
|
2546
|
+
"""
|
|
2547
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2548
|
+
if not active_agent:
|
|
2549
|
+
return None
|
|
2550
|
+
|
|
2551
|
+
# Refresh agent from disk to pick up any changes (unless skipped)
|
|
2552
|
+
if not skip_reload:
|
|
2553
|
+
self.agent_manager.refresh()
|
|
2554
|
+
# Re-get active agent in case it was refreshed
|
|
2555
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2556
|
+
if not active_agent:
|
|
2557
|
+
return None
|
|
2558
|
+
|
|
2559
|
+
skills = active_agent.list_skills()
|
|
2560
|
+
active_skills = active_agent.active_skills
|
|
2561
|
+
|
|
2562
|
+
# Check project and global defaults
|
|
2563
|
+
from pathlib import Path
|
|
2564
|
+
import json
|
|
2565
|
+
|
|
2566
|
+
local_config = (self.agent_manager.local_agents_dir / active_agent.name / "agent.json"
|
|
2567
|
+
if self.agent_manager.local_agents_dir else None)
|
|
2568
|
+
global_config = self.agent_manager.global_agents_dir / active_agent.name / "agent.json"
|
|
2569
|
+
|
|
2570
|
+
project_defaults = set()
|
|
2571
|
+
global_defaults = set()
|
|
2572
|
+
|
|
2573
|
+
if local_config and local_config.exists():
|
|
2574
|
+
try:
|
|
2575
|
+
config_data = json.loads(local_config.read_text(encoding="utf-8"))
|
|
2576
|
+
project_defaults = set(config_data.get("default_skills", []))
|
|
2577
|
+
except Exception:
|
|
2578
|
+
pass
|
|
2579
|
+
|
|
2580
|
+
if global_config.exists():
|
|
2581
|
+
try:
|
|
2582
|
+
config_data = json.loads(global_config.read_text(encoding="utf-8"))
|
|
2583
|
+
global_defaults = set(config_data.get("default_skills", []))
|
|
2584
|
+
except Exception:
|
|
2585
|
+
pass
|
|
2586
|
+
|
|
2587
|
+
if not skills:
|
|
2588
|
+
return None
|
|
2589
|
+
|
|
2590
|
+
# Build skill list for modal
|
|
2591
|
+
skill_items = []
|
|
2592
|
+
for skill in skills:
|
|
2593
|
+
is_loaded = skill.name in active_skills
|
|
2594
|
+
is_proj_default = skill.name in project_defaults
|
|
2595
|
+
is_global_default = skill.name in global_defaults
|
|
2596
|
+
|
|
2597
|
+
# Show markers: [*] loaded, [d] proj default, [g] global default
|
|
2598
|
+
# Examples: [*dg] [*d ] [ g] [ ]
|
|
2599
|
+
loaded_char = "*" if is_loaded else " "
|
|
2600
|
+
proj_char = "d" if is_proj_default else " "
|
|
2601
|
+
global_char = "g" if is_global_default else " "
|
|
2602
|
+
marker = f"[{loaded_char}{proj_char}{global_char}]"
|
|
2603
|
+
|
|
2604
|
+
action = "unload_skill" if is_loaded else "load_skill"
|
|
2605
|
+
description = skill.description or f"Skill file: {skill.file_path.name}"
|
|
2606
|
+
|
|
2607
|
+
skill_items.append({
|
|
2608
|
+
"name": f"{marker} {skill.name}",
|
|
2609
|
+
"description": description,
|
|
2610
|
+
"skill_name": skill.name,
|
|
2611
|
+
"action": action,
|
|
2612
|
+
"loaded": is_loaded,
|
|
2613
|
+
"is_default": is_proj_default or is_global_default
|
|
2614
|
+
})
|
|
2615
|
+
|
|
2616
|
+
loaded_count = len(active_skills)
|
|
2617
|
+
total_count = len(skills)
|
|
2618
|
+
default_count = len(project_defaults | global_defaults)
|
|
2619
|
+
|
|
2620
|
+
# Management options
|
|
2621
|
+
management_items = [
|
|
2622
|
+
{
|
|
2623
|
+
"name": " [+] Create New Skill",
|
|
2624
|
+
"description": "Create a new skill file for this agent",
|
|
2625
|
+
"action": "create_skill_prompt"
|
|
2626
|
+
}
|
|
2627
|
+
]
|
|
2628
|
+
|
|
2629
|
+
return {
|
|
2630
|
+
"title": f"Skills - {active_agent.name}",
|
|
2631
|
+
"footer": "*=loaded d=proj g=global | ↑↓ Enter | d/g dflt | e r",
|
|
2632
|
+
"width": 70,
|
|
2633
|
+
"height": 18,
|
|
2634
|
+
"sections": [
|
|
2635
|
+
{
|
|
2636
|
+
"title": f"Available Skills ({loaded_count}/{total_count} loaded, {default_count} default)",
|
|
2637
|
+
"commands": skill_items
|
|
2638
|
+
},
|
|
2639
|
+
{
|
|
2640
|
+
"title": "Management",
|
|
2641
|
+
"commands": management_items
|
|
2642
|
+
}
|
|
2643
|
+
],
|
|
2644
|
+
"actions": [
|
|
2645
|
+
{"key": "Enter", "label": "Toggle", "action": "toggle"},
|
|
2646
|
+
{"key": "d", "label": "Project Default", "action": "toggle_default_skill"},
|
|
2647
|
+
{"key": "g", "label": "Global Default", "action": "toggle_global_default_skill"},
|
|
2648
|
+
{"key": "e", "label": "Edit", "action": "edit_skill_prompt"},
|
|
2649
|
+
{"key": "r", "label": "Delete", "action": "delete_skill_prompt"},
|
|
2650
|
+
{"key": "Escape", "label": "Close", "action": "cancel"}
|
|
2651
|
+
]
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
async def _show_skills_modal(self) -> CommandResult:
|
|
2655
|
+
"""Show skill selection modal for active agent.
|
|
2656
|
+
|
|
2657
|
+
Returns:
|
|
2658
|
+
Command result with modal UI.
|
|
2659
|
+
"""
|
|
2660
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2661
|
+
if not active_agent:
|
|
2662
|
+
return CommandResult(
|
|
2663
|
+
success=False,
|
|
2664
|
+
message="No active agent",
|
|
2665
|
+
display_type="error"
|
|
2666
|
+
)
|
|
2667
|
+
|
|
2668
|
+
modal_definition = self._get_skills_modal_definition()
|
|
2669
|
+
if not modal_definition:
|
|
2670
|
+
return CommandResult(
|
|
2671
|
+
success=True,
|
|
2672
|
+
message=f"Agent '{active_agent.name}' has no skills defined.\nAdd .md files to the agent directory to create skills.",
|
|
2673
|
+
display_type="info"
|
|
2674
|
+
)
|
|
2675
|
+
|
|
2676
|
+
return CommandResult(
|
|
2677
|
+
success=True,
|
|
2678
|
+
message="Select a skill to load/unload",
|
|
2679
|
+
ui_config=UIConfig(
|
|
2680
|
+
type="modal",
|
|
2681
|
+
title=modal_definition["title"],
|
|
2682
|
+
width=modal_definition["width"],
|
|
2683
|
+
height=modal_definition["height"],
|
|
2684
|
+
modal_config=modal_definition
|
|
2685
|
+
),
|
|
2686
|
+
display_type="modal"
|
|
2687
|
+
)
|
|
2688
|
+
|
|
2689
|
+
async def _load_skill(self, skill_name: str) -> CommandResult:
|
|
2690
|
+
"""Load a skill into active agent.
|
|
2691
|
+
|
|
2692
|
+
Args:
|
|
2693
|
+
skill_name: Name of skill to load.
|
|
2694
|
+
|
|
2695
|
+
Returns:
|
|
2696
|
+
Command result.
|
|
2697
|
+
"""
|
|
2698
|
+
agent = self.agent_manager.get_active_agent()
|
|
2699
|
+
skill = agent.get_skill(skill_name) if agent else None
|
|
2700
|
+
if skill and self.agent_manager.load_skill(skill_name):
|
|
2701
|
+
# Inject skill content as user message
|
|
2702
|
+
if self.llm_service:
|
|
2703
|
+
skill_message = f"## Skill: {skill_name}\n\n{skill.content}"
|
|
2704
|
+
self.llm_service._add_conversation_message("user", skill_message)
|
|
2705
|
+
return CommandResult(
|
|
2706
|
+
success=True,
|
|
2707
|
+
message=f"Loaded skill: {skill_name}",
|
|
2708
|
+
display_type="success"
|
|
2709
|
+
)
|
|
2710
|
+
else:
|
|
2711
|
+
active_agent = self.agent_manager.get_active_agent()
|
|
2712
|
+
available = ", ".join(s.name for s in active_agent.list_skills()) if active_agent else ""
|
|
2713
|
+
return CommandResult(
|
|
2714
|
+
success=False,
|
|
2715
|
+
message=f"Skill not found: {skill_name}\nAvailable: {available}",
|
|
2716
|
+
display_type="error"
|
|
2717
|
+
)
|
|
2718
|
+
|
|
2719
|
+
async def _unload_skill(self, skill_name: str) -> CommandResult:
|
|
2720
|
+
"""Unload a skill from active agent.
|
|
2721
|
+
|
|
2722
|
+
Args:
|
|
2723
|
+
skill_name: Name of skill to unload.
|
|
298
2724
|
|
|
2725
|
+
Returns:
|
|
2726
|
+
Command result.
|
|
2727
|
+
"""
|
|
2728
|
+
if self.agent_manager.unload_skill(skill_name):
|
|
2729
|
+
# Add message indicating skill was unloaded
|
|
2730
|
+
if self.llm_service:
|
|
2731
|
+
self.llm_service._add_conversation_message(
|
|
2732
|
+
"user",
|
|
2733
|
+
f"[Skill '{skill_name}' has been unloaded - please disregard its instructions]"
|
|
2734
|
+
)
|
|
2735
|
+
return CommandResult(
|
|
2736
|
+
success=True,
|
|
2737
|
+
message=f"Unloaded skill: {skill_name}",
|
|
2738
|
+
display_type="success"
|
|
2739
|
+
)
|
|
2740
|
+
else:
|
|
2741
|
+
return CommandResult(
|
|
2742
|
+
success=False,
|
|
2743
|
+
message=f"Skill not loaded: {skill_name}",
|
|
2744
|
+
display_type="error"
|
|
2745
|
+
)
|
|
299
2746
|
|
|
300
2747
|
async def _show_command_help(self, command_name: str) -> CommandResult:
|
|
301
2748
|
"""Show help for a specific command.
|
|
@@ -377,7 +2824,7 @@ Mode: {command_def.mode.value}"""
|
|
|
377
2824
|
width=80,
|
|
378
2825
|
modal_config={
|
|
379
2826
|
"sections": command_sections,
|
|
380
|
-
"footer": "
|
|
2827
|
+
"footer": "Esc/Enter close • /help <command> for details",
|
|
381
2828
|
"scrollable": True
|
|
382
2829
|
}
|
|
383
2830
|
),
|
|
@@ -467,13 +2914,13 @@ class SystemStatusUI:
|
|
|
467
2914
|
f"│ Plugins: {stats['plugins']} active │",
|
|
468
2915
|
f"│ Categories: {stats['categories']} in use │",
|
|
469
2916
|
"│ │",
|
|
470
|
-
"│ Event Bus: [
|
|
471
|
-
"│ Input Handler: [
|
|
472
|
-
"│ Terminal Renderer: [
|
|
2917
|
+
"│ Event Bus: [ok] Active │",
|
|
2918
|
+
"│ Input Handler: [ok] Running │",
|
|
2919
|
+
"│ Terminal Renderer: [ok] Active │",
|
|
473
2920
|
"│ │",
|
|
474
2921
|
"│ Memory Usage: ~ 45MB │",
|
|
475
2922
|
"│ Uptime: 00:15:32 │",
|
|
476
2923
|
"│ │",
|
|
477
2924
|
"╰─────────────────────────────────────────────────────────────╯",
|
|
478
2925
|
" ↑↓ navigate • Esc exit"
|
|
479
|
-
]
|
|
2926
|
+
]
|