bone-agent 1.4.0 → 2.0.0
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.
- package/bin/bone.js +39 -0
- package/package.json +25 -39
- package/LICENSE +0 -21
- package/README.md +0 -201
- package/bin/npm-wrapper.js +0 -235
- package/bin/rg +0 -0
- package/bin/rg.exe +0 -0
- package/config.yaml.example +0 -144
- package/prompts/main/ask_questions.md +0 -31
- package/prompts/main/batch_independent_calls.md +0 -5
- package/prompts/main/casual_interactions.md +0 -11
- package/prompts/main/code_references.md +0 -8
- package/prompts/main/communication_style.md +0 -12
- package/prompts/main/context_reliability.md +0 -12
- package/prompts/main/conversational_tool_calling.md +0 -15
- package/prompts/main/dream.md +0 -50
- package/prompts/main/editing_pattern.md +0 -13
- package/prompts/main/error_handling.md +0 -6
- package/prompts/main/exploration_pattern.md +0 -21
- package/prompts/main/intro.md +0 -1
- package/prompts/main/obsidian.md +0 -16
- package/prompts/main/obsidian_project.md +0 -79
- package/prompts/main/professional_objectivity.md +0 -3
- package/prompts/main/skills.md +0 -3
- package/prompts/main/targeted_searching.md +0 -10
- package/prompts/main/task_lists_pattern.md +0 -8
- package/prompts/main/temp_folder.md +0 -9
- package/prompts/main/think_before_acting.md +0 -10
- package/prompts/main/tone_and_style.md +0 -4
- package/prompts/main/tool_preferences.md +0 -24
- package/prompts/main/trust_subagent_context.md +0 -21
- package/prompts/main/when_to_use_sub_agent.md +0 -7
- package/prompts/micro/ask_questions.md +0 -1
- package/prompts/micro/batch_independent_calls.md +0 -1
- package/prompts/micro/casual_interactions.md +0 -1
- package/prompts/micro/code_references.md +0 -1
- package/prompts/micro/communication_style.md +0 -1
- package/prompts/micro/context_reliability.md +0 -1
- package/prompts/micro/conversational_tool_calling.md +0 -1
- package/prompts/micro/editing_pattern.md +0 -1
- package/prompts/micro/error_handling.md +0 -1
- package/prompts/micro/exploration_pattern.md +0 -1
- package/prompts/micro/intro.md +0 -1
- package/prompts/micro/obsidian.md +0 -4
- package/prompts/micro/obsidian_project.md +0 -5
- package/prompts/micro/professional_objectivity.md +0 -1
- package/prompts/micro/skills.md +0 -1
- package/prompts/micro/targeted_searching.md +0 -1
- package/prompts/micro/task_lists_pattern.md +0 -1
- package/prompts/micro/temp_folder.md +0 -1
- package/prompts/micro/think_before_acting.md +0 -5
- package/prompts/micro/tone_and_style.md +0 -1
- package/prompts/micro/tool_preferences.md +0 -1
- package/prompts/micro/trust_subagent_context.md +0 -1
- package/prompts/micro/when_to_use_sub_agent.md +0 -1
- package/requirements.txt +0 -9
- package/src/__init__.py +0 -11
- package/src/core/__init__.py +0 -1
- package/src/core/agentic.py +0 -1085
- package/src/core/chat_manager.py +0 -1577
- package/src/core/config_manager.py +0 -260
- package/src/core/cron.py +0 -578
- package/src/core/cron_allowlist.py +0 -118
- package/src/core/memory.py +0 -145
- package/src/core/metadata.py +0 -75
- package/src/core/retry.py +0 -71
- package/src/core/skills.py +0 -463
- package/src/core/sub_agent.py +0 -376
- package/src/core/tool_approval.py +0 -220
- package/src/core/tool_feedback.py +0 -789
- package/src/exceptions.py +0 -79
- package/src/llm/__init__.py +0 -1
- package/src/llm/client.py +0 -176
- package/src/llm/codex_provider.py +0 -350
- package/src/llm/config.py +0 -536
- package/src/llm/prompts.py +0 -494
- package/src/llm/providers.py +0 -438
- package/src/llm/streaming.py +0 -163
- package/src/llm/token_tracker.py +0 -399
- package/src/tools/__init__.py +0 -151
- package/src/tools/constants.py +0 -59
- package/src/tools/create_file.py +0 -136
- package/src/tools/directory.py +0 -389
- package/src/tools/edit.py +0 -549
- package/src/tools/file_reader.py +0 -322
- package/src/tools/helpers/__init__.py +0 -99
- package/src/tools/helpers/base.py +0 -599
- package/src/tools/helpers/converters.py +0 -44
- package/src/tools/helpers/file_helpers.py +0 -189
- package/src/tools/helpers/formatters.py +0 -411
- package/src/tools/helpers/loader.py +0 -145
- package/src/tools/helpers/parallel_executor.py +0 -231
- package/src/tools/helpers/path_resolver.py +0 -283
- package/src/tools/helpers/plugin_manifest.py +0 -185
- package/src/tools/obsidian.py +0 -96
- package/src/tools/review_sub_agent.py +0 -190
- package/src/tools/rg_search.py +0 -477
- package/src/tools/search_plugins.py +0 -177
- package/src/tools/select_option.py +0 -600
- package/src/tools/shell.py +0 -302
- package/src/tools/sub_agent.py +0 -139
- package/src/tools/task_list.py +0 -269
- package/src/tools/web_search.py +0 -61
- package/src/ui/__init__.py +0 -1
- package/src/ui/banner.py +0 -87
- package/src/ui/commands.py +0 -3131
- package/src/ui/displays.py +0 -239
- package/src/ui/loader.py +0 -284
- package/src/ui/main.py +0 -643
- package/src/ui/prompt_utils.py +0 -113
- package/src/ui/setting_selector.py +0 -590
- package/src/ui/setup_wizard.py +0 -294
- package/src/ui/sub_agent_panel.py +0 -234
- package/src/ui/tool_confirmation.py +0 -226
- package/src/utils/__init__.py +0 -1
- package/src/utils/citation_parser.py +0 -199
- package/src/utils/editor.py +0 -207
- package/src/utils/gitignore_filter.py +0 -149
- package/src/utils/logger.py +0 -254
- package/src/utils/paths.py +0 -30
- package/src/utils/result_parsers.py +0 -108
- package/src/utils/safe_commands.py +0 -243
- package/src/utils/settings.py +0 -195
- package/src/utils/user_message_logger.py +0 -120
- package/src/utils/validation.py +0 -201
- package/src/utils/web_search.py +0 -173
package/src/tools/shell.py
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
"""Shell command execution tool with core command execution logic and @tool decorator."""
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
import shlex
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Optional
|
|
8
|
-
from rich.panel import Panel
|
|
9
|
-
from llm.config import TOOLS_REQUIRE_CONFIRMATION
|
|
10
|
-
from utils.settings import tool_settings
|
|
11
|
-
from exceptions import CommandExecutionError
|
|
12
|
-
from utils.validation import CHAINING_OPERATORS
|
|
13
|
-
|
|
14
|
-
from .helpers.base import tool
|
|
15
|
-
from .helpers.formatters import format_tool_result
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def normalize_command(command, rg_exe_path):
|
|
19
|
-
"""Parse command and return (executable, args_list, needs_shell).
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
tuple: (executable_path, args_list, needs_shell)
|
|
23
|
-
- executable_path: Path object for rg.exe, or None for shell commands
|
|
24
|
-
- args_list: List of arguments for direct execution, or command string for shell
|
|
25
|
-
- needs_shell: Boolean indicating if command should run through shell
|
|
26
|
-
"""
|
|
27
|
-
command = command.strip()
|
|
28
|
-
|
|
29
|
-
# Handle rg commands
|
|
30
|
-
if command.startswith("rg ") or command == "rg":
|
|
31
|
-
if command == "rg":
|
|
32
|
-
return rg_exe_path, [], False
|
|
33
|
-
|
|
34
|
-
args_str = command[3:].strip() # Everything after "rg "
|
|
35
|
-
# Parse only the arguments, not the full command string.
|
|
36
|
-
# On Windows, posix=False preserves backslashes in paths.
|
|
37
|
-
use_posix = os.name != "nt"
|
|
38
|
-
args = shlex.split(args_str, posix=use_posix) if args_str else []
|
|
39
|
-
args = [arg[1:-1] if len(arg) >= 2 and arg[0] == arg[-1] and arg[0] in ("'", '"') else arg for arg in args]
|
|
40
|
-
return rg_exe_path, args, False
|
|
41
|
-
|
|
42
|
-
# Other commands go through shell
|
|
43
|
-
return None, command, True
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def confirm_tool(command, console, reason=None, requires_approval=True, approve_mode="safe", use_panel=True, is_edit_tool=False, cycle_approve_mode=None):
|
|
47
|
-
"""Prompt user for tool execution confirmation.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
command: Command to execute
|
|
51
|
-
console: Rich console for output (currently unused, kept for compatibility)
|
|
52
|
-
reason: Optional reason for requiring confirmation
|
|
53
|
-
requires_approval: Whether this command specifically requires approval (overrides global flag when True)
|
|
54
|
-
approve_mode: Approval mode setting - "safe" requires confirmation, "accept_edits" auto-approves edits, "danger" auto-approves all
|
|
55
|
-
use_panel: When True, display interactive confirmation panel. When False, auto-cancel without UI (default: True)
|
|
56
|
-
is_edit_tool: Whether this is an edit tool (shows extra toggle option in panel)
|
|
57
|
-
cycle_approve_mode: Optional callback to cycle approve_mode
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
tuple: (action, guidance_text) where action is "accept", "advise", or "cancel"
|
|
61
|
-
and guidance_text contains the user's input when action is "advise"
|
|
62
|
-
"""
|
|
63
|
-
# Skip confirmation only if: global flag is off AND command doesn't require approval
|
|
64
|
-
if not TOOLS_REQUIRE_CONFIRMATION and not requires_approval:
|
|
65
|
-
return ("accept", None)
|
|
66
|
-
|
|
67
|
-
# In danger mode, auto-approve everything except unsafe commands.
|
|
68
|
-
# Safe commands (per SAFE_COMMAND_RULES) auto-approve.
|
|
69
|
-
# Unsafe commands (e.g., git push) still require confirmation.
|
|
70
|
-
if approve_mode == "danger":
|
|
71
|
-
from utils.safe_commands import is_safe_command
|
|
72
|
-
if is_safe_command(command):
|
|
73
|
-
return ("accept", None)
|
|
74
|
-
# Auto-accept all edit operations in danger mode
|
|
75
|
-
if is_edit_tool or (requires_approval and "edit_file" in command):
|
|
76
|
-
return ("accept", None)
|
|
77
|
-
# Fall through to normal confirmation for unsafe commands
|
|
78
|
-
|
|
79
|
-
# Skip confirmation for edit operations in accept_edits mode
|
|
80
|
-
# This only applies to file edits, not execute_command
|
|
81
|
-
if approve_mode == "accept_edits" and requires_approval and "edit_file" in command:
|
|
82
|
-
return ("accept", None)
|
|
83
|
-
|
|
84
|
-
# Handle case where console is None (e.g., parallel execution)
|
|
85
|
-
if console is None:
|
|
86
|
-
# Cancel by default when console is unavailable
|
|
87
|
-
return ("cancel", None)
|
|
88
|
-
|
|
89
|
-
# Try to use interactive panel
|
|
90
|
-
if use_panel:
|
|
91
|
-
from ui.tool_confirmation import ToolConfirmationPanel
|
|
92
|
-
panel = ToolConfirmationPanel(command, reason, is_edit_tool=is_edit_tool, cycle_approve_mode=cycle_approve_mode)
|
|
93
|
-
return panel.run()
|
|
94
|
-
|
|
95
|
-
# Cancel by default if panel is disabled or fails
|
|
96
|
-
return ("cancel", None)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _prepare_execution_environment(repo_root, rg_exe_path):
|
|
100
|
-
"""Prepare environment variables for command execution.
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
dict: Environment variables with updated PATH
|
|
104
|
-
"""
|
|
105
|
-
env = os.environ.copy()
|
|
106
|
-
rg_parent = Path(rg_exe_path).parent if rg_exe_path else None
|
|
107
|
-
|
|
108
|
-
if rg_parent and rg_parent.exists():
|
|
109
|
-
bin_path = str(rg_parent)
|
|
110
|
-
else:
|
|
111
|
-
bin_path = str(repo_root / "bin")
|
|
112
|
-
|
|
113
|
-
env["PATH"] = f"{bin_path}{os.pathsep}{env.get('PATH', '')}"
|
|
114
|
-
return env
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _execute_direct_command(cmd_list, repo_root, env, debug_mode, console):
|
|
118
|
-
"""Execute command directly (rg.exe) without PowerShell.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
subprocess.CompletedProcess
|
|
122
|
-
"""
|
|
123
|
-
if debug_mode and console:
|
|
124
|
-
console.print(f"[dim]→ Executing: {cmd_list}[/dim]")
|
|
125
|
-
console.print(f"[dim]→ Working dir: {repo_root}[/dim]")
|
|
126
|
-
|
|
127
|
-
result = subprocess.run(
|
|
128
|
-
cmd_list,
|
|
129
|
-
capture_output=True,
|
|
130
|
-
text=True,
|
|
131
|
-
encoding='utf-8',
|
|
132
|
-
errors='replace',
|
|
133
|
-
timeout=tool_settings.command_timeout_sec,
|
|
134
|
-
cwd=str(repo_root),
|
|
135
|
-
env=env,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
if debug_mode and console:
|
|
139
|
-
console.print(f"[dim]→ Exit code: {result.returncode}[/dim]")
|
|
140
|
-
|
|
141
|
-
return result
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _execute_shell_command(command, repo_root, env, debug_mode, console):
|
|
145
|
-
"""Execute command via shell (PowerShell on Windows, /bin/sh on Unix/Linux).
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
subprocess.CompletedProcess
|
|
149
|
-
"""
|
|
150
|
-
# Detect platform and use appropriate shell
|
|
151
|
-
is_windows = os.name == 'nt'
|
|
152
|
-
|
|
153
|
-
if is_windows:
|
|
154
|
-
shell_cmd = ["powershell", "-NoProfile", "-NonInteractive", "-Command", str(command)]
|
|
155
|
-
shell_name = "PowerShell"
|
|
156
|
-
else:
|
|
157
|
-
shell_cmd = ["/bin/sh", "-c", str(command)]
|
|
158
|
-
shell_name = "/bin/sh"
|
|
159
|
-
|
|
160
|
-
if debug_mode and console:
|
|
161
|
-
console.print(f"[dim]→ Executing via {shell_name}: {command}[/dim]")
|
|
162
|
-
console.print(f"[dim]→ Working dir: {repo_root}[/dim]")
|
|
163
|
-
|
|
164
|
-
result = subprocess.run(
|
|
165
|
-
shell_cmd,
|
|
166
|
-
capture_output=True,
|
|
167
|
-
text=True,
|
|
168
|
-
encoding='utf-8',
|
|
169
|
-
errors='replace',
|
|
170
|
-
timeout=tool_settings.command_timeout_sec,
|
|
171
|
-
cwd=str(repo_root),
|
|
172
|
-
env=env,
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
if debug_mode and console:
|
|
176
|
-
console.print(f"[dim]→ Exit code: {result.returncode}[/dim]")
|
|
177
|
-
|
|
178
|
-
return result
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def run_shell_command(command, repo_root, rg_exe_path, console, debug_mode, gitignore_spec=None, max_matches=None):
|
|
182
|
-
"""Execute command via rg (direct) or shell (PowerShell on Windows, /bin/sh on Unix/Linux).
|
|
183
|
-
|
|
184
|
-
Args:
|
|
185
|
-
command: Command string to execute
|
|
186
|
-
repo_root: Path to repository root
|
|
187
|
-
rg_exe_path: Path to rg.exe
|
|
188
|
-
console: Rich console for output
|
|
189
|
-
debug_mode: Whether to show debug output
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
str: Formatted tool result
|
|
193
|
-
|
|
194
|
-
Raises:
|
|
195
|
-
CommandExecutionError: If command execution fails
|
|
196
|
-
"""
|
|
197
|
-
try:
|
|
198
|
-
env = _prepare_execution_environment(repo_root, rg_exe_path)
|
|
199
|
-
executable, args, needs_shell = normalize_command(command, rg_exe_path)
|
|
200
|
-
|
|
201
|
-
if not needs_shell:
|
|
202
|
-
# Direct execution (rg)
|
|
203
|
-
cmd_list = [str(executable)] + args
|
|
204
|
-
result = _execute_direct_command(cmd_list, repo_root, env, debug_mode, console)
|
|
205
|
-
# AI gets truncated results (via format_tool_result); user sees summary via _display_tool_feedback
|
|
206
|
-
formatted_result = format_tool_result(result, command=command, is_rg=True, debug_mode=True, max_matches=max_matches)
|
|
207
|
-
else:
|
|
208
|
-
# Shell execution (PowerShell on Windows, /bin/sh on Unix/Linux)
|
|
209
|
-
result = _execute_shell_command(args, repo_root, env, debug_mode, console)
|
|
210
|
-
# AI gets full results; user sees summary via _display_tool_feedback
|
|
211
|
-
formatted_result = format_tool_result(result, command=command, debug_mode=True)
|
|
212
|
-
|
|
213
|
-
if debug_mode and console:
|
|
214
|
-
console.print()
|
|
215
|
-
console.print(f"[dim]→ AI receives:\n{formatted_result}[/dim]")
|
|
216
|
-
|
|
217
|
-
return formatted_result
|
|
218
|
-
except CommandExecutionError:
|
|
219
|
-
# Re-raise our custom exceptions
|
|
220
|
-
raise
|
|
221
|
-
except Exception as exc:
|
|
222
|
-
raise CommandExecutionError(
|
|
223
|
-
f"Command execution failed",
|
|
224
|
-
details={"command": command, "original_error": str(exc)}
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# =============================================================================
|
|
229
|
-
# @tool decorated function
|
|
230
|
-
# =============================================================================
|
|
231
|
-
|
|
232
|
-
@tool(
|
|
233
|
-
name="execute_command",
|
|
234
|
-
description="Execute shell commands for git, system tasks, file ops, network, and package management. Runs from repo root. Multi-line shell commands are supported and preserved exactly, including heredocs. Use for git, ps, systemctl, rm, mv, cp, mkdir, ping, curl, wget, ssh, pacman, pip, npm, apt. Disallowed: rg, cat, ls, grep, find, head, tail, sed, awk, sort, uniq, wc, echo, touch, get-content, type, get-childitem, dir, new-item, set-content, add-content, tee. Use native tools instead.",
|
|
235
|
-
parameters={
|
|
236
|
-
"type": "object",
|
|
237
|
-
"properties": {
|
|
238
|
-
"command": {
|
|
239
|
-
"type": "string",
|
|
240
|
-
"description": "Shell command to execute from the repo root. May be a single-line command or a multi-line shell script/heredoc; newlines are preserved exactly."
|
|
241
|
-
},
|
|
242
|
-
"reason": {
|
|
243
|
-
"type": "string",
|
|
244
|
-
"description": "Brief explanation (shown during confirmation)"
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
"required": ["command"]
|
|
248
|
-
},
|
|
249
|
-
requires_approval=True,
|
|
250
|
-
terminal_policy="stop"
|
|
251
|
-
)
|
|
252
|
-
def execute_command(
|
|
253
|
-
command: str,
|
|
254
|
-
repo_root: Path,
|
|
255
|
-
rg_exe_path: str,
|
|
256
|
-
console,
|
|
257
|
-
chat_manager,
|
|
258
|
-
debug_mode: bool = False,
|
|
259
|
-
gitignore_spec = None,
|
|
260
|
-
reason: str = None
|
|
261
|
-
) -> str:
|
|
262
|
-
"""Execute a shell command.
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
command: Command string to execute. May contain newlines/heredocs; preserved exactly for shell execution.
|
|
266
|
-
repo_root: Repository root directory (injected by context)
|
|
267
|
-
rg_exe_path: Path to rg executable (injected by context)
|
|
268
|
-
console: Rich console for output (injected by context)
|
|
269
|
-
chat_manager: ChatManager instance (injected by context)
|
|
270
|
-
debug_mode: Whether debug mode is enabled (injected by context)
|
|
271
|
-
gitignore_spec: PathSpec for .gitignore filtering (injected by context)
|
|
272
|
-
|
|
273
|
-
Returns:
|
|
274
|
-
Command output with exit code
|
|
275
|
-
"""
|
|
276
|
-
# Import validation functions here to avoid circular dependency
|
|
277
|
-
from utils.validation import check_command, check_for_silent_blocked_command
|
|
278
|
-
|
|
279
|
-
if not isinstance(command, str) or not command.strip():
|
|
280
|
-
return "exit_code=1\nerror: 'command' argument must be a non-empty string."
|
|
281
|
-
|
|
282
|
-
# Check for commands that should use native tools instead (silent blocking)
|
|
283
|
-
is_blocked, reprompt_msg = check_for_silent_blocked_command(command)
|
|
284
|
-
if is_blocked:
|
|
285
|
-
# Return reprompt message to guide the AI to use the native tool
|
|
286
|
-
# This is not shown to the user - the AI sees it and can retry
|
|
287
|
-
if debug_mode and console:
|
|
288
|
-
console.print(f"[dim]Silently blocked command: {command.split()[0]}[/dim]")
|
|
289
|
-
return f"exit_code=1\n{reprompt_msg}"
|
|
290
|
-
|
|
291
|
-
# Validate command
|
|
292
|
-
is_safe, reason = check_command(command)
|
|
293
|
-
if not is_safe:
|
|
294
|
-
return reason
|
|
295
|
-
|
|
296
|
-
# Execute command (approval workflow handled by orchestrator)
|
|
297
|
-
try:
|
|
298
|
-
return run_shell_command(
|
|
299
|
-
command, repo_root, rg_exe_path, console, debug_mode, gitignore_spec
|
|
300
|
-
)
|
|
301
|
-
except Exception as e:
|
|
302
|
-
return f"exit_code=1\nCommand execution failed: {str(e)}"
|
package/src/tools/sub_agent.py
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"""Sub-agent tool for complex multi-file exploration."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from .helpers.base import tool
|
|
6
|
-
from core.sub_agent import run_sub_agent
|
|
7
|
-
from utils.citation_parser import inject_file_contents
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SimplePanelUpdater:
|
|
11
|
-
"""Simple panel updater for non-parallel tool execution.
|
|
12
|
-
|
|
13
|
-
This is a fallback implementation used when panel_updater is None,
|
|
14
|
-
typically in sequential mode where live updates aren't needed.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(self, console):
|
|
18
|
-
"""Initialize the simple panel updater.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
console: Rich console for output
|
|
22
|
-
"""
|
|
23
|
-
self.console = console
|
|
24
|
-
self.total_tool_calls = 0
|
|
25
|
-
|
|
26
|
-
def __enter__(self):
|
|
27
|
-
"""Enter context manager."""
|
|
28
|
-
return self
|
|
29
|
-
|
|
30
|
-
def __exit__(self, *args):
|
|
31
|
-
"""Exit context manager."""
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
def append(self, text):
|
|
35
|
-
"""Append text to panel (no-op in simple mode)."""
|
|
36
|
-
pass # No live updates in sequential mode
|
|
37
|
-
|
|
38
|
-
def add_tool_call(self, tool_name, tool_result=None, command=None):
|
|
39
|
-
"""Track a tool call."""
|
|
40
|
-
self.total_tool_calls += 1
|
|
41
|
-
|
|
42
|
-
def set_complete(self, usage=None):
|
|
43
|
-
"""Mark panel as complete."""
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
def set_error(self, message):
|
|
47
|
-
"""Display error message."""
|
|
48
|
-
self.console.print(f"[red]Sub-Agent Error: {message}[/red]")
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@tool(
|
|
52
|
-
name="sub_agent",
|
|
53
|
-
description="Required: Call this first before any rg or read_file when answering 'how something works', architecture, patterns, multi-file flows, or broad exploration. Do not search manually — this tool is 10x faster. Examples: 'How does authentication work?', 'Explain the data flow', 'Where is X handled?'",
|
|
54
|
-
parameters={
|
|
55
|
-
"type": "object",
|
|
56
|
-
"properties": {
|
|
57
|
-
"query": {
|
|
58
|
-
"type": "string",
|
|
59
|
-
"description": "Task query, e.g. 'How does the chat manager handle history?'"
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"required": ["query"]
|
|
63
|
-
},
|
|
64
|
-
requires_approval=False,
|
|
65
|
-
terminal_policy="yield"
|
|
66
|
-
)
|
|
67
|
-
def sub_agent(
|
|
68
|
-
query: str,
|
|
69
|
-
repo_root: Path,
|
|
70
|
-
rg_exe_path: str,
|
|
71
|
-
console,
|
|
72
|
-
chat_manager,
|
|
73
|
-
gitignore_spec = None,
|
|
74
|
-
panel_updater = None
|
|
75
|
-
) -> str:
|
|
76
|
-
"""Run sub-agent for complex multi-file exploration.
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
query: Task query for the sub-agent
|
|
80
|
-
repo_root: Repository root directory (injected by context)
|
|
81
|
-
rg_exe_path: Path to rg executable (injected by context)
|
|
82
|
-
console: Rich console for output (injected by context)
|
|
83
|
-
chat_manager: ChatManager instance (injected by context)
|
|
84
|
-
gitignore_spec: PathSpec for .gitignore filtering (injected by context)
|
|
85
|
-
panel_updater: Optional SubAgentPanel for live updates (injected by context)
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Sub-agent result with injected file contents
|
|
89
|
-
"""
|
|
90
|
-
if not query or not isinstance(query, str) or not query.strip():
|
|
91
|
-
return "exit_code=1\nsub_agent requires a non-empty 'query' argument."
|
|
92
|
-
|
|
93
|
-
# Import SimplePanelUpdater if not provided
|
|
94
|
-
if panel_updater is None:
|
|
95
|
-
# If running in sequential mode, create a simple panel updater
|
|
96
|
-
panel_updater = SimplePanelUpdater(console)
|
|
97
|
-
|
|
98
|
-
# Use panel for streaming tool output
|
|
99
|
-
with panel_updater as panel:
|
|
100
|
-
sub_agent_data = run_sub_agent(
|
|
101
|
-
task_query=query,
|
|
102
|
-
repo_root=repo_root,
|
|
103
|
-
rg_exe_path=rg_exe_path,
|
|
104
|
-
console=console,
|
|
105
|
-
panel_updater=panel,
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
# Check for errors
|
|
109
|
-
if sub_agent_data.get('error'):
|
|
110
|
-
panel.set_error(sub_agent_data['error'])
|
|
111
|
-
return f"exit_code=1\n{sub_agent_data['error']}"
|
|
112
|
-
|
|
113
|
-
# Track usage
|
|
114
|
-
usage = sub_agent_data.get('usage', {})
|
|
115
|
-
if usage:
|
|
116
|
-
chat_manager.token_tracker.add_usage(usage, model_name=sub_agent_data.get("model", ""))
|
|
117
|
-
panel.set_complete({
|
|
118
|
-
'prompt_tokens': usage.get('prompt_tokens', 0),
|
|
119
|
-
'completion_tokens': usage.get('completion_tokens', 0),
|
|
120
|
-
'total_tokens': usage.get('total_tokens', 0),
|
|
121
|
-
'context_tokens': usage.get('context_tokens', 0),
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
# Display sub-agent result summary (used for context)
|
|
125
|
-
raw_result = sub_agent_data.get('result', '')
|
|
126
|
-
|
|
127
|
-
# If hard limit was exceeded, skip injection and return raw dump
|
|
128
|
-
if sub_agent_data.get('hard_limit_exceeded'):
|
|
129
|
-
return raw_result
|
|
130
|
-
|
|
131
|
-
# Parse and inject file contents
|
|
132
|
-
injected_result = inject_file_contents(
|
|
133
|
-
raw_result, repo_root, gitignore_spec, console
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
return injected_result
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|