agentpool-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentpool/__init__.py +3 -0
- agentpool/agent_io.py +134 -0
- agentpool/artifacts.py +151 -0
- agentpool/cli.py +1199 -0
- agentpool/config.py +373 -0
- agentpool/docs/agentpool-skill.md +85 -0
- agentpool/docs/onboarding.md +169 -0
- agentpool/event_detection.py +150 -0
- agentpool/fixtures/__init__.py +1 -0
- agentpool/fixtures/fake_agents/__init__.py +1 -0
- agentpool/fixtures/fake_agents/fake_approval_agent.py +16 -0
- agentpool/fixtures/fake_agents/fake_common.py +44 -0
- agentpool/fixtures/fake_agents/fake_completed_agent.py +13 -0
- agentpool/fixtures/fake_agents/fake_idle_agent.py +16 -0
- agentpool/fixtures/fake_agents/fake_limit_agent.py +14 -0
- agentpool/fixtures/fake_agents/fake_patch_agent.py +17 -0
- agentpool/fixtures/fake_agents/fake_question_agent.py +16 -0
- agentpool/git_worktree.py +144 -0
- agentpool/mcp/__init__.py +1 -0
- agentpool/mcp/resources.py +64 -0
- agentpool/mcp/tools.py +259 -0
- agentpool/mcp_server.py +487 -0
- agentpool/models.py +310 -0
- agentpool/onboarding.py +1279 -0
- agentpool/policy.py +63 -0
- agentpool/provider_model_catalog.json +997 -0
- agentpool/providers/__init__.py +3 -0
- agentpool/providers/base.py +411 -0
- agentpool/providers/registry.py +139 -0
- agentpool/redaction.py +30 -0
- agentpool/runtimes/__init__.py +3 -0
- agentpool/runtimes/base.py +36 -0
- agentpool/runtimes/tmux.py +133 -0
- agentpool/session_manager.py +1061 -0
- agentpool/stats/__init__.py +6 -0
- agentpool/stats/card.py +74 -0
- agentpool/stats/compute.py +496 -0
- agentpool/stats/queries.py +138 -0
- agentpool/stats/render.py +103 -0
- agentpool/stats/window.py +85 -0
- agentpool/store.py +478 -0
- agentpool/usage/__init__.py +1 -0
- agentpool/usage/_common.py +223 -0
- agentpool/usage/ccusage.py +130 -0
- agentpool/usage/claude.py +23 -0
- agentpool/usage/codex.py +210 -0
- agentpool/usage/codexbar.py +186 -0
- agentpool/usage/combine.py +71 -0
- agentpool/usage/copilot.py +146 -0
- agentpool/usage/devin.py +265 -0
- agentpool/usage/parsers.py +41 -0
- agentpool/usage/probes.py +52 -0
- agentpool/usage/provider_parsers.py +276 -0
- agentpool/usage/summary.py +166 -0
- agentpool/utils.py +59 -0
- agentpool_cli-0.1.0.dist-info/METADATA +292 -0
- agentpool_cli-0.1.0.dist-info/RECORD +60 -0
- agentpool_cli-0.1.0.dist-info/WHEEL +4 -0
- agentpool_cli-0.1.0.dist-info/entry_points.txt +2 -0
- agentpool_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
agentpool/mcp/tools.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from agentpool.agent_io import (
|
|
9
|
+
collect_payload,
|
|
10
|
+
compact_artifact_manifest,
|
|
11
|
+
lockdown_resource,
|
|
12
|
+
observe_payload,
|
|
13
|
+
parse_detail,
|
|
14
|
+
)
|
|
15
|
+
from agentpool.config import DEFAULT_MODEL_CATALOG_PATH, validate_model_catalog_path
|
|
16
|
+
from agentpool.models import SpawnWorkerRequest, ToolError
|
|
17
|
+
from agentpool.session_manager import SessionManager
|
|
18
|
+
from agentpool.stats.card import render_stats_card
|
|
19
|
+
from agentpool.stats.compute import compute_stats, filter_sections
|
|
20
|
+
from agentpool.stats.window import parse_window
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def structured_error(exc: ToolError) -> dict[str, Any]:
|
|
24
|
+
return {"error": exc.error.model_dump(mode="json")}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _jsonable_validation_errors(exc: ValidationError) -> list[dict[str, Any]]:
|
|
28
|
+
errors = exc.errors(include_url=False)
|
|
29
|
+
for error in errors:
|
|
30
|
+
ctx = error.get("ctx")
|
|
31
|
+
if isinstance(ctx, dict):
|
|
32
|
+
error["ctx"] = {key: str(value) for key, value in ctx.items()}
|
|
33
|
+
return errors
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_inventory(manager: SessionManager, include_usage: bool = True) -> dict[str, Any]:
|
|
37
|
+
return manager.inventory(include_usage=include_usage)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_usage_snapshot(
|
|
41
|
+
manager: SessionManager,
|
|
42
|
+
provider_id: str | None = None,
|
|
43
|
+
refresh: bool = True,
|
|
44
|
+
backend: str = "combined",
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
if refresh:
|
|
47
|
+
return manager.usage_snapshot(provider_id, backend=backend)
|
|
48
|
+
return manager.cached_usage_snapshot(provider_id)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_usage_summary(
|
|
52
|
+
manager: SessionManager,
|
|
53
|
+
provider_id: str | None = None,
|
|
54
|
+
refresh: bool = False,
|
|
55
|
+
backend: str = "combined",
|
|
56
|
+
) -> dict[str, Any]:
|
|
57
|
+
return manager.usage_summary(provider_id=provider_id, refresh=refresh, backend=backend)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_provider_models(manager: SessionManager, provider_id: str | None = None) -> dict[str, Any]:
|
|
61
|
+
return manager.provider_models(provider_id)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_model_catalog(manager: SessionManager, path: str | None = None) -> dict[str, Any]:
|
|
65
|
+
return validate_model_catalog_path(
|
|
66
|
+
Path(path).expanduser() if path else DEFAULT_MODEL_CATALOG_PATH,
|
|
67
|
+
known_provider_ids=set(manager.config.providers),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def filter_candidates(
|
|
72
|
+
manager: SessionManager,
|
|
73
|
+
required_capabilities: list[str] | None = None,
|
|
74
|
+
avoid_statuses: list[str] | None = None,
|
|
75
|
+
allowed_providers: list[str] | None = None,
|
|
76
|
+
include_usage_unknown: bool = True,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
return manager.filter_candidates(
|
|
79
|
+
required_capabilities=required_capabilities,
|
|
80
|
+
avoid_statuses=avoid_statuses,
|
|
81
|
+
allowed_providers=allowed_providers,
|
|
82
|
+
include_usage_unknown=include_usage_unknown,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def spawn_worker(manager: SessionManager, **kwargs: Any) -> dict[str, Any]:
|
|
87
|
+
try:
|
|
88
|
+
request = SpawnWorkerRequest.model_validate(kwargs)
|
|
89
|
+
except ValidationError as exc:
|
|
90
|
+
raise ToolError(
|
|
91
|
+
"INVALID_REQUEST",
|
|
92
|
+
"Invalid spawn_worker request.",
|
|
93
|
+
{"errors": _jsonable_validation_errors(exc)},
|
|
94
|
+
) from exc
|
|
95
|
+
return manager.spawn_worker(request)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def observe_worker(
|
|
99
|
+
manager: SessionManager,
|
|
100
|
+
session_id: str,
|
|
101
|
+
wait_for: list[str] | None = None,
|
|
102
|
+
timeout_seconds: int = 0,
|
|
103
|
+
detail: str = "summary",
|
|
104
|
+
max_lines: int | None = None,
|
|
105
|
+
lockdown: bool = False,
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
parsed_detail = parse_detail(detail)
|
|
108
|
+
response = manager.observe_worker(
|
|
109
|
+
session_id,
|
|
110
|
+
wait_for=wait_for,
|
|
111
|
+
timeout_seconds=timeout_seconds,
|
|
112
|
+
include_screen=parsed_detail != "summary" and not lockdown,
|
|
113
|
+
include_recent_log=False,
|
|
114
|
+
max_lines=max_lines,
|
|
115
|
+
)
|
|
116
|
+
return observe_payload(response.model_dump(mode="json"), manager.artifact_manifest(session_id), parsed_detail, lockdown)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def send_worker_message(
|
|
120
|
+
manager: SessionManager, session_id: str, message: str, submit: bool = True
|
|
121
|
+
) -> dict[str, Any]:
|
|
122
|
+
return manager.send_worker_message(session_id, message, submit)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def send_worker_keys(manager: SessionManager, session_id: str, keys: list[str]) -> dict[str, Any]:
|
|
126
|
+
return manager.send_worker_keys(session_id, keys)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def interrupt_worker(manager: SessionManager, session_id: str) -> dict[str, Any]:
|
|
130
|
+
return manager.interrupt_worker(session_id)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def attach_info(manager: SessionManager, session_id: str) -> dict[str, Any]:
|
|
134
|
+
return manager.attach_info(session_id)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def collect_worker_artifacts(
|
|
138
|
+
manager: SessionManager,
|
|
139
|
+
session_id: str,
|
|
140
|
+
include_diff: bool = True,
|
|
141
|
+
include_transcript: bool = True,
|
|
142
|
+
mark_completed: bool = False,
|
|
143
|
+
detail: str = "summary",
|
|
144
|
+
lockdown: bool = False,
|
|
145
|
+
) -> dict[str, Any]:
|
|
146
|
+
parsed_detail = parse_detail(detail)
|
|
147
|
+
result = manager.collect_worker_artifacts(session_id, include_diff, include_transcript, mark_completed)
|
|
148
|
+
return collect_payload(result, parsed_detail, lockdown)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_artifact_manifest(
|
|
152
|
+
manager: SessionManager,
|
|
153
|
+
session_id: str,
|
|
154
|
+
lockdown: bool = False,
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
|
+
return compact_artifact_manifest(manager.artifact_manifest(session_id), lockdown=lockdown)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def read_worker_transcript(
|
|
160
|
+
manager: SessionManager,
|
|
161
|
+
session_id: str,
|
|
162
|
+
offset: int = 0,
|
|
163
|
+
limit: int = 4000,
|
|
164
|
+
tail_lines: int | None = None,
|
|
165
|
+
lockdown: bool = False,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
if lockdown:
|
|
168
|
+
session = manager._require_session(session_id)
|
|
169
|
+
return {"session_id": session_id, **lockdown_resource(session.transcript_path, "transcript")}
|
|
170
|
+
return manager.read_transcript(session_id, offset=offset, limit=limit, tail_lines=tail_lines)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def acquire_file_lease(
|
|
174
|
+
manager: SessionManager,
|
|
175
|
+
session_id: str,
|
|
176
|
+
file_path: str,
|
|
177
|
+
mode: str = "write",
|
|
178
|
+
ttl_seconds: int | None = None,
|
|
179
|
+
) -> dict[str, Any]:
|
|
180
|
+
return manager.acquire_file_lease(session_id, file_path, mode=mode, ttl_seconds=ttl_seconds)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def list_file_leases(
|
|
184
|
+
manager: SessionManager,
|
|
185
|
+
session_id: str | None = None,
|
|
186
|
+
repo_path: str | None = None,
|
|
187
|
+
active_only: bool = True,
|
|
188
|
+
) -> dict[str, Any]:
|
|
189
|
+
return manager.list_file_leases(session_id=session_id, repo_path=repo_path, active_only=active_only)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def release_file_lease(
|
|
193
|
+
manager: SessionManager,
|
|
194
|
+
lease_id: int | None = None,
|
|
195
|
+
session_id: str | None = None,
|
|
196
|
+
file_path: str | None = None,
|
|
197
|
+
) -> dict[str, Any]:
|
|
198
|
+
try:
|
|
199
|
+
return manager.release_file_lease(lease_id=lease_id, session_id=session_id, file_path=file_path)
|
|
200
|
+
except ValueError as exc:
|
|
201
|
+
raise ToolError("INVALID_LEASE_RELEASE", str(exc)) from exc
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def list_worktrees(manager: SessionManager, repo_path: str) -> dict[str, Any]:
|
|
205
|
+
return manager.list_worktrees(repo_path)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cleanup_worktree(manager: SessionManager, session_id: str, force: bool = False) -> dict[str, Any]:
|
|
209
|
+
return manager.cleanup_worktree(session_id, force=force)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def list_sessions(
|
|
213
|
+
manager: SessionManager,
|
|
214
|
+
state: list[str] | str | None = None,
|
|
215
|
+
provider_id: str | None = None,
|
|
216
|
+
include_all: bool = False,
|
|
217
|
+
limit: int | None = 50,
|
|
218
|
+
offset: int = 0,
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
return manager.list_sessions(state, provider_id, include_all=include_all, limit=limit, offset=offset)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_stats(
|
|
224
|
+
manager: SessionManager,
|
|
225
|
+
window: str = "7d",
|
|
226
|
+
provider_id: str | None = None,
|
|
227
|
+
sections: list[str] | None = None,
|
|
228
|
+
scope: str = "mine",
|
|
229
|
+
) -> dict[str, Any]:
|
|
230
|
+
manager.reconcile_sessions()
|
|
231
|
+
parsed = parse_window(window)
|
|
232
|
+
stats = compute_stats(
|
|
233
|
+
store=manager.store,
|
|
234
|
+
config=manager.config,
|
|
235
|
+
registry=manager.registry,
|
|
236
|
+
window=parsed,
|
|
237
|
+
provider_id=provider_id,
|
|
238
|
+
scope=scope,
|
|
239
|
+
coordinator_id=manager.coordinator_id,
|
|
240
|
+
)
|
|
241
|
+
return filter_sections(stats, sections)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def get_stats_card(
|
|
245
|
+
manager: SessionManager,
|
|
246
|
+
window: str = "7d",
|
|
247
|
+
output_path: str | None = None,
|
|
248
|
+
scope: str = "mine",
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
stats = get_stats(manager, window=window, scope=scope)
|
|
251
|
+
return render_stats_card(stats, output_path)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def get_session(manager: SessionManager, session_id: str) -> dict[str, Any]:
|
|
255
|
+
return manager.get_session(session_id)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def terminate_worker(manager: SessionManager, session_id: str, reason: str | None = None) -> dict[str, Any]:
|
|
259
|
+
return manager.terminate_worker(session_id, reason)
|