feat: deliver installable local alpha sessions
Some checks failed
tamq-ci / test (push) Failing after 6s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
tegwick 2026-08-24 16:10:59 +02:00
parent d56bd3c79f
commit 5774fbd548
18 changed files with 890 additions and 94 deletions

View file

@ -1,4 +1,6 @@
from tamq.cli import main
import json
from tamq.cli import build_parser, main
import pytest
@ -13,6 +15,122 @@ def test_help_and_version(capsys):
def test_attach_delegates_to_tmux(monkeypatch):
calls = []
monkeypatch.delenv("TMUX", raising=False)
monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append((args, check)) or type("R", (), {"returncode": 0})())
assert main(["attach", "--session", "demo"]) == 0
assert calls == [(["tmux", "attach-session", "-t", "demo"], False)]
def test_attach_switches_client_when_already_in_tmux(monkeypatch):
calls = []
monkeypatch.setenv("TMUX", "/tmp/tmux,1,0")
monkeypatch.setattr("tamq.cli.subprocess.run", lambda args, check=False: calls.append(args) or type("R", (), {"returncode": 0})())
assert main(["attach"]) == 0
assert calls == [["tmux", "switch-client", "-t", "tamq"]]
def test_start_parser_supports_command_and_cmd_alias():
parser = build_parser()
canonical = parser.parse_args(["start", "--command", "codex --quiet", "a", "b"])
compatibility = parser.parse_args(["start", "--cmd", "claude", "a"])
assert canonical.agent_command == "codex --quiet"
assert canonical.repos == ["a", "b"]
assert compatibility.agent_command == "claude"
def test_detached_no_service_start_skips_socket_registration(monkeypatch, capsys):
endpoint = type(
"Endpoint",
(),
{
"endpoint_id": "tmux-amq-42",
"instance_key": "tmux-amq-42-boot",
"pid": 42,
"session": "tamq",
"repos": ["a", "b"],
},
)()
class Manager:
def preflight(self, repos, command):
assert repos == ["a", "b"]
assert command == "codex"
return "plan"
def ensure_plan(self, plan):
assert plan == "plan"
return endpoint
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
monkeypatch.setattr("tamq.cli.request", lambda payload: pytest.fail("socket request must not run"))
monkeypatch.setattr("tamq.cli.attach_session", lambda session: pytest.fail("detached start must not attach"))
assert main(["start", "--detach", "--no-service", "a", "b"]) == 0
summary = json.loads(capsys.readouterr().out)
assert summary["service"] is False
assert summary["messaging"] is False
assert summary["repos"] == ["a", "b"]
def test_interactive_no_service_start_attaches(monkeypatch, capsys):
endpoint = type(
"Endpoint",
(),
{
"endpoint_id": "tmux-amq-42",
"instance_key": "tmux-amq-42-boot",
"session": "tamq",
"repos": ["a"],
},
)()
class Manager:
def preflight(self, repos, command):
return "plan"
def ensure_plan(self, plan):
return endpoint
attached = []
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
monkeypatch.setattr("tamq.cli.attach_session", lambda session: attached.append(session) or 0)
assert main(["start", "--no-service", "a"]) == 0
assert attached == ["tamq"]
assert json.loads(capsys.readouterr().out)["repos"] == ["a"]
def test_registration_failure_rolls_back_created_windows(monkeypatch, capsys):
endpoint = type(
"Endpoint",
(),
{
"endpoint_id": "tmux-amq-42",
"instance_key": "tmux-amq-42-boot",
"pid": 42,
"session": "tamq",
"repos": ["a"],
},
)()
rolled_back = []
class Manager:
def preflight(self, repos, command):
return "plan"
def ensure_plan(self, plan):
return endpoint
def rollback(self, value):
rolled_back.append(value)
async def failed_registration(payload):
return {"ok": False, "error": "denied"}
monkeypatch.setattr("tamq.cli.TmuxManager", Manager)
monkeypatch.setattr("tamq.cli.preflight_runtime_paths", lambda: None)
monkeypatch.setattr("tamq.cli.ensure_service", lambda: True)
monkeypatch.setattr("tamq.cli.request", failed_registration)
assert main(["start", "--detach", "a"]) == 1
assert rolled_back == [endpoint]
assert "endpoint registration failed: denied" in capsys.readouterr().err

View file

