ripperdoc 0.2.4__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ripperdoc/__init__.py +1 -1
- ripperdoc/__main__.py +0 -5
- ripperdoc/cli/cli.py +37 -16
- ripperdoc/cli/commands/__init__.py +2 -0
- ripperdoc/cli/commands/agents_cmd.py +12 -9
- ripperdoc/cli/commands/compact_cmd.py +7 -3
- ripperdoc/cli/commands/context_cmd.py +33 -13
- ripperdoc/cli/commands/doctor_cmd.py +27 -14
- ripperdoc/cli/commands/exit_cmd.py +1 -1
- ripperdoc/cli/commands/mcp_cmd.py +13 -8
- ripperdoc/cli/commands/memory_cmd.py +5 -5
- ripperdoc/cli/commands/models_cmd.py +47 -16
- ripperdoc/cli/commands/permissions_cmd.py +302 -0
- ripperdoc/cli/commands/resume_cmd.py +1 -2
- ripperdoc/cli/commands/tasks_cmd.py +24 -13
- ripperdoc/cli/ui/rich_ui.py +500 -406
- ripperdoc/cli/ui/tool_renderers.py +298 -0
- ripperdoc/core/agents.py +17 -9
- ripperdoc/core/config.py +130 -6
- ripperdoc/core/default_tools.py +7 -2
- ripperdoc/core/permissions.py +20 -14
- ripperdoc/core/providers/anthropic.py +107 -4
- ripperdoc/core/providers/base.py +33 -4
- ripperdoc/core/providers/gemini.py +169 -50
- ripperdoc/core/providers/openai.py +257 -23
- ripperdoc/core/query.py +294 -61
- ripperdoc/core/query_utils.py +50 -6
- ripperdoc/core/skills.py +295 -0
- ripperdoc/core/system_prompt.py +13 -7
- ripperdoc/core/tool.py +8 -6
- ripperdoc/sdk/client.py +14 -1
- ripperdoc/tools/ask_user_question_tool.py +20 -22
- ripperdoc/tools/background_shell.py +19 -13
- ripperdoc/tools/bash_tool.py +356 -209
- ripperdoc/tools/dynamic_mcp_tool.py +428 -0
- ripperdoc/tools/enter_plan_mode_tool.py +5 -2
- ripperdoc/tools/exit_plan_mode_tool.py +6 -3
- ripperdoc/tools/file_edit_tool.py +53 -10
- ripperdoc/tools/file_read_tool.py +17 -7
- ripperdoc/tools/file_write_tool.py +49 -13
- ripperdoc/tools/glob_tool.py +10 -9
- ripperdoc/tools/grep_tool.py +182 -51
- ripperdoc/tools/ls_tool.py +6 -6
- ripperdoc/tools/mcp_tools.py +106 -456
- ripperdoc/tools/multi_edit_tool.py +49 -9
- ripperdoc/tools/notebook_edit_tool.py +57 -13
- ripperdoc/tools/skill_tool.py +205 -0
- ripperdoc/tools/task_tool.py +7 -8
- ripperdoc/tools/todo_tool.py +12 -12
- ripperdoc/tools/tool_search_tool.py +5 -6
- ripperdoc/utils/coerce.py +34 -0
- ripperdoc/utils/context_length_errors.py +252 -0
- ripperdoc/utils/file_watch.py +5 -4
- ripperdoc/utils/json_utils.py +4 -4
- ripperdoc/utils/log.py +3 -3
- ripperdoc/utils/mcp.py +36 -15
- ripperdoc/utils/memory.py +9 -6
- ripperdoc/utils/message_compaction.py +16 -11
- ripperdoc/utils/messages.py +73 -8
- ripperdoc/utils/path_ignore.py +677 -0
- ripperdoc/utils/permissions/__init__.py +7 -1
- ripperdoc/utils/permissions/path_validation_utils.py +5 -3
- ripperdoc/utils/permissions/shell_command_validation.py +496 -18
- ripperdoc/utils/prompt.py +1 -1
- ripperdoc/utils/safe_get_cwd.py +5 -2
- ripperdoc/utils/session_history.py +38 -19
- ripperdoc/utils/todo.py +6 -2
- ripperdoc/utils/token_estimation.py +4 -3
- {ripperdoc-0.2.4.dist-info → ripperdoc-0.2.5.dist-info}/METADATA +12 -1
- ripperdoc-0.2.5.dist-info/RECORD +107 -0
- ripperdoc-0.2.4.dist-info/RECORD +0 -99
- {ripperdoc-0.2.4.dist-info → ripperdoc-0.2.5.dist-info}/WHEEL +0 -0
- {ripperdoc-0.2.4.dist-info → ripperdoc-0.2.5.dist-info}/entry_points.txt +0 -0
- {ripperdoc-0.2.4.dist-info → ripperdoc-0.2.5.dist-info}/licenses/LICENSE +0 -0
- {ripperdoc-0.2.4.dist-info → ripperdoc-0.2.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Slash command to manage permission rules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, List, Literal
|
|
7
|
+
|
|
8
|
+
from rich.markup import escape
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from ripperdoc.core.config import (
|
|
13
|
+
config_manager,
|
|
14
|
+
get_global_config,
|
|
15
|
+
get_project_config,
|
|
16
|
+
get_project_local_config,
|
|
17
|
+
save_global_config,
|
|
18
|
+
save_project_config,
|
|
19
|
+
save_project_local_config,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .base import SlashCommand
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
ScopeType = Literal["user", "project", "local"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _shorten_path(path: Path, project_path: Path) -> str:
|
|
29
|
+
"""Return a short, user-friendly path."""
|
|
30
|
+
try:
|
|
31
|
+
return str(path.resolve().relative_to(project_path.resolve()))
|
|
32
|
+
except (ValueError, OSError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
home = Path.home()
|
|
36
|
+
try:
|
|
37
|
+
rel_home = path.resolve().relative_to(home)
|
|
38
|
+
return f"~/{rel_home}"
|
|
39
|
+
except (ValueError, OSError):
|
|
40
|
+
return str(path)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_scope_info(scope: ScopeType, project_path: Path) -> tuple[str, str]:
|
|
44
|
+
"""Return (heading, config_path) for a given scope."""
|
|
45
|
+
if scope == "user":
|
|
46
|
+
return "User (global)", str(Path.home() / ".ripperdoc.json")
|
|
47
|
+
elif scope == "project":
|
|
48
|
+
return "Project (shared)", str(project_path / ".ripperdoc" / "config.json")
|
|
49
|
+
else: # local
|
|
50
|
+
return "Local (private)", str(project_path / ".ripperdoc" / "config.local.json")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_rules_for_scope(
|
|
54
|
+
scope: ScopeType, project_path: Path
|
|
55
|
+
) -> tuple[List[str], List[str]]:
|
|
56
|
+
"""Return (allow_rules, deny_rules) for a given scope."""
|
|
57
|
+
if scope == "user":
|
|
58
|
+
config = get_global_config()
|
|
59
|
+
return list(config.user_allow_rules), list(config.user_deny_rules)
|
|
60
|
+
elif scope == "project":
|
|
61
|
+
config = get_project_config(project_path)
|
|
62
|
+
return list(config.bash_allow_rules), list(config.bash_deny_rules)
|
|
63
|
+
else: # local
|
|
64
|
+
config = get_project_local_config(project_path)
|
|
65
|
+
return list(config.local_allow_rules), list(config.local_deny_rules)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _add_rule(
|
|
69
|
+
scope: ScopeType,
|
|
70
|
+
rule_type: Literal["allow", "deny"],
|
|
71
|
+
rule: str,
|
|
72
|
+
project_path: Path,
|
|
73
|
+
) -> bool:
|
|
74
|
+
"""Add a rule to the specified scope. Returns True if added, False if already exists."""
|
|
75
|
+
if scope == "user":
|
|
76
|
+
config = get_global_config()
|
|
77
|
+
rules = config.user_allow_rules if rule_type == "allow" else config.user_deny_rules
|
|
78
|
+
if rule in rules:
|
|
79
|
+
return False
|
|
80
|
+
rules.append(rule)
|
|
81
|
+
save_global_config(config)
|
|
82
|
+
elif scope == "project":
|
|
83
|
+
config = get_project_config(project_path)
|
|
84
|
+
rules = config.bash_allow_rules if rule_type == "allow" else config.bash_deny_rules
|
|
85
|
+
if rule in rules:
|
|
86
|
+
return False
|
|
87
|
+
rules.append(rule)
|
|
88
|
+
save_project_config(config, project_path)
|
|
89
|
+
else: # local
|
|
90
|
+
config = get_project_local_config(project_path)
|
|
91
|
+
rules = config.local_allow_rules if rule_type == "allow" else config.local_deny_rules
|
|
92
|
+
if rule in rules:
|
|
93
|
+
return False
|
|
94
|
+
rules.append(rule)
|
|
95
|
+
save_project_local_config(config, project_path)
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _remove_rule(
|
|
100
|
+
scope: ScopeType,
|
|
101
|
+
rule_type: Literal["allow", "deny"],
|
|
102
|
+
rule: str,
|
|
103
|
+
project_path: Path,
|
|
104
|
+
) -> bool:
|
|
105
|
+
"""Remove a rule from the specified scope. Returns True if removed, False if not found."""
|
|
106
|
+
if scope == "user":
|
|
107
|
+
config = get_global_config()
|
|
108
|
+
rules = config.user_allow_rules if rule_type == "allow" else config.user_deny_rules
|
|
109
|
+
if rule not in rules:
|
|
110
|
+
return False
|
|
111
|
+
rules.remove(rule)
|
|
112
|
+
save_global_config(config)
|
|
113
|
+
elif scope == "project":
|
|
114
|
+
config = get_project_config(project_path)
|
|
115
|
+
rules = config.bash_allow_rules if rule_type == "allow" else config.bash_deny_rules
|
|
116
|
+
if rule not in rules:
|
|
117
|
+
return False
|
|
118
|
+
rules.remove(rule)
|
|
119
|
+
save_project_config(config, project_path)
|
|
120
|
+
else: # local
|
|
121
|
+
config = get_project_local_config(project_path)
|
|
122
|
+
rules = config.local_allow_rules if rule_type == "allow" else config.local_deny_rules
|
|
123
|
+
if rule not in rules:
|
|
124
|
+
return False
|
|
125
|
+
rules.remove(rule)
|
|
126
|
+
save_project_local_config(config, project_path)
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _render_all_rules(console: Any, project_path: Path) -> None:
|
|
131
|
+
"""Display all permission rules from all scopes."""
|
|
132
|
+
table = Table(title="Permission Rules", show_header=True, header_style="bold cyan")
|
|
133
|
+
table.add_column("Scope", style="bold")
|
|
134
|
+
table.add_column("Type", style="dim")
|
|
135
|
+
table.add_column("Rule")
|
|
136
|
+
|
|
137
|
+
has_rules = False
|
|
138
|
+
|
|
139
|
+
for scope in ("user", "project", "local"):
|
|
140
|
+
allow_rules, deny_rules = _get_rules_for_scope(scope, project_path) # type: ignore
|
|
141
|
+
|
|
142
|
+
for rule in allow_rules:
|
|
143
|
+
table.add_row(scope, "[green]allow[/green]", escape(rule))
|
|
144
|
+
has_rules = True
|
|
145
|
+
|
|
146
|
+
for rule in deny_rules:
|
|
147
|
+
table.add_row(scope, "[red]deny[/red]", escape(rule))
|
|
148
|
+
has_rules = True
|
|
149
|
+
|
|
150
|
+
if has_rules:
|
|
151
|
+
console.print(table)
|
|
152
|
+
else:
|
|
153
|
+
console.print("[yellow]No permission rules configured yet.[/yellow]")
|
|
154
|
+
|
|
155
|
+
console.print()
|
|
156
|
+
console.print("[dim]Scopes (in priority order):[/dim]")
|
|
157
|
+
console.print("[dim] - user: Global rules (~/.ripperdoc.json)[/dim]")
|
|
158
|
+
console.print("[dim] - project: Shared project rules (.ripperdoc/config.json)[/dim]")
|
|
159
|
+
console.print("[dim] - local: Private project rules (.ripperdoc/config.local.json)[/dim]")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _render_scope_rules(console: Any, scope: ScopeType, project_path: Path) -> None:
|
|
163
|
+
"""Display rules for a specific scope."""
|
|
164
|
+
heading, config_path = _get_scope_info(scope, project_path)
|
|
165
|
+
allow_rules, deny_rules = _get_rules_for_scope(scope, project_path)
|
|
166
|
+
|
|
167
|
+
table = Table(title=f"{heading} Permission Rules", show_header=True, header_style="bold cyan")
|
|
168
|
+
table.add_column("Type", style="dim")
|
|
169
|
+
table.add_column("Rule")
|
|
170
|
+
|
|
171
|
+
has_rules = False
|
|
172
|
+
for rule in allow_rules:
|
|
173
|
+
table.add_row("[green]allow[/green]", escape(rule))
|
|
174
|
+
has_rules = True
|
|
175
|
+
|
|
176
|
+
for rule in deny_rules:
|
|
177
|
+
table.add_row("[red]deny[/red]", escape(rule))
|
|
178
|
+
has_rules = True
|
|
179
|
+
|
|
180
|
+
if has_rules:
|
|
181
|
+
console.print(table)
|
|
182
|
+
else:
|
|
183
|
+
console.print(f"[yellow]No {scope} rules configured.[/yellow]")
|
|
184
|
+
|
|
185
|
+
console.print(f"[dim]Config file: {escape(config_path)}[/dim]")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
189
|
+
project_path = getattr(ui, "project_path", Path.cwd())
|
|
190
|
+
args = trimmed_arg.strip().split()
|
|
191
|
+
|
|
192
|
+
# No args: show all rules
|
|
193
|
+
if not args:
|
|
194
|
+
_render_all_rules(ui.console, project_path)
|
|
195
|
+
ui.console.print()
|
|
196
|
+
ui.console.print("[dim]Usage:[/dim]")
|
|
197
|
+
ui.console.print("[dim] /permissions - Show all rules[/dim]")
|
|
198
|
+
ui.console.print("[dim] /permissions <scope> - Show rules for scope[/dim]")
|
|
199
|
+
ui.console.print("[dim] /permissions add <scope> <type> <rule> - Add a rule[/dim]")
|
|
200
|
+
ui.console.print("[dim] /permissions remove <scope> <type> <rule> - Remove a rule[/dim]")
|
|
201
|
+
ui.console.print("[dim]Scopes: user, project, local[/dim]")
|
|
202
|
+
ui.console.print("[dim]Types: allow, deny[/dim]")
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
# Parse command
|
|
206
|
+
action = args[0].lower()
|
|
207
|
+
scope_aliases = {
|
|
208
|
+
"user": "user",
|
|
209
|
+
"global": "user",
|
|
210
|
+
"project": "project",
|
|
211
|
+
"workspace": "project",
|
|
212
|
+
"local": "local",
|
|
213
|
+
"private": "local",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Single scope display
|
|
217
|
+
if action in scope_aliases:
|
|
218
|
+
scope = scope_aliases[action]
|
|
219
|
+
_render_scope_rules(ui.console, scope, project_path) # type: ignore
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
# Add rule
|
|
223
|
+
if action == "add":
|
|
224
|
+
if len(args) < 4:
|
|
225
|
+
ui.console.print("[red]Usage: /permissions add <scope> <type> <rule>[/red]")
|
|
226
|
+
ui.console.print("[dim]Example: /permissions add local allow npm test[/dim]")
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
scope_arg = args[1].lower()
|
|
230
|
+
if scope_arg not in scope_aliases:
|
|
231
|
+
ui.console.print(f"[red]Unknown scope: {escape(scope_arg)}[/red]")
|
|
232
|
+
ui.console.print("[dim]Available scopes: user, project, local[/dim]")
|
|
233
|
+
return True
|
|
234
|
+
scope: ScopeType = scope_aliases[scope_arg] # type: ignore
|
|
235
|
+
|
|
236
|
+
rule_type_arg = args[2].lower()
|
|
237
|
+
if rule_type_arg not in ("allow", "deny"):
|
|
238
|
+
ui.console.print(f"[red]Unknown rule type: {escape(rule_type_arg)}[/red]")
|
|
239
|
+
ui.console.print("[dim]Available types: allow, deny[/dim]")
|
|
240
|
+
return True
|
|
241
|
+
rule_type: Literal["allow", "deny"] = rule_type_arg # type: ignore
|
|
242
|
+
|
|
243
|
+
rule = " ".join(args[3:])
|
|
244
|
+
if _add_rule(scope, rule_type, rule, project_path):
|
|
245
|
+
ui.console.print(
|
|
246
|
+
Panel(
|
|
247
|
+
f"Added [{'green' if rule_type == 'allow' else 'red'}]{rule_type}[/] rule to {scope}:\n{escape(rule)}",
|
|
248
|
+
title="/permissions",
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
ui.console.print(f"[yellow]Rule already exists in {scope}.[/yellow]")
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
# Remove rule
|
|
256
|
+
if action in ("remove", "rm", "delete", "del"):
|
|
257
|
+
if len(args) < 4:
|
|
258
|
+
ui.console.print("[red]Usage: /permissions remove <scope> <type> <rule>[/red]")
|
|
259
|
+
ui.console.print("[dim]Example: /permissions remove local allow npm test[/dim]")
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
scope_arg = args[1].lower()
|
|
263
|
+
if scope_arg not in scope_aliases:
|
|
264
|
+
ui.console.print(f"[red]Unknown scope: {escape(scope_arg)}[/red]")
|
|
265
|
+
ui.console.print("[dim]Available scopes: user, project, local[/dim]")
|
|
266
|
+
return True
|
|
267
|
+
scope = scope_aliases[scope_arg] # type: ignore
|
|
268
|
+
|
|
269
|
+
rule_type_arg = args[2].lower()
|
|
270
|
+
if rule_type_arg not in ("allow", "deny"):
|
|
271
|
+
ui.console.print(f"[red]Unknown rule type: {escape(rule_type_arg)}[/red]")
|
|
272
|
+
ui.console.print("[dim]Available types: allow, deny[/dim]")
|
|
273
|
+
return True
|
|
274
|
+
rule_type = rule_type_arg # type: ignore
|
|
275
|
+
|
|
276
|
+
rule = " ".join(args[3:])
|
|
277
|
+
if _remove_rule(scope, rule_type, rule, project_path):
|
|
278
|
+
ui.console.print(
|
|
279
|
+
Panel(
|
|
280
|
+
f"Removed [{'green' if rule_type == 'allow' else 'red'}]{rule_type}[/] rule from {scope}:\n{escape(rule)}",
|
|
281
|
+
title="/permissions",
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
ui.console.print(f"[yellow]Rule not found in {scope}.[/yellow]")
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
# Unknown command
|
|
289
|
+
ui.console.print(f"[red]Unknown action: {escape(action)}[/red]")
|
|
290
|
+
ui.console.print("[dim]Available actions: add, remove, or a scope name[/dim]")
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
command = SlashCommand(
|
|
295
|
+
name="permissions",
|
|
296
|
+
description="Manage permission rules for tools",
|
|
297
|
+
handler=_handle,
|
|
298
|
+
aliases=(),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
__all__ = ["command"]
|
|
@@ -61,8 +61,7 @@ def _choose_session(ui: Any, arg: str) -> Optional[SessionSummary]:
|
|
|
61
61
|
idx = int(choice_text)
|
|
62
62
|
if idx < 0 or idx >= len(sessions):
|
|
63
63
|
ui.console.print(
|
|
64
|
-
f"[red]Invalid session index {escape(str(idx))}. "
|
|
65
|
-
f"Choose 0-{len(sessions) - 1}.[/red]"
|
|
64
|
+
f"[red]Invalid session index {escape(str(idx))}. Choose 0-{len(sessions) - 1}.[/red]"
|
|
66
65
|
)
|
|
67
66
|
return None
|
|
68
67
|
return sessions[idx]
|
|
@@ -105,10 +105,11 @@ def _list_tasks(ui: Any) -> bool:
|
|
|
105
105
|
for task_id in sorted(task_ids):
|
|
106
106
|
try:
|
|
107
107
|
status = get_background_status(task_id, consume=False)
|
|
108
|
-
except
|
|
108
|
+
except (KeyError, ValueError, RuntimeError, OSError) as exc:
|
|
109
109
|
table.add_row(escape(task_id), "[red]error[/]", escape(str(exc)), "-")
|
|
110
|
-
logger.
|
|
111
|
-
"[tasks_cmd] Failed to read background task status",
|
|
110
|
+
logger.warning(
|
|
111
|
+
"[tasks_cmd] Failed to read background task status: %s: %s",
|
|
112
|
+
type(exc).__name__, exc,
|
|
112
113
|
extra={"task_id": task_id, "session_id": getattr(ui, "session_id", None)},
|
|
113
114
|
)
|
|
114
115
|
continue
|
|
@@ -143,10 +144,11 @@ def _kill_task(ui: Any, task_id: str) -> bool:
|
|
|
143
144
|
except KeyError:
|
|
144
145
|
console.print(f"[red]No task found with id '{escape(task_id)}'.[/red]")
|
|
145
146
|
return True
|
|
146
|
-
except
|
|
147
|
+
except (ValueError, RuntimeError, OSError) as exc:
|
|
147
148
|
console.print(f"[red]Failed to read task '{escape(task_id)}': {escape(str(exc))}[/red]")
|
|
148
|
-
logger.
|
|
149
|
-
"[tasks_cmd] Failed to read task before kill",
|
|
149
|
+
logger.warning(
|
|
150
|
+
"[tasks_cmd] Failed to read task before kill: %s: %s",
|
|
151
|
+
type(exc).__name__, exc,
|
|
150
152
|
extra={"task_id": task_id, "session_id": getattr(ui, "session_id", None)},
|
|
151
153
|
)
|
|
152
154
|
return True
|
|
@@ -157,12 +159,20 @@ def _kill_task(ui: Any, task_id: str) -> bool:
|
|
|
157
159
|
)
|
|
158
160
|
return True
|
|
159
161
|
|
|
162
|
+
runner = getattr(ui, "run_async", None)
|
|
163
|
+
|
|
160
164
|
try:
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
if callable(runner):
|
|
166
|
+
killed = runner(kill_background_task(task_id))
|
|
167
|
+
else:
|
|
168
|
+
killed = asyncio.run(kill_background_task(task_id))
|
|
169
|
+
except (OSError, RuntimeError, asyncio.CancelledError) as exc:
|
|
170
|
+
if isinstance(exc, asyncio.CancelledError):
|
|
171
|
+
raise
|
|
163
172
|
console.print(f"[red]Error stopping task {escape(task_id)}: {escape(str(exc))}[/red]")
|
|
164
|
-
logger.
|
|
165
|
-
"[tasks_cmd] Error stopping background task",
|
|
173
|
+
logger.warning(
|
|
174
|
+
"[tasks_cmd] Error stopping background task: %s: %s",
|
|
175
|
+
type(exc).__name__, exc,
|
|
166
176
|
extra={"task_id": task_id, "session_id": getattr(ui, "session_id", None)},
|
|
167
177
|
)
|
|
168
178
|
return True
|
|
@@ -183,10 +193,11 @@ def _show_task(ui: Any, task_id: str) -> bool:
|
|
|
183
193
|
except KeyError:
|
|
184
194
|
console.print(f"[red]No task found with id '{escape(task_id)}'.[/red]")
|
|
185
195
|
return True
|
|
186
|
-
except
|
|
196
|
+
except (ValueError, RuntimeError, OSError) as exc:
|
|
187
197
|
console.print(f"[red]Failed to read task '{escape(task_id)}': {escape(str(exc))}[/red]")
|
|
188
|
-
logger.
|
|
189
|
-
"[tasks_cmd] Failed to read task for detail view",
|
|
198
|
+
logger.warning(
|
|
199
|
+
"[tasks_cmd] Failed to read task for detail view: %s: %s",
|
|
200
|
+
type(exc).__name__, exc,
|
|
190
201
|
extra={"task_id": task_id, "session_id": getattr(ui, "session_id", None)},
|
|
191
202
|
)
|
|
192
203
|
return True
|