kader 0.1.5__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {kader-0.1.5 → kader-1.0.0}/PKG-INFO +38 -1
  2. {kader-0.1.5 → kader-1.0.0}/README.md +36 -0
  3. {kader-0.1.5 → kader-1.0.0}/cli/app.py +98 -61
  4. kader-1.0.0/cli/app.tcss +309 -0
  5. {kader-0.1.5 → kader-1.0.0}/cli/utils.py +1 -6
  6. kader-1.0.0/cli/widgets/conversation.py +101 -0
  7. kader-1.0.0/examples/planner_executor_example.py +74 -0
  8. {kader-0.1.5 → kader-1.0.0}/examples/tools_example.py +63 -0
  9. {kader-0.1.5 → kader-1.0.0}/kader/__init__.py +2 -0
  10. {kader-0.1.5 → kader-1.0.0}/kader/agent/agents.py +8 -0
  11. {kader-0.1.5 → kader-1.0.0}/kader/agent/base.py +68 -5
  12. {kader-0.1.5 → kader-1.0.0}/kader/memory/types.py +60 -0
  13. kader-1.0.0/kader/prompts/__init__.py +17 -0
  14. kader-1.0.0/kader/prompts/agent_prompts.py +55 -0
  15. kader-1.0.0/kader/prompts/templates/executor_agent.j2 +70 -0
  16. kader-1.0.0/kader/prompts/templates/kader_planner.j2 +71 -0
  17. {kader-0.1.5 → kader-1.0.0}/kader/providers/ollama.py +2 -2
  18. {kader-0.1.5 → kader-1.0.0}/kader/tools/__init__.py +26 -0
  19. kader-1.0.0/kader/tools/agent.py +452 -0
  20. {kader-0.1.5 → kader-1.0.0}/kader/tools/filesys.py +1 -1
  21. {kader-0.1.5 → kader-1.0.0}/kader/tools/todo.py +43 -2
  22. kader-1.0.0/kader/utils/__init__.py +10 -0
  23. kader-1.0.0/kader/utils/checkpointer.py +371 -0
  24. kader-1.0.0/kader/utils/context_aggregator.py +347 -0
  25. kader-1.0.0/kader/workflows/__init__.py +13 -0
  26. kader-1.0.0/kader/workflows/base.py +71 -0
  27. kader-1.0.0/kader/workflows/planner_executor.py +251 -0
  28. {kader-0.1.5 → kader-1.0.0}/pyproject.toml +2 -1
  29. kader-1.0.0/tests/conftest.py +50 -0
  30. {kader-0.1.5 → kader-1.0.0}/tests/test_agent_logger_integration.py +21 -11
  31. {kader-0.1.5 → kader-1.0.0}/tests/test_todo_tool.py +58 -5
  32. kader-1.0.0/tests/tools/test_agent_tool.py +275 -0
  33. kader-1.0.0/tests/tools/test_agent_tool_persistence.py +213 -0
  34. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_filesystem_tools.py +3 -3
  35. {kader-0.1.5 → kader-1.0.0}/uv.lock +12 -1
  36. kader-0.1.5/cli/app.tcss +0 -664
  37. kader-0.1.5/cli/widgets/conversation.py +0 -55
  38. kader-0.1.5/kader/prompts/__init__.py +0 -9
  39. kader-0.1.5/kader/prompts/agent_prompts.py +0 -27
  40. kader-0.1.5/tests/conftest.py +0 -14
  41. {kader-0.1.5 → kader-1.0.0}/.github/workflows/ci.yml +0 -0
  42. {kader-0.1.5 → kader-1.0.0}/.github/workflows/release.yml +0 -0
  43. {kader-0.1.5 → kader-1.0.0}/.gitignore +0 -0
  44. {kader-0.1.5 → kader-1.0.0}/.python-version +0 -0
  45. {kader-0.1.5 → kader-1.0.0}/.qwen/QWEN.md +0 -0
  46. {kader-0.1.5 → kader-1.0.0}/.qwen/agents/technical-writer.md +0 -0
  47. {kader-0.1.5 → kader-1.0.0}/.qwen/agents/test-automation-specialist.md +0 -0
  48. {kader-0.1.5 → kader-1.0.0}/cli/README.md +0 -0
  49. {kader-0.1.5 → kader-1.0.0}/cli/__init__.py +0 -0
  50. {kader-0.1.5 → kader-1.0.0}/cli/__main__.py +0 -0
  51. {kader-0.1.5 → kader-1.0.0}/cli/widgets/__init__.py +0 -0
  52. {kader-0.1.5 → kader-1.0.0}/cli/widgets/confirmation.py +0 -0
  53. {kader-0.1.5 → kader-1.0.0}/cli/widgets/loading.py +0 -0
  54. {kader-0.1.5 → kader-1.0.0}/examples/.gitignore +0 -0
  55. {kader-0.1.5 → kader-1.0.0}/examples/README.md +0 -0
  56. {kader-0.1.5 → kader-1.0.0}/examples/memory_example.py +0 -0
  57. {kader-0.1.5 → kader-1.0.0}/examples/ollama_example.py +0 -0
  58. {kader-0.1.5 → kader-1.0.0}/examples/planning_agent_example.py +0 -0
  59. {kader-0.1.5 → kader-1.0.0}/examples/python_developer/main.py +0 -0
  60. {kader-0.1.5 → kader-1.0.0}/examples/python_developer/template.yaml +0 -0
  61. {kader-0.1.5 → kader-1.0.0}/examples/react_agent_example.py +0 -0
  62. {kader-0.1.5 → kader-1.0.0}/examples/simple_agent.py +0 -0
  63. {kader-0.1.5 → kader-1.0.0}/examples/todo_agent/main.py +0 -0
  64. {kader-0.1.5 → kader-1.0.0}/kader/agent/__init__.py +0 -0
  65. {kader-0.1.5 → kader-1.0.0}/kader/agent/logger.py +0 -0
  66. {kader-0.1.5 → kader-1.0.0}/kader/config.py +0 -0
  67. {kader-0.1.5 → kader-1.0.0}/kader/memory/__init__.py +0 -0
  68. {kader-0.1.5 → kader-1.0.0}/kader/memory/conversation.py +0 -0
  69. {kader-0.1.5 → kader-1.0.0}/kader/memory/session.py +0 -0
  70. {kader-0.1.5 → kader-1.0.0}/kader/memory/state.py +0 -0
  71. {kader-0.1.5 → kader-1.0.0}/kader/prompts/base.py +0 -0
  72. {kader-0.1.5 → kader-1.0.0}/kader/prompts/templates/planning_agent.j2 +0 -0
  73. {kader-0.1.5 → kader-1.0.0}/kader/prompts/templates/react_agent.j2 +0 -0
  74. {kader-0.1.5 → kader-1.0.0}/kader/providers/__init__.py +0 -0
  75. {kader-0.1.5 → kader-1.0.0}/kader/providers/base.py +0 -0
  76. {kader-0.1.5 → kader-1.0.0}/kader/providers/mock.py +0 -0
  77. {kader-0.1.5 → kader-1.0.0}/kader/tools/README.md +0 -0
  78. {kader-0.1.5 → kader-1.0.0}/kader/tools/base.py +0 -0
  79. {kader-0.1.5 → kader-1.0.0}/kader/tools/exec_commands.py +0 -0
  80. {kader-0.1.5 → kader-1.0.0}/kader/tools/filesystem.py +0 -0
  81. {kader-0.1.5 → kader-1.0.0}/kader/tools/protocol.py +0 -0
  82. {kader-0.1.5 → kader-1.0.0}/kader/tools/rag.py +0 -0
  83. {kader-0.1.5 → kader-1.0.0}/kader/tools/utils.py +0 -0
  84. {kader-0.1.5 → kader-1.0.0}/kader/tools/web.py +0 -0
  85. {kader-0.1.5 → kader-1.0.0}/tests/providers/test_mock.py +0 -0
  86. {kader-0.1.5 → kader-1.0.0}/tests/providers/test_ollama.py +0 -0
  87. {kader-0.1.5 → kader-1.0.0}/tests/providers/test_providers_base.py +0 -0
  88. {kader-0.1.5 → kader-1.0.0}/tests/test_agent_logger.py +0 -0
  89. {kader-0.1.5 → kader-1.0.0}/tests/test_base_agent.py +0 -0
  90. {kader-0.1.5 → kader-1.0.0}/tests/test_file_memory.py +0 -0
  91. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_exec_commands.py +0 -0
  92. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_filesys_tools.py +0 -0
  93. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_rag.py +0 -0
  94. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_tools_base.py +0 -0
  95. {kader-0.1.5 → kader-1.0.0}/tests/tools/test_web.py +0 -0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kader
