mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 11:46:24 +00:00
24b8c44451
The mise-init agent wedged on launch and the UI reported it green. Three distinct problems, fixed together: 1. Onboarding wedge (the proximate bug). A freshly-installed claude opens interactive setup — theme picker, then a folder-trust prompt — before the REPL. A detached tmux agent has no one to answer it, so it sat on the theme picker forever while agents.status said 'working'. New control.claude_config.ensure_onboarded() marks onboarding complete and trusts the working dir in ~/.claude.json (merge-only, so the login flow's oauthAccount survives); spawn() calls it before launching. 2. Config-name gate. control/mise.py only recognized `.mise.toml`, so a repo shipping `mise.toml` (no dot) — or config under `.config/mise/` — failed the [tasks.test] gate even when healthy. It now accepts the filenames mise itself reads and scans them all for the test task. 3. "Done" != done (the design gap). A spawned agent's real state lives in its tmux pane, but the socket is control-container-only, so the API couldn't see it. The worker now snapshots each working agent's pane tail (last ~40 lines) into two new agents columns (last_output, output_at, migration 0007) on its existing poll loop; the API serializes them and AgentsSection renders a live-output <pre> under each running agent. A wedged agent now shows the theme picker instead of a misleading green badge. Tests: home-dir writes are isolated to tmp in conftest; added coverage for claude_config seeding/merge, the mise filename set, the worker capture (including dead-session skip), and the API serialization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BxKY28XKCM6o4ag3nmaVsZ
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""Project + agent routes, and project isolation (README 3.4)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def _mk_project(client, auth, pid="proj", root="/tmp/proj"):
|
|
return client.post("/projects", json={"id": pid, "root_dir": root}, headers=auth)
|
|
|
|
|
|
def test_create_and_list_project(client, auth):
|
|
r = _mk_project(client, auth)
|
|
assert r.status_code == 201
|
|
assert r.json()["id"] == "proj"
|
|
listing = client.get("/projects", headers=auth).json()
|
|
assert [p["id"] for p in listing] == ["proj"]
|
|
|
|
|
|
def test_duplicate_project_conflicts(client, auth):
|
|
_mk_project(client, auth)
|
|
assert _mk_project(client, auth).status_code == 409
|
|
|
|
|
|
def test_create_and_list_agent(client, auth):
|
|
_mk_project(client, auth)
|
|
r = client.post(
|
|
"/projects/proj/agents",
|
|
json={"name": "api", "working_dir": "/tmp/proj/api"},
|
|
headers=auth,
|
|
)
|
|
assert r.status_code == 201
|
|
agents = client.get("/projects/proj/agents", headers=auth).json()
|
|
assert [a["name"] for a in agents] == ["api"]
|
|
|
|
|
|
def test_agent_serializes_live_output_snapshot(client, auth, engine):
|
|
from handler.db import repository as repo
|
|
|
|
_mk_project(client, auth)
|
|
client.post(
|
|
"/projects/proj/agents",
|
|
json={"name": "api", "working_dir": "/tmp/proj/api", "status": "working"},
|
|
headers=auth,
|
|
)
|
|
with engine.begin() as conn:
|
|
agent = repo.get_agent_by_name(conn, "proj", "api")
|
|
repo.update_agent_output(conn, agent["id"], "boot\nTheme picker")
|
|
|
|
listed = client.get("/projects/proj/agents", headers=auth).json()[0]
|
|
assert listed["last_output"] == "boot\nTheme picker"
|
|
assert listed["output_at"] is not None
|
|
|
|
|
|
def test_agent_under_missing_project_is_404(client, auth):
|
|
r = client.get("/projects/ghost/agents", headers=auth)
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_project_isolation_same_agent_name(client, auth):
|
|
# Two projects can each have an agent named "api"; neither leaks into the other.
|
|
_mk_project(client, auth, "a", "/tmp/a")
|
|
_mk_project(client, auth, "b", "/tmp/b")
|
|
client.post(
|
|
"/projects/a/agents",
|
|
json={"name": "api", "working_dir": "/tmp/a/api"},
|
|
headers=auth,
|
|
)
|
|
a_agents = client.get("/projects/a/agents", headers=auth).json()
|
|
b_agents = client.get("/projects/b/agents", headers=auth).json()
|
|
assert [x["name"] for x in a_agents] == ["api"]
|
|
assert b_agents == []
|
|
# The agent is invisible under project b.
|
|
assert client.get("/projects/b/agents/api/checkmark", headers=auth).status_code == 404
|
|
|
|
|
|
def test_checkmark_404_before_any_checkpoint(client, auth):
|
|
_mk_project(client, auth)
|
|
client.post(
|
|
"/projects/proj/agents",
|
|
json={"name": "api", "working_dir": "/tmp/proj/api"},
|
|
headers=auth,
|
|
)
|
|
assert client.get("/projects/proj/agents/api/checkmark", headers=auth).status_code == 404
|