Files
handler/tests/test_api_projects_agents.py
Claude 24b8c44451 fix(mise-init): unblock the bootstrap agent + surface live agent output
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
2026-07-16 19:30:58 +00:00

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