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.
Files changed (205) hide show
  1. crabcode-0.1.6.dist-info/METADATA +63 -0
  2. crabcode-0.1.6.dist-info/RECORD +205 -0
  3. crabcode-0.1.6.dist-info/WHEEL +4 -0
  4. crabcode-0.1.6.dist-info/entry_points.txt +2 -0
  5. crabcode_cli/__init__.py +7 -0
  6. crabcode_cli/__main__.py +4 -0
  7. crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
  8. crabcode_cli/_native/runtime.json +6 -0
  9. crabcode_cli/app.py +776 -0
  10. crabcode_cli/banner.py +39 -0
  11. crabcode_cli/computer_use.py +286 -0
  12. crabcode_cli/model_manager.py +649 -0
  13. crabcode_cli/model_picker.py +168 -0
  14. crabcode_cli/pipe.py +148 -0
  15. crabcode_cli/render/__init__.py +0 -0
  16. crabcode_cli/render/messages.py +19 -0
  17. crabcode_cli/repl.py +4841 -0
  18. crabcode_cli/session_picker.py +239 -0
  19. crabcode_core/__init__.py +7 -0
  20. crabcode_core/_version.py +3 -0
  21. crabcode_core/_windows_process.py +68 -0
  22. crabcode_core/agent_manager.py +1731 -0
  23. crabcode_core/api/__init__.py +20 -0
  24. crabcode_core/api/anthropic_adapter.py +648 -0
  25. crabcode_core/api/azure_adapter.py +69 -0
  26. crabcode_core/api/base.py +233 -0
  27. crabcode_core/api/codex_adapter.py +1327 -0
  28. crabcode_core/api/error_logging.py +132 -0
  29. crabcode_core/api/gemini_adapter.py +26 -0
  30. crabcode_core/api/model_info.py +257 -0
  31. crabcode_core/api/network.py +62 -0
  32. crabcode_core/api/ollama_adapter.py +27 -0
  33. crabcode_core/api/openai_adapter.py +323 -0
  34. crabcode_core/api/registry.py +58 -0
  35. crabcode_core/command_line.py +18 -0
  36. crabcode_core/compact/__init__.py +17 -0
  37. crabcode_core/compact/compact.py +514 -0
  38. crabcode_core/compact/context_tokens.py +165 -0
  39. crabcode_core/computer_use_mode.py +38 -0
  40. crabcode_core/computer_use_observation.py +144 -0
  41. crabcode_core/config/__init__.py +3 -0
  42. crabcode_core/config/claudemd.py +61 -0
  43. crabcode_core/config/manager.py +237 -0
  44. crabcode_core/config/model_catalog.py +256 -0
  45. crabcode_core/events.py +4896 -0
  46. crabcode_core/file_lock.py +81 -0
  47. crabcode_core/filesystem.py +22 -0
  48. crabcode_core/goal.py +149 -0
  49. crabcode_core/hooks/__init__.py +5 -0
  50. crabcode_core/hooks/manager.py +290 -0
  51. crabcode_core/image_io.py +111 -0
  52. crabcode_core/io_worker.py +211 -0
  53. crabcode_core/logging_utils.py +168 -0
  54. crabcode_core/lsp/__init__.py +6 -0
  55. crabcode_core/lsp/client.py +786 -0
  56. crabcode_core/lsp/diagnostics.py +150 -0
  57. crabcode_core/lsp/manager.py +583 -0
  58. crabcode_core/mcp/__init__.py +4 -0
  59. crabcode_core/mcp/client.py +169 -0
  60. crabcode_core/mcp/config.py +57 -0
  61. crabcode_core/path_validation.py +34 -0
  62. crabcode_core/paths.py +24 -0
  63. crabcode_core/peer/__init__.py +10 -0
  64. crabcode_core/peer/runtime.py +518 -0
  65. crabcode_core/permissions/__init__.py +7 -0
  66. crabcode_core/permissions/ai_reviewer.py +184 -0
  67. crabcode_core/permissions/manager.py +187 -0
  68. crabcode_core/plan/__init__.py +5 -0
  69. crabcode_core/plan/executor.py +270 -0
  70. crabcode_core/plan/types.py +169 -0
  71. crabcode_core/prompts/__init__.py +0 -0
  72. crabcode_core/prompts/blocks.py +10 -0
  73. crabcode_core/prompts/context.py +229 -0
  74. crabcode_core/prompts/library.py +92 -0
  75. crabcode_core/prompts/profile.py +79 -0
  76. crabcode_core/prompts/system.py +356 -0
  77. crabcode_core/prompts/templates.py +104 -0
  78. crabcode_core/query/__init__.py +0 -0
  79. crabcode_core/query/computer_use_history.py +83 -0
  80. crabcode_core/query/loop.py +2446 -0
  81. crabcode_core/query/retry.py +116 -0
  82. crabcode_core/schedule/__init__.py +13 -0
  83. crabcode_core/schedule/manager.py +631 -0
  84. crabcode_core/schedule/models.py +204 -0
  85. crabcode_core/schedule/store.py +558 -0
  86. crabcode_core/schedule/timing.py +287 -0
  87. crabcode_core/session/__init__.py +12 -0
  88. crabcode_core/session/export.py +141 -0
  89. crabcode_core/session/meta_db.py +684 -0
  90. crabcode_core/session/storage.py +2032 -0
  91. crabcode_core/session/title_gen.py +52 -0
  92. crabcode_core/skills/__init__.py +6 -0
  93. crabcode_core/skills/context.py +23 -0
  94. crabcode_core/skills/loader.py +214 -0
  95. crabcode_core/skills/matcher.py +203 -0
  96. crabcode_core/snapshot/__init__.py +21 -0
  97. crabcode_core/snapshot/snapshot.py +436 -0
  98. crabcode_core/snapshot/tracker.py +359 -0
  99. crabcode_core/subprocess_utils.py +260 -0
  100. crabcode_core/team/__init__.py +27 -0
  101. crabcode_core/team/bridge.py +66 -0
  102. crabcode_core/team/inbox.py +237 -0
  103. crabcode_core/team/manager.py +1660 -0
  104. crabcode_core/team/message_bus.py +821 -0
  105. crabcode_core/team/models.py +191 -0
  106. crabcode_core/team/recovery.py +192 -0
  107. crabcode_core/text_io.py +52 -0
  108. crabcode_core/tools/__init__.py +112 -0
  109. crabcode_core/tools/_input_helpers.py +39 -0
  110. crabcode_core/tools/_input_schema.py +130 -0
  111. crabcode_core/tools/agent.py +413 -0
  112. crabcode_core/tools/apply_patch.py +587 -0
  113. crabcode_core/tools/ask_user.py +122 -0
  114. crabcode_core/tools/bash.py +154 -0
  115. crabcode_core/tools/browser.py +678 -0
  116. crabcode_core/tools/checklist.py +241 -0
  117. crabcode_core/tools/checkpoint.py +78 -0
  118. crabcode_core/tools/computer_use.py +599 -0
  119. crabcode_core/tools/diff_utils.py +123 -0
  120. crabcode_core/tools/file_edit.py +240 -0
  121. crabcode_core/tools/file_read.py +187 -0
  122. crabcode_core/tools/file_write.py +154 -0
  123. crabcode_core/tools/glob.py +123 -0
  124. crabcode_core/tools/goal.py +134 -0
  125. crabcode_core/tools/grep.py +247 -0
  126. crabcode_core/tools/image.py +173 -0
  127. crabcode_core/tools/image_generate.py +115 -0
  128. crabcode_core/tools/lint.py +388 -0
  129. crabcode_core/tools/loading.py +187 -0
  130. crabcode_core/tools/memory.py +456 -0
  131. crabcode_core/tools/monitor.py +677 -0
  132. crabcode_core/tools/peer.py +103 -0
  133. crabcode_core/tools/revert.py +90 -0
  134. crabcode_core/tools/schedule.py +683 -0
  135. crabcode_core/tools/skill.py +108 -0
  136. crabcode_core/tools/switch_mode.py +213 -0
  137. crabcode_core/tools/team.py +329 -0
  138. crabcode_core/tools/tool_search.py +64 -0
  139. crabcode_core/tools/web_search.py +444 -0
  140. crabcode_core/turn_details.py +64 -0
  141. crabcode_core/types/__init__.py +95 -0
  142. crabcode_core/types/config.py +434 -0
  143. crabcode_core/types/event.py +334 -0
  144. crabcode_core/types/message.py +263 -0
  145. crabcode_core/types/tool.py +208 -0
  146. crabcode_core/usage.py +289 -0
  147. crabcode_core/utf8_sanitize.py +29 -0
  148. crabcode_debugger/__init__.py +5 -0
  149. crabcode_debugger/adapters.py +332 -0
  150. crabcode_debugger/dap.py +304 -0
  151. crabcode_debugger/memory.py +1806 -0
  152. crabcode_debugger/process.py +707 -0
  153. crabcode_debugger/runtime.py +58 -0
  154. crabcode_debugger/sessions.py +387 -0
  155. crabcode_debugger/tool.py +731 -0
  156. crabcode_gateway/__init__.py +18 -0
  157. crabcode_gateway/acp/__init__.py +13 -0
  158. crabcode_gateway/acp/agent.py +1581 -0
  159. crabcode_gateway/acp/session.py +114 -0
  160. crabcode_gateway/acp/transport.py +39 -0
  161. crabcode_gateway/acp/types.py +90 -0
  162. crabcode_gateway/adapter.py +30 -0
  163. crabcode_gateway/auth.py +139 -0
  164. crabcode_gateway/computer_use.py +364 -0
  165. crabcode_gateway/document_engine.py +637 -0
  166. crabcode_gateway/document_worker.py +301 -0
  167. crabcode_gateway/event_bus.py +655 -0
  168. crabcode_gateway/grpc/__init__.py +0 -0
  169. crabcode_gateway/grpc/crabcode_pb2.py +70 -0
  170. crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
  171. crabcode_gateway/grpc/proto/crabcode.proto +160 -0
  172. crabcode_gateway/grpc/server.py +765 -0
  173. crabcode_gateway/middleware.py +344 -0
  174. crabcode_gateway/protocol.py +17 -0
  175. crabcode_gateway/routes/__init__.py +0 -0
  176. crabcode_gateway/routes/agent.py +392 -0
  177. crabcode_gateway/routes/auth.py +150 -0
  178. crabcode_gateway/routes/computer_use.py +101 -0
  179. crabcode_gateway/routes/config.py +1698 -0
  180. crabcode_gateway/routes/document.py +1810 -0
  181. crabcode_gateway/routes/event.py +3801 -0
  182. crabcode_gateway/routes/git_review.py +232 -0
  183. crabcode_gateway/routes/health.py +14 -0
  184. crabcode_gateway/routes/peer.py +78 -0
  185. crabcode_gateway/routes/permission.py +121 -0
  186. crabcode_gateway/routes/schedule.py +300 -0
  187. crabcode_gateway/routes/session.py +1496 -0
  188. crabcode_gateway/routes/snapshot.py +211 -0
  189. crabcode_gateway/routes/tasks.py +318 -0
  190. crabcode_gateway/routes/team.py +392 -0
  191. crabcode_gateway/routes/usage.py +49 -0
  192. crabcode_gateway/routes/workspace.py +299 -0
  193. crabcode_gateway/safe_file.py +63 -0
  194. crabcode_gateway/schemas.py +1820 -0
  195. crabcode_gateway/server.py +514 -0
  196. crabcode_gateway/session_registry.py +36 -0
  197. crabcode_gateway/task_registry.py +708 -0
  198. crabcode_gateway/turn_details.py +60 -0
  199. crabcode_search/__init__.py +11 -0
  200. crabcode_search/background.py +368 -0
  201. crabcode_search/chunker.py +517 -0
  202. crabcode_search/embedder.py +497 -0
  203. crabcode_search/indexer.py +215 -0
  204. crabcode_search/store.py +222 -0
  205. crabcode_search/tool.py +268 -0
