ripperdoc 0.2.9__tar.gz → 0.2.10__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.
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/PKG-INFO +8 -2
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/README.md +1 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/pyproject.toml +4 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/__init__.py +1 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/cli.py +235 -14
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/__init__.py +2 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/agents_cmd.py +132 -5
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/clear_cmd.py +8 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/exit_cmd.py +1 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/models_cmd.py +3 -3
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/resume_cmd.py +4 -0
- ripperdoc-0.2.10/ripperdoc/cli/commands/stats_cmd.py +244 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/panels.py +1 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/rich_ui.py +295 -24
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/spinner.py +30 -18
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/thinking_spinner.py +1 -2
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/wizard.py +6 -8
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/agents.py +10 -3
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/config.py +3 -6
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/default_tools.py +90 -10
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/events.py +4 -0
- ripperdoc-0.2.10/ripperdoc/core/hooks/llm_callback.py +59 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/permissions.py +78 -4
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/providers/openai.py +29 -19
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/query.py +192 -31
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/tool.py +9 -4
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/sdk/client.py +77 -2
- ripperdoc-0.2.10/ripperdoc/tools/background_shell.py +561 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/bash_tool.py +42 -13
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/file_edit_tool.py +159 -50
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/file_read_tool.py +20 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/file_write_tool.py +7 -8
- ripperdoc-0.2.10/ripperdoc/tools/lsp_tool.py +615 -0
- ripperdoc-0.2.10/ripperdoc/tools/task_tool.py +829 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/conversation_compaction.py +1 -1
- ripperdoc-0.2.10/ripperdoc/utils/file_watch.py +344 -0
- ripperdoc-0.2.10/ripperdoc/utils/lsp.py +806 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/message_formatting.py +5 -2
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/messages.py +21 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/permissions/tool_permission_utils.py +174 -15
- ripperdoc-0.2.10/ripperdoc/utils/session_heatmap.py +244 -0
- ripperdoc-0.2.10/ripperdoc/utils/session_stats.py +293 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/PKG-INFO +8 -2
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/SOURCES.txt +9 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/requires.txt +9 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_background_shell_shutdown.py +7 -7
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_cli_commands.py +8 -3
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_config.py +0 -1
- ripperdoc-0.2.10/tests/test_hooks.py +2080 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_permissions.py +101 -1
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_shell_permissions.py +294 -8
- ripperdoc-0.2.10/tests/test_utils.py +1097 -0
- ripperdoc-0.2.9/ripperdoc/tools/background_shell.py +0 -390
- ripperdoc-0.2.9/ripperdoc/tools/task_tool.py +0 -380
- ripperdoc-0.2.9/ripperdoc/utils/file_watch.py +0 -141
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/LICENSE +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/__main__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/base.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/compact_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/config_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/context_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/cost_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/doctor_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/help_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/hooks_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/mcp_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/memory_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/permissions_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/status_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/tasks_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/todos_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/commands/tools_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/context_display.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/file_mention_completer.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/helpers.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/interrupt_handler.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/message_display.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/provider_options.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/cli/ui/tool_renderers.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/commands.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/custom_commands.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/config.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/executor.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/integration.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/hooks/manager.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/providers/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/providers/anthropic.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/providers/base.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/providers/gemini.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/query_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/skills.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/core/system_prompt.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/sdk/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/ask_user_question_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/bash_output_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/dynamic_mcp_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/enter_plan_mode_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/exit_plan_mode_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/glob_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/grep_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/kill_bash_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/ls_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/mcp_tools.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/multi_edit_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/notebook_edit_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/skill_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/todo_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/tools/tool_search_tool.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/bash_constants.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/bash_output_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/coerce.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/context_length_errors.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/exit_code_handlers.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/git_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/json_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/log.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/mcp.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/memory.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/message_compaction.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/output_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/path_ignore.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/path_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/permissions/__init__.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/permissions/path_validation_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/permissions/shell_command_validation.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/prompt.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/safe_get_cwd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/sandbox_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/session_history.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/session_usage.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/shell_token_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/shell_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/todo.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc/utils/token_estimation.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/dependency_links.txt +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/entry_points.txt +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/ripperdoc.egg-info/top_level.txt +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/setup.cfg +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/setup.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_compact.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_context_length_errors.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_context_limits.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_custom_commands.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_file_mention_completer.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_hooks_cmd.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_mcp_config.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_messages.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_output_utils.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_path_ignore.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_query_abort.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_sdk.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_skills.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_todo.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_tool_search.py +0 -0
- {ripperdoc-0.2.9 → ripperdoc-0.2.10}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ripperdoc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
4
4
|
Summary: AI-powered terminal assistant for coding tasks
|
|
5
5
|
Author: Ripperdoc Team
|
|
6
6
|
License: Apache-2.0
|
|
@@ -33,6 +33,12 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
|
33
33
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
34
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
35
35
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
+
Provides-Extra: anthropic
|
|
37
|
+
Requires-Dist: anthropic>=0.39.0; extra == "anthropic"
|
|
38
|
+
Provides-Extra: openai
|
|
39
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
40
|
+
Provides-Extra: gemini
|
|
41
|
+
Requires-Dist: google-genai>=0.3.0; extra == "gemini"
|
|
36
42
|
Dynamic: license-file
|
|
37
43
|
|
|
38
44
|
<div align="center">
|
|
@@ -137,7 +143,7 @@ See the [examples/](examples/) directory for complete SDK usage examples.
|
|
|
137
143
|
|
|
138
144
|
### Safe Mode Permissions
|
|
139
145
|
|
|
140
|
-
Safe mode is the default. Use `--
|
|
146
|
+
Safe mode is the default. Use `--yolo` to skip permission prompts. Choose `a`/`always` to allow a tool for the current session (not persisted across sessions).
|
|
141
147
|
|
|
142
148
|
### Agent Skills
|
|
143
149
|
|
|
@@ -100,7 +100,7 @@ See the [examples/](examples/) directory for complete SDK usage examples.
|
|
|
100
100
|
|
|
101
101
|
### Safe Mode Permissions
|
|
102
102
|
|
|
103
|
-
Safe mode is the default. Use `--
|
|
103
|
+
Safe mode is the default. Use `--yolo` to skip permission prompts. Choose `a`/`always` to allow a tool for the current session (not persisted across sessions).
|
|
104
104
|
|
|
105
105
|
### Agent Skills
|
|
106
106
|
|
|
@@ -46,6 +46,9 @@ dev = [
|
|
|
46
46
|
"black>=23.0.0",
|
|
47
47
|
"ruff>=0.1.0",
|
|
48
48
|
]
|
|
49
|
+
anthropic = ["anthropic>=0.39.0"]
|
|
50
|
+
openai = ["openai>=1.0.0"]
|
|
51
|
+
gemini = ["google-genai>=0.3.0"]
|
|
49
52
|
|
|
50
53
|
[project.scripts]
|
|
51
54
|
ripperdoc = "ripperdoc.cli.cli:main"
|
|
@@ -68,7 +71,7 @@ warn_return_any = true
|
|
|
68
71
|
warn_unused_configs = true
|
|
69
72
|
disallow_untyped_defs = true
|
|
70
73
|
files = ["ripperdoc"]
|
|
71
|
-
exclude = ["^tests/"]
|
|
74
|
+
exclude = ["^tests/", "^setup\\.py$"]
|
|
72
75
|
|
|
73
76
|
[tool.ruff]
|
|
74
77
|
line-length = 100
|
|
@@ -6,6 +6,7 @@ This module provides the command-line interface for the Ripperdoc agent.
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import click
|
|
8
8
|
import sys
|
|
9
|
+
import time
|
|
9
10
|
import uuid
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any, Dict, List, Optional
|
|
@@ -16,19 +17,27 @@ from ripperdoc.core.config import (
|
|
|
16
17
|
get_project_config,
|
|
17
18
|
)
|
|
18
19
|
from ripperdoc.cli.ui.wizard import check_onboarding
|
|
19
|
-
from ripperdoc.core.default_tools import get_default_tools
|
|
20
|
+
from ripperdoc.core.default_tools import get_default_tools, BUILTIN_TOOL_NAMES
|
|
20
21
|
from ripperdoc.core.query import query, QueryContext
|
|
21
22
|
from ripperdoc.core.system_prompt import build_system_prompt
|
|
22
23
|
from ripperdoc.core.skills import build_skill_summary, load_all_skills
|
|
23
24
|
from ripperdoc.core.hooks.manager import hook_manager
|
|
25
|
+
from ripperdoc.core.hooks.llm_callback import build_hook_llm_callback
|
|
24
26
|
from ripperdoc.utils.messages import create_user_message
|
|
25
27
|
from ripperdoc.utils.memory import build_memory_instructions
|
|
26
28
|
from ripperdoc.core.permissions import make_permission_checker
|
|
29
|
+
from ripperdoc.utils.session_history import (
|
|
30
|
+
SessionHistory,
|
|
31
|
+
list_session_summaries,
|
|
32
|
+
load_session_messages,
|
|
33
|
+
)
|
|
27
34
|
from ripperdoc.utils.mcp import (
|
|
28
35
|
load_mcp_servers_async,
|
|
29
36
|
format_mcp_instructions,
|
|
30
37
|
shutdown_mcp_runtime,
|
|
31
38
|
)
|
|
39
|
+
from ripperdoc.utils.lsp import shutdown_lsp_manager
|
|
40
|
+
from ripperdoc.tools.background_shell import shutdown_background_shell
|
|
32
41
|
from ripperdoc.tools.mcp_tools import load_dynamic_mcp_tools_async, merge_tools_with_dynamic
|
|
33
42
|
from ripperdoc.utils.log import enable_session_file_logging, get_logger
|
|
34
43
|
|
|
@@ -42,12 +51,51 @@ console = Console()
|
|
|
42
51
|
logger = get_logger()
|
|
43
52
|
|
|
44
53
|
|
|
54
|
+
def parse_tools_option(tools_arg: Optional[str]) -> Optional[List[str]]:
|
|
55
|
+
"""Parse the --tools argument.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
tools_arg: The raw tools argument from CLI.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
None for default (all tools), empty list for "" (no tools),
|
|
62
|
+
or a list of tool names for filtering.
|
|
63
|
+
"""
|
|
64
|
+
if tools_arg is None:
|
|
65
|
+
return None # Use all default tools
|
|
66
|
+
|
|
67
|
+
tools_arg = tools_arg.strip()
|
|
68
|
+
|
|
69
|
+
if tools_arg == "":
|
|
70
|
+
return [] # Disable all tools
|
|
71
|
+
|
|
72
|
+
if tools_arg.lower() == "default":
|
|
73
|
+
return None # Use all default tools
|
|
74
|
+
|
|
75
|
+
# Parse comma-separated list
|
|
76
|
+
tool_names = [name.strip() for name in tools_arg.split(",") if name.strip()]
|
|
77
|
+
|
|
78
|
+
# Validate tool names
|
|
79
|
+
invalid_tools = [name for name in tool_names if name not in BUILTIN_TOOL_NAMES]
|
|
80
|
+
if invalid_tools:
|
|
81
|
+
logger.warning(
|
|
82
|
+
"[cli] Unknown tools specified: %s. Available tools: %s",
|
|
83
|
+
", ".join(invalid_tools),
|
|
84
|
+
", ".join(BUILTIN_TOOL_NAMES),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return tool_names if tool_names else None
|
|
88
|
+
|
|
89
|
+
|
|
45
90
|
async def run_query(
|
|
46
91
|
prompt: str,
|
|
47
92
|
tools: list,
|
|
48
93
|
yolo_mode: bool = False,
|
|
49
94
|
verbose: bool = False,
|
|
50
95
|
session_id: Optional[str] = None,
|
|
96
|
+
custom_system_prompt: Optional[str] = None,
|
|
97
|
+
append_system_prompt: Optional[str] = None,
|
|
98
|
+
model: Optional[str] = None,
|
|
51
99
|
) -> None:
|
|
52
100
|
"""Run a single query and print the response."""
|
|
53
101
|
|
|
@@ -58,6 +106,9 @@ async def run_query(
|
|
|
58
106
|
"verbose": verbose,
|
|
59
107
|
"session_id": session_id,
|
|
60
108
|
"prompt_length": len(prompt),
|
|
109
|
+
"model": model,
|
|
110
|
+
"has_custom_system_prompt": custom_system_prompt is not None,
|
|
111
|
+
"has_append_system_prompt": append_system_prompt is not None,
|
|
61
112
|
},
|
|
62
113
|
)
|
|
63
114
|
if prompt:
|
|
@@ -72,15 +123,32 @@ async def run_query(
|
|
|
72
123
|
# Initialize hook manager
|
|
73
124
|
hook_manager.set_project_dir(project_path)
|
|
74
125
|
hook_manager.set_session_id(session_id)
|
|
126
|
+
hook_manager.set_llm_callback(build_hook_llm_callback())
|
|
127
|
+
session_history = SessionHistory(project_path, session_id or str(uuid.uuid4()))
|
|
128
|
+
hook_manager.set_transcript_path(str(session_history.path))
|
|
129
|
+
|
|
130
|
+
def _collect_hook_contexts(result: Any) -> List[str]:
|
|
131
|
+
contexts: List[str] = []
|
|
132
|
+
system_message = getattr(result, "system_message", None)
|
|
133
|
+
additional_context = getattr(result, "additional_context", None)
|
|
134
|
+
if system_message:
|
|
135
|
+
contexts.append(str(system_message))
|
|
136
|
+
if additional_context:
|
|
137
|
+
contexts.append(str(additional_context))
|
|
138
|
+
return contexts
|
|
75
139
|
|
|
76
140
|
# Create initial user message
|
|
77
141
|
from ripperdoc.utils.messages import UserMessage, AssistantMessage, ProgressMessage
|
|
78
142
|
|
|
79
143
|
messages: List[UserMessage | AssistantMessage | ProgressMessage] = [create_user_message(prompt)]
|
|
144
|
+
session_history.append(messages[0])
|
|
80
145
|
|
|
81
146
|
# Create query context
|
|
82
|
-
query_context = QueryContext(
|
|
147
|
+
query_context = QueryContext(
|
|
148
|
+
tools=tools, yolo_mode=yolo_mode, verbose=verbose, model=model or "main"
|
|
149
|
+
)
|
|
83
150
|
|
|
151
|
+
session_start_time = time.time()
|
|
84
152
|
try:
|
|
85
153
|
context: Dict[str, Any] = {}
|
|
86
154
|
# System prompt
|
|
@@ -103,13 +171,46 @@ async def run_query(
|
|
|
103
171
|
memory_instructions = build_memory_instructions()
|
|
104
172
|
if memory_instructions:
|
|
105
173
|
additional_instructions.append(memory_instructions)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
additional_instructions
|
|
111
|
-
|
|
112
|
-
)
|
|
174
|
+
|
|
175
|
+
session_start_result = await hook_manager.run_session_start_async("startup")
|
|
176
|
+
session_hook_contexts = _collect_hook_contexts(session_start_result)
|
|
177
|
+
if session_hook_contexts:
|
|
178
|
+
additional_instructions.extend(session_hook_contexts)
|
|
179
|
+
|
|
180
|
+
prompt_hook_result = await hook_manager.run_user_prompt_submit_async(prompt)
|
|
181
|
+
if prompt_hook_result.should_block or not prompt_hook_result.should_continue:
|
|
182
|
+
reason = (
|
|
183
|
+
prompt_hook_result.block_reason
|
|
184
|
+
or prompt_hook_result.stop_reason
|
|
185
|
+
or "Prompt blocked by hook."
|
|
186
|
+
)
|
|
187
|
+
console.print(f"[red]{escape(str(reason))}[/red]")
|
|
188
|
+
return
|
|
189
|
+
prompt_hook_contexts = _collect_hook_contexts(prompt_hook_result)
|
|
190
|
+
if prompt_hook_contexts:
|
|
191
|
+
additional_instructions.extend(prompt_hook_contexts)
|
|
192
|
+
|
|
193
|
+
# Build system prompt based on options:
|
|
194
|
+
# - custom_system_prompt: replaces the default entirely
|
|
195
|
+
# - append_system_prompt: appends to the default system prompt
|
|
196
|
+
if custom_system_prompt:
|
|
197
|
+
# Complete replacement
|
|
198
|
+
system_prompt = custom_system_prompt
|
|
199
|
+
# Still append if both are provided
|
|
200
|
+
if append_system_prompt:
|
|
201
|
+
system_prompt = f"{system_prompt}\n\n{append_system_prompt}"
|
|
202
|
+
else:
|
|
203
|
+
# Build default with optional append
|
|
204
|
+
all_instructions = list(additional_instructions) if additional_instructions else []
|
|
205
|
+
if append_system_prompt:
|
|
206
|
+
all_instructions.append(append_system_prompt)
|
|
207
|
+
system_prompt = build_system_prompt(
|
|
208
|
+
tools,
|
|
209
|
+
prompt,
|
|
210
|
+
context,
|
|
211
|
+
additional_instructions=all_instructions or None,
|
|
212
|
+
mcp_instructions=mcp_instructions,
|
|
213
|
+
)
|
|
113
214
|
|
|
114
215
|
# Run the query
|
|
115
216
|
try:
|
|
@@ -158,6 +259,7 @@ async def run_query(
|
|
|
158
259
|
|
|
159
260
|
# Add message to history
|
|
160
261
|
messages.append(message) # type: ignore[arg-type]
|
|
262
|
+
session_history.append(message) # type: ignore[arg-type]
|
|
161
263
|
|
|
162
264
|
except KeyboardInterrupt:
|
|
163
265
|
console.print("\n[yellow]Interrupted by user[/yellow]")
|
|
@@ -180,7 +282,29 @@ async def run_query(
|
|
|
180
282
|
extra={"session_id": session_id, "message_count": len(messages)},
|
|
181
283
|
)
|
|
182
284
|
finally:
|
|
285
|
+
duration = max(time.time() - session_start_time, 0.0)
|
|
286
|
+
try:
|
|
287
|
+
await hook_manager.run_session_end_async(
|
|
288
|
+
"other", duration_seconds=duration, message_count=len(messages)
|
|
289
|
+
)
|
|
290
|
+
except (OSError, RuntimeError, ConnectionError, ValueError, TypeError) as exc:
|
|
291
|
+
logger.warning(
|
|
292
|
+
"[cli] SessionEnd hook failed: %s: %s",
|
|
293
|
+
type(exc).__name__,
|
|
294
|
+
exc,
|
|
295
|
+
extra={"session_id": session_id},
|
|
296
|
+
)
|
|
183
297
|
await shutdown_mcp_runtime()
|
|
298
|
+
await shutdown_lsp_manager()
|
|
299
|
+
# Shutdown background shell manager
|
|
300
|
+
try:
|
|
301
|
+
shutdown_background_shell(force=True)
|
|
302
|
+
except (OSError, RuntimeError) as exc:
|
|
303
|
+
logger.debug(
|
|
304
|
+
"[cli] Failed to shut down background shell: %s: %s",
|
|
305
|
+
type(exc).__name__,
|
|
306
|
+
exc,
|
|
307
|
+
)
|
|
184
308
|
logger.debug("[cli] Shutdown MCP runtime", extra={"session_id": session_id})
|
|
185
309
|
|
|
186
310
|
|
|
@@ -194,11 +318,59 @@ async def run_query(
|
|
|
194
318
|
help="YOLO mode: skip all permission prompts for tools",
|
|
195
319
|
)
|
|
196
320
|
@click.option("--verbose", is_flag=True, help="Verbose output")
|
|
197
|
-
@click.option(
|
|
321
|
+
@click.option(
|
|
322
|
+
"--show-full-thinking/--hide-full-thinking",
|
|
323
|
+
default=None,
|
|
324
|
+
help="Show full reasoning content instead of truncated preview",
|
|
325
|
+
)
|
|
198
326
|
@click.option("-p", "--prompt", type=str, help="Direct prompt (non-interactive)")
|
|
327
|
+
@click.option(
|
|
328
|
+
"--tools",
|
|
329
|
+
type=str,
|
|
330
|
+
default=None,
|
|
331
|
+
help=(
|
|
332
|
+
'Specify the list of available tools. Use "" to disable all tools, '
|
|
333
|
+
'"default" to use all tools, or specify tool names (e.g. "Bash,Edit,Read").'
|
|
334
|
+
),
|
|
335
|
+
)
|
|
336
|
+
@click.option(
|
|
337
|
+
"--system-prompt",
|
|
338
|
+
type=str,
|
|
339
|
+
default=None,
|
|
340
|
+
help="System prompt to use for the session (replaces default).",
|
|
341
|
+
)
|
|
342
|
+
@click.option(
|
|
343
|
+
"--append-system-prompt",
|
|
344
|
+
type=str,
|
|
345
|
+
default=None,
|
|
346
|
+
help="Additional instructions to append to the system prompt.",
|
|
347
|
+
)
|
|
348
|
+
@click.option(
|
|
349
|
+
"--model",
|
|
350
|
+
type=str,
|
|
351
|
+
default=None,
|
|
352
|
+
help="Model profile for the current session.",
|
|
353
|
+
)
|
|
354
|
+
@click.option(
|
|
355
|
+
"-c",
|
|
356
|
+
"--continue",
|
|
357
|
+
"continue_session",
|
|
358
|
+
is_flag=True,
|
|
359
|
+
help="Continue the most recent conversation in the current directory.",
|
|
360
|
+
)
|
|
199
361
|
@click.pass_context
|
|
200
362
|
def cli(
|
|
201
|
-
ctx: click.Context,
|
|
363
|
+
ctx: click.Context,
|
|
364
|
+
cwd: Optional[str],
|
|
365
|
+
yolo: bool,
|
|
366
|
+
verbose: bool,
|
|
367
|
+
show_full_thinking: Optional[bool],
|
|
368
|
+
prompt: Optional[str],
|
|
369
|
+
tools: Optional[str],
|
|
370
|
+
system_prompt: Optional[str],
|
|
371
|
+
append_system_prompt: Optional[str],
|
|
372
|
+
model: Optional[str],
|
|
373
|
+
continue_session: bool,
|
|
202
374
|
) -> None:
|
|
203
375
|
"""Ripperdoc - AI-powered coding agent"""
|
|
204
376
|
session_id = str(uuid.uuid4())
|
|
@@ -237,15 +409,59 @@ def cli(
|
|
|
237
409
|
get_project_config(project_path)
|
|
238
410
|
|
|
239
411
|
yolo_mode = yolo
|
|
412
|
+
# Parse --tools option
|
|
413
|
+
allowed_tools = parse_tools_option(tools)
|
|
414
|
+
|
|
415
|
+
# Handle --continue option: load the most recent session
|
|
416
|
+
resume_messages = None
|
|
417
|
+
if continue_session:
|
|
418
|
+
summaries = list_session_summaries(project_path)
|
|
419
|
+
if summaries:
|
|
420
|
+
most_recent = summaries[0]
|
|
421
|
+
session_id = most_recent.session_id
|
|
422
|
+
resume_messages = load_session_messages(project_path, session_id)
|
|
423
|
+
logger.info(
|
|
424
|
+
"[cli] Continuing session",
|
|
425
|
+
extra={
|
|
426
|
+
"session_id": session_id,
|
|
427
|
+
"message_count": len(resume_messages),
|
|
428
|
+
"last_prompt": most_recent.last_prompt,
|
|
429
|
+
},
|
|
430
|
+
)
|
|
431
|
+
console.print(f"[dim]Continuing session: {most_recent.last_prompt}[/dim]")
|
|
432
|
+
else:
|
|
433
|
+
logger.warning("[cli] No previous sessions found to continue")
|
|
434
|
+
console.print("[yellow]No previous sessions found in this directory.[/yellow]")
|
|
435
|
+
|
|
240
436
|
logger.debug(
|
|
241
437
|
"[cli] Configuration initialized",
|
|
242
|
-
extra={
|
|
438
|
+
extra={
|
|
439
|
+
"session_id": session_id,
|
|
440
|
+
"yolo_mode": yolo_mode,
|
|
441
|
+
"verbose": verbose,
|
|
442
|
+
"allowed_tools": allowed_tools,
|
|
443
|
+
"model": model,
|
|
444
|
+
"has_system_prompt": system_prompt is not None,
|
|
445
|
+
"has_append_system_prompt": append_system_prompt is not None,
|
|
446
|
+
"continue_session": continue_session,
|
|
447
|
+
},
|
|
243
448
|
)
|
|
244
449
|
|
|
245
450
|
# If prompt is provided, run directly
|
|
246
451
|
if prompt:
|
|
247
|
-
|
|
248
|
-
asyncio.run(
|
|
452
|
+
tool_list = get_default_tools(allowed_tools=allowed_tools)
|
|
453
|
+
asyncio.run(
|
|
454
|
+
run_query(
|
|
455
|
+
prompt,
|
|
456
|
+
tool_list,
|
|
457
|
+
yolo_mode,
|
|
458
|
+
verbose,
|
|
459
|
+
session_id=session_id,
|
|
460
|
+
custom_system_prompt=system_prompt,
|
|
461
|
+
append_system_prompt=append_system_prompt,
|
|
462
|
+
model=model,
|
|
463
|
+
)
|
|
464
|
+
)
|
|
249
465
|
return
|
|
250
466
|
|
|
251
467
|
# If no command specified, start interactive REPL with Rich interface
|
|
@@ -259,6 +475,11 @@ def cli(
|
|
|
259
475
|
show_full_thinking=show_full_thinking,
|
|
260
476
|
session_id=session_id,
|
|
261
477
|
log_file_path=log_file,
|
|
478
|
+
allowed_tools=allowed_tools,
|
|
479
|
+
custom_system_prompt=system_prompt,
|
|
480
|
+
append_system_prompt=append_system_prompt,
|
|
481
|
+
model=model,
|
|
482
|
+
resume_messages=resume_messages,
|
|
262
483
|
)
|
|
263
484
|
return
|
|
264
485
|
|
|
@@ -21,6 +21,7 @@ from .mcp_cmd import command as mcp_command
|
|
|
21
21
|
from .models_cmd import command as models_command
|
|
22
22
|
from .permissions_cmd import command as permissions_command
|
|
23
23
|
from .resume_cmd import command as resume_command
|
|
24
|
+
from .stats_cmd import command as stats_command
|
|
24
25
|
from .tasks_cmd import command as tasks_command
|
|
25
26
|
from .status_cmd import command as status_command
|
|
26
27
|
from .todos_cmd import command as todos_command
|
|
@@ -51,6 +52,7 @@ ALL_COMMANDS: List[SlashCommand] = [
|
|
|
51
52
|
models_command,
|
|
52
53
|
exit_command,
|
|
53
54
|
status_command,
|
|
55
|
+
stats_command,
|
|
54
56
|
doctor_command,
|
|
55
57
|
memory_command,
|
|
56
58
|
permissions_command,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
from rich.markup import escape
|
|
2
|
+
from rich import box
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from rich.table import Table
|
|
2
5
|
|
|
3
6
|
from ripperdoc.core.agents import (
|
|
4
7
|
AGENT_DIR_NAME,
|
|
@@ -8,14 +11,38 @@ from ripperdoc.core.agents import (
|
|
|
8
11
|
save_agent_definition,
|
|
9
12
|
)
|
|
10
13
|
from ripperdoc.core.config import get_global_config
|
|
14
|
+
from ripperdoc.tools.task_tool import (
|
|
15
|
+
list_agent_runs,
|
|
16
|
+
get_agent_run_snapshot,
|
|
17
|
+
cancel_agent_run,
|
|
18
|
+
)
|
|
11
19
|
from ripperdoc.utils.log import get_logger
|
|
12
20
|
|
|
13
|
-
from typing import Any
|
|
21
|
+
from typing import Any, Dict, Optional
|
|
14
22
|
from .base import SlashCommand
|
|
15
23
|
|
|
16
24
|
logger = get_logger()
|
|
17
25
|
|
|
18
26
|
|
|
27
|
+
def _format_duration(duration_ms: float | None) -> str:
|
|
28
|
+
if duration_ms is None:
|
|
29
|
+
return "-"
|
|
30
|
+
try:
|
|
31
|
+
duration = float(duration_ms)
|
|
32
|
+
except (TypeError, ValueError):
|
|
33
|
+
return "-"
|
|
34
|
+
if duration < 1000:
|
|
35
|
+
return f"{int(duration)} ms"
|
|
36
|
+
seconds = duration / 1000.0
|
|
37
|
+
if seconds < 60:
|
|
38
|
+
return f"{seconds:.1f}s"
|
|
39
|
+
minutes, secs = divmod(int(seconds), 60)
|
|
40
|
+
if minutes < 60:
|
|
41
|
+
return f"{minutes}m {secs}s"
|
|
42
|
+
hours, mins = divmod(minutes, 60)
|
|
43
|
+
return f"{hours}h {mins}m"
|
|
44
|
+
|
|
45
|
+
|
|
19
46
|
def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
20
47
|
console = ui.console
|
|
21
48
|
tokens = trimmed_arg.split()
|
|
@@ -39,18 +66,118 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
39
66
|
"[bold]/agents delete <name> [location][/bold] — "
|
|
40
67
|
"delete agent (location: user|project, default user)"
|
|
41
68
|
)
|
|
69
|
+
console.print("[bold]/agents runs[/bold] — list subagent runs")
|
|
70
|
+
console.print("[bold]/agents show <id>[/bold] — show subagent run details")
|
|
71
|
+
console.print("[bold]/agents cancel <id>[/bold] — cancel a background subagent run")
|
|
42
72
|
console.print(
|
|
43
73
|
f"[dim]Agent files live in ~/.ripperdoc/{AGENT_DIR_NAME} "
|
|
44
74
|
f"or ./.ripperdoc/{AGENT_DIR_NAME}[/dim]"
|
|
45
75
|
)
|
|
46
76
|
console.print(
|
|
47
|
-
"[dim]Model can be a profile name or pointer (
|
|
77
|
+
"[dim]Model can be a profile name or pointer (main/quick). Defaults to 'main'.[/dim]"
|
|
48
78
|
)
|
|
49
79
|
|
|
50
80
|
if subcmd in ("help", "-h", "--help"):
|
|
51
81
|
print_agents_usage()
|
|
52
82
|
return True
|
|
53
83
|
|
|
84
|
+
if subcmd in ("runs", "run", "tasks", "status"):
|
|
85
|
+
console = ui.console
|
|
86
|
+
run_ids = list_agent_runs()
|
|
87
|
+
if not run_ids:
|
|
88
|
+
console.print(
|
|
89
|
+
Panel("No subagent runs recorded", title="Subagent runs", box=box.ROUNDED)
|
|
90
|
+
)
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
table = Table(box=box.SIMPLE_HEAVY, expand=True)
|
|
94
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
95
|
+
table.add_column("Status", style="magenta", no_wrap=True)
|
|
96
|
+
table.add_column("Agent", style="white", no_wrap=True)
|
|
97
|
+
table.add_column("Duration", style="dim", no_wrap=True)
|
|
98
|
+
table.add_column("Background", style="dim", no_wrap=True)
|
|
99
|
+
table.add_column("Result", style="white")
|
|
100
|
+
|
|
101
|
+
for run_id in sorted(run_ids):
|
|
102
|
+
snapshot: Dict[Any, Any] = get_agent_run_snapshot(run_id) or {}
|
|
103
|
+
result_text = snapshot.get("result_text") or snapshot.get("error") or ""
|
|
104
|
+
result_preview = (
|
|
105
|
+
result_text if len(result_text) <= 80 else result_text[:77] + "..."
|
|
106
|
+
)
|
|
107
|
+
table.add_row(
|
|
108
|
+
escape(run_id),
|
|
109
|
+
escape(snapshot.get("status") or "unknown"),
|
|
110
|
+
escape(snapshot.get("agent_type") or "unknown"),
|
|
111
|
+
_format_duration(snapshot.get("duration_ms")),
|
|
112
|
+
"yes" if snapshot.get("is_background") else "no",
|
|
113
|
+
escape(result_preview),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
console.print(
|
|
117
|
+
Panel(table, title="Subagent runs", box=box.ROUNDED, padding=(1, 2)),
|
|
118
|
+
markup=False,
|
|
119
|
+
)
|
|
120
|
+
console.print(
|
|
121
|
+
"[dim]Use /agents show <id> for details or /agents cancel <id> to stop a background run.[/dim]"
|
|
122
|
+
)
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
if subcmd in ("show", "info", "details"):
|
|
126
|
+
if len(tokens) < 2:
|
|
127
|
+
console.print("[red]Usage: /agents show <id>[/red]")
|
|
128
|
+
return True
|
|
129
|
+
run_id = tokens[1]
|
|
130
|
+
snapshot = get_agent_run_snapshot(run_id) # type: ignore[assignment]
|
|
131
|
+
if not snapshot:
|
|
132
|
+
console.print(f"[red]No subagent run found with id '{escape(run_id)}'.[/red]")
|
|
133
|
+
return True
|
|
134
|
+
details = Table(box=box.SIMPLE_HEAVY, show_header=False)
|
|
135
|
+
details.add_row("ID", escape(run_id))
|
|
136
|
+
details.add_row("Status", escape(snapshot.get("status") or "unknown"))
|
|
137
|
+
details.add_row("Agent", escape(snapshot.get("agent_type") or "unknown"))
|
|
138
|
+
details.add_row("Duration", _format_duration(snapshot.get("duration_ms")))
|
|
139
|
+
details.add_row(
|
|
140
|
+
"Background", "yes" if snapshot.get("is_background") else "no"
|
|
141
|
+
)
|
|
142
|
+
if snapshot.get("model_used"):
|
|
143
|
+
details.add_row("Model", escape(str(snapshot.get("model_used"))))
|
|
144
|
+
if snapshot.get("tool_use_count"):
|
|
145
|
+
details.add_row("Tool uses", str(snapshot.get("tool_use_count")))
|
|
146
|
+
if snapshot.get("missing_tools"):
|
|
147
|
+
details.add_row("Missing tools", escape(", ".join(snapshot["missing_tools"])))
|
|
148
|
+
if snapshot.get("error"):
|
|
149
|
+
details.add_row("Error", escape(str(snapshot.get("error"))))
|
|
150
|
+
console.print(
|
|
151
|
+
Panel(details, title=f"Subagent {escape(run_id)}", box=box.ROUNDED, padding=(1, 2)),
|
|
152
|
+
markup=False,
|
|
153
|
+
)
|
|
154
|
+
result_text = snapshot.get("result_text")
|
|
155
|
+
if result_text:
|
|
156
|
+
console.print(Panel(escape(result_text), title="Result", box=box.SIMPLE))
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
if subcmd in ("cancel", "kill", "stop"):
|
|
160
|
+
if len(tokens) < 2:
|
|
161
|
+
console.print("[red]Usage: /agents cancel <id>[/red]")
|
|
162
|
+
return True
|
|
163
|
+
run_id = tokens[1]
|
|
164
|
+
runner = getattr(ui, "run_async", None)
|
|
165
|
+
try:
|
|
166
|
+
if callable(runner):
|
|
167
|
+
cancelled = runner(cancel_agent_run(run_id))
|
|
168
|
+
else:
|
|
169
|
+
import asyncio
|
|
170
|
+
|
|
171
|
+
cancelled = asyncio.run(cancel_agent_run(run_id))
|
|
172
|
+
except (OSError, RuntimeError, ValueError) as exc:
|
|
173
|
+
console.print(f"[red]Failed to cancel '{escape(run_id)}': {escape(str(exc))}[/red]")
|
|
174
|
+
return True
|
|
175
|
+
if cancelled:
|
|
176
|
+
console.print(f"[green]Cancelled subagent {escape(run_id)}[/green]")
|
|
177
|
+
else:
|
|
178
|
+
console.print(f"[yellow]No running subagent found for '{escape(run_id)}'.[/yellow]")
|
|
179
|
+
return True
|
|
180
|
+
|
|
54
181
|
if subcmd in ("create", "add"):
|
|
55
182
|
agent_name = tokens[1] if len(tokens) > 1 else console.input("Agent name: ").strip()
|
|
56
183
|
if not agent_name:
|
|
@@ -84,7 +211,7 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
84
211
|
|
|
85
212
|
config = get_global_config()
|
|
86
213
|
pointer_map = config.model_pointers.model_dump()
|
|
87
|
-
default_model_value = model_arg or pointer_map.get("
|
|
214
|
+
default_model_value = model_arg or pointer_map.get("main", "main")
|
|
88
215
|
model_input = (
|
|
89
216
|
console.input(f"Model profile or pointer [{default_model_value}]: ").strip()
|
|
90
217
|
or default_model_value
|
|
@@ -205,7 +332,7 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
205
332
|
|
|
206
333
|
config = get_global_config()
|
|
207
334
|
pointer_map = config.model_pointers.model_dump()
|
|
208
|
-
model_default = target_agent.model or pointer_map.get("
|
|
335
|
+
model_default = target_agent.model or pointer_map.get("main", "main")
|
|
209
336
|
model_input = (
|
|
210
337
|
console.input(f"Model profile or pointer [{model_default}]: ").strip() or model_default
|
|
211
338
|
)
|
|
@@ -245,7 +372,7 @@ def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
|
245
372
|
console.print(f" • {escape(agent.agent_type)} ({escape(str(location))})", markup=False)
|
|
246
373
|
console.print(f" {escape(agent.when_to_use)}", markup=False)
|
|
247
374
|
console.print(f" tools: {escape(tools_str)}", markup=False)
|
|
248
|
-
console.print(f" model: {escape(agent.model or '
|
|
375
|
+
console.print(f" model: {escape(agent.model or 'main (default)')}", markup=False)
|
|
249
376
|
if agents.failed_files:
|
|
250
377
|
console.print("[yellow]Some agent files could not be loaded:[/yellow]")
|
|
251
378
|
for path, error in agents.failed_files:
|
|
@@ -3,8 +3,16 @@ from .base import SlashCommand
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def _handle(ui: Any, _: str) -> bool:
|
|
6
|
+
try:
|
|
7
|
+
ui._run_session_end("clear")
|
|
8
|
+
except (AttributeError, RuntimeError, ValueError):
|
|
9
|
+
pass
|
|
6
10
|
ui.conversation_messages = []
|
|
7
11
|
ui.console.print("[green]✓ Conversation cleared[/green]")
|
|
12
|
+
try:
|
|
13
|
+
ui._run_session_start("clear")
|
|
14
|
+
except (AttributeError, RuntimeError, ValueError):
|
|
15
|
+
pass
|
|
8
16
|
return True
|
|
9
17
|
|
|
10
18
|
|