@ -13,3 +13,12 @@ def test_endpoint_resolves_visible_pid_id(tmp_path):
store = Store(tmp_path / "queue.sqlite3")
store.register_endpoint("tmux-amq-1-boot", 1, "tamq", ["a"])
assert store.endpoint("tmux-amq-1")["endpoint_id"] == "tmux-amq-1-boot"
def test_endpoint_registration_retires_previous_instance_for_session(tmp_path):
store = Store(tmp_path / "queue.sqlite3")
store.register_endpoint("tmux-amq-1-old", 1, "tamq", ["a"])
store.register_endpoint("tmux-amq-1-new", 1, "tamq", ["a", "b"])
assert store.endpoint("tmux-amq-1-old") is None
assert [row["endpoint_id"] for row in store.endpoints()] == ["tmux-amq-1-new"]

View file

@ -0,0 +1,180 @@
import json
import os
import shutil
import subprocess
import time
from pathlib import Path
import pytest
def test_make_install_uses_isolated_uv_tool_install():
makefile = Path("Makefile").read_text(encoding="utf-8")
assert "install:" in makefile
assert "uv tool install --force --reinstall --refresh ." in makefile
assert "tamq --version" in makefile
assert "uninstall:" in makefile
assert "uv tool uninstall tmux-amq" in makefile
@pytest.mark.skipif(
shutil.which("uv") is None or shutil.which("tmux") is None,
reason="installed-package smoke requires uv and tmux",
)
def test_isolated_installed_tool_session_smoke(tmp_path):
project = Path(__file__).resolve().parents[1]
tool_dir = tmp_path / "tools"
bin_dir = tmp_path / "bin"
runtime_dir = tmp_path / "runtime"
state_dir = tmp_path / "state"
repo_a = tmp_path / "railiance-platform"
repo_b = tmp_path / "activity-core"
for path in (bin_dir, runtime_dir, repo_a, repo_b):
path.mkdir(parents=True)
gita = bin_dir / "gita"
gita.write_text(
"#!/usr/bin/env python3\n"
"import sys\n"
f"paths = {{'railiance-platform': {str(repo_a)!r}, 'activity-core': {str(repo_b)!r}}}\n"
"if sys.argv[1:] == ['ls']:\n"
" print('railiance-platform activity-core')\n"
"elif sys.argv[1:] == ['freeze']:\n"
" for slug, path in paths.items(): print(f'local,{slug},{path}')\n"
"else:\n"
" raise SystemExit(2)\n",
encoding="utf-8",
)
agent = bin_dir / "alpha-agent"
agent.write_text(
"#!/bin/sh\nprintf 'installed-alpha-ready\\n'\nexec sleep 30\n",
encoding="utf-8",
)
gita.chmod(0o755)
agent.chmod(0o755)
socket_name = f"tamq-installed-{os.getpid()}"
env = os.environ.copy()
env.update(
{
"PATH": f"{bin_dir}:{env['PATH']}",
"UV_TOOL_DIR": str(tool_dir),
"UV_TOOL_BIN_DIR": str(bin_dir),
"UV_CACHE_DIR": str(tmp_path / "uv-cache"),
"XDG_RUNTIME_DIR": str(runtime_dir),
"TAMQ_STATE_DIR": str(state_dir),
"TAMQ_TMUX_SOCKET": socket_name,
}
)
tamq = bin_dir / "tamq"
tmux = ["tmux", "-L", socket_name]
def run(*args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
args,
cwd=tmp_path,
env=env,
text=True,
capture_output=True,
check=True,
timeout=30,
)
try:
subprocess.run(
["uv", "tool", "install", "--force", "--reinstall", "--refresh", str(project)],
cwd=project,
env=env,
text=True,
capture_output=True,
check=True,
timeout=60,
)
assert run(str(tamq), "--version").stdout.strip() == "0.1.0"
started = json.loads(
run(
str(tamq),
"start",
"--detach",
"--command",
"alpha-agent",
"railiance-platform",
"activity-core",
).stdout
)
assert started["repos"] == ["railiance-platform", "activity-core"]
rows = run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}",
).stdout.splitlines()
parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)}
assert parsed["railiance-platform"][0] == str(repo_a)
assert parsed["activity-core"][0] == str(repo_b)
repeated = json.loads(
run(
str(tamq),
"start",
"--detach",
"--command",
"alpha-agent",
"railiance-platform",
"activity-core",
).stdout
)
assert repeated["instance_id"] == started["instance_id"]
repeated_rows = run(
*tmux,
"list-windows",
"-t",
"tamq",
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}",
).stdout.splitlines()
assert repeated_rows == rows
pre_delivery_status = json.loads(run(str(tamq), "status").stdout)
assert pre_delivery_status["service"] is True
assert [item["endpoint_id"] for item in pre_delivery_status["endpoints"]] == [
started["instance_id"]
]
message_id = run(
str(tamq), "send", "@activity-core:", "installed-message"
).stdout.strip()
deadline = time.monotonic() + 5
history = []
while time.monotonic() < deadline:
history = [
json.loads(line)
for line in run(str(tamq), "history", "--repo", "activity-core").stdout.splitlines()
]
if history and history[-1]["state"] == "injected":
break
time.sleep(0.05)
assert history[-1]["message_id"] == message_id
assert history[-1]["state"] == "injected"
status = json.loads(run(str(tamq), "status").stdout)
assert status["service"] is True
assert [item["endpoint_id"] for item in status["endpoints"]] == [started["instance_id"]]
assert run(str(tamq), "stop").returncode == 0
finally:
subprocess.run(
[str(tamq), "stop"],
cwd=tmp_path,
env=env,
text=True,
capture_output=True,
check=False,
timeout=5,
)
subprocess.run(
[*tmux, "kill-server"],
env=env,
text=True,
capture_output=True,
check=False,
)