@@ -0,0 +1,707 @@
1
+ """Process-level debugging and diagnostics helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import os
7
+ import platform
8
+ import shutil
9
+ import signal
10
+ from dataclasses import dataclass
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from crabcode_core.subprocess_utils import (
16
+ decode_subprocess_output,
17
+ managed_process_command,
18
+ powershell_command,
19
+ resolve_executable_command,
20
+ subprocess_group_options,
21
+ terminate_process_tree,
22
+ )
23
+ from crabcode_debugger.memory import MemoryInspector
24
+
25
+
26
+ @dataclass
27
+ class ProcessInfo:
28
+ pid: int
29
+ ppid: int | None
30
+ name: str
31
+ command: str
32
+ status: str | None = None
33
+ cwd: str | None = None
34
+ language_guess: str | None = None
35
+
36
+ def to_dict(self) -> dict[str, Any]:
37
+ return {
38
+ "pid": self.pid,
39
+ "ppid": self.ppid,
40
+ "name": self.name,
41
+ "command": self.command,
42
+ "status": self.status,
43
+ "cwd": self.cwd,
44
+ "language_guess": self.language_guess,
45
+ }
46
+
47
+
48
+ def _guess_language(command: str) -> str | None:
49
+ lower = command.lower()
50
+ markers = [
51
+ ("python", "python"),
52
+ ("debugpy", "python"),
53
+ ("go test", "go"),
54
+ ("dlv", "go"),
55
+ ("java", "java"),
56
+ ("node", "javascript"),
57
+ ("deno", "typescript"),
58
+ ("bun", "javascript"),
59
+ ("cargo", "rust"),
60
+ ("rust", "rust"),
61
+ ("gdb", "cpp"),
62
+ ("lldb", "cpp"),
63
+ ]
64
+ for marker, language in markers:
65
+ if marker in lower:
66
+ return language
67
+ for suffix, language in {
68
+ ".py": "python",
69
+ ".go": "go",
70
+ ".java": "java",
71
+ ".js": "javascript",
72
+ ".mjs": "javascript",
73
+ ".cjs": "javascript",
74
+ ".ts": "typescript",
75
+ ".rs": "rust",
76
+ ".cpp": "cpp",
77
+ ".cc": "cpp",
78
+ ".cxx": "cpp",
79
+ }.items():
80
+ if suffix in lower:
81
+ return language
82
+ return None
83
+
84
+
85
+ async def _run(
86
+ command: list[str],
87
+ *,
88
+ cwd: str | None = None,
89
+ timeout: float = 10,
90
+ ) -> tuple[int, str, str]:
91
+ proc: asyncio.subprocess.Process | None = None
92
+ try:
93
+ launch_command = resolve_executable_command(command, cwd=cwd)
94
+ proc = await asyncio.create_subprocess_exec(
95
+ *managed_process_command(launch_command),
96
+ cwd=cwd,
97
+ stdout=asyncio.subprocess.PIPE,
98
+ stderr=asyncio.subprocess.PIPE,
99
+ **subprocess_group_options(),
100
+ )
101
+ stdout_bytes, stderr_bytes = await asyncio.wait_for(proc.communicate(), timeout=timeout)
102
+ return (
103
+ proc.returncode or 0,
104
+ decode_subprocess_output(stdout_bytes),
105
+ decode_subprocess_output(stderr_bytes),
106
+ )
107
+ except asyncio.TimeoutError:
108
+ await terminate_process_tree(proc)
109
+ return -1, "", f"command timed out after {timeout}s"
110
+ except asyncio.CancelledError:
111
+ await terminate_process_tree(proc)
112
+ raise
113
+ except FileNotFoundError:
114
+ return -1, "", f"command not found: {command[0]}"
115
+ except Exception as exc:
116
+ return -1, "", str(exc)
117
+
118
+
119
+ class ProcessInspector:
120
+ """Best-effort process inspection across macOS, Linux, and Windows."""
121
+
122
+ def __init__(self, *, cwd: str, config: dict[str, Any] | None = None) -> None:
123
+ self.cwd = cwd
124
+ self.config = config or {}
125
+ self.timeout = float(self.config.get("default_timeout_seconds", 15))
126
+ self.dump_dir = Path(
127
+ self.config.get("dump_dir")
128
+ or Path(cwd) / ".crabcode" / "debugger" / "dumps"
129
+ )
130
+ self.memory = MemoryInspector(config=self.config)
131
+
132
+ async def close(self) -> None:
133
+ await self.memory.close()
134
+
135
+ def capabilities(self) -> dict[str, Any]:
136
+ system = platform.system().lower()
137
+ memory_capabilities = self.memory.capabilities()
138
+ tools = {
139
+ name: shutil.which(name)
140
+ for name in [
141
+ "lldb",
142
+ "lldb-dap",
143
+ "gdb",
144
+ "gcore",
145
+ "strace",
146
+ "perf",
147
+ "pstack",
148
+ "sample",
149
+ "vmmap",
150
+ "procdump",
151
+ "cdb",
152
+ "powershell",
153
+ "pwsh",
154
+ "taskkill",
155
+ ]
156
+ }
157
+ actions = {
158
+ "list_processes": True,
159
+ "inspect_process": True,
160
+ "attach_debugger": True,
161
+ "sample_stack": bool(
162
+ (system == "darwin" and tools.get("sample"))
163
+ or (system == "linux" and (tools.get("gdb") or tools.get("pstack")))
164
+ or (system == "windows" and (tools.get("cdb") or tools.get("procdump")))
165
+ ),
166
+ "dump_core": bool(
167
+ (system == "darwin" and tools.get("lldb"))
168
+ or (system == "linux" and (tools.get("gcore") or tools.get("gdb")))
169
+ or (system == "windows" and tools.get("procdump"))
170
+ ),
171
+ "memory_maps": bool(
172
+ system == "linux"
173
+ or (system == "darwin" and tools.get("vmmap"))
174
+ ),
175
+ "trace_syscalls": bool(system == "linux" and tools.get("strace")),
176
+ "memory_regions": bool(memory_capabilities.get("memory_regions")),
177
+ "memory_read": bool(memory_capabilities.get("memory_read")),
178
+ "memory_search": bool(memory_capabilities.get("memory_search")),
179
+ "memory_refine": bool(memory_capabilities.get("memory_refine")),
180
+ "memory_write": bool(memory_capabilities.get("memory_write")),
181
+ "memory_freeze": bool(memory_capabilities.get("memory_freeze")),
182
+ "memory_unfreeze": bool(memory_capabilities.get("memory_unfreeze")),
183
+ "memory_freezes": bool(memory_capabilities.get("memory_freezes")),
184
+ "aob_scan": bool(memory_capabilities.get("aob_scan")),
185
+ "pointer_scan": bool(memory_capabilities.get("pointer_scan")),
186
+ "pointer_resolve": bool(memory_capabilities.get("pointer_resolve")),
187
+ "code_read": bool(memory_capabilities.get("code_read")),
188
+ "code_patch": bool(memory_capabilities.get("code_patch")),
189
+ "code_restore": bool(memory_capabilities.get("code_restore")),
190
+ "code_patches": bool(memory_capabilities.get("code_patches")),
191
+ "terminate": True,
192
+ "kill": True,
193
+ }
194
+ return {
195
+ "platform": {
196
+ "system": platform.system(),
197
+ "release": platform.release(),
198
+ "machine": platform.machine(),
199
+ },
200
+ "tools": tools,
201
+ "actions": actions,
202
+ "memory": memory_capabilities,
203
+ }
204
+
205
+ async def list_processes(self, *, limit: int = 100, query: str | None = None) -> dict[str, Any]:
206
+ system = platform.system().lower()
207
+ if system == "windows":
208
+ processes = await self._list_windows()
209
+ else:
210
+ processes = await self._list_posix()
211
+ if query:
212
+ q = query.lower()
213
+ processes = [p for p in processes if q in p.command.lower() or q in p.name.lower()]
214
+ return {"processes": [p.to_dict() for p in processes[: max(1, limit)]]}
215
+
216
+ async def inspect_process(self, pid: int) -> dict[str, Any]:
217
+ system = platform.system().lower()
218
+ if system == "windows":
219
+ return await self._inspect_windows(pid)
220
+ return await self._inspect_posix(pid)
221
+
222
+ async def sample_stack(self, pid: int, *, duration_seconds: int = 3) -> dict[str, Any]:
223
+ system = platform.system().lower()
224
+ duration = max(1, min(int(duration_seconds), 30))
225
+ if system == "darwin":
226
+ code, stdout, stderr = await _run(["sample", str(pid), str(duration)], timeout=duration + 10)
227
+ return self._command_result("sample_stack", code, stdout, stderr)
228
+ if system == "linux":
229
+ if shutil.which("gdb"):
230
+ code, stdout, stderr = await _run(
231
+ ["gdb", "-batch", "-ex", "thread apply all bt", "-p", str(pid)],
232
+ timeout=max(duration + 10, self.timeout),
233
+ )
234
+ return self._command_result("sample_stack", code, stdout, stderr)
235
+ if shutil.which("pstack"):
236
+ code, stdout, stderr = await _run(["pstack", str(pid)], timeout=self.timeout)
237
+ return self._command_result("sample_stack", code, stdout, stderr)
238
+ if system == "windows" and shutil.which("cdb"):
239
+ code, stdout, stderr = await _run(
240
+ ["cdb", "-pv", "-p", str(pid), "-c", "~*kb;q"],
241
+ timeout=self.timeout,
242
+ )
243
+ return self._command_result("sample_stack", code, stdout, stderr)
244
+ return {"error": f"sample_stack unavailable on {platform.system()} with current tools"}
245
+
246
+ async def dump_core(self, pid: int, *, output_path: str | None = None) -> dict[str, Any]:
247
+ system = platform.system().lower()
248
+ self.dump_dir.mkdir(parents=True, exist_ok=True)
249
+ timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
250
+ if output_path:
251
+ target = Path(output_path)
252
+ if not target.is_absolute():
253
+ target = Path(self.cwd) / target
254
+ else:
255
+ suffix = ".dmp" if system == "windows" else ".core"
256
+ target = self.dump_dir / f"process-{pid}-{timestamp}{suffix}"
257
+
258
+ if system == "darwin" and shutil.which("lldb"):
259
+ code, stdout, stderr = await _run(
260
+ [
261
+ "lldb",
262
+ "-b",
263
+ "-p",
264
+ str(pid),
265
+ "-o",
266
+ f"process save-core {target}",
267
+ "-o",
268
+ "detach",
269
+ "-o",
270
+ "quit",
271
+ ],
272
+ timeout=max(self.timeout, 30),
273
+ )
274
+ return {**self._command_result("dump_core", code, stdout, stderr), "path": str(target)}
275
+ if system == "linux":
276
+ if shutil.which("gcore"):
277
+ prefix = str(target.with_suffix(""))
278
+ code, stdout, stderr = await _run(["gcore", "-o", prefix, str(pid)], timeout=max(self.timeout, 30))
279
+ return {**self._command_result("dump_core", code, stdout, stderr), "path_prefix": prefix}
280
+ if shutil.which("gdb"):
281
+ code, stdout, stderr = await _run(
282
+ ["gdb", "-batch", "-ex", f"gcore {target}", "-ex", "detach", "-p", str(pid)],
283
+ timeout=max(self.timeout, 30),
284
+ )
285
+ return {**self._command_result("dump_core", code, stdout, stderr), "path": str(target)}
286
+ if system == "windows" and shutil.which("procdump"):
287
+ code, stdout, stderr = await _run(["procdump", "-ma", str(pid), str(target)], timeout=max(self.timeout, 30))
288
+ return {**self._command_result("dump_core", code, stdout, stderr), "path": str(target)}
289
+ return {"error": f"dump_core unavailable on {platform.system()} with current tools"}
290
+
291
+ async def memory_maps(self, pid: int) -> dict[str, Any]:
292
+ system = platform.system().lower()
293
+ if system == "linux":
294
+ path = Path(f"/proc/{pid}/maps")
295
+ try:
296
+ text = path.read_text(encoding="utf-8", errors="replace")
297
+ except OSError as exc:
298
+ return {"error": str(exc)}
299
+ return {"pid": pid, "maps": text}
300
+ if system == "darwin" and shutil.which("vmmap"):
301
+ code, stdout, stderr = await _run(["vmmap", str(pid)], timeout=self.timeout)
302
+ return self._command_result("memory_maps", code, stdout, stderr)
303
+ return {"error": f"memory_maps unavailable on {platform.system()} with current tools"}
304
+
305
+ async def memory_regions(
306
+ self,
307
+ pid: int,
308
+ *,
309
+ readable: bool = False,
310
+ writable: bool = False,
311
+ executable: bool | None = None,
312
+ limit: int = 500,
313
+ ) -> dict[str, Any]:
314
+ return await asyncio.to_thread(
315
+ self.memory.regions,
316
+ pid,
317
+ readable=readable,
318
+ writable=writable,
319
+ executable=executable,
320
+ limit=limit,
321
+ )
322
+
323
+ async def memory_read(self, pid: int, *, address: int | str, size: int) -> dict[str, Any]:
324
+ return self.memory.read(pid, address=address, size=size)
325
+
326
+ async def memory_search(
327
+ self,
328
+ pid: int,
329
+ *,
330
+ value_type: str,
331
+ value: Any = None,
332
+ value_hex: str | None = None,
333
+ endian: str = "little",
334
+ writable_only: bool = True,
335
+ max_results: int | None = None,
336
+ max_scan_bytes: int | None = None,
337
+ ) -> dict[str, Any]:
338
+ return await asyncio.to_thread(
339
+ self.memory.search,
340
+ pid,
341
+ value_type=value_type,
342
+ value=value,
343
+ value_hex=value_hex,
344
+ endian=endian,
345
+ writable_only=writable_only,
346
+ max_results=max_results,
347
+ max_scan_bytes=max_scan_bytes,
348
+ )
349
+
350
+ async def memory_refine(
351
+ self,
352
+ search_id: str,
353
+ *,
354
+ comparison: str = "changed",
355
+ value: Any = None,
356
+ value_hex: str | None = None,
357
+ max_results: int | None = None,
358
+ ) -> dict[str, Any]:
359
+ return await asyncio.to_thread(
360
+ self.memory.refine,
361
+ search_id,
362
+ comparison=comparison,
363
+ value=value,
364
+ value_hex=value_hex,
365
+ max_results=max_results,
366
+ )
367
+
368
+ async def memory_write(
369
+ self,
370
+ pid: int,
371
+ *,
372
+ address: int | str,
373
+ value_type: str,
374
+ value: Any = None,
375
+ value_hex: str | None = None,
376
+ endian: str = "little",
377
+ ) -> dict[str, Any]:
378
+ return self.memory.write(
379
+ pid,
380
+ address=address,
381
+ value_type=value_type,
382
+ value=value,
383
+ value_hex=value_hex,
384
+ endian=endian,
385
+ )
386
+
387
+ async def memory_freeze(
388
+ self,
389
+ pid: int,
390
+ *,
391
+ address: int | str,
392
+ value_type: str,
393
+ value: Any = None,
394
+ value_hex: str | None = None,
395
+ endian: str = "little",
396
+ interval_seconds: float | None = None,
397
+ ) -> dict[str, Any]:
398
+ return await self.memory.freeze(
399
+ pid,
400
+ address=address,
401
+ value_type=value_type,
402
+ value=value,
403
+ value_hex=value_hex,
404
+ endian=endian,
405
+ interval_seconds=interval_seconds,
406
+ )
407
+
408
+ async def memory_unfreeze(
409
+ self,
410
+ *,
411
+ freeze_id: str | None = None,
412
+ all_freezes: bool = False,
413
+ ) -> dict[str, Any]:
414
+ return await self.memory.unfreeze(freeze_id, all_freezes=all_freezes)
415
+
416
+ async def memory_freezes(self) -> dict[str, Any]:
417
+ return self.memory.freezes()
418
+
419
+ async def aob_scan(
420
+ self,
421
+ pid: int,
422
+ *,
423
+ pattern: str,
424
+ executable_only: bool = True,
425
+ writable_only: bool = False,
426
+ module_filter: str | None = None,
427
+ max_results: int | None = None,
428
+ max_scan_bytes: int | None = None,
429
+ ) -> dict[str, Any]:
430
+ return await asyncio.to_thread(
431
+ self.memory.aob_scan,
432
+ pid,
433
+ pattern=pattern,
434
+ executable_only=executable_only,
435
+ writable_only=writable_only,
436
+ module_filter=module_filter,
437
+ max_results=max_results,
438
+ max_scan_bytes=max_scan_bytes,
439
+ )
440
+
441
+ async def pointer_scan(
442
+ self,
443
+ pid: int,
444
+ *,
445
+ target_address: int | str,
446
+ max_depth: int = 3,
447
+ max_offset: int = 4096,
448
+ pointer_size: int | None = None,
449
+ align: int | None = None,
450
+ writable_only: bool = True,
451
+ max_results: int | None = None,
452
+ max_scan_bytes: int | None = None,
453
+ ) -> dict[str, Any]:
454
+ return await asyncio.to_thread(
455
+ self.memory.pointer_scan,
456
+ pid,
457
+ target_address=target_address,
458
+ max_depth=max_depth,
459
+ max_offset=max_offset,
460
+ pointer_size=pointer_size,
461
+ align=align,
462
+ writable_only=writable_only,
463
+ max_results=max_results,
464
+ max_scan_bytes=max_scan_bytes,
465
+ )
466
+
467
+ async def pointer_resolve(
468
+ self,
469
+ pid: int,
470
+ *,
471
+ base_address: int | str | None = None,
472
+ offsets: list[Any] | None = None,
473
+ module_path: str | None = None,
474
+ module_offset: int | str | None = None,
475
+ pointer_size: int | None = None,
476
+ endian: str = "little",
477
+ ) -> dict[str, Any]:
478
+ return await asyncio.to_thread(
479
+ self.memory.pointer_resolve,
480
+ pid,
481
+ base_address=base_address,
482
+ offsets=offsets,
483
+ module_path=module_path,
484
+ module_offset=module_offset,
485
+ pointer_size=pointer_size,
486
+ endian=endian,
487
+ )
488
+
489
+ async def code_read(self, pid: int, *, address: int | str, size: int) -> dict[str, Any]:
490
+ return self.memory.code_read(pid, address=address, size=size)
491
+
492
+ async def code_patch(
493
+ self,
494
+ pid: int,
495
+ *,
496
+ address: int | str,
497
+ patch_hex: str,
498
+ expected_hex: str | None = None,
499
+ patch_id: str | None = None,
500
+ ) -> dict[str, Any]:
501
+ return self.memory.code_patch(
502
+ pid,
503
+ address=address,
504
+ patch_hex=patch_hex,
505
+ expected_hex=expected_hex,
506
+ patch_id=patch_id,
507
+ )
508
+
509
+ async def code_restore(
510
+ self,
511
+ *,
512
+ patch_id: str | None = None,
513
+ all_patches: bool = False,
514
+ ) -> dict[str, Any]:
515
+ return self.memory.code_restore(patch_id=patch_id, all_patches=all_patches)
516
+
517
+ async def code_patches(self) -> dict[str, Any]:
518
+ return self.memory.code_patches()
519
+
520
+ async def trace_syscalls(self, pid: int, *, duration_seconds: int = 5, output_path: str | None = None) -> dict[str, Any]:
521
+ system = platform.system().lower()
522
+ duration = max(1, min(int(duration_seconds), 30))
523
+ if system != "linux" or not shutil.which("strace"):
524
+ return {"error": f"trace_syscalls unavailable on {platform.system()} with current tools"}
525
+ out = Path(output_path) if output_path else self.dump_dir / f"strace-{pid}-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}.log"
526
+ if not out.is_absolute():
527
+ out = Path(self.cwd) / out
528
+ out.parent.mkdir(parents=True, exist_ok=True)
529
+ proc = await asyncio.create_subprocess_exec(
530
+ "strace",
531
+ "-p",
532
+ str(pid),
533
+ "-f",
534
+ "-tt",
535
+ "-T",
536
+ "-o",
537
+ str(out),
538
+ stdout=asyncio.subprocess.PIPE,
539
+ stderr=asyncio.subprocess.PIPE,
540
+ )
541
+ try:
542
+ await asyncio.wait_for(proc.communicate(), timeout=duration)
543
+ except asyncio.TimeoutError:
544
+ proc.terminate()
545
+ try:
546
+ await asyncio.wait_for(proc.wait(), timeout=3)
547
+ except asyncio.TimeoutError:
548
+ proc.kill()
549
+ await proc.wait()
550
+ return {"pid": pid, "path": str(out), "duration_seconds": duration}
551
+
552
+ async def signal_process(self, pid: int, *, sig: str) -> dict[str, Any]:
553
+ if isinstance(pid, bool) or pid <= 0:
554
+ return {"pid": pid, "error": "pid must be a positive integer"}
555
+ system = platform.system().lower()
556
+ normalized = sig.lower()
557
+ if system == "windows":
558
+ if normalized in {"terminate", "term", "sigterm"}:
559
+ if shutil.which("taskkill"):
560
+ code, stdout, stderr = await _run(
561
+ ["taskkill", "/PID", str(pid), "/T"],
562
+ timeout=self.timeout,
563
+ )
564
+ return self._command_result("terminate", code, stdout, stderr)
565
+ return {"error": "taskkill not found"}
566
+ if shutil.which("taskkill"):
567
+ code, stdout, stderr = await _run(
568
+ ["taskkill", "/F", "/PID", str(pid), "/T"],
569
+ timeout=self.timeout,
570
+ )
571
+ return self._command_result("kill", code, stdout, stderr)
572
+ return {"error": "taskkill not found"}
573
+
574
+ signum = signal.SIGTERM if normalized in {"terminate", "term", "sigterm"} else signal.SIGKILL
575
+ try:
576
+ os.kill(pid, signum)
577
+ return {"pid": pid, "signal": signum.name, "sent": True}
578
+ except OSError as exc:
579
+ return {"pid": pid, "signal": signum.name, "sent": False, "error": str(exc)}
580
+
581
+ async def _list_posix(self) -> list[ProcessInfo]:
582
+ code, stdout, stderr = await _run(["ps", "-axo", "pid=,ppid=,stat=,comm=,args="], timeout=self.timeout)
583
+ if code != 0:
584
+ return [ProcessInfo(pid=-1, ppid=None, name="ps", command=stderr, status="error")]
585
+ processes: list[ProcessInfo] = []
586
+ for line in stdout.splitlines():
587
+ parts = line.strip().split(None, 4)
588
+ if len(parts) < 4:
589
+ continue
590
+ pid_raw, ppid_raw, stat, name = parts[:4]
591
+ args = parts[4] if len(parts) > 4 else name
592
+ try:
593
+ pid = int(pid_raw)
594
+ ppid = int(ppid_raw)
595
+ except ValueError:
596
+ continue
597
+ processes.append(
598
+ ProcessInfo(
599
+ pid=pid,
600
+ ppid=ppid,
601
+ name=name,
602
+ command=args,
603
+ status=stat,
604
+ cwd=self._linux_cwd(pid),
605
+ language_guess=_guess_language(args),
606
+ )
607
+ )
608
+ return processes
609
+
610
+ async def _list_windows(self) -> list[ProcessInfo]:
611
+ shell = shutil.which("pwsh") or shutil.which("powershell")
612
+ if not shell:
613
+ return []
614
+ script = (
615
+ "Get-CimInstance Win32_Process | "
616
+ "Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress"
617
+ )
618
+ code, stdout, stderr = await _run(
619
+ powershell_command(shell, script),
620
+ timeout=self.timeout,
621
+ )
622
+ if code != 0:
623
+ return [ProcessInfo(pid=-1, ppid=None, name="powershell", command=stderr, status="error")]
624
+ import json
625
+
626
+ try:
627
+ data = json.loads(stdout or "[]")
628
+ except json.JSONDecodeError:
629
+ return []
630
+ if isinstance(data, dict):
631
+ data = [data]
632
+ processes: list[ProcessInfo] = []
633
+ for item in data if isinstance(data, list) else []:
634
+ command = str(item.get("CommandLine") or item.get("Name") or "")
635
+ processes.append(
636
+ ProcessInfo(
637
+ pid=int(item.get("ProcessId") or 0),
638
+ ppid=int(item.get("ParentProcessId") or 0),
639
+ name=str(item.get("Name") or ""),
640
+ command=command,
641
+ language_guess=_guess_language(command),
642
+ )
643
+ )
644
+ return processes
645
+
646
+ async def _inspect_posix(self, pid: int) -> dict[str, Any]:
647
+ code, stdout, stderr = await _run(
648
+ ["ps", "-p", str(pid), "-o", "pid=,ppid=,stat=,%cpu=,%mem=,etime=,comm=,args="],
649
+ timeout=self.timeout,
650
+ )
651
+ if code != 0:
652
+ return {"pid": pid, "error": stderr.strip() or "process not found"}
653
+ info: dict[str, Any] = {"pid": pid, "ps": stdout.strip()}
654
+ proc_dir = Path(f"/proc/{pid}")
655
+ if proc_dir.exists():
656
+ info["cwd"] = self._linux_cwd(pid)
657
+ try:
658
+ info["cmdline"] = proc_dir.joinpath("cmdline").read_bytes().replace(b"\x00", b" ").decode("utf-8", errors="replace").strip()
659
+ except OSError:
660
+ pass
661
+ try:
662
+ info["open_file_count"] = len(list(proc_dir.joinpath("fd").iterdir()))
663
+ except OSError:
664
+ pass
665
+ try:
666
+ info["status"] = proc_dir.joinpath("status").read_text(
667
+ encoding="utf-8", errors="replace"
668
+ )
669
+ except OSError:
670
+ pass
671
+ return info
672
+
673
+ async def _inspect_windows(self, pid: int) -> dict[str, Any]:
674
+ shell = shutil.which("pwsh") or shutil.which("powershell")
675
+ if not shell:
676
+ return {"pid": pid, "error": "powershell not found"}
677
+ script = (
678
+ f"Get-CimInstance Win32_Process -Filter \"ProcessId={pid}\" | "
679
+ "Select-Object ProcessId,ParentProcessId,Name,CommandLine,ExecutablePath | ConvertTo-Json -Compress"
680
+ )
681
+ code, stdout, stderr = await _run(
682
+ powershell_command(shell, script),
683
+ timeout=self.timeout,
684
+ )
685
+ if code != 0:
686
+ return {"pid": pid, "error": stderr.strip()}
687
+ return {"pid": pid, "process": stdout.strip()}
688
+
689
+ @staticmethod
690
+ def _linux_cwd(pid: int) -> str | None:
691
+ link = Path(f"/proc/{pid}/cwd")
692
+ if not link.exists():
693
+ return None
694
+ try:
695
+ return os.readlink(link)
696
+ except OSError:
697
+ return None
698
+
699
+ @staticmethod
700
+ def _command_result(action: str, code: int, stdout: str, stderr: str) -> dict[str, Any]:
701
+ return {
702
+ "action": action,
703
+ "exit_code": code,
704
+ "stdout": stdout,
705
+ "stderr": stderr,
706
+ "ok": code == 0,
707
+ }