kader 2.6.2__tar.gz → 2.6.4__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.
- {kader-2.6.2 → kader-2.6.4}/PKG-INFO +13 -1
- {kader-2.6.2 → kader-2.6.4}/README.md +12 -0
- {kader-2.6.2 → kader-2.6.4}/cli/app.py +22 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/exec_commands.py +85 -27
- {kader-2.6.2 → kader-2.6.4}/kader/workflows/planner_executor.py +8 -1
- {kader-2.6.2 → kader-2.6.4}/pyproject.toml +1 -1
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_exec_commands.py +81 -69
- {kader-2.6.2 → kader-2.6.4}/uv.lock +1 -1
- {kader-2.6.2 → kader-2.6.4}/.github/workflows/ci.yml +0 -0
- {kader-2.6.2 → kader-2.6.4}/.github/workflows/release.yml +0 -0
- {kader-2.6.2 → kader-2.6.4}/.gitignore +0 -0
- {kader-2.6.2 → kader-2.6.4}/.kader/KADER.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.kader/skills/contributing-to-kader/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.kader/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.kader/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.kader/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/.opencode/skills/contributing-to-kader/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.opencode/skills/contributing-to-kader/assets/contributor_checklist.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.opencode/skills/contributing-to-kader/references/kader_agent_instructions.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/.opencode/skills/contributing-to-kader/scripts/dev_helper.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/.python-version +0 -0
- {kader-2.6.2 → kader-2.6.4}/AGENTS.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/CONTRIBUTING.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/LICENSE +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/cli_integration.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/cli_message_flow.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/cli_tool_confirmation.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/memory_flow.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/planner_executor_sequence.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/architecture/planner_executor_workflow.mmd +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/code.html +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/code1.html +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/code2.html +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/code3.html +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/screen.png +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/screen1.png +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/screen2.png +0 -0
- {kader-2.6.2 → kader-2.6.4}/assets/design/v2/screen3.png +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/README.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/__main__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/commands/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/commands/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/commands/initialize.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/llm_factory.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/cli/utils.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/.gitignore +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/README.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/anthropic_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/google_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/memory_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/mistral_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/ollama_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/openai_compatible_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/planner_executor_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/planning_agent_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/python_developer/main.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/python_developer/template.yaml +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/react_agent_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/simple_agent.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/hello_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/react_agent.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/calculator/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/calculator/scripts/calculate.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/github/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/hello/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/hello/scripts/hello.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/joke/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/skills/skills/visualization/SKILL.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/todo_agent/main.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/examples/tools_example.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/README.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/agent/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/agent/agents.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/agent/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/agent/logger.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/config.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/compression.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/conversation.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/session.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/state.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/summarization.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/memory/types.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/agent_prompts.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/cli_prompts.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/templates/executor_agent.j2 +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/templates/init_command_prompt.j2 +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/templates/kader_planner.j2 +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/templates/planning_agent.j2 +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/prompts/templates/react_agent.j2 +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/anthropic.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/google.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/mistral.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/mock.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/ollama.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/providers/openai_compatible.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/README.md +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/agent.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/filesys.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/filesystem.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/protocol.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/rag.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/skills.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/todo.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/utils.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/tools/web.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/utils/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/utils/checkpointer.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/utils/context_aggregator.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/utils/ignore.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/workflows/__init__.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/kader/workflows/base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/conftest.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_anthropic_provider.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_google.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_mistral_provider.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_mock.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_ollama.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_openai_compatible_provider.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/providers/test_providers_base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_agent_logger.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_agent_logger_integration.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_base_agent.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_file_memory.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_hierarchical_memory.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/test_todo_tool.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_agent_tool.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_agent_tool_persistence.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_agent_tool_skills.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_filesys_tools.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_filesystem_tools.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_rag.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_skills.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_tools_base.py +0 -0
- {kader-2.6.2 → kader-2.6.4}/tests/tools/test_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kader
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.4
|
|
4
4
|
Summary: kader coding agent
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -69,6 +69,18 @@ uv sync
|
|
|
69
69
|
uv run python -m cli
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Using uv tool
|
|
73
|
+
|
|
74
|
+
With uv tool, you can install Kader globally and run it directly with the `kader` command:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Install Kader globally using uv tool
|
|
78
|
+
uv tool install kader
|
|
79
|
+
|
|
80
|
+
# Run the CLI
|
|
81
|
+
kader
|
|
82
|
+
```
|
|
83
|
+
|
|
72
84
|
### Using pip
|
|
73
85
|
|
|
74
86
|
```bash
|
|
@@ -43,6 +43,18 @@ uv sync
|
|
|
43
43
|
uv run python -m cli
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
### Using uv tool
|
|
47
|
+
|
|
48
|
+
With uv tool, you can install Kader globally and run it directly with the `kader` command:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Install Kader globally using uv tool
|
|
52
|
+
uv tool install kader
|
|
53
|
+
|
|
54
|
+
# Run the CLI
|
|
55
|
+
kader
|
|
56
|
+
```
|
|
57
|
+
|
|
46
58
|
### Using pip
|
|
47
59
|
|
|
48
60
|
```bash
|
|
@@ -133,11 +133,33 @@ class KaderApp:
|
|
|
133
133
|
self.console.print(
|
|
134
134
|
rf" [kader.green]\[+] {tool_name}[/kader.green] completed successfully"
|
|
135
135
|
)
|
|
136
|
+
if tool_name == "execute_command" and ":\n" in result:
|
|
137
|
+
output = result.split(":\n", 1)[1]
|
|
138
|
+
self.console.print()
|
|
139
|
+
self.console.print(
|
|
140
|
+
Panel(
|
|
141
|
+
output.strip(),
|
|
142
|
+
title="[kader.orange]Command Output[/kader.orange]",
|
|
143
|
+
border_style="dark_orange",
|
|
144
|
+
padding=(0, 1),
|
|
145
|
+
)
|
|
146
|
+
)
|
|
136
147
|
else:
|
|
137
148
|
error_preview = result[:100] + "..." if len(result) > 100 else result
|
|
138
149
|
self.console.print(
|
|
139
150
|
rf" [kader.red]\[-] {tool_name}[/kader.red] failed: {error_preview}"
|
|
140
151
|
)
|
|
152
|
+
if tool_name == "execute_command" and ":\n" in result:
|
|
153
|
+
output = result.split(":\n", 1)[1]
|
|
154
|
+
self.console.print()
|
|
155
|
+
self.console.print(
|
|
156
|
+
Panel(
|
|
157
|
+
output.strip(),
|
|
158
|
+
title="[kader.red]Command Output/Error[/kader.red]",
|
|
159
|
+
border_style="red",
|
|
160
|
+
padding=(0, 1),
|
|
161
|
+
)
|
|
162
|
+
)
|
|
141
163
|
self._start_spinner()
|
|
142
164
|
|
|
143
165
|
def _tool_confirmation_callback(self, message: str) -> tuple[bool, Optional[str]]:
|
|
@@ -173,9 +173,87 @@ class CommandExecutorTool(BaseTool[str]):
|
|
|
173
173
|
|
|
174
174
|
return True, ""
|
|
175
175
|
|
|
176
|
+
def _stream_command_output(self, command: str, timeout: int) -> tuple[int, str]:
|
|
177
|
+
"""
|
|
178
|
+
Streams the output of a shell command and collects it.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
command: The command to execute
|
|
182
|
+
timeout: Timeout in seconds
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Tuple of (return_code, captured_output)
|
|
186
|
+
"""
|
|
187
|
+
import time
|
|
188
|
+
from queue import Empty, Queue
|
|
189
|
+
from threading import Thread
|
|
190
|
+
|
|
191
|
+
# Prepare kwargs for Popen based on OS
|
|
192
|
+
kwargs = {
|
|
193
|
+
"stdout": subprocess.PIPE,
|
|
194
|
+
"stderr": subprocess.STDOUT,
|
|
195
|
+
"text": True,
|
|
196
|
+
"shell": True,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if self._host_os != "windows":
|
|
200
|
+
kwargs["executable"] = self._shell
|
|
201
|
+
|
|
202
|
+
process = subprocess.Popen(command, **kwargs)
|
|
203
|
+
captured_output = []
|
|
204
|
+
|
|
205
|
+
# Use a queue to capture output asynchronously for timeout handling
|
|
206
|
+
q = Queue()
|
|
207
|
+
|
|
208
|
+
def enqueue_output(out, queue):
|
|
209
|
+
for line in out:
|
|
210
|
+
queue.put(line)
|
|
211
|
+
try:
|
|
212
|
+
out.close()
|
|
213
|
+
except AttributeError:
|
|
214
|
+
pass # mock lists in tests might not have close()
|
|
215
|
+
|
|
216
|
+
t = Thread(target=enqueue_output, args=(process.stdout, q))
|
|
217
|
+
t.daemon = True
|
|
218
|
+
t.start()
|
|
219
|
+
|
|
220
|
+
start_time = time.time()
|
|
221
|
+
|
|
222
|
+
while True:
|
|
223
|
+
# Check for timeout
|
|
224
|
+
if timeout > 0 and time.time() - start_time > timeout:
|
|
225
|
+
process.terminate()
|
|
226
|
+
try:
|
|
227
|
+
process.wait(timeout=1)
|
|
228
|
+
except subprocess.TimeoutExpired:
|
|
229
|
+
process.kill()
|
|
230
|
+
raise subprocess.TimeoutExpired(
|
|
231
|
+
command, timeout, "".join(captured_output).encode()
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Read line with a short timeout to allow checking overall execution timeout
|
|
236
|
+
line = q.get(timeout=0.1)
|
|
237
|
+
|
|
238
|
+
# Collect output
|
|
239
|
+
captured_output.append(line)
|
|
240
|
+
except Empty:
|
|
241
|
+
# Queue is empty, check if process indicates it's done
|
|
242
|
+
# Note: t.is_alive() is checked because poll() might return before stdout is fully read
|
|
243
|
+
if process.poll() is not None and not t.is_alive():
|
|
244
|
+
break
|
|
245
|
+
|
|
246
|
+
# Ensure thread finished
|
|
247
|
+
t.join(timeout=1)
|
|
248
|
+
|
|
249
|
+
# Ensure process finished
|
|
250
|
+
process.wait()
|
|
251
|
+
|
|
252
|
+
return process.returncode, "".join(captured_output).strip()
|
|
253
|
+
|
|
176
254
|
def execute(self, command: str, timeout: int = 30) -> str:
|
|
177
255
|
"""
|
|
178
|
-
Execute a command on the host system.
|
|
256
|
+
Execute a command on the host system and stream its output.
|
|
179
257
|
|
|
180
258
|
Args:
|
|
181
259
|
command: The command to execute
|
|
@@ -190,39 +268,19 @@ class CommandExecutorTool(BaseTool[str]):
|
|
|
190
268
|
if not is_valid:
|
|
191
269
|
return f"Validation Error: {reason}"
|
|
192
270
|
|
|
193
|
-
# Execute the command
|
|
194
|
-
|
|
195
|
-
# On Windows, use shell=True to allow for more complex commands
|
|
196
|
-
result = subprocess.run(
|
|
197
|
-
command, shell=True, capture_output=True, text=True, timeout=timeout
|
|
198
|
-
)
|
|
199
|
-
else:
|
|
200
|
-
# On Unix-like systems, use the appropriate shell
|
|
201
|
-
result = subprocess.run(
|
|
202
|
-
command,
|
|
203
|
-
shell=True, # Using shell=True to handle pipes and redirections
|
|
204
|
-
capture_output=True,
|
|
205
|
-
text=True,
|
|
206
|
-
timeout=timeout,
|
|
207
|
-
executable=self._shell,
|
|
208
|
-
)
|
|
271
|
+
# Execute and stream the command output
|
|
272
|
+
returncode, output = self._stream_command_output(command, timeout)
|
|
209
273
|
|
|
210
274
|
# Format result as a string
|
|
211
|
-
if
|
|
212
|
-
output = result.stdout.strip()
|
|
275
|
+
if returncode == 0:
|
|
213
276
|
if output:
|
|
214
277
|
return f"Command executed successfully:\n{output}"
|
|
215
278
|
else:
|
|
216
279
|
return "Command executed successfully (no output)"
|
|
217
280
|
else:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
output_parts = [f"Command failed with exit code {result.returncode}"]
|
|
222
|
-
if stdout:
|
|
223
|
-
output_parts.append(stdout)
|
|
224
|
-
if stderr:
|
|
225
|
-
output_parts.append(stderr)
|
|
281
|
+
output_parts = [f"Command failed with exit code {returncode}"]
|
|
282
|
+
if output:
|
|
283
|
+
output_parts.append(output)
|
|
226
284
|
|
|
227
285
|
return (
|
|
228
286
|
":\n".join(output_parts)
|
|
@@ -17,6 +17,7 @@ from kader.memory.types import get_default_memory_dir
|
|
|
17
17
|
from kader.prompts import KaderPlannerPrompt
|
|
18
18
|
from kader.providers.base import BaseLLMProvider, Message
|
|
19
19
|
from kader.tools import AgentTool, TodoTool, ToolRegistry
|
|
20
|
+
from kader.tools.filesys import GlobTool, GrepTool, ReadDirectoryTool, ReadFileTool
|
|
20
21
|
from kader.utils import Checkpointer
|
|
21
22
|
|
|
22
23
|
from .base import BaseWorkflow
|
|
@@ -115,12 +116,18 @@ class PlannerExecutorWorkflow(BaseWorkflow):
|
|
|
115
116
|
return None
|
|
116
117
|
|
|
117
118
|
def _build_planner(self) -> PlanningAgent:
|
|
118
|
-
"""Build the PlanningAgent with TodoTool and AgentTool(s)."""
|
|
119
|
+
"""Build the PlanningAgent with TodoTool, file system tools, and AgentTool(s)."""
|
|
119
120
|
registry = ToolRegistry()
|
|
120
121
|
|
|
121
122
|
# TodoTool is added implicitly by PlanningAgent, but we ensure it's there
|
|
122
123
|
registry.register(TodoTool())
|
|
123
124
|
|
|
125
|
+
# Add reading and file system search tools
|
|
126
|
+
registry.register(ReadFileTool())
|
|
127
|
+
registry.register(ReadDirectoryTool())
|
|
128
|
+
registry.register(GlobTool())
|
|
129
|
+
registry.register(GrepTool())
|
|
130
|
+
|
|
124
131
|
# Add AgentTool(s) for sub-task delegation
|
|
125
132
|
for executor_name in self.executor_names:
|
|
126
133
|
agent_tool = AgentTool(
|
|
@@ -108,27 +108,27 @@ class TestCommandExecutorTool:
|
|
|
108
108
|
assert "Empty command" in reason
|
|
109
109
|
|
|
110
110
|
@patch("platform.system")
|
|
111
|
-
@patch("subprocess.
|
|
112
|
-
def test_execute_success(self,
|
|
111
|
+
@patch("subprocess.Popen")
|
|
112
|
+
def test_execute_success(self, mock_subprocess_popen, mock_platform_system):
|
|
113
113
|
"""Test successful command execution."""
|
|
114
114
|
# Mock platform and subprocess
|
|
115
115
|
mock_platform_system.return_value = "Linux"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
mock_process = Mock()
|
|
117
|
+
mock_process.returncode = 0
|
|
118
|
+
mock_process.stdout = ["Command output\n"]
|
|
119
|
+
mock_process.poll.return_value = 0
|
|
120
|
+
mock_subprocess_popen.return_value = mock_process
|
|
121
121
|
|
|
122
122
|
tool = CommandExecutorTool()
|
|
123
123
|
result = tool.execute("echo hello", timeout=10)
|
|
124
124
|
|
|
125
125
|
# Verify subprocess was called correctly
|
|
126
|
-
|
|
126
|
+
mock_subprocess_popen.assert_called_once_with(
|
|
127
127
|
"echo hello",
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
stdout=subprocess.PIPE,
|
|
129
|
+
stderr=subprocess.STDOUT,
|
|
130
130
|
text=True,
|
|
131
|
-
|
|
131
|
+
shell=True,
|
|
132
132
|
executable="/bin/bash",
|
|
133
133
|
)
|
|
134
134
|
|
|
@@ -137,27 +137,27 @@ class TestCommandExecutorTool:
|
|
|
137
137
|
assert "Command output" in result
|
|
138
138
|
|
|
139
139
|
@patch("platform.system")
|
|
140
|
-
@patch("subprocess.
|
|
141
|
-
def test_execute_success_windows(self,
|
|
140
|
+
@patch("subprocess.Popen")
|
|
141
|
+
def test_execute_success_windows(self, mock_subprocess_popen, mock_platform_system):
|
|
142
142
|
"""Test successful command execution on Windows."""
|
|
143
143
|
# Mock platform and subprocess
|
|
144
144
|
mock_platform_system.return_value = "Windows"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
mock_process = Mock()
|
|
146
|
+
mock_process.returncode = 0
|
|
147
|
+
mock_process.stdout = ["Windows output\n"]
|
|
148
|
+
mock_process.poll.return_value = 0
|
|
149
|
+
mock_subprocess_popen.return_value = mock_process
|
|
150
150
|
|
|
151
151
|
tool = CommandExecutorTool()
|
|
152
152
|
result = tool.execute("echo hello")
|
|
153
153
|
|
|
154
154
|
# Verify subprocess was called correctly for Windows
|
|
155
|
-
|
|
155
|
+
mock_subprocess_popen.assert_called_once_with(
|
|
156
156
|
"echo hello",
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
stdout=subprocess.PIPE,
|
|
158
|
+
stderr=subprocess.STDOUT,
|
|
159
159
|
text=True,
|
|
160
|
-
|
|
160
|
+
shell=True,
|
|
161
161
|
)
|
|
162
162
|
|
|
163
163
|
# Verify the result
|
|
@@ -165,16 +165,16 @@ class TestCommandExecutorTool:
|
|
|
165
165
|
assert "Windows output" in result
|
|
166
166
|
|
|
167
167
|
@patch("platform.system")
|
|
168
|
-
@patch("subprocess.
|
|
169
|
-
def test_execute_command_failed(self,
|
|
168
|
+
@patch("subprocess.Popen")
|
|
169
|
+
def test_execute_command_failed(self, mock_subprocess_popen, mock_platform_system):
|
|
170
170
|
"""Test command execution that fails."""
|
|
171
171
|
# Mock platform and subprocess
|
|
172
172
|
mock_platform_system.return_value = "Linux"
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
mock_process = Mock()
|
|
174
|
+
mock_process.returncode = 1
|
|
175
|
+
mock_process.stdout = ["Command failed\n"]
|
|
176
|
+
mock_process.poll.return_value = 1
|
|
177
|
+
mock_subprocess_popen.return_value = mock_process
|
|
178
178
|
|
|
179
179
|
tool = CommandExecutorTool()
|
|
180
180
|
result = tool.execute("invalid_command")
|
|
@@ -184,38 +184,40 @@ class TestCommandExecutorTool:
|
|
|
184
184
|
assert "Command failed" in result
|
|
185
185
|
|
|
186
186
|
@patch("platform.system")
|
|
187
|
-
@patch("subprocess.
|
|
187
|
+
@patch("subprocess.Popen")
|
|
188
188
|
def test_execute_command_failed_with_output(
|
|
189
|
-
self,
|
|
189
|
+
self, mock_subprocess_popen, mock_platform_system
|
|
190
190
|
):
|
|
191
191
|
"""Test command execution that fails but has stdout."""
|
|
192
192
|
# Mock platform and subprocess
|
|
193
193
|
mock_platform_system.return_value = "Linux"
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
mock_process = Mock()
|
|
195
|
+
mock_process.returncode = 2
|
|
196
|
+
mock_process.stdout = ["Partial output\n", "Error message\n"]
|
|
197
|
+
mock_process.poll.return_value = 2
|
|
198
|
+
mock_subprocess_popen.return_value = mock_process
|
|
199
199
|
|
|
200
200
|
tool = CommandExecutorTool()
|
|
201
201
|
result = tool.execute("problematic_command")
|
|
202
202
|
|
|
203
|
-
# Verify the result includes both stdout and stderr
|
|
203
|
+
# Verify the result includes both stdout and stderr (which are merged in Popen)
|
|
204
204
|
assert "Command failed with exit code 2" in result
|
|
205
205
|
assert "Partial output" in result
|
|
206
206
|
assert "Error message" in result
|
|
207
207
|
|
|
208
208
|
@patch("platform.system")
|
|
209
|
-
@patch("subprocess.
|
|
210
|
-
def test_execute_command_no_output(
|
|
209
|
+
@patch("subprocess.Popen")
|
|
210
|
+
def test_execute_command_no_output(
|
|
211
|
+
self, mock_subprocess_popen, mock_platform_system
|
|
212
|
+
):
|
|
211
213
|
"""Test command execution with no output."""
|
|
212
214
|
# Mock platform and subprocess
|
|
213
215
|
mock_platform_system.return_value = "Linux"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
mock_process = Mock()
|
|
217
|
+
mock_process.returncode = 0
|
|
218
|
+
mock_process.stdout = []
|
|
219
|
+
mock_process.poll.return_value = 0
|
|
220
|
+
mock_subprocess_popen.return_value = mock_process
|
|
219
221
|
|
|
220
222
|
tool = CommandExecutorTool()
|
|
221
223
|
result = tool.execute("no_output_command")
|
|
@@ -224,26 +226,36 @@ class TestCommandExecutorTool:
|
|
|
224
226
|
assert "Command executed successfully (no output)" in result
|
|
225
227
|
|
|
226
228
|
@patch("platform.system")
|
|
227
|
-
@patch("subprocess.
|
|
228
|
-
def test_execute_command_timeout(self,
|
|
229
|
+
@patch("subprocess.Popen")
|
|
230
|
+
def test_execute_command_timeout(self, mock_subprocess_popen, mock_platform_system):
|
|
229
231
|
"""Test command execution that times out."""
|
|
230
|
-
# Mock platform and subprocess
|
|
232
|
+
# Mock platform and subprocess
|
|
231
233
|
mock_platform_system.return_value = "Linux"
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
mock_process = Mock()
|
|
235
|
+
mock_process.stdout = []
|
|
236
|
+
mock_process.poll.return_value = None # Process is still running
|
|
237
|
+
mock_subprocess_popen.return_value = mock_process
|
|
238
|
+
|
|
239
|
+
# We need to simulate the slow process by making the real time pass faster or wait,
|
|
240
|
+
# but time.time() is used, so we need to patch time.time
|
|
241
|
+
with patch("time.time") as mock_time:
|
|
242
|
+
# First call is start time, second is in loop check
|
|
243
|
+
mock_time.side_effect = [100.0, 110.0]
|
|
244
|
+
tool = CommandExecutorTool()
|
|
245
|
+
result = tool.execute("slow_command", timeout=5)
|
|
236
246
|
|
|
237
247
|
# Verify the result
|
|
238
248
|
assert "Command timed out after 5 seconds" in result
|
|
239
249
|
|
|
240
250
|
@patch("platform.system")
|
|
241
|
-
@patch("subprocess.
|
|
242
|
-
def test_execute_command_exception(
|
|
251
|
+
@patch("subprocess.Popen")
|
|
252
|
+
def test_execute_command_exception(
|
|
253
|
+
self, mock_subprocess_popen, mock_platform_system
|
|
254
|
+
):
|
|
243
255
|
"""Test command execution that raises an exception."""
|
|
244
256
|
# Mock platform and subprocess to raise a general exception
|
|
245
257
|
mock_platform_system.return_value = "Linux"
|
|
246
|
-
|
|
258
|
+
mock_subprocess_popen.side_effect = Exception("Unexpected error")
|
|
247
259
|
|
|
248
260
|
tool = CommandExecutorTool()
|
|
249
261
|
result = tool.execute("error_command")
|
|
@@ -252,9 +264,9 @@ class TestCommandExecutorTool:
|
|
|
252
264
|
assert "Execution Error: Unexpected error" in result
|
|
253
265
|
|
|
254
266
|
@patch("platform.system")
|
|
255
|
-
@patch("subprocess.
|
|
267
|
+
@patch("subprocess.Popen")
|
|
256
268
|
def test_execute_invalid_command_windows(
|
|
257
|
-
self,
|
|
269
|
+
self, mock_subprocess_popen, mock_platform_system
|
|
258
270
|
):
|
|
259
271
|
"""Test executing an invalid command on Windows."""
|
|
260
272
|
mock_platform_system.return_value = "Windows"
|
|
@@ -267,13 +279,13 @@ class TestCommandExecutorTool:
|
|
|
267
279
|
assert "Unix/Linux command" in result
|
|
268
280
|
assert "PowerShell" in result
|
|
269
281
|
|
|
270
|
-
# subprocess.
|
|
271
|
-
|
|
282
|
+
# subprocess.Popen should not be called
|
|
283
|
+
mock_subprocess_popen.assert_not_called()
|
|
272
284
|
|
|
273
285
|
@patch("platform.system")
|
|
274
|
-
@patch("subprocess.
|
|
286
|
+
@patch("subprocess.Popen")
|
|
275
287
|
def test_execute_invalid_command_unix(
|
|
276
|
-
self,
|
|
288
|
+
self, mock_subprocess_popen, mock_platform_system
|
|
277
289
|
):
|
|
278
290
|
"""Test executing an invalid command on Unix."""
|
|
279
291
|
mock_platform_system.return_value = "Linux"
|
|
@@ -286,21 +298,21 @@ class TestCommandExecutorTool:
|
|
|
286
298
|
assert "Windows command" in result
|
|
287
299
|
assert "Unix equivalent" in result
|
|
288
300
|
|
|
289
|
-
# subprocess.
|
|
290
|
-
|
|
301
|
+
# subprocess.Popen should not be called
|
|
302
|
+
mock_subprocess_popen.assert_not_called()
|
|
291
303
|
|
|
292
304
|
@patch("platform.system")
|
|
293
|
-
@patch("subprocess.
|
|
305
|
+
@patch("subprocess.Popen")
|
|
294
306
|
@pytest.mark.asyncio
|
|
295
|
-
async def test_aexecute(self,
|
|
307
|
+
async def test_aexecute(self, mock_subprocess_popen, mock_platform_system):
|
|
296
308
|
"""Test asynchronous command execution."""
|
|
297
309
|
# Mock platform and subprocess
|
|
298
310
|
mock_platform_system.return_value = "Linux"
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
311
|
+
mock_process = Mock()
|
|
312
|
+
mock_process.returncode = 0
|
|
313
|
+
mock_process.stdout = ["Async output\n"]
|
|
314
|
+
mock_process.poll.return_value = 0
|
|
315
|
+
mock_subprocess_popen.return_value = mock_process
|
|
304
316
|
|
|
305
317
|
tool = CommandExecutorTool()
|
|
306
318
|
result = await tool.aexecute("echo async", timeout=15)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kader-2.6.2 → kader-2.6.4}/.kader/skills/contributing-to-kader/assets/contributor_checklist.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kader-2.6.2 → kader-2.6.4}/.opencode/skills/contributing-to-kader/assets/contributor_checklist.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|