View file

@ -0,0 +1,113 @@
import os
import shutil
import sys
import time
from pathlib import Path
from uuid import uuid4
import pytest
from tamq.broker import BrokerIdentity, InputBroker
from tamq.control import ControlModeClient
from tamq.store import Store
from tamq.tmux import LaunchPlan, TmuxManager
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
def test_real_tmux_starts_two_repo_windows_and_reuses_them(tmp_path, monkeypatch):
repo_a = tmp_path / "railiance-platform"
repo_b = tmp_path / "activity-core"
repo_a.mkdir()
repo_b.mkdir()
project_src = str(Path(__file__).resolve().parents[1] / "src")
existing_pythonpath = os.environ.get("PYTHONPATH")
monkeypatch.setenv("PYTHONPATH", project_src if not existing_pythonpath else f"{project_src}:{existing_pythonpath}")
monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state"))
socket_name = f"tamq-pytest-{os.getpid()}-{uuid4().hex[:8]}"
session = f"tamq-test-{uuid4().hex[:8]}"
manager = TmuxManager(
session,
tmux_command=("tmux", "-L", socket_name),
tamq_command=(sys.executable, "-m", "tamq.cli"),
)
plan = LaunchPlan(
("railiance-platform", "activity-core"),
{"railiance-platform": str(repo_a), "activity-core": str(repo_b)},
("sh", "-c", "printf 'alpha-ready\\n'; exec sleep 30"),
)
try:
endpoint = manager.ensure_plan(plan)
assert endpoint.created_session is True
assert endpoint.created_windows == ("railiance-platform", "activity-core")
rows = manager._run(
"list-windows",
"-t",
session,
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}",
).splitlines()
parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in rows)}
assert set(parsed) == {"railiance-platform", "activity-core"}
assert parsed["railiance-platform"][0] == str(repo_a)
assert parsed["activity-core"][0] == str(repo_b)
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
captures = [
manager._run("capture-pane", "-p", "-t", f"{session}:{repo}")
for repo in plan.repos
]
if all("alpha-ready" in capture for capture in captures):
break
time.sleep(0.05)
assert all("alpha-ready" in capture for capture in captures)
reused = manager.ensure_plan(plan)
assert reused.created_session is False
assert reused.created_windows == ()
reused_rows = manager._run(
"list-windows",
"-t",
session,
"-F",
"#{window_name}|#{pane_current_path}|#{pane_pid}",
).splitlines()
reused_parsed = {name: (path, pid) for name, path, pid in (row.split("|", 2) for row in reused_rows)}
assert reused_parsed == parsed
assert reused.instance_id == endpoint.instance_id
store = Store(tmp_path / "state" / "tamq.sqlite3")
control = ControlModeClient(
session,
tmux_command=("tmux", "-L", socket_name),
)
try:
message_id = store.add("local", "activity-core", "integration-message")
control.start()
delivered = InputBroker(
store,
BrokerIdentity(endpoint.instance_id, "railiance-platform"),
).deliver_pending(
control,
window_for_repo=lambda repo: f"{session}:{repo}",
)
assert delivered == 1
assert store.list(state="injected")[0]["message_id"] == message_id
deadline = time.monotonic() + 5
while time.monotonic() < deadline:
capture = manager._run(
"capture-pane", "-p", "-t", f"{session}:activity-core"
)
if "#local: integration-message" in capture:
break
time.sleep(0.05)
assert "#local: integration-message" in capture
finally:
control.close()
store.close()
finally:
manager._run("kill-server", check=False)

