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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from agentpool.models import RuntimeKind, TmuxSessionRef, ToolError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TmuxRuntime:
|
|
13
|
+
kind = RuntimeKind.TMUX
|
|
14
|
+
|
|
15
|
+
def __init__(self, tmux_binary: str | None = None):
|
|
16
|
+
self.tmux_binary = tmux_binary or shutil.which("tmux")
|
|
17
|
+
|
|
18
|
+
def require_tmux(self) -> str:
|
|
19
|
+
if not self.tmux_binary:
|
|
20
|
+
raise ToolError("TMUX_NOT_FOUND", "tmux is required for AgentPool v0.1.")
|
|
21
|
+
return self.tmux_binary
|
|
22
|
+
|
|
23
|
+
def spawn(
|
|
24
|
+
self, command: list[str], cwd: Path, env: dict[str, str] | None, session_name: str
|
|
25
|
+
) -> TmuxSessionRef:
|
|
26
|
+
tmux = self.require_tmux()
|
|
27
|
+
cwd.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
merged_env = os.environ.copy()
|
|
29
|
+
if env:
|
|
30
|
+
merged_env.update(env)
|
|
31
|
+
try:
|
|
32
|
+
subprocess.run(
|
|
33
|
+
[tmux, "new-session", "-d", "-s", session_name, "-c", str(cwd), *command],
|
|
34
|
+
env=merged_env,
|
|
35
|
+
text=True,
|
|
36
|
+
capture_output=True,
|
|
37
|
+
check=True,
|
|
38
|
+
)
|
|
39
|
+
except subprocess.CalledProcessError as exc:
|
|
40
|
+
raise ToolError(
|
|
41
|
+
"SPAWN_FAILED",
|
|
42
|
+
f"Failed to create tmux session {session_name}.",
|
|
43
|
+
{"stderr": exc.stderr, "stdout": exc.stdout, "command": command},
|
|
44
|
+
) from exc
|
|
45
|
+
return TmuxSessionRef(session_name=session_name)
|
|
46
|
+
|
|
47
|
+
def capture(self, ref: TmuxSessionRef, lines: int = 300) -> str:
|
|
48
|
+
tmux = self.require_tmux()
|
|
49
|
+
proc = subprocess.run(
|
|
50
|
+
[tmux, "capture-pane", "-p", "-J", "-t", ref.target, "-S", f"-{lines}"],
|
|
51
|
+
text=True,
|
|
52
|
+
capture_output=True,
|
|
53
|
+
check=False,
|
|
54
|
+
)
|
|
55
|
+
if proc.returncode != 0:
|
|
56
|
+
raise ToolError(
|
|
57
|
+
"TMUX_SESSION_NOT_FOUND",
|
|
58
|
+
f"Could not capture tmux pane {ref.target}.",
|
|
59
|
+
{"stderr": proc.stderr},
|
|
60
|
+
)
|
|
61
|
+
return proc.stdout
|
|
62
|
+
|
|
63
|
+
def send_message(self, ref: TmuxSessionRef, text: str, submit: bool = True) -> None:
|
|
64
|
+
if text == "" and submit:
|
|
65
|
+
self.send_keys(ref, ["Enter"])
|
|
66
|
+
return
|
|
67
|
+
tmux = self.require_tmux()
|
|
68
|
+
buffer_name = f"agentpool-{ref.session_name}"
|
|
69
|
+
subprocess.run([tmux, "load-buffer", "-b", buffer_name, "-"], input=text, text=True, check=True)
|
|
70
|
+
subprocess.run([tmux, "paste-buffer", "-b", buffer_name, "-t", ref.target], check=True)
|
|
71
|
+
if submit and not text.endswith("\n"):
|
|
72
|
+
self.send_keys(ref, ["Enter"])
|
|
73
|
+
|
|
74
|
+
def send_keys(self, ref: TmuxSessionRef, keys: list[str]) -> None:
|
|
75
|
+
tmux = self.require_tmux()
|
|
76
|
+
proc = subprocess.run(
|
|
77
|
+
[tmux, "send-keys", "-t", ref.target, *keys],
|
|
78
|
+
text=True,
|
|
79
|
+
capture_output=True,
|
|
80
|
+
check=False,
|
|
81
|
+
)
|
|
82
|
+
if proc.returncode != 0:
|
|
83
|
+
raise ToolError(
|
|
84
|
+
"TMUX_SESSION_NOT_FOUND",
|
|
85
|
+
f"Could not send keys to tmux pane {ref.target}.",
|
|
86
|
+
{"stderr": proc.stderr, "keys": keys},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def interrupt(self, ref: TmuxSessionRef) -> None:
|
|
90
|
+
self.send_keys(ref, ["C-c"])
|
|
91
|
+
|
|
92
|
+
def attach_command(self, ref: TmuxSessionRef) -> str:
|
|
93
|
+
return f"tmux attach -t {ref.session_name}"
|
|
94
|
+
|
|
95
|
+
def terminate(self, ref: TmuxSessionRef) -> None:
|
|
96
|
+
tmux = self.require_tmux()
|
|
97
|
+
pgid = self._pane_process_group(ref)
|
|
98
|
+
subprocess.run([tmux, "kill-session", "-t", ref.session_name], check=False)
|
|
99
|
+
if pgid is not None:
|
|
100
|
+
try:
|
|
101
|
+
os.killpg(pgid, signal.SIGTERM)
|
|
102
|
+
except ProcessLookupError:
|
|
103
|
+
pass
|
|
104
|
+
except PermissionError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def exists(self, ref: TmuxSessionRef) -> bool:
|
|
108
|
+
if not self.tmux_binary:
|
|
109
|
+
return False
|
|
110
|
+
proc = subprocess.run(
|
|
111
|
+
[self.tmux_binary, "has-session", "-t", ref.session_name],
|
|
112
|
+
text=True,
|
|
113
|
+
capture_output=True,
|
|
114
|
+
check=False,
|
|
115
|
+
)
|
|
116
|
+
return proc.returncode == 0
|
|
117
|
+
|
|
118
|
+
def _pane_process_group(self, ref: TmuxSessionRef) -> int | None:
|
|
119
|
+
if not self.tmux_binary:
|
|
120
|
+
return None
|
|
121
|
+
proc = subprocess.run(
|
|
122
|
+
[self.tmux_binary, "display-message", "-p", "-t", ref.target, "#{pane_pid}"],
|
|
123
|
+
text=True,
|
|
124
|
+
capture_output=True,
|
|
125
|
+
check=False,
|
|
126
|
+
)
|
|
127
|
+
if proc.returncode != 0:
|
|
128
|
+
return None
|
|
129
|
+
try:
|
|
130
|
+
pane_pid = int(proc.stdout.strip())
|
|
131
|
+
return os.getpgid(pane_pid)
|
|
132
|
+
except (ValueError, ProcessLookupError, PermissionError):
|
|
133
|
+
return None
|