3
- Version: 0.1.5
3
+ Version: 1.0.0
4
4
  Summary: kader coding agent
5
5
  Requires-Python: >=3.11
6
+ Requires-Dist: aiofiles>=25.1.0
6
7
  Requires-Dist: faiss-cpu>=1.9.0
7
8
  Requires-Dist: jinja2>=3.1.6
8
9
  Requires-Dist: loguru>=0.7.3
@@ -32,6 +33,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
32
33
  - 🔄 **ReAct Agent Framework** - Reasoning and Acting agent architecture
33
34
  - 🗂️ **File System Tools** - Read, write, search, and edit files
34
35
  - 🔍 **Planning Agent** - Task planning and execution capabilities
36
+ - 🤝 **Agent-As-Tool** - Spawn sub-agents for specific tasks with isolated memory
35
37
 
36
38
  ## Installation
37
39
 
@@ -177,6 +179,40 @@ Kader provides several agent types:
177
179
  - **PlanningAgent**: Agent that plans multi-step tasks
178
180
  - **BaseAgent**: Base agent class for creating custom agents
179
181
 
182
+ ### Agent-As-Tool (AgentTool)
183
+
184
+ The `AgentTool` allows you to wrap a `ReActAgent` as a callable tool, enabling agents to spawn sub-agents for specific tasks with isolated memory contexts.
185
+
186
+ ```python
187
+ from kader.tools import AgentTool
188
+
189
+ # Autonomous execution (runs without pausing for confirmation)
190
+ autonomous_agent = AgentTool(
191
+ name="research_agent",
192
+ description="Research topics autonomously",
193
+ interrupt_before_tool=False,
194
+ )
195
+ result = autonomous_agent.execute(task="Find info about topic X")
196
+
197
+ # Interactive execution (pauses for user confirmation before each tool)
198
+ def my_callback(tool_call_dict, llm_content=None):
199
+ user_input = input("Execute? [y/n]: ")
200
+ return (user_input.lower() == 'y', None)
201
+
202
+ interactive_agent = AgentTool(
203
+ name="interactive_agent",
204
+ interrupt_before_tool=True,
205
+ tool_confirmation_callback=my_callback,
206
+ )
207
+ result = interactive_agent.execute(task="Analyze data and generate report")
208
+ ```
209
+
210
+ **Key Features:**
211
+ - Each sub-agent has isolated memory (separate `SlidingWindowConversationManager`)
212
+ - Default tools included: filesystem, web search, command executor
213
+ - Optional `interrupt_before_tool` for user confirmation before tool execution
214
+ - Task completes when the agent returns its final response
215
+
180
216
  ### Memory Management