View file

@ -1,11 +1,17 @@
from tamq import tmux
import pytest
def test_tmux_manager_builds_tap_windows(monkeypatch):
def test_tmux_manager_builds_tap_windows(tmp_path, monkeypatch):
calls = []
manager = tmux.TmuxManager("tamq-test")
repo_a = tmp_path / "a"
repo_b = tmp_path / "b"
repo_a.mkdir()
repo_b.mkdir()
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": "/tmp/a", "b": "/tmp/b"})
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo_a), "b": str(repo_b)})
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
def run(*args, check=True):
calls.append(args)
if args[:2] == ("list-windows", "-t"):
@ -15,6 +21,95 @@ def test_tmux_manager_builds_tap_windows(monkeypatch):
return ""
monkeypatch.setattr(manager, "_run", run)
monkeypatch.setattr(tmux.subprocess, "run", lambda *args, **kwargs: type("R", (), {"returncode": 1})())
endpoint = manager.ensure(["a", "b"], "codex")
endpoint = manager.ensure(["a", "b"], "codex --quiet")
assert endpoint.endpoint_id == "tmux-amq-42"
assert any("tamq tap" in " ".join(call) for call in calls if call and call[0] == "send-keys")
assert endpoint.instance_id.startswith("tmux-amq-42-")
assert endpoint.repos == ["a", "b"]
new_session = next(call for call in calls if call and call[0] == "new-session")
assert new_session[-2:] == ("-c", str(repo_a))
assert "sleep" not in new_session
new_window = next(call for call in calls if call and call[0] == "new-window")
assert new_window[-2:] == ("-c", str(repo_b))
tap_calls = [call for call in calls if call and call[0] == "send-keys"]
assert len(tap_calls) == 2
assert all("tamq tap" in " ".join(call) for call in tap_calls)
assert all("codex --quiet" in " ".join(call) for call in tap_calls)
assert ("select-window", "-t", "tamq-test:a") in calls
assert any(call and call[0] == "set-option" for call in calls)
def test_tmux_manager_reuses_session_instance_id(tmp_path, monkeypatch):
repo = tmp_path / "a"
repo.mkdir()
manager = tmux.TmuxManager("tamq-test")
options = {"@tamq_managed": "1"}
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
def run(*args, check=True):
if args[:2] == ("display-message", "-p"):
return "42"
if args and args[0] == "show-options":
return options.get(args[-1], "")
if args and args[0] == "set-option":
options[args[-2]] = args[-1]
if args[:2] == ("list-windows", "-t"):
return "a"
return ""
monkeypatch.setattr(manager, "_run", run)
monkeypatch.setattr(
tmux.subprocess,
"run",
lambda *args, **kwargs: type("R", (), {"returncode": 0})(),
)
first = manager.ensure(["a"])
second = manager.ensure(["a"])
assert first.instance_id == second.instance_id
def test_preflight_rejects_foreign_session(tmp_path, monkeypatch):
repo = tmp_path / "a"
repo.mkdir()
manager = tmux.TmuxManager("tamq-test")
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
monkeypatch.setattr(
tmux.subprocess,
"run",
lambda *args, **kwargs: type("R", (), {"returncode": 0})(),
)
def run(*args, check=True):
if args[:2] == ("display-message", "-p"):
return "42"
return ""
monkeypatch.setattr(manager, "_run", run)
with pytest.raises(tmux.TmuxError, match="not managed by tamq"):
manager.preflight(["a"])
def test_preflight_rejects_missing_agent(tmp_path, monkeypatch):
repo = tmp_path / "a"
repo.mkdir()
manager = tmux.TmuxManager("tamq-test")
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
monkeypatch.setattr(tmux.shutil, "which", lambda command: None if command == "missing-agent" else f"/bin/{command}")
with pytest.raises(tmux.TmuxError, match="agent command not found"):
manager.preflight(["a"], "missing-agent")
def test_preflight_deduplicates_repositories(tmp_path, monkeypatch):
repo = tmp_path / "a"
repo.mkdir()
manager = tmux.TmuxManager("tamq-test")
monkeypatch.setattr(tmux, "validate_targets", lambda repos: None)
monkeypatch.setattr(tmux, "repository_paths", lambda: {"a": str(repo)})
monkeypatch.setattr(tmux.shutil, "which", lambda command: f"/bin/{command}")
plan = manager.preflight(["a", "a"], "codex")
assert plan.repos == ("a",)