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.
Files changed (60) hide show
  1. agentpool/__init__.py +3 -0
  2. agentpool/agent_io.py +134 -0
  3. agentpool/artifacts.py +151 -0
  4. agentpool/cli.py +1199 -0
  5. agentpool/config.py +373 -0
  6. agentpool/docs/agentpool-skill.md +85 -0
  7. agentpool/docs/onboarding.md +169 -0
  8. agentpool/event_detection.py +150 -0
  9. agentpool/fixtures/__init__.py +1 -0
  10. agentpool/fixtures/fake_agents/__init__.py +1 -0
  11. agentpool/fixtures/fake_agents/fake_approval_agent.py +16 -0
  12. agentpool/fixtures/fake_agents/fake_common.py +44 -0
  13. agentpool/fixtures/fake_agents/fake_completed_agent.py +13 -0
  14. agentpool/fixtures/fake_agents/fake_idle_agent.py +16 -0
  15. agentpool/fixtures/fake_agents/fake_limit_agent.py +14 -0
  16. agentpool/fixtures/fake_agents/fake_patch_agent.py +17 -0
  17. agentpool/fixtures/fake_agents/fake_question_agent.py +16 -0
  18. agentpool/git_worktree.py +144 -0
  19. agentpool/mcp/__init__.py +1 -0
  20. agentpool/mcp/resources.py +64 -0
  21. agentpool/mcp/tools.py +259 -0
  22. agentpool/mcp_server.py +487 -0
  23. agentpool/models.py +310 -0
  24. agentpool/onboarding.py +1279 -0
  25. agentpool/policy.py +63 -0
  26. agentpool/provider_model_catalog.json +997 -0
  27. agentpool/providers/__init__.py +3 -0
  28. agentpool/providers/base.py +411 -0
  29. agentpool/providers/registry.py +139 -0
  30. agentpool/redaction.py +30 -0
  31. agentpool/runtimes/__init__.py +3 -0
  32. agentpool/runtimes/base.py +36 -0
  33. agentpool/runtimes/tmux.py +133 -0
  34. agentpool/session_manager.py +1061 -0
  35. agentpool/stats/__init__.py +6 -0
  36. agentpool/stats/card.py +74 -0
  37. agentpool/stats/compute.py +496 -0
  38. agentpool/stats/queries.py +138 -0
  39. agentpool/stats/render.py +103 -0
  40. agentpool/stats/window.py +85 -0
  41. agentpool/store.py +478 -0
  42. agentpool/usage/__init__.py +1 -0
  43. agentpool/usage/_common.py +223 -0
  44. agentpool/usage/ccusage.py +130 -0
  45. agentpool/usage/claude.py +23 -0
  46. agentpool/usage/codex.py +210 -0
  47. agentpool/usage/codexbar.py +186 -0
  48. agentpool/usage/combine.py +71 -0
  49. agentpool/usage/copilot.py +146 -0
  50. agentpool/usage/devin.py +265 -0
  51. agentpool/usage/parsers.py +41 -0
  52. agentpool/usage/probes.py +52 -0
  53. agentpool/usage/provider_parsers.py +276 -0
  54. agentpool/usage/summary.py +166 -0
  55. agentpool/utils.py +59 -0
  56. agentpool_cli-0.1.0.dist-info/METADATA +292 -0
  57. agentpool_cli-0.1.0.dist-info/RECORD +60 -0
  58. agentpool_cli-0.1.0.dist-info/WHEEL +4 -0
  59. agentpool_cli-0.1.0.dist-info/entry_points.txt +2 -0
  60. 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)