crabcode 0.1.6__py3-none-win_amd64.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.
- crabcode-0.1.6.dist-info/METADATA +63 -0
- crabcode-0.1.6.dist-info/RECORD +205 -0
- crabcode-0.1.6.dist-info/WHEEL +4 -0
- crabcode-0.1.6.dist-info/entry_points.txt +2 -0
- crabcode_cli/__init__.py +7 -0
- crabcode_cli/__main__.py +4 -0
- crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
- crabcode_cli/_native/runtime.json +6 -0
- crabcode_cli/app.py +776 -0
- crabcode_cli/banner.py +39 -0
- crabcode_cli/computer_use.py +286 -0
- crabcode_cli/model_manager.py +649 -0
- crabcode_cli/model_picker.py +168 -0
- crabcode_cli/pipe.py +148 -0
- crabcode_cli/render/__init__.py +0 -0
- crabcode_cli/render/messages.py +19 -0
- crabcode_cli/repl.py +4841 -0
- crabcode_cli/session_picker.py +239 -0
- crabcode_core/__init__.py +7 -0
- crabcode_core/_version.py +3 -0
- crabcode_core/_windows_process.py +68 -0
- crabcode_core/agent_manager.py +1731 -0
- crabcode_core/api/__init__.py +20 -0
- crabcode_core/api/anthropic_adapter.py +648 -0
- crabcode_core/api/azure_adapter.py +69 -0
- crabcode_core/api/base.py +233 -0
- crabcode_core/api/codex_adapter.py +1327 -0
- crabcode_core/api/error_logging.py +132 -0
- crabcode_core/api/gemini_adapter.py +26 -0
- crabcode_core/api/model_info.py +257 -0
- crabcode_core/api/network.py +62 -0
- crabcode_core/api/ollama_adapter.py +27 -0
- crabcode_core/api/openai_adapter.py +323 -0
- crabcode_core/api/registry.py +58 -0
- crabcode_core/command_line.py +18 -0
- crabcode_core/compact/__init__.py +17 -0
- crabcode_core/compact/compact.py +514 -0
- crabcode_core/compact/context_tokens.py +165 -0
- crabcode_core/computer_use_mode.py +38 -0
- crabcode_core/computer_use_observation.py +144 -0
- crabcode_core/config/__init__.py +3 -0
- crabcode_core/config/claudemd.py +61 -0
- crabcode_core/config/manager.py +237 -0
- crabcode_core/config/model_catalog.py +256 -0
- crabcode_core/events.py +4896 -0
- crabcode_core/file_lock.py +81 -0
- crabcode_core/filesystem.py +22 -0
- crabcode_core/goal.py +149 -0
- crabcode_core/hooks/__init__.py +5 -0
- crabcode_core/hooks/manager.py +290 -0
- crabcode_core/image_io.py +111 -0
- crabcode_core/io_worker.py +211 -0
- crabcode_core/logging_utils.py +168 -0
- crabcode_core/lsp/__init__.py +6 -0
- crabcode_core/lsp/client.py +786 -0
- crabcode_core/lsp/diagnostics.py +150 -0
- crabcode_core/lsp/manager.py +583 -0
- crabcode_core/mcp/__init__.py +4 -0
- crabcode_core/mcp/client.py +169 -0
- crabcode_core/mcp/config.py +57 -0
- crabcode_core/path_validation.py +34 -0
- crabcode_core/paths.py +24 -0
- crabcode_core/peer/__init__.py +10 -0
- crabcode_core/peer/runtime.py +518 -0
- crabcode_core/permissions/__init__.py +7 -0
- crabcode_core/permissions/ai_reviewer.py +184 -0
- crabcode_core/permissions/manager.py +187 -0
- crabcode_core/plan/__init__.py +5 -0
- crabcode_core/plan/executor.py +270 -0
- crabcode_core/plan/types.py +169 -0
- crabcode_core/prompts/__init__.py +0 -0
- crabcode_core/prompts/blocks.py +10 -0
- crabcode_core/prompts/context.py +229 -0
- crabcode_core/prompts/library.py +92 -0
- crabcode_core/prompts/profile.py +79 -0
- crabcode_core/prompts/system.py +356 -0
- crabcode_core/prompts/templates.py +104 -0
- crabcode_core/query/__init__.py +0 -0
- crabcode_core/query/computer_use_history.py +83 -0
- crabcode_core/query/loop.py +2446 -0
- crabcode_core/query/retry.py +116 -0
- crabcode_core/schedule/__init__.py +13 -0
- crabcode_core/schedule/manager.py +631 -0
- crabcode_core/schedule/models.py +204 -0
- crabcode_core/schedule/store.py +558 -0
- crabcode_core/schedule/timing.py +287 -0
- crabcode_core/session/__init__.py +12 -0
- crabcode_core/session/export.py +141 -0
- crabcode_core/session/meta_db.py +684 -0
- crabcode_core/session/storage.py +2032 -0
- crabcode_core/session/title_gen.py +52 -0
- crabcode_core/skills/__init__.py +6 -0
- crabcode_core/skills/context.py +23 -0
- crabcode_core/skills/loader.py +214 -0
- crabcode_core/skills/matcher.py +203 -0
- crabcode_core/snapshot/__init__.py +21 -0
- crabcode_core/snapshot/snapshot.py +436 -0
- crabcode_core/snapshot/tracker.py +359 -0
- crabcode_core/subprocess_utils.py +260 -0
- crabcode_core/team/__init__.py +27 -0
- crabcode_core/team/bridge.py +66 -0
- crabcode_core/team/inbox.py +237 -0
- crabcode_core/team/manager.py +1660 -0
- crabcode_core/team/message_bus.py +821 -0
- crabcode_core/team/models.py +191 -0
- crabcode_core/team/recovery.py +192 -0
- crabcode_core/text_io.py +52 -0
- crabcode_core/tools/__init__.py +112 -0
- crabcode_core/tools/_input_helpers.py +39 -0
- crabcode_core/tools/_input_schema.py +130 -0
- crabcode_core/tools/agent.py +413 -0
- crabcode_core/tools/apply_patch.py +587 -0
- crabcode_core/tools/ask_user.py +122 -0
- crabcode_core/tools/bash.py +154 -0
- crabcode_core/tools/browser.py +678 -0
- crabcode_core/tools/checklist.py +241 -0
- crabcode_core/tools/checkpoint.py +78 -0
- crabcode_core/tools/computer_use.py +599 -0
- crabcode_core/tools/diff_utils.py +123 -0
- crabcode_core/tools/file_edit.py +240 -0
- crabcode_core/tools/file_read.py +187 -0
- crabcode_core/tools/file_write.py +154 -0
- crabcode_core/tools/glob.py +123 -0
- crabcode_core/tools/goal.py +134 -0
- crabcode_core/tools/grep.py +247 -0
- crabcode_core/tools/image.py +173 -0
- crabcode_core/tools/image_generate.py +115 -0
- crabcode_core/tools/lint.py +388 -0
- crabcode_core/tools/loading.py +187 -0
- crabcode_core/tools/memory.py +456 -0
- crabcode_core/tools/monitor.py +677 -0
- crabcode_core/tools/peer.py +103 -0
- crabcode_core/tools/revert.py +90 -0
- crabcode_core/tools/schedule.py +683 -0
- crabcode_core/tools/skill.py +108 -0
- crabcode_core/tools/switch_mode.py +213 -0
- crabcode_core/tools/team.py +329 -0
- crabcode_core/tools/tool_search.py +64 -0
- crabcode_core/tools/web_search.py +444 -0
- crabcode_core/turn_details.py +64 -0
- crabcode_core/types/__init__.py +95 -0
- crabcode_core/types/config.py +434 -0
- crabcode_core/types/event.py +334 -0
- crabcode_core/types/message.py +263 -0
- crabcode_core/types/tool.py +208 -0
- crabcode_core/usage.py +289 -0
- crabcode_core/utf8_sanitize.py +29 -0
- crabcode_debugger/__init__.py +5 -0
- crabcode_debugger/adapters.py +332 -0
- crabcode_debugger/dap.py +304 -0
- crabcode_debugger/memory.py +1806 -0
- crabcode_debugger/process.py +707 -0
- crabcode_debugger/runtime.py +58 -0
- crabcode_debugger/sessions.py +387 -0
- crabcode_debugger/tool.py +731 -0
- crabcode_gateway/__init__.py +18 -0
- crabcode_gateway/acp/__init__.py +13 -0
- crabcode_gateway/acp/agent.py +1581 -0
- crabcode_gateway/acp/session.py +114 -0
- crabcode_gateway/acp/transport.py +39 -0
- crabcode_gateway/acp/types.py +90 -0
- crabcode_gateway/adapter.py +30 -0
- crabcode_gateway/auth.py +139 -0
- crabcode_gateway/computer_use.py +364 -0
- crabcode_gateway/document_engine.py +637 -0
- crabcode_gateway/document_worker.py +301 -0
- crabcode_gateway/event_bus.py +655 -0
- crabcode_gateway/grpc/__init__.py +0 -0
- crabcode_gateway/grpc/crabcode_pb2.py +70 -0
- crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
- crabcode_gateway/grpc/proto/crabcode.proto +160 -0
- crabcode_gateway/grpc/server.py +765 -0
- crabcode_gateway/middleware.py +344 -0
- crabcode_gateway/protocol.py +17 -0
- crabcode_gateway/routes/__init__.py +0 -0
- crabcode_gateway/routes/agent.py +392 -0
- crabcode_gateway/routes/auth.py +150 -0
- crabcode_gateway/routes/computer_use.py +101 -0
- crabcode_gateway/routes/config.py +1698 -0
- crabcode_gateway/routes/document.py +1810 -0
- crabcode_gateway/routes/event.py +3801 -0
- crabcode_gateway/routes/git_review.py +232 -0
- crabcode_gateway/routes/health.py +14 -0
- crabcode_gateway/routes/peer.py +78 -0
- crabcode_gateway/routes/permission.py +121 -0
- crabcode_gateway/routes/schedule.py +300 -0
- crabcode_gateway/routes/session.py +1496 -0
- crabcode_gateway/routes/snapshot.py +211 -0
- crabcode_gateway/routes/tasks.py +318 -0
- crabcode_gateway/routes/team.py +392 -0
- crabcode_gateway/routes/usage.py +49 -0
- crabcode_gateway/routes/workspace.py +299 -0
- crabcode_gateway/safe_file.py +63 -0
- crabcode_gateway/schemas.py +1820 -0
- crabcode_gateway/server.py +514 -0
- crabcode_gateway/session_registry.py +36 -0
- crabcode_gateway/task_registry.py +708 -0
- crabcode_gateway/turn_details.py +60 -0
- crabcode_search/__init__.py +11 -0
- crabcode_search/background.py +368 -0
- crabcode_search/chunker.py +517 -0
- crabcode_search/embedder.py +497 -0
- crabcode_search/indexer.py +215 -0
- crabcode_search/store.py +222 -0
- crabcode_search/tool.py +268 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
"""CrabCode tool wrappers for debugging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from crabcode_core.types.tool import PermissionBehavior, PermissionResult, Tool, ToolContext, ToolResult
|
|
9
|
+
|
|
10
|
+
from crabcode_debugger.adapters import AdapterRegistry, infer_language
|
|
11
|
+
from crabcode_debugger.process import ProcessInspector
|
|
12
|
+
from crabcode_debugger.runtime import get_debug_session_manager, release_debug_session_manager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DEBUGGER_ACTIONS = {
|
|
16
|
+
"adapters",
|
|
17
|
+
"sessions",
|
|
18
|
+
"start",
|
|
19
|
+
"attach",
|
|
20
|
+
"set_breakpoints",
|
|
21
|
+
"configuration_done",
|
|
22
|
+
"continue",
|
|
23
|
+
"pause",
|
|
24
|
+
"step_over",
|
|
25
|
+
"step_in",
|
|
26
|
+
"step_out",
|
|
27
|
+
"threads",
|
|
28
|
+
"stack",
|
|
29
|
+
"scopes",
|
|
30
|
+
"variables",
|
|
31
|
+
"evaluate",
|
|
32
|
+
"events",
|
|
33
|
+
"stop",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_PROCESS_ACTIONS = {
|
|
37
|
+
"capabilities",
|
|
38
|
+
"list_processes",
|
|
39
|
+
"inspect_process",
|
|
40
|
+
"attach_debugger",
|
|
41
|
+
"sample_stack",
|
|
42
|
+
"dump_core",
|
|
43
|
+
"memory_maps",
|
|
44
|
+
"memory_regions",
|
|
45
|
+
"memory_read",
|
|
46
|
+
"memory_search",
|
|
47
|
+
"memory_refine",
|
|
48
|
+
"memory_write",
|
|
49
|
+
"memory_freeze",
|
|
50
|
+
"memory_unfreeze",
|
|
51
|
+
"memory_freezes",
|
|
52
|
+
"aob_scan",
|
|
53
|
+
"pointer_scan",
|
|
54
|
+
"pointer_resolve",
|
|
55
|
+
"code_read",
|
|
56
|
+
"code_patch",
|
|
57
|
+
"code_restore",
|
|
58
|
+
"code_patches",
|
|
59
|
+
"trace_syscalls",
|
|
60
|
+
"detach",
|
|
61
|
+
"terminate",
|
|
62
|
+
"kill",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def _json_result(data: Any, *, max_chars: int = 20_000) -> str:
|
|
66
|
+
text = json.dumps(data, ensure_ascii=False, indent=2, default=str)
|
|
67
|
+
if len(text) > max_chars:
|
|
68
|
+
return text[:max_chars] + "\n... (truncated)"
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _as_int(value: Any, default: int | None = None) -> int | None:
|
|
73
|
+
if value is None:
|
|
74
|
+
return default
|
|
75
|
+
try:
|
|
76
|
+
return int(value)
|
|
77
|
+
except (TypeError, ValueError):
|
|
78
|
+
return default
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_positive_pid(value: Any) -> bool:
|
|
82
|
+
if isinstance(value, bool):
|
|
83
|
+
return False
|
|
84
|
+
try:
|
|
85
|
+
return int(value) > 0
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _debug_owner_id(context: ToolContext) -> str:
|
|
91
|
+
if context.session_id:
|
|
92
|
+
return context.session_id
|
|
93
|
+
if context.session is not None:
|
|
94
|
+
return f"session-object:{id(context.session)}"
|
|
95
|
+
return f"tool-context:{id(context)}"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DebuggerTool(Tool):
|
|
99
|
+
name = "Debugger"
|
|
100
|
+
description = "Debug programs through official Debug Adapter Protocol adapters."
|
|
101
|
+
is_read_only = False
|
|
102
|
+
is_concurrency_safe = False
|
|
103
|
+
uses_tool_permission_policy = True
|
|
104
|
+
input_schema = {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"action": {"type": "string", "enum": sorted(_DEBUGGER_ACTIONS)},
|
|
108
|
+
"session_id": {"type": "string"},
|
|
109
|
+
"language": {"type": "string"},
|
|
110
|
+
"adapter_id": {"type": "string"},
|
|
111
|
+
"program": {"type": "string"},
|
|
112
|
+
"args": {"type": "array", "items": {"type": "string"}},
|
|
113
|
+
"cwd": {"type": "string"},
|
|
114
|
+
"pid": {"type": "integer"},
|
|
115
|
+
"path": {"type": "string"},
|
|
116
|
+
"lines": {"type": "array", "items": {"type": "integer"}},
|
|
117
|
+
"thread_id": {"type": "integer"},
|
|
118
|
+
"frame_id": {"type": "integer"},
|
|
119
|
+
"levels": {"type": "integer"},
|
|
120
|
+
"variables_reference": {"type": "integer"},
|
|
121
|
+
"start": {"type": "integer"},
|
|
122
|
+
"count": {"type": "integer"},
|
|
123
|
+
"expression": {"type": "string"},
|
|
124
|
+
"context": {"type": "string"},
|
|
125
|
+
"terminate_debuggee": {"type": "boolean"},
|
|
126
|
+
"launch_config": {"type": "object", "additionalProperties": True},
|
|
127
|
+
"attach_config": {"type": "object", "additionalProperties": True},
|
|
128
|
+
},
|
|
129
|
+
"required": ["action"],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def __init__(self) -> None:
|
|
133
|
+
self._config: dict[str, Any] = {}
|
|
134
|
+
self._manager: Any = None
|
|
135
|
+
self._max_chars = 20_000
|
|
136
|
+
self._allow_evaluate = False
|
|
137
|
+
self._manager_scope: tuple[str, str] | None = None
|
|
138
|
+
|
|
139
|
+
async def setup(self, context: ToolContext) -> None:
|
|
140
|
+
await super().setup(context)
|
|
141
|
+
await self._release_manager()
|
|
142
|
+
self._config = dict(context.tool_config)
|
|
143
|
+
self._max_chars = int(self._config.get("max_output_chars", 20_000))
|
|
144
|
+
self._allow_evaluate = bool(self._config.get("allow_evaluate", False))
|
|
145
|
+
owner_id = _debug_owner_id(context)
|
|
146
|
+
self._manager = get_debug_session_manager(
|
|
147
|
+
cwd=context.cwd,
|
|
148
|
+
owner_id=owner_id,
|
|
149
|
+
env=context.env,
|
|
150
|
+
config=self._config,
|
|
151
|
+
)
|
|
152
|
+
self._manager_scope = (context.cwd, owner_id)
|
|
153
|
+
|
|
154
|
+
async def close(self) -> None:
|
|
155
|
+
await self._release_manager()
|
|
156
|
+
|
|
157
|
+
async def _release_manager(self) -> None:
|
|
158
|
+
if self._manager is None or self._manager_scope is None:
|
|
159
|
+
return
|
|
160
|
+
cwd, owner_id = self._manager_scope
|
|
161
|
+
manager = self._manager
|
|
162
|
+
self._manager = None
|
|
163
|
+
self._manager_scope = None
|
|
164
|
+
await release_debug_session_manager(cwd=cwd, owner_id=owner_id, manager=manager)
|
|
165
|
+
|
|
166
|
+
async def get_prompt(self, **kwargs: Any) -> str:
|
|
167
|
+
return (
|
|
168
|
+
"Use Debugger to run or attach to programs through official Debug Adapter Protocol adapters. "
|
|
169
|
+
"Supported first-class languages are C/C++, Rust, Python, Go, Java, TypeScript, and JavaScript. "
|
|
170
|
+
"Call action=adapters first when you need to know which adapters are installed. "
|
|
171
|
+
"Use start or attach to create a session, set_breakpoints before configuration_done when the adapter requires it, "
|
|
172
|
+
"then use continue, pause, step_over, step_in, step_out, stack, scopes, variables, and evaluate. "
|
|
173
|
+
"Use events to drain stopped/terminated events and stop to disconnect."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
async def validate_input(self, tool_input: dict[str, Any]) -> str | None:
|
|
177
|
+
action = str(tool_input.get("action", "")).strip()
|
|
178
|
+
if action not in _DEBUGGER_ACTIONS:
|
|
179
|
+
return f"action must be one of: {', '.join(sorted(_DEBUGGER_ACTIONS))}"
|
|
180
|
+
if action in _DEBUGGER_ACTIONS - {"adapters", "sessions", "start", "attach"}:
|
|
181
|
+
if not str(tool_input.get("session_id", "")).strip():
|
|
182
|
+
return "session_id is required for this action"
|
|
183
|
+
if action == "start" and not (tool_input.get("language") or tool_input.get("program")):
|
|
184
|
+
return "language or program is required for start"
|
|
185
|
+
if action == "attach" and not _is_positive_pid(tool_input.get("pid")):
|
|
186
|
+
return "pid must be a positive integer for attach"
|
|
187
|
+
if action == "attach" and not tool_input.get("language"):
|
|
188
|
+
return "language is required for attach"
|
|
189
|
+
if action == "set_breakpoints" and not (tool_input.get("path") and tool_input.get("lines")):
|
|
190
|
+
return "path and lines are required for set_breakpoints"
|
|
191
|
+
if action == "scopes" and tool_input.get("frame_id") is None:
|
|
192
|
+
return "frame_id is required for scopes"
|
|
193
|
+
if action == "variables" and tool_input.get("variables_reference") is None:
|
|
194
|
+
return "variables_reference is required for variables"
|
|
195
|
+
if action == "evaluate" and not str(tool_input.get("expression", "")).strip():
|
|
196
|
+
return "expression is required for evaluate"
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
async def check_permissions(self, tool_input: dict[str, Any], context: ToolContext) -> PermissionResult:
|
|
200
|
+
action = str(tool_input.get("action", ""))
|
|
201
|
+
if action == "adapters":
|
|
202
|
+
probe_commands = self._configured_probe_commands()
|
|
203
|
+
if probe_commands:
|
|
204
|
+
return PermissionResult(
|
|
205
|
+
behavior=PermissionBehavior.ASK,
|
|
206
|
+
reason=f"Adapter discovery will execute configured probe commands: {probe_commands}",
|
|
207
|
+
permission_key=self.get_permission_key(tool_input),
|
|
208
|
+
)
|
|
209
|
+
return PermissionResult(behavior=PermissionBehavior.ALLOW)
|
|
210
|
+
if action in {"sessions", "events"}:
|
|
211
|
+
return PermissionResult(behavior=PermissionBehavior.ALLOW)
|
|
212
|
+
if action == "evaluate" and not self._allow_evaluate:
|
|
213
|
+
return PermissionResult(
|
|
214
|
+
behavior=PermissionBehavior.ASK,
|
|
215
|
+
reason="Debugger evaluate can execute code in the debuggee process",
|
|
216
|
+
permission_key=self.get_permission_key(tool_input),
|
|
217
|
+
)
|
|
218
|
+
if action == "stop" and bool(tool_input.get("terminate_debuggee", False)):
|
|
219
|
+
return PermissionResult(
|
|
220
|
+
behavior=PermissionBehavior.ASK,
|
|
221
|
+
reason="Stopping with terminate_debuggee=true can terminate the debugged process",
|
|
222
|
+
permission_key=self.get_permission_key(tool_input),
|
|
223
|
+
)
|
|
224
|
+
return PermissionResult(
|
|
225
|
+
behavior=PermissionBehavior.ASK,
|
|
226
|
+
reason=f"Debugger action '{action}' can inspect or control a debuggee process",
|
|
227
|
+
permission_key=self.get_permission_key(tool_input),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def _configured_probe_commands(self) -> list[list[str]]:
|
|
231
|
+
adapters = self._config.get("adapters", {})
|
|
232
|
+
if not isinstance(adapters, dict):
|
|
233
|
+
return []
|
|
234
|
+
commands: list[list[str]] = []
|
|
235
|
+
for raw in adapters.values():
|
|
236
|
+
if not isinstance(raw, dict):
|
|
237
|
+
continue
|
|
238
|
+
probe = raw.get("probe_command")
|
|
239
|
+
if isinstance(probe, list) and probe:
|
|
240
|
+
commands.append([str(part) for part in probe])
|
|
241
|
+
return commands
|
|
242
|
+
|
|
243
|
+
def get_permission_key(self, tool_input: dict[str, Any]) -> str:
|
|
244
|
+
action = str(tool_input.get("action", ""))
|
|
245
|
+
if action in {"start", "attach"}:
|
|
246
|
+
target = tool_input.get("program") or tool_input.get("pid") or "target"
|
|
247
|
+
language = infer_language(str(tool_input.get("language") or target)) or "unknown"
|
|
248
|
+
return f"Debugger:{action}:{language}:{target}"
|
|
249
|
+
if action == "evaluate":
|
|
250
|
+
return f"Debugger:evaluate:{tool_input.get('session_id', '')}"
|
|
251
|
+
if action == "stop":
|
|
252
|
+
return f"Debugger:stop:{tool_input.get('session_id', '')}"
|
|
253
|
+
return f"Debugger:{action}"
|
|
254
|
+
|
|
255
|
+
async def call(self, tool_input: dict[str, Any], context: ToolContext) -> ToolResult:
|
|
256
|
+
action = str(tool_input["action"])
|
|
257
|
+
try:
|
|
258
|
+
data = await self._call(action, tool_input)
|
|
259
|
+
is_error = isinstance(data, dict) and "error" in data
|
|
260
|
+
text = _json_result(data, max_chars=self._max_chars)
|
|
261
|
+
return ToolResult(data=data, result_for_model=text, result_for_display=text, is_error=is_error)
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
return ToolResult(result_for_model=f"Debugger error: {exc}", is_error=True)
|
|
264
|
+
|
|
265
|
+
async def _call(self, action: str, tool_input: dict[str, Any]) -> Any:
|
|
266
|
+
if action == "adapters":
|
|
267
|
+
registry = AdapterRegistry(self._config)
|
|
268
|
+
language = tool_input.get("language")
|
|
269
|
+
return {"adapters": [status.to_dict() for status in registry.status(language)]}
|
|
270
|
+
if action == "sessions":
|
|
271
|
+
return self._manager.list_sessions()
|
|
272
|
+
if action == "start":
|
|
273
|
+
return await self._manager.start(
|
|
274
|
+
language=str(tool_input.get("language") or tool_input.get("program") or ""),
|
|
275
|
+
program=tool_input.get("program"),
|
|
276
|
+
args=[str(a) for a in tool_input.get("args", [])],
|
|
277
|
+
cwd=tool_input.get("cwd"),
|
|
278
|
+
adapter_id=tool_input.get("adapter_id"),
|
|
279
|
+
launch_config=tool_input.get("launch_config") if isinstance(tool_input.get("launch_config"), dict) else None,
|
|
280
|
+
)
|
|
281
|
+
if action == "attach":
|
|
282
|
+
return await self._manager.attach(
|
|
283
|
+
language=str(tool_input["language"]),
|
|
284
|
+
pid=int(tool_input["pid"]),
|
|
285
|
+
adapter_id=tool_input.get("adapter_id"),
|
|
286
|
+
attach_config=tool_input.get("attach_config") if isinstance(tool_input.get("attach_config"), dict) else None,
|
|
287
|
+
)
|
|
288
|
+
session_id = str(tool_input.get("session_id", ""))
|
|
289
|
+
if action == "set_breakpoints":
|
|
290
|
+
return await self._manager.set_breakpoints(
|
|
291
|
+
session_id,
|
|
292
|
+
path=str(tool_input["path"]),
|
|
293
|
+
lines=[int(line) for line in tool_input["lines"]],
|
|
294
|
+
)
|
|
295
|
+
if action == "configuration_done":
|
|
296
|
+
return await self._manager.configuration_done(session_id)
|
|
297
|
+
if action == "continue":
|
|
298
|
+
return await self._manager.continue_thread(session_id, _as_int(tool_input.get("thread_id")))
|
|
299
|
+
if action == "pause":
|
|
300
|
+
return await self._manager.pause(session_id, _as_int(tool_input.get("thread_id")))
|
|
301
|
+
if action in {"step_over", "step_in", "step_out"}:
|
|
302
|
+
return await self._manager.step(session_id, action, _as_int(tool_input.get("thread_id")))
|
|
303
|
+
if action == "threads":
|
|
304
|
+
return await self._manager.threads(session_id)
|
|
305
|
+
if action == "stack":
|
|
306
|
+
return await self._manager.stack(
|
|
307
|
+
session_id,
|
|
308
|
+
_as_int(tool_input.get("thread_id")),
|
|
309
|
+
levels=int(tool_input.get("levels", 20)),
|
|
310
|
+
)
|
|
311
|
+
if action == "scopes":
|
|
312
|
+
return await self._manager.scopes(session_id, int(tool_input["frame_id"]))
|
|
313
|
+
if action == "variables":
|
|
314
|
+
return await self._manager.variables(
|
|
315
|
+
session_id,
|
|
316
|
+
int(tool_input["variables_reference"]),
|
|
317
|
+
start=int(tool_input.get("start", 0)),
|
|
318
|
+
count=int(tool_input.get("count", 100)),
|
|
319
|
+
)
|
|
320
|
+
if action == "evaluate":
|
|
321
|
+
return await self._manager.evaluate(
|
|
322
|
+
session_id,
|
|
323
|
+
str(tool_input["expression"]),
|
|
324
|
+
frame_id=_as_int(tool_input.get("frame_id")),
|
|
325
|
+
context=str(tool_input.get("context", "repl")),
|
|
326
|
+
)
|
|
327
|
+
if action == "events":
|
|
328
|
+
return await self._manager.events(session_id)
|
|
329
|
+
if action == "stop":
|
|
330
|
+
return await self._manager.stop(
|
|
331
|
+
session_id,
|
|
332
|
+
terminate_debuggee=bool(tool_input.get("terminate_debuggee", False)),
|
|
333
|
+
)
|
|
334
|
+
return {"error": f"unhandled action: {action}"}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class ProcessDebuggerTool(Tool):
|
|
338
|
+
name = "ProcessDebugger"
|
|
339
|
+
description = "Inspect, attach to, and diagnose operating-system processes."
|
|
340
|
+
is_read_only = False
|
|
341
|
+
is_concurrency_safe = False
|
|
342
|
+
uses_tool_permission_policy = True
|
|
343
|
+
input_schema = {
|
|
344
|
+
"type": "object",
|
|
345
|
+
"properties": {
|
|
346
|
+
"action": {"type": "string", "enum": sorted(_PROCESS_ACTIONS)},
|
|
347
|
+
"pid": {"type": "integer"},
|
|
348
|
+
"query": {"type": "string"},
|
|
349
|
+
"limit": {"type": "integer"},
|
|
350
|
+
"language": {"type": "string"},
|
|
351
|
+
"adapter_id": {"type": "string"},
|
|
352
|
+
"session_id": {"type": "string"},
|
|
353
|
+
"duration_seconds": {"type": "integer"},
|
|
354
|
+
"interval_seconds": {"type": "number"},
|
|
355
|
+
"output_path": {"type": "string"},
|
|
356
|
+
"attach_config": {"type": "object", "additionalProperties": True},
|
|
357
|
+
"pattern": {"type": "string"},
|
|
358
|
+
"module_filter": {"type": "string"},
|
|
359
|
+
"target_address": {
|
|
360
|
+
"description": "Target address for pointer_scan.",
|
|
361
|
+
},
|
|
362
|
+
"max_depth": {"type": "integer"},
|
|
363
|
+
"max_offset": {"type": "integer"},
|
|
364
|
+
"pointer_size": {"type": "integer", "enum": [4, 8]},
|
|
365
|
+
"align": {"type": "integer"},
|
|
366
|
+
"offsets": {
|
|
367
|
+
"type": "array",
|
|
368
|
+
"items": {},
|
|
369
|
+
"description": "Pointer offsets as integers or hex strings.",
|
|
370
|
+
},
|
|
371
|
+
"module_path": {"type": "string"},
|
|
372
|
+
"module_offset": {
|
|
373
|
+
"description": "Module-relative base offset as an integer or hex string.",
|
|
374
|
+
},
|
|
375
|
+
"patch_hex": {"type": "string"},
|
|
376
|
+
"expected_hex": {"type": "string"},
|
|
377
|
+
"patch_id": {"type": "string"},
|
|
378
|
+
"address": {
|
|
379
|
+
"description": "Memory address as an integer or hex string.",
|
|
380
|
+
},
|
|
381
|
+
"base_address": {
|
|
382
|
+
"description": "Pointer-chain base address as an integer or hex string.",
|
|
383
|
+
},
|
|
384
|
+
"size": {"type": "integer"},
|
|
385
|
+
"value_type": {
|
|
386
|
+
"type": "string",
|
|
387
|
+
"enum": [
|
|
388
|
+
"bytes",
|
|
389
|
+
"string",
|
|
390
|
+
"int8",
|
|
391
|
+
"uint8",
|
|
392
|
+
"int16",
|
|
393
|
+
"uint16",
|
|
394
|
+
"int32",
|
|
395
|
+
"uint32",
|
|
396
|
+
"int64",
|
|
397
|
+
"uint64",
|
|
398
|
+
"float32",
|
|
399
|
+
"float64",
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
"value": {
|
|
403
|
+
"description": "Value for memory_search, memory_refine equals, or memory_write.",
|
|
404
|
+
},
|
|
405
|
+
"value_hex": {"type": "string"},
|
|
406
|
+
"endian": {"type": "string", "enum": ["little", "big"]},
|
|
407
|
+
"writable_only": {"type": "boolean"},
|
|
408
|
+
"executable_only": {"type": "boolean"},
|
|
409
|
+
"readable": {"type": "boolean"},
|
|
410
|
+
"writable": {"type": "boolean"},
|
|
411
|
+
"executable": {"type": "boolean"},
|
|
412
|
+
"max_results": {"type": "integer"},
|
|
413
|
+
"max_scan_bytes": {"type": "integer"},
|
|
414
|
+
"search_id": {"type": "string"},
|
|
415
|
+
"freeze_id": {"type": "string"},
|
|
416
|
+
"all": {"type": "boolean"},
|
|
417
|
+
"comparison": {
|
|
418
|
+
"type": "string",
|
|
419
|
+
"enum": ["equals", "changed", "unchanged", "increased", "decreased"],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
"required": ["action"],
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
def __init__(self) -> None:
|
|
426
|
+
self._config: dict[str, Any] = {}
|
|
427
|
+
self._inspector: ProcessInspector | None = None
|
|
428
|
+
self._debug_manager: Any = None
|
|
429
|
+
self._max_chars = 20_000
|
|
430
|
+
self._manager_scope: tuple[str, str] | None = None
|
|
431
|
+
|
|
432
|
+
async def setup(self, context: ToolContext) -> None:
|
|
433
|
+
await super().setup(context)
|
|
434
|
+
await self._release_manager()
|
|
435
|
+
if self._inspector is not None:
|
|
436
|
+
await self._inspector.close()
|
|
437
|
+
self._inspector = None
|
|
438
|
+
self._config = dict(context.tool_config)
|
|
439
|
+
self._max_chars = int(self._config.get("max_output_chars", 20_000))
|
|
440
|
+
self._inspector = ProcessInspector(cwd=context.cwd, config=self._config)
|
|
441
|
+
debugger_config: dict[str, Any] = {}
|
|
442
|
+
if isinstance(self._config.get("adapters"), dict):
|
|
443
|
+
debugger_config["adapters"] = self._config["adapters"]
|
|
444
|
+
if isinstance(self._config.get("debugger"), dict):
|
|
445
|
+
debugger_config.update(self._config["debugger"])
|
|
446
|
+
owner_id = _debug_owner_id(context)
|
|
447
|
+
self._debug_manager = get_debug_session_manager(
|
|
448
|
+
cwd=context.cwd,
|
|
449
|
+
owner_id=owner_id,
|
|
450
|
+
env=context.env,
|
|
451
|
+
config=debugger_config,
|
|
452
|
+
)
|
|
453
|
+
self._manager_scope = (context.cwd, owner_id)
|
|
454
|
+
|
|
455
|
+
async def close(self) -> None:
|
|
456
|
+
if self._inspector is not None:
|
|
457
|
+
await self._inspector.close()
|
|
458
|
+
self._inspector = None
|
|
459
|
+
await self._release_manager()
|
|
460
|
+
|
|
461
|
+
async def _release_manager(self) -> None:
|
|
462
|
+
if self._debug_manager is None or self._manager_scope is None:
|
|
463
|
+
return
|
|
464
|
+
cwd, owner_id = self._manager_scope
|
|
465
|
+
manager = self._debug_manager
|
|
466
|
+
self._debug_manager = None
|
|
467
|
+
self._manager_scope = None
|
|
468
|
+
await release_debug_session_manager(cwd=cwd, owner_id=owner_id, manager=manager)
|
|
469
|
+
|
|
470
|
+
async def get_prompt(self, **kwargs: Any) -> str:
|
|
471
|
+
return (
|
|
472
|
+
"Use ProcessDebugger for process-level diagnostics: list_processes, inspect_process, "
|
|
473
|
+
"sample_stack, dump_core, memory_maps, memory_regions, memory_read, memory_search, "
|
|
474
|
+
"memory_refine, memory_write, memory_freeze, memory_unfreeze, memory_freezes, "
|
|
475
|
+
"aob_scan, pointer_scan, pointer_resolve, code_read, code_patch, code_restore, "
|
|
476
|
+
"code_patches, trace_syscalls, attach_debugger, detach, terminate, and kill. "
|
|
477
|
+
"Use capabilities first to see what the current OS supports. "
|
|
478
|
+
"Most actions ask for permission by default; default_mode=run_everything bypasses "
|
|
479
|
+
"those confirmations through the normal CrabCode permission mode."
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
async def validate_input(self, tool_input: dict[str, Any]) -> str | None:
|
|
483
|
+
action = str(tool_input.get("action", "")).strip()
|
|
484
|
+
if action not in _PROCESS_ACTIONS:
|
|
485
|
+
return f"action must be one of: {', '.join(sorted(_PROCESS_ACTIONS))}"
|
|
486
|
+
if action == "memory_refine":
|
|
487
|
+
if not str(tool_input.get("search_id", "")).strip():
|
|
488
|
+
return "search_id is required for memory_refine"
|
|
489
|
+
if tool_input.get("comparison") == "equals" and tool_input.get("value") is None and tool_input.get("value_hex") is None:
|
|
490
|
+
return "value or value_hex is required for memory_refine comparison=equals"
|
|
491
|
+
return None
|
|
492
|
+
if action == "memory_unfreeze":
|
|
493
|
+
if not str(tool_input.get("freeze_id", "")).strip() and not bool(tool_input.get("all", False)):
|
|
494
|
+
return "freeze_id is required unless all=true"
|
|
495
|
+
return None
|
|
496
|
+
if action == "memory_freezes":
|
|
497
|
+
return None
|
|
498
|
+
if action == "code_restore":
|
|
499
|
+
if not str(tool_input.get("patch_id", "")).strip() and not bool(tool_input.get("all", False)):
|
|
500
|
+
return "patch_id is required unless all=true"
|
|
501
|
+
return None
|
|
502
|
+
if action == "code_patches":
|
|
503
|
+
return None
|
|
504
|
+
if action in _PROCESS_ACTIONS - {"capabilities", "list_processes"}:
|
|
505
|
+
if action == "detach":
|
|
506
|
+
if not str(tool_input.get("session_id", "")).strip():
|
|
507
|
+
return "session_id is required for detach"
|
|
508
|
+
elif tool_input.get("pid") is None:
|
|
509
|
+
return "pid is required for this action"
|
|
510
|
+
elif not _is_positive_pid(tool_input.get("pid")):
|
|
511
|
+
return "pid must be a positive integer for this action"
|
|
512
|
+
if action == "attach_debugger" and not tool_input.get("language"):
|
|
513
|
+
return "language is required for attach_debugger"
|
|
514
|
+
if action == "memory_read":
|
|
515
|
+
if tool_input.get("address") is None or tool_input.get("size") is None:
|
|
516
|
+
return "address and size are required for memory_read"
|
|
517
|
+
if action == "aob_scan" and not str(tool_input.get("pattern", "")).strip():
|
|
518
|
+
return "pattern is required for aob_scan"
|
|
519
|
+
if action == "pointer_scan" and tool_input.get("target_address") is None:
|
|
520
|
+
return "target_address is required for pointer_scan"
|
|
521
|
+
if action == "pointer_resolve":
|
|
522
|
+
has_base = tool_input.get("base_address") is not None or tool_input.get("address") is not None
|
|
523
|
+
has_module = tool_input.get("module_path") and tool_input.get("module_offset") is not None
|
|
524
|
+
if not has_base and not has_module:
|
|
525
|
+
return "base_address/address or module_path+module_offset is required for pointer_resolve"
|
|
526
|
+
if action == "code_read":
|
|
527
|
+
if tool_input.get("address") is None or tool_input.get("size") is None:
|
|
528
|
+
return "address and size are required for code_read"
|
|
529
|
+
if action == "code_patch":
|
|
530
|
+
if tool_input.get("address") is None or not str(tool_input.get("patch_hex", "")).strip():
|
|
531
|
+
return "address and patch_hex are required for code_patch"
|
|
532
|
+
if action in {"memory_search", "memory_write", "memory_freeze"}:
|
|
533
|
+
if not tool_input.get("value_type"):
|
|
534
|
+
return "value_type is required"
|
|
535
|
+
if tool_input.get("value") is None and tool_input.get("value_hex") is None:
|
|
536
|
+
return "value or value_hex is required"
|
|
537
|
+
if action in {"memory_write", "memory_freeze"} and tool_input.get("address") is None:
|
|
538
|
+
return f"address is required for {action}"
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
async def check_permissions(self, tool_input: dict[str, Any], context: ToolContext) -> PermissionResult:
|
|
542
|
+
action = str(tool_input.get("action", ""))
|
|
543
|
+
if action == "capabilities":
|
|
544
|
+
return PermissionResult(behavior=PermissionBehavior.ALLOW)
|
|
545
|
+
return PermissionResult(
|
|
546
|
+
behavior=PermissionBehavior.ASK,
|
|
547
|
+
reason=f"ProcessDebugger action '{action}' can inspect, attach to, read, or alter an OS process",
|
|
548
|
+
permission_key=self.get_permission_key(tool_input),
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
def get_permission_key(self, tool_input: dict[str, Any]) -> str:
|
|
552
|
+
action = str(tool_input.get("action", ""))
|
|
553
|
+
if action.startswith("memory_") or action in {"aob_scan", "pointer_scan", "pointer_resolve", "code_read", "code_patch", "code_restore", "code_patches"}:
|
|
554
|
+
target = (
|
|
555
|
+
f"{tool_input.get('pid', 'state')}:"
|
|
556
|
+
f"{tool_input.get('address') or tool_input.get('target_address') or tool_input.get('search_id') or tool_input.get('freeze_id') or tool_input.get('patch_id') or action}"
|
|
557
|
+
)
|
|
558
|
+
return f"ProcessDebugger:{action}:{target}"
|
|
559
|
+
target = tool_input.get("pid") or tool_input.get("session_id") or tool_input.get("search_id") or "all"
|
|
560
|
+
return f"ProcessDebugger:{action}:{target}"
|
|
561
|
+
|
|
562
|
+
async def call(self, tool_input: dict[str, Any], context: ToolContext) -> ToolResult:
|
|
563
|
+
if self._inspector is None:
|
|
564
|
+
return ToolResult(result_for_model="ProcessDebugger is not initialized", is_error=True)
|
|
565
|
+
action = str(tool_input["action"])
|
|
566
|
+
try:
|
|
567
|
+
data = await self._call(action, tool_input)
|
|
568
|
+
is_error = isinstance(data, dict) and "error" in data
|
|
569
|
+
text = _json_result(data, max_chars=self._max_chars)
|
|
570
|
+
return ToolResult(data=data, result_for_model=text, result_for_display=text, is_error=is_error)
|
|
571
|
+
except Exception as exc:
|
|
572
|
+
return ToolResult(result_for_model=f"ProcessDebugger error: {exc}", is_error=True)
|
|
573
|
+
|
|
574
|
+
async def _call(self, action: str, tool_input: dict[str, Any]) -> Any:
|
|
575
|
+
assert self._inspector is not None
|
|
576
|
+
if action == "capabilities":
|
|
577
|
+
return self._inspector.capabilities()
|
|
578
|
+
if action == "list_processes":
|
|
579
|
+
return await self._inspector.list_processes(
|
|
580
|
+
limit=int(tool_input.get("limit", 100)),
|
|
581
|
+
query=tool_input.get("query"),
|
|
582
|
+
)
|
|
583
|
+
if action == "inspect_process":
|
|
584
|
+
return await self._inspector.inspect_process(int(tool_input["pid"]))
|
|
585
|
+
if action == "attach_debugger":
|
|
586
|
+
return await self._debug_manager.attach(
|
|
587
|
+
language=str(tool_input["language"]),
|
|
588
|
+
pid=int(tool_input["pid"]),
|
|
589
|
+
adapter_id=tool_input.get("adapter_id"),
|
|
590
|
+
attach_config=tool_input.get("attach_config") if isinstance(tool_input.get("attach_config"), dict) else None,
|
|
591
|
+
)
|
|
592
|
+
if action == "sample_stack":
|
|
593
|
+
return await self._inspector.sample_stack(
|
|
594
|
+
int(tool_input["pid"]),
|
|
595
|
+
duration_seconds=int(tool_input.get("duration_seconds", 3)),
|
|
596
|
+
)
|
|
597
|
+
if action == "dump_core":
|
|
598
|
+
return await self._inspector.dump_core(
|
|
599
|
+
int(tool_input["pid"]),
|
|
600
|
+
output_path=tool_input.get("output_path"),
|
|
601
|
+
)
|
|
602
|
+
if action == "memory_maps":
|
|
603
|
+
return await self._inspector.memory_maps(int(tool_input["pid"]))
|
|
604
|
+
if action == "memory_regions":
|
|
605
|
+
return await self._inspector.memory_regions(
|
|
606
|
+
int(tool_input["pid"]),
|
|
607
|
+
readable=bool(tool_input.get("readable", False)),
|
|
608
|
+
writable=bool(tool_input.get("writable", False)),
|
|
609
|
+
executable=tool_input.get("executable") if isinstance(tool_input.get("executable"), bool) else None,
|
|
610
|
+
limit=int(tool_input.get("limit", 500)),
|
|
611
|
+
)
|
|
612
|
+
if action == "memory_read":
|
|
613
|
+
return await self._inspector.memory_read(
|
|
614
|
+
int(tool_input["pid"]),
|
|
615
|
+
address=tool_input["address"],
|
|
616
|
+
size=int(tool_input["size"]),
|
|
617
|
+
)
|
|
618
|
+
if action == "memory_search":
|
|
619
|
+
return await self._inspector.memory_search(
|
|
620
|
+
int(tool_input["pid"]),
|
|
621
|
+
value_type=str(tool_input["value_type"]),
|
|
622
|
+
value=tool_input.get("value"),
|
|
623
|
+
value_hex=tool_input.get("value_hex"),
|
|
624
|
+
endian=str(tool_input.get("endian", "little")),
|
|
625
|
+
writable_only=bool(tool_input.get("writable_only", True)),
|
|
626
|
+
max_results=_as_int(tool_input.get("max_results")),
|
|
627
|
+
max_scan_bytes=_as_int(tool_input.get("max_scan_bytes")),
|
|
628
|
+
)
|
|
629
|
+
if action == "memory_refine":
|
|
630
|
+
return await self._inspector.memory_refine(
|
|
631
|
+
str(tool_input["search_id"]),
|
|
632
|
+
comparison=str(tool_input.get("comparison", "changed")),
|
|
633
|
+
value=tool_input.get("value"),
|
|
634
|
+
value_hex=tool_input.get("value_hex"),
|
|
635
|
+
max_results=_as_int(tool_input.get("max_results")),
|
|
636
|
+
)
|
|
637
|
+
if action == "memory_write":
|
|
638
|
+
return await self._inspector.memory_write(
|
|
639
|
+
int(tool_input["pid"]),
|
|
640
|
+
address=tool_input["address"],
|
|
641
|
+
value_type=str(tool_input["value_type"]),
|
|
642
|
+
value=tool_input.get("value"),
|
|
643
|
+
value_hex=tool_input.get("value_hex"),
|
|
644
|
+
endian=str(tool_input.get("endian", "little")),
|
|
645
|
+
)
|
|
646
|
+
if action == "memory_freeze":
|
|
647
|
+
return await self._inspector.memory_freeze(
|
|
648
|
+
int(tool_input["pid"]),
|
|
649
|
+
address=tool_input["address"],
|
|
650
|
+
value_type=str(tool_input["value_type"]),
|
|
651
|
+
value=tool_input.get("value"),
|
|
652
|
+
value_hex=tool_input.get("value_hex"),
|
|
653
|
+
endian=str(tool_input.get("endian", "little")),
|
|
654
|
+
interval_seconds=float(tool_input["interval_seconds"]) if tool_input.get("interval_seconds") is not None else None,
|
|
655
|
+
)
|
|
656
|
+
if action == "memory_unfreeze":
|
|
657
|
+
return await self._inspector.memory_unfreeze(
|
|
658
|
+
freeze_id=tool_input.get("freeze_id"),
|
|
659
|
+
all_freezes=bool(tool_input.get("all", False)),
|
|
660
|
+
)
|
|
661
|
+
if action == "memory_freezes":
|
|
662
|
+
return await self._inspector.memory_freezes()
|
|
663
|
+
if action == "aob_scan":
|
|
664
|
+
return await self._inspector.aob_scan(
|
|
665
|
+
int(tool_input["pid"]),
|
|
666
|
+
pattern=str(tool_input["pattern"]),
|
|
667
|
+
executable_only=bool(tool_input.get("executable_only", True)),
|
|
668
|
+
writable_only=bool(tool_input.get("writable_only", False)),
|
|
669
|
+
module_filter=tool_input.get("module_filter"),
|
|
670
|
+
max_results=_as_int(tool_input.get("max_results")),
|
|
671
|
+
max_scan_bytes=_as_int(tool_input.get("max_scan_bytes")),
|
|
672
|
+
)
|
|
673
|
+
if action == "pointer_scan":
|
|
674
|
+
return await self._inspector.pointer_scan(
|
|
675
|
+
int(tool_input["pid"]),
|
|
676
|
+
target_address=tool_input["target_address"],
|
|
677
|
+
max_depth=int(tool_input.get("max_depth", 3)),
|
|
678
|
+
max_offset=int(tool_input.get("max_offset", 4096)),
|
|
679
|
+
pointer_size=_as_int(tool_input.get("pointer_size")),
|
|
680
|
+
align=_as_int(tool_input.get("align")),
|
|
681
|
+
writable_only=bool(tool_input.get("writable_only", True)),
|
|
682
|
+
max_results=_as_int(tool_input.get("max_results")),
|
|
683
|
+
max_scan_bytes=_as_int(tool_input.get("max_scan_bytes")),
|
|
684
|
+
)
|
|
685
|
+
if action == "pointer_resolve":
|
|
686
|
+
return await self._inspector.pointer_resolve(
|
|
687
|
+
int(tool_input["pid"]),
|
|
688
|
+
base_address=tool_input.get("base_address", tool_input.get("address")),
|
|
689
|
+
offsets=tool_input.get("offsets") if isinstance(tool_input.get("offsets"), list) else None,
|
|
690
|
+
module_path=tool_input.get("module_path"),
|
|
691
|
+
module_offset=tool_input.get("module_offset"),
|
|
692
|
+
pointer_size=_as_int(tool_input.get("pointer_size")),
|
|
693
|
+
endian=str(tool_input.get("endian", "little")),
|
|
694
|
+
)
|
|
695
|
+
if action == "code_read":
|
|
696
|
+
return await self._inspector.code_read(
|
|
697
|
+
int(tool_input["pid"]),
|
|
698
|
+
address=tool_input["address"],
|
|
699
|
+
size=int(tool_input["size"]),
|
|
700
|
+
)
|
|
701
|
+
if action == "code_patch":
|
|
702
|
+
return await self._inspector.code_patch(
|
|
703
|
+
int(tool_input["pid"]),
|
|
704
|
+
address=tool_input["address"],
|
|
705
|
+
patch_hex=str(tool_input["patch_hex"]),
|
|
706
|
+
expected_hex=tool_input.get("expected_hex"),
|
|
707
|
+
patch_id=tool_input.get("patch_id"),
|
|
708
|
+
)
|
|
709
|
+
if action == "code_restore":
|
|
710
|
+
return await self._inspector.code_restore(
|
|
711
|
+
patch_id=tool_input.get("patch_id"),
|
|
712
|
+
all_patches=bool(tool_input.get("all", False)),
|
|
713
|
+
)
|
|
714
|
+
if action == "code_patches":
|
|
715
|
+
return await self._inspector.code_patches()
|
|
716
|
+
if action == "trace_syscalls":
|
|
717
|
+
return await self._inspector.trace_syscalls(
|
|
718
|
+
int(tool_input["pid"]),
|
|
719
|
+
duration_seconds=int(tool_input.get("duration_seconds", 5)),
|
|
720
|
+
output_path=tool_input.get("output_path"),
|
|
721
|
+
)
|
|
722
|
+
if action == "detach":
|
|
723
|
+
return await self._debug_manager.stop(
|
|
724
|
+
str(tool_input["session_id"]),
|
|
725
|
+
terminate_debuggee=False,
|
|
726
|
+
)
|
|
727
|
+
if action == "terminate":
|
|
728
|
+
return await self._inspector.signal_process(int(tool_input["pid"]), sig="terminate")
|
|
729
|
+
if action == "kill":
|
|
730
|
+
return await self._inspector.signal_process(int(tool_input["pid"]), sig="kill")
|
|
731
|
+
return {"error": f"unhandled action: {action}"}
|