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,58 @@
|
|
|
1
|
+
"""Shared debugger runtime state for multiple tool instances."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from crabcode_debugger.sessions import DebugSessionManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class _ManagerEntry:
|
|
13
|
+
manager: DebugSessionManager
|
|
14
|
+
users: int = 1
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_MANAGERS: dict[tuple[str, str], _ManagerEntry] = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _manager_key(cwd: str, owner_id: str) -> tuple[str, str]:
|
|
21
|
+
return (str(Path(cwd).resolve()), owner_id or "legacy")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_debug_session_manager(
|
|
25
|
+
*,
|
|
26
|
+
cwd: str,
|
|
27
|
+
owner_id: str = "",
|
|
28
|
+
env: dict[str, str] | None = None,
|
|
29
|
+
config: dict | None = None,
|
|
30
|
+
) -> DebugSessionManager:
|
|
31
|
+
key = _manager_key(cwd, owner_id)
|
|
32
|
+
entry = _MANAGERS.get(key)
|
|
33
|
+
if entry is None:
|
|
34
|
+
manager = DebugSessionManager(cwd=cwd, env=env, config=config)
|
|
35
|
+
_MANAGERS[key] = _ManagerEntry(manager=manager)
|
|
36
|
+
else:
|
|
37
|
+
manager = entry.manager
|
|
38
|
+
entry.users += 1
|
|
39
|
+
manager.update_config(config)
|
|
40
|
+
return manager
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def release_debug_session_manager(
|
|
44
|
+
*,
|
|
45
|
+
cwd: str,
|
|
46
|
+
owner_id: str = "",
|
|
47
|
+
manager: DebugSessionManager,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Release one tool's reference and close the manager after the last user."""
|
|
50
|
+
key = _manager_key(cwd, owner_id)
|
|
51
|
+
entry = _MANAGERS.get(key)
|
|
52
|
+
if entry is None or entry.manager is not manager:
|
|
53
|
+
return
|
|
54
|
+
entry.users -= 1
|
|
55
|
+
if entry.users > 0:
|
|
56
|
+
return
|
|
57
|
+
_MANAGERS.pop(key, None)
|
|
58
|
+
await manager.close()
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""Debug session manager built on DAP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import uuid
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from crabcode_debugger.adapters import AdapterRegistry, AdapterStatus, infer_language
|
|
12
|
+
from crabcode_debugger.dap import DAPClient, DAPEvent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class DebugSession:
|
|
17
|
+
session_id: str
|
|
18
|
+
language: str
|
|
19
|
+
adapter: AdapterStatus
|
|
20
|
+
client: DAPClient
|
|
21
|
+
target: dict[str, Any]
|
|
22
|
+
capabilities: dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
last_stopped_thread_id: int | None = None
|
|
24
|
+
pending_configuration_seq: int | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DebugSessionManager:
|
|
28
|
+
"""Tracks live DAP sessions."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
cwd: str,
|
|
34
|
+
env: dict[str, str] | None = None,
|
|
35
|
+
config: dict[str, Any] | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self.cwd = str(Path(cwd).resolve())
|
|
38
|
+
self.env = dict(env or {})
|
|
39
|
+
self.config = config or {}
|
|
40
|
+
self.registry = AdapterRegistry(self.config)
|
|
41
|
+
self.sessions: dict[str, DebugSession] = {}
|
|
42
|
+
self.timeout = float(self.config.get("default_timeout_seconds", 30))
|
|
43
|
+
self.disconnect_timeout = float(self.config.get("disconnect_timeout_seconds", 10))
|
|
44
|
+
|
|
45
|
+
def update_config(self, config: dict[str, Any] | None) -> None:
|
|
46
|
+
"""Merge later tool configuration into the shared manager."""
|
|
47
|
+
if not config:
|
|
48
|
+
return
|
|
49
|
+
self.config = _merge_dicts(self.config, config)
|
|
50
|
+
self.registry = AdapterRegistry(self.config)
|
|
51
|
+
self.timeout = float(self.config.get("default_timeout_seconds", self.timeout))
|
|
52
|
+
self.disconnect_timeout = float(
|
|
53
|
+
self.config.get("disconnect_timeout_seconds", self.disconnect_timeout)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
async def close(self) -> None:
|
|
57
|
+
for session_id in list(self.sessions):
|
|
58
|
+
try:
|
|
59
|
+
await self.stop(session_id, terminate_debuggee=False)
|
|
60
|
+
except Exception:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
async def start(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
language: str,
|
|
67
|
+
program: str | None = None,
|
|
68
|
+
args: list[str] | None = None,
|
|
69
|
+
cwd: str | None = None,
|
|
70
|
+
adapter_id: str | None = None,
|
|
71
|
+
launch_config: dict[str, Any] | None = None,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
normalized = infer_language(language or program)
|
|
74
|
+
if not normalized:
|
|
75
|
+
return {"error": "language is required or could not be inferred"}
|
|
76
|
+
adapter = self.registry.resolve(normalized, adapter_id)
|
|
77
|
+
if not adapter:
|
|
78
|
+
return {
|
|
79
|
+
"error": f"no available debug adapter for {normalized}",
|
|
80
|
+
"install_hints": self.registry.install_hints(normalized),
|
|
81
|
+
"adapters": [s.to_dict() for s in self.registry.status(normalized)],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
client = DAPClient(
|
|
85
|
+
adapter.command,
|
|
86
|
+
cwd=cwd or self.cwd,
|
|
87
|
+
env=self.env,
|
|
88
|
+
timeout=self.timeout,
|
|
89
|
+
max_events=int(self.config.get("max_dap_events", 10_000)),
|
|
90
|
+
)
|
|
91
|
+
try:
|
|
92
|
+
await client.start()
|
|
93
|
+
capabilities = await client.request(
|
|
94
|
+
"initialize",
|
|
95
|
+
{
|
|
96
|
+
"clientID": "crabcode",
|
|
97
|
+
"clientName": "CrabCode",
|
|
98
|
+
"adapterID": adapter.adapter_id,
|
|
99
|
+
"pathFormat": "path",
|
|
100
|
+
"linesStartAt1": True,
|
|
101
|
+
"columnsStartAt1": True,
|
|
102
|
+
"supportsVariableType": True,
|
|
103
|
+
"supportsVariablePaging": True,
|
|
104
|
+
"supportsRunInTerminalRequest": False,
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
launch_args: dict[str, Any] = dict(launch_config or {})
|
|
109
|
+
launch_args.setdefault("cwd", cwd or self.cwd)
|
|
110
|
+
if program:
|
|
111
|
+
program_path = Path(program)
|
|
112
|
+
if not program_path.is_absolute():
|
|
113
|
+
program_path = Path(cwd or self.cwd) / program_path
|
|
114
|
+
launch_args.setdefault("program", str(program_path.resolve()))
|
|
115
|
+
if args is not None:
|
|
116
|
+
launch_args.setdefault("args", args)
|
|
117
|
+
|
|
118
|
+
pending_seq = await client.start_request("launch", launch_args)
|
|
119
|
+
session = DebugSession(
|
|
120
|
+
session_id=f"dbg-{uuid.uuid4().hex[:10]}",
|
|
121
|
+
language=normalized,
|
|
122
|
+
adapter=adapter,
|
|
123
|
+
client=client,
|
|
124
|
+
target={"mode": "launch", "program": program, "cwd": cwd or self.cwd},
|
|
125
|
+
capabilities=capabilities,
|
|
126
|
+
pending_configuration_seq=pending_seq,
|
|
127
|
+
)
|
|
128
|
+
self.sessions[session.session_id] = session
|
|
129
|
+
except BaseException:
|
|
130
|
+
await self._close_failed_client(client)
|
|
131
|
+
raise
|
|
132
|
+
return {
|
|
133
|
+
"session_id": session.session_id,
|
|
134
|
+
"language": normalized,
|
|
135
|
+
"adapter": adapter.to_dict(),
|
|
136
|
+
"capabilities": capabilities,
|
|
137
|
+
"events": [event.to_dict() for event in await self._drain_and_track(session)],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async def attach(
|
|
141
|
+
self,
|
|
142
|
+
*,
|
|
143
|
+
language: str,
|
|
144
|
+
pid: int,
|
|
145
|
+
adapter_id: str | None = None,
|
|
146
|
+
attach_config: dict[str, Any] | None = None,
|
|
147
|
+
) -> dict[str, Any]:
|
|
148
|
+
if isinstance(pid, bool) or pid <= 0:
|
|
149
|
+
return {"error": "pid must be a positive integer for attach"}
|
|
150
|
+
normalized = infer_language(language)
|
|
151
|
+
if not normalized:
|
|
152
|
+
return {"error": "language is required for attach"}
|
|
153
|
+
adapter = self.registry.resolve(normalized, adapter_id)
|
|
154
|
+
if not adapter:
|
|
155
|
+
return {
|
|
156
|
+
"error": f"no available debug adapter for {normalized}",
|
|
157
|
+
"install_hints": self.registry.install_hints(normalized),
|
|
158
|
+
"adapters": [s.to_dict() for s in self.registry.status(normalized)],
|
|
159
|
+
}
|
|
160
|
+
client = DAPClient(
|
|
161
|
+
adapter.command,
|
|
162
|
+
cwd=self.cwd,
|
|
163
|
+
env=self.env,
|
|
164
|
+
timeout=self.timeout,
|
|
165
|
+
max_events=int(self.config.get("max_dap_events", 10_000)),
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
await client.start()
|
|
169
|
+
capabilities = await client.request(
|
|
170
|
+
"initialize",
|
|
171
|
+
{
|
|
172
|
+
"clientID": "crabcode",
|
|
173
|
+
"clientName": "CrabCode",
|
|
174
|
+
"adapterID": adapter.adapter_id,
|
|
175
|
+
"pathFormat": "path",
|
|
176
|
+
"linesStartAt1": True,
|
|
177
|
+
"columnsStartAt1": True,
|
|
178
|
+
"supportsVariableType": True,
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
args = dict(attach_config or {})
|
|
182
|
+
args.setdefault("processId", pid)
|
|
183
|
+
pending_seq = await client.start_request("attach", args)
|
|
184
|
+
session = DebugSession(
|
|
185
|
+
session_id=f"dbg-{uuid.uuid4().hex[:10]}",
|
|
186
|
+
language=normalized,
|
|
187
|
+
adapter=adapter,
|
|
188
|
+
client=client,
|
|
189
|
+
target={"mode": "attach", "pid": pid},
|
|
190
|
+
capabilities=capabilities,
|
|
191
|
+
pending_configuration_seq=pending_seq,
|
|
192
|
+
)
|
|
193
|
+
self.sessions[session.session_id] = session
|
|
194
|
+
except BaseException:
|
|
195
|
+
await self._close_failed_client(client)
|
|
196
|
+
raise
|
|
197
|
+
return {
|
|
198
|
+
"session_id": session.session_id,
|
|
199
|
+
"language": normalized,
|
|
200
|
+
"adapter": adapter.to_dict(),
|
|
201
|
+
"capabilities": capabilities,
|
|
202
|
+
"events": [event.to_dict() for event in await self._drain_and_track(session)],
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async def set_breakpoints(
|
|
206
|
+
self,
|
|
207
|
+
session_id: str,
|
|
208
|
+
*,
|
|
209
|
+
path: str,
|
|
210
|
+
lines: list[int],
|
|
211
|
+
) -> dict[str, Any]:
|
|
212
|
+
session = self._require(session_id)
|
|
213
|
+
source_path = Path(path)
|
|
214
|
+
if not source_path.is_absolute():
|
|
215
|
+
source_path = Path(str(session.target.get("cwd") or self.cwd)) / source_path
|
|
216
|
+
body = await session.client.request(
|
|
217
|
+
"setBreakpoints",
|
|
218
|
+
{
|
|
219
|
+
"source": {"path": str(source_path)},
|
|
220
|
+
"breakpoints": [{"line": int(line)} for line in lines],
|
|
221
|
+
},
|
|
222
|
+
)
|
|
223
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
224
|
+
|
|
225
|
+
async def configuration_done(self, session_id: str) -> dict[str, Any]:
|
|
226
|
+
session = self._require(session_id)
|
|
227
|
+
body = await session.client.request("configurationDone", {})
|
|
228
|
+
pending_response: dict[str, Any] | None = None
|
|
229
|
+
if session.pending_configuration_seq is not None:
|
|
230
|
+
pending_response = await session.client.wait_response(
|
|
231
|
+
session.pending_configuration_seq,
|
|
232
|
+
timeout=self.timeout,
|
|
233
|
+
)
|
|
234
|
+
session.pending_configuration_seq = None
|
|
235
|
+
if pending_response is not None:
|
|
236
|
+
body["pending_configuration_response"] = pending_response
|
|
237
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
238
|
+
|
|
239
|
+
async def continue_thread(self, session_id: str, thread_id: int | None = None) -> dict[str, Any]:
|
|
240
|
+
session = self._require(session_id)
|
|
241
|
+
body = await session.client.request("continue", {"threadId": self._thread_id(session, thread_id)})
|
|
242
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
243
|
+
|
|
244
|
+
async def pause(self, session_id: str, thread_id: int | None = None) -> dict[str, Any]:
|
|
245
|
+
session = self._require(session_id)
|
|
246
|
+
body = await session.client.request("pause", {"threadId": self._thread_id(session, thread_id)})
|
|
247
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
248
|
+
|
|
249
|
+
async def step(self, session_id: str, command: str, thread_id: int | None = None) -> dict[str, Any]:
|
|
250
|
+
session = self._require(session_id)
|
|
251
|
+
dap_command = {
|
|
252
|
+
"step_over": "next",
|
|
253
|
+
"step_in": "stepIn",
|
|
254
|
+
"step_out": "stepOut",
|
|
255
|
+
}[command]
|
|
256
|
+
body = await session.client.request(dap_command, {"threadId": self._thread_id(session, thread_id)})
|
|
257
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
258
|
+
|
|
259
|
+
async def threads(self, session_id: str) -> dict[str, Any]:
|
|
260
|
+
session = self._require(session_id)
|
|
261
|
+
body = await session.client.request("threads", {})
|
|
262
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
263
|
+
|
|
264
|
+
async def stack(self, session_id: str, thread_id: int | None = None, levels: int = 20) -> dict[str, Any]:
|
|
265
|
+
session = self._require(session_id)
|
|
266
|
+
body = await session.client.request(
|
|
267
|
+
"stackTrace",
|
|
268
|
+
{"threadId": self._thread_id(session, thread_id), "levels": levels},
|
|
269
|
+
)
|
|
270
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
271
|
+
|
|
272
|
+
async def scopes(self, session_id: str, frame_id: int) -> dict[str, Any]:
|
|
273
|
+
session = self._require(session_id)
|
|
274
|
+
body = await session.client.request("scopes", {"frameId": frame_id})
|
|
275
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
276
|
+
|
|
277
|
+
async def variables(self, session_id: str, variables_reference: int, start: int = 0, count: int = 100) -> dict[str, Any]:
|
|
278
|
+
session = self._require(session_id)
|
|
279
|
+
body = await session.client.request(
|
|
280
|
+
"variables",
|
|
281
|
+
{
|
|
282
|
+
"variablesReference": variables_reference,
|
|
283
|
+
"start": start,
|
|
284
|
+
"count": count,
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
288
|
+
|
|
289
|
+
async def evaluate(
|
|
290
|
+
self,
|
|
291
|
+
session_id: str,
|
|
292
|
+
expression: str,
|
|
293
|
+
*,
|
|
294
|
+
frame_id: int | None = None,
|
|
295
|
+
context: str = "repl",
|
|
296
|
+
) -> dict[str, Any]:
|
|
297
|
+
session = self._require(session_id)
|
|
298
|
+
args: dict[str, Any] = {"expression": expression, "context": context}
|
|
299
|
+
if frame_id is not None:
|
|
300
|
+
args["frameId"] = frame_id
|
|
301
|
+
body = await session.client.request("evaluate", args)
|
|
302
|
+
return {**body, "events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
303
|
+
|
|
304
|
+
async def events(self, session_id: str) -> dict[str, Any]:
|
|
305
|
+
session = self._require(session_id)
|
|
306
|
+
return {"events": [event.to_dict() for event in await self._drain_and_track(session)]}
|
|
307
|
+
|
|
308
|
+
async def stop(self, session_id: str, *, terminate_debuggee: bool = False) -> dict[str, Any]:
|
|
309
|
+
session = self._require(session_id)
|
|
310
|
+
result: dict[str, Any] = {}
|
|
311
|
+
disconnect_error: str | None = None
|
|
312
|
+
try:
|
|
313
|
+
result = await session.client.request(
|
|
314
|
+
"disconnect",
|
|
315
|
+
{"terminateDebuggee": terminate_debuggee},
|
|
316
|
+
timeout=self.disconnect_timeout,
|
|
317
|
+
)
|
|
318
|
+
except Exception as exc:
|
|
319
|
+
disconnect_error = f"{type(exc).__name__}: {exc}"
|
|
320
|
+
finally:
|
|
321
|
+
try:
|
|
322
|
+
await session.client.close()
|
|
323
|
+
finally:
|
|
324
|
+
self.sessions.pop(session_id, None)
|
|
325
|
+
response = {
|
|
326
|
+
"stopped": session_id,
|
|
327
|
+
"terminate_debuggee": terminate_debuggee,
|
|
328
|
+
"result": result,
|
|
329
|
+
}
|
|
330
|
+
if disconnect_error is not None:
|
|
331
|
+
response["disconnect_warning"] = disconnect_error
|
|
332
|
+
return response
|
|
333
|
+
|
|
334
|
+
def list_sessions(self) -> dict[str, Any]:
|
|
335
|
+
return {
|
|
336
|
+
"sessions": [
|
|
337
|
+
{
|
|
338
|
+
"session_id": s.session_id,
|
|
339
|
+
"language": s.language,
|
|
340
|
+
"adapter": s.adapter.to_dict(),
|
|
341
|
+
"target": s.target,
|
|
342
|
+
"last_stopped_thread_id": s.last_stopped_thread_id,
|
|
343
|
+
}
|
|
344
|
+
for s in self.sessions.values()
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
def _require(self, session_id: str) -> DebugSession:
|
|
349
|
+
if session_id not in self.sessions:
|
|
350
|
+
raise KeyError(f"unknown debug session: {session_id}")
|
|
351
|
+
return self.sessions[session_id]
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def _thread_id(session: DebugSession, thread_id: int | None) -> int:
|
|
355
|
+
if thread_id is not None:
|
|
356
|
+
return int(thread_id)
|
|
357
|
+
if session.last_stopped_thread_id is not None:
|
|
358
|
+
return session.last_stopped_thread_id
|
|
359
|
+
return 1
|
|
360
|
+
|
|
361
|
+
async def _drain_and_track(self, session: DebugSession) -> list[DAPEvent]:
|
|
362
|
+
events = await session.client.drain_events()
|
|
363
|
+
for event in events:
|
|
364
|
+
if event.event == "stopped":
|
|
365
|
+
thread_id = event.body.get("threadId")
|
|
366
|
+
if isinstance(thread_id, int):
|
|
367
|
+
session.last_stopped_thread_id = thread_id
|
|
368
|
+
if event.event in {"terminated", "exited"}:
|
|
369
|
+
session.last_stopped_thread_id = None
|
|
370
|
+
return events
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
async def _close_failed_client(client: DAPClient) -> None:
|
|
374
|
+
try:
|
|
375
|
+
await asyncio.shield(client.close())
|
|
376
|
+
except Exception:
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _merge_dicts(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
|
381
|
+
merged = dict(base)
|
|
382
|
+
for key, value in override.items():
|
|
383
|
+
if isinstance(merged.get(key), dict) and isinstance(value, dict):
|
|
384
|
+
merged[key] = _merge_dicts(merged[key], value)
|
|
385
|
+
else:
|
|
386
|
+
merged[key] = value
|
|
387
|
+
return merged
|