181
217
 
182
218
  Kader's memory system includes:
@@ -194,6 +230,7 @@ Kader includes a rich set of tools:
194
230
  - **Command Executor**: Execute shell commands safely
195
231
  - **Web Tools**: Search and fetch web content
196
232
  - **RAG Tools**: Retrieval Augmented Generation capabilities
233
+ - **AgentTool**: Spawn sub-agents for specific tasks
197
234
 
198
235
  ## Examples
199
236
 
@@ -15,6 +15,7 @@ Kader is an intelligent coding agent designed to assist with software developmen
15
15
  - 🔄 **ReAct Agent Framework** - Reasoning and Acting agent architecture
16
16
  - 🗂️ **File System Tools** - Read, write, search, and edit files
17
17
  - 🔍 **Planning Agent** - Task planning and execution capabilities
18
+ - 🤝 **Agent-As-Tool** - Spawn sub-agents for specific tasks with isolated memory
18
19
 
19
20
  ## Installation
20
21
 
@@ -160,6 +161,40 @@ Kader provides several agent types:
160
161
  - **PlanningAgent**: Agent that plans multi-step tasks
161
162
  - **BaseAgent**: Base agent class for creating custom agents
162
163
 
164
+ ### Agent-As-Tool (AgentTool)
165
+
166
+ The `AgentTool` allows you to wrap a `ReActAgent` as a callable tool, enabling agents to spawn sub-agents for specific tasks with isolated memory contexts.
167
+
168
+ ```python
169
+ from kader.tools import AgentTool
170
+
171
+ # Autonomous execution (runs without pausing for confirmation)
172
+ autonomous_agent = AgentTool(
173
+ name="research_agent",
174
+ description="Research topics autonomously",
175
+ interrupt_before_tool=False,
176
+ )
177
+ result = autonomous_agent.execute(task="Find info about topic X")
178
+
179
+ # Interactive execution (pauses for user confirmation before each tool)
180
+ def my_callback(tool_call_dict, llm_content=None):
181
+ user_input = input("Execute? [y/n]: ")
182
+ return (user_input.lower() == 'y', None)
183
+
184
+ interactive_agent = AgentTool(
185
+ name="interactive_agent",
186
+ interrupt_before_tool=True,
187
+ tool_confirmation_callback=my_callback,
188
+ )
189
+ result = interactive_agent.execute(task="Analyze data and generate report")
190
+ ```
191
+
192
+ **Key Features:**
193
+ - Each sub-agent has isolated memory (separate `SlidingWindowConversationManager`)
194
+ - Default tools included: filesystem, web search, command executor
195
+ - Optional `interrupt_before_tool` for user confirmation before tool execution
196
+ - Task completes when the agent returns its final response
197
+
163
198
  ### Memory Management
164
199
 
165
200
  Kader's memory system includes:
@@ -177,6 +212,7 @@ Kader includes a rich set of tools:
177
212
  - **Command Executor**: Execute shell commands safely
178
213
  - **Web Tools**: Search and fetch web content
179
214
  - **RAG Tools**: Retrieval Augmented Generation capabilities
215
+ - **AgentTool**: Spawn sub-agents for specific tasks
180
216
 
181
217
  ## Examples
182
218
 
@@ -1,7 +1,9 @@
1
1
  """Kader CLI - Modern Vibe Coding CLI with Textual."""
2
2
 
3
3
  import asyncio
4
+ import atexit
4
5
  import threading
6
+ from concurrent.futures import ThreadPoolExecutor
5
7
  from importlib.metadata import version as get_version
6
8
  from pathlib import Path
7
9
  from typing import Optional
@@ -18,18 +20,15 @@ from textual.widgets import (
18
20
  Tree,
19
21
  )
20
22
 
21
- from kader.agent.agents import ReActAgent
22
23
  from kader.memory import (
23
24
  FileSessionManager,
24
25
  MemoryConfig,
25
- SlidingWindowConversationManager,
26
26
  )
27
- from kader.tools import get_default_registry
27
+ from kader.workflows import PlannerExecutorWorkflow
28
28
 
29
29
  from .utils import (
30
30
  DEFAULT_MODEL,
31
31
  HELP_TEXT,
32
- THEME_NAMES,
33
32
  )
34
33
  from .widgets import ConversationView, InlineSelector, LoadingSpinner, ModelSelector
35
34
 
@@ -51,7 +50,6 @@ Type a message below to start chatting, or use one of the commands:
51
50
 
52
51
  - `/help` - Show available commands
53
52
  - `/models` - View available LLM models
54
- - `/theme` - Change the color theme
55
53
  - `/clear` - Clear the conversation
56
54
  - `/save` - Save current session
57
55
  - `/load` - Load a saved session
@@ -83,7 +81,6 @@ class KaderApp(App):
83
81
  BINDINGS = [
84
82
  Binding("ctrl+q", "quit", "Quit"),
85
83
  Binding("ctrl+l", "clear", "Clear"),
86
- Binding("ctrl+t", "cycle_theme", "Theme"),
87
84
  Binding("ctrl+s", "save_session", "Save"),
88
85
  Binding("ctrl+r", "refresh_tree", "Refresh"),
89
86
  Binding("tab", "focus_next", "Next", show=False),
@@ -92,7 +89,6 @@ class KaderApp(App):
92
89
 
93
90
  def __init__(self) -> None:
94
91
  super().__init__()
95
- self._current_theme_index = 0
96
92
  self._is_processing = False
97
93
  self._current_model = DEFAULT_MODEL
98
94
  self._current_session_id: str | None = None
@@ -107,22 +103,79 @@ class KaderApp(App):
107
103
  self._model_selector: Optional[ModelSelector] = None
108
104
  self._update_info: Optional[str] = None # Latest version if update available
109
105
 
110
- self._agent = self._create_agent(self._current_model)
106
+ # Dedicated thread pool for agent invocation (isolated from default pool)
107
+ self._agent_executor = ThreadPoolExecutor(
108
+ max_workers=2, thread_name_prefix="kader_agent"
109
+ )
110
+ # Ensure executor is properly shut down on exit
111
+ atexit.register(self._agent_executor.shutdown, wait=False)
112
+
113
+ self._workflow = self._create_workflow(self._current_model)
111
114
 
112
- def _create_agent(self, model_name: str) -> ReActAgent:
113
- """Create a new ReActAgent with the specified model."""
114
- registry = get_default_registry()
115
- memory = SlidingWindowConversationManager(window_size=10)
116
- return ReActAgent(
115
+ def _create_workflow(self, model_name: str) -> PlannerExecutorWorkflow:
116
+ """Create a new PlannerExecutorWorkflow with the specified model."""
117
+ return PlannerExecutorWorkflow(
117
118
  name="kader_cli",
118
- tools=registry,
119
- memory=memory,
120
119
  model_name=model_name,
121
- use_persistence=True,
122
120
  interrupt_before_tool=True,
123
121
  tool_confirmation_callback=self._tool_confirmation_callback,
122
+ direct_execution_callback=self._direct_execution_callback,
123
+ tool_execution_result_callback=self._tool_execution_result_callback,
124
+ use_persistence=True,
125
+ executor_names=["executor"],
126
+ )
127
+
128
+ def _direct_execution_callback(self, message: str, tool_name: str) -> None:
129
+ """
130
+ Callback for direct execution tools - called from agent thread.
131
+
132
+ Shows a message in the conversation view without blocking for confirmation.
133
+ """
134
+ # Schedule message display on main thread
135
+ self.call_from_thread(self._show_direct_execution_message, message, tool_name)
136
+
137
+ def _show_direct_execution_message(self, message: str, tool_name: str) -> None:
138
+ """Show a direct execution message in the conversation view."""
139
+ try:
140
+ conversation = self.query_one("#conversation-view", ConversationView)
141
+ # User-friendly message showing the tool is executing
142
+ friendly_message = f"[>] Executing {tool_name}..."
143
+ conversation.add_message(friendly_message, "assistant")
144
+ conversation.scroll_end()
145
+ except Exception:
146
+ pass
147
+
148
+ def _tool_execution_result_callback(
149
+ self, tool_name: str, success: bool, result: str
150
+ ) -> None:
151
+ """
152
+ Callback for tool execution results - called from agent thread.
153
+
154
+ Updates the conversation view with the execution result.
155
+ """
156
+ # Schedule result display on main thread
157
+ self.call_from_thread(
158
+ self._show_tool_execution_result, tool_name, success, result
124
159
  )
125
160
 
161
+ def _show_tool_execution_result(
162
+ self, tool_name: str, success: bool, result: str
163
+ ) -> None:
164
+ """Show the tool execution result in the conversation view."""
165
+ try:
166
+ conversation = self.query_one("#conversation-view", ConversationView)
167
+ if success:
168
+ # User-friendly success message
169
+ friendly_message = f"(+) {tool_name} completed successfully"
170
+ else:
171
+ # User-friendly error message with truncated result
172
+ error_preview = result[:100] + "..." if len(result) > 100 else result
173
+ friendly_message = f"(-) {tool_name} failed: {error_preview}"
174
+ conversation.add_message(friendly_message, "assistant")
175
+ conversation.scroll_end()
176
+ except Exception:
177
+ pass
178
+
126
179
  def _tool_confirmation_callback(self, message: str) -> tuple[bool, Optional[str]]:
127
180
  """
128
181
  Callback for tool confirmation - called from agent thread.
@@ -139,7 +192,10 @@ class KaderApp(App):
139
192
 
140
193
  # Wait for user response (blocking in agent thread)
141
194
  # This is safe because we're in a background thread
142
- self._confirmation_event.wait()
195
+ # Timeout after 5 minutes to prevent indefinite blocking
196
+ if not self._confirmation_event.wait(timeout=300):
197
+ # Timeout occurred - decline tool execution gracefully
198
+ return (False, "Tool confirmation timed out after 5 minutes")
143
199
 
144
200
  # Return the result
145
201
  return self._confirmation_result
@@ -187,7 +243,8 @@ class KaderApp(App):
187
243
  if event.confirmed:
188
244
  if tool_message:
189
245
  conversation.add_message(tool_message, "assistant")
190
- conversation.add_message("(+) Executing tool...", "assistant")
246
+ # Show executing message - will be updated by result callback
247
+ conversation.add_message("[>] Executing tool...", "assistant")
191
248
  # Restart spinner
192
249
  try:
193
250
  spinner = self.query_one(LoadingSpinner)
@@ -253,7 +310,7 @@ class KaderApp(App):
253
310
  # Update model and recreate agent
254
311
  old_model = self._current_model
255
312
  self._current_model = event.model
256
- self._agent = self._create_agent(self._current_model)
313
+ self._workflow = self._create_workflow(self._current_model)
257
314
 
258
315
  conversation.add_message(
259
316
  f"(+) Model changed from `{old_model}` to `{self._current_model}`",
@@ -433,16 +490,10 @@ Please resize your terminal."""
433
490
  conversation.add_message(HELP_TEXT, "assistant")
434
491
  elif cmd == "/models":
435
492
  await self._show_model_selector(conversation)
436
- elif cmd == "/theme":
437
- self._cycle_theme()
438
- theme_name = THEME_NAMES[self._current_theme_index]
439
- conversation.add_message(
440
- f"{{~}} Theme changed to **{theme_name}**!", "assistant"
441
- )
442
493
  elif cmd == "/clear":
443
494
  conversation.clear_messages()
444
- self._agent.memory.clear()
445
- self._agent.provider.reset_tracking() # Reset usage/cost tracking
495
+ self._workflow.planner.memory.clear()
496
+ self._workflow.planner.provider.reset_tracking() # Reset usage/cost tracking
446
497
  self._current_session_id = None
447
498
  self.notify("Conversation cleared!", severity="information")
448
499
  elif cmd == "/save":
@@ -472,7 +523,7 @@ Please resize your terminal."""
472
523
  )
473
524
 
474
525
  async def _handle_chat(self, message: str) -> None:
475
- """Handle regular chat messages with ReActAgent."""
526
+ """Handle regular chat messages with PlannerExecutorWorkflow."""
476
527
  if self._is_processing:
477
528
  self.notify("Please wait for the current response...", severity="warning")
478
529
  return
@@ -500,16 +551,21 @@ Please resize your terminal."""
500
551
  spinner = self.query_one(LoadingSpinner)
501
552
 
502
553
  try:
503
- # Run the agent invoke in a thread
554
+ # Run the workflow in a dedicated thread pool
504
555
  loop = asyncio.get_event_loop()
505
556
  response = await loop.run_in_executor(
506
- None, lambda: self._agent.invoke(message)
557
+ self._agent_executor, lambda: self._workflow.run(message)
507
558
  )
508
559
 
509
560
  # Hide spinner and show response (this runs on main thread via await)
510
561
  spinner.stop()
511
- if response and response.content:
512
- conversation.add_message(response.content, "assistant")
562
+ if response:
563
+ conversation.add_message(
564
+ response,
565
+ "assistant",
566
+ model_name=self._workflow.planner.provider.model,
567
+ usage_cost=self._workflow.planner.provider.total_cost.total_cost,
568
+ )
513
569
 
514
570
  except Exception as e:
515
571
  spinner.stop()
@@ -522,34 +578,13 @@ Please resize your terminal."""
522
578
  # Auto-refresh directory tree in case agent created/modified files
523
579
  self._refresh_directory_tree()
524
580
 
525
- def _cycle_theme(self) -> None:
526
- """Cycle through available themes."""
527
- # Remove current theme class if it's not dark
528
- current_theme = THEME_NAMES[self._current_theme_index]
529
- if current_theme != "dark":
530
- self.remove_class(f"theme-{current_theme}")
531
-
532
- # Move to next theme
533
- self._current_theme_index = (self._current_theme_index + 1) % len(THEME_NAMES)
534
- new_theme = THEME_NAMES[self._current_theme_index]
535
-
536
- # Apply new theme class (dark is default, no class needed)
537
- if new_theme != "dark":
538
- self.add_class(f"theme-{new_theme}")
539
-
540
581
  def action_clear(self) -> None:
541
582
  """Clear the conversation (Ctrl+L)."""
542
583
  conversation = self.query_one("#conversation-view", ConversationView)
543
584
  conversation.clear_messages()
544
- self._agent.memory.clear()
585
+ self._workflow.planner.memory.clear()
545
586
  self.notify("Conversation cleared!", severity="information")
546
587
 
547
- def action_cycle_theme(self) -> None:
548
- """Cycle theme (Ctrl+T)."""
549
- self._cycle_theme()
550
- theme_name = THEME_NAMES[self._current_theme_index]
551
- self.notify(f"Theme: {theme_name}", severity="information")
552
-
553
588
  def action_save_session(self) -> None:
554
589
  """Save session (Ctrl+S)."""
555
590
  conversation = self.query_one("#conversation-view", ConversationView)
@@ -579,8 +614,10 @@ Please resize your terminal."""
579
614
  session = self._session_manager.create_session("kader_cli")
580
615
  self._current_session_id = session.session_id
581
616
 
582
- # Get messages from agent memory and save
583
- messages = [msg.message for msg in self._agent.memory.get_messages()]
617
+ # Get messages from planner memory and save
618
+ messages = [
619
+ msg.message for msg in self._workflow.planner.memory.get_messages()
620
+ ]
584
621
  self._session_manager.save_conversation(self._current_session_id, messages)
585
622
 
586
623
  conversation.add_message(
@@ -611,11 +648,11 @@ Please resize your terminal."""
611
648
 
612
649
  # Clear current state
613
650
  conversation.clear_messages()
614
- self._agent.memory.clear()
651
+ self._workflow.planner.memory.clear()
615
652
 
616
653
  # Add loaded messages to memory and UI
617
654
  for msg in messages:
618
- self._agent.memory.add_message(msg)
655
+ self._workflow.planner.memory.add_message(msg)
619
656
  role = msg.get("role", "user")
620
657
  content = msg.get("content", "")
621
658
  if role in ["user", "assistant"] and content:
@@ -664,9 +701,9 @@ Please resize your terminal."""
664
701
  """Display LLM usage costs."""
665
702
  try:
666
703
  # Get cost and usage from the provider
667
- cost = self._agent.provider.total_cost
668
- usage = self._agent.provider.total_usage
669
- model = self._agent.provider.model
704
+ cost = self._workflow.planner.provider.total_cost
705
+ usage = self._workflow.planner.provider.total_usage
706
+ model = self._workflow.planner.provider.model
670
707
 
671
708
  lines = [
672
709
  "## Usage Costs ($)\n",