Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions sandbox-image/apemind_computerd.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
_children: dict[str, subprocess.Popen] = {}
_ports: dict[str, int] = {}
_crash_until: dict[str, float] = {}
_stop = False
_shutdown = False


def _log(msg: str) -> None:
Expand Down Expand Up @@ -113,6 +113,15 @@ def _start(agent: dict) -> None:
env["HOME"] = str(work)
env["XDG_CONFIG_HOME"] = str(work / ".config")
env["XDG_DATA_HOME"] = str(work / ".local" / "share")
owner = str(agent.get("owner_user_id") or "")
if owner:
env["APEMIND_USER_ID"] = owner
ident = work / ".apemind" / "identity"
ident.parent.mkdir(parents=True, exist_ok=True)
tmp = ident.with_name(ident.name + ".tmp")
tmp.write_text(json.dumps({"user_id": owner}))
tmp.chmod(0o600)
tmp.replace(ident)
proc = subprocess.Popen(
["dsh", "web", "--no-open", "--port", str(port)],
cwd=str(work),
Expand All @@ -125,7 +134,7 @@ def _start(agent: dict) -> None:
_log(f"started {agent_id} on 127.0.0.1:{port}")


def _stop(agent_id: str) -> None:
def _stop_agent(agent_id: str) -> None:
proc = _children.pop(agent_id, None)
_ports.pop(agent_id, None)
if proc is None:
Expand Down Expand Up @@ -184,8 +193,8 @@ def _observe(known_ids: set[str] | None = None) -> list[dict]:


def _handle_stop(_signum, _frame) -> None:
global _stop
_stop = True
global _shutdown
_shutdown = True


def main() -> int:
Expand All @@ -195,13 +204,13 @@ def main() -> int:
token = os.environ.get("APEMIND_JOIN_TOKEN", "").strip()
if not base or not token:
_log("APEMIND_URL and APEMIND_JOIN_TOKEN are required; idle")
while not _stop:
while not _shutdown:
time.sleep(POLL_SECONDS)
return 0
session = ""
delay = POLL_SECONDS
applied_rev: dict[str, int] = {}
while not _stop:
while not _shutdown:
try:
if not session:
state = _join(base, token)
Expand All @@ -218,11 +227,11 @@ def main() -> int:
_start(agent)
applied_rev[agent["id"]] = rev
else:
_stop(agent["id"])
_stop_agent(agent["id"])
applied_rev[agent["id"]] = rev
for agent_id in list(_children):
if agent_id not in want_ids:
_stop(agent_id)
_stop_agent(agent_id)
_api(
base,
"/api/v2/computer-control/observed",
Expand All @@ -240,10 +249,10 @@ def main() -> int:
_log(f"loop error: {exc}")
delay = _next_backoff(delay)
deadline = time.time() + delay
while not _stop and time.time() < deadline:
while not _shutdown and time.time() < deadline:
time.sleep(min(1.0, max(0.0, deadline - time.time())))
for agent_id in list(_children):
_stop(agent_id)
_stop_agent(agent_id)
return 0


Expand Down
13 changes: 11 additions & 2 deletions sandbox-image/test_apemind_computerd.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@
def test_start_sets_private_dsh_home(tmp_path):
daemon._children.clear()
daemon._ports.clear()
agent = {"id": "agt-a", "work_dir": str(tmp_path / "a")}
agent = {"id": "agt-a", "work_dir": str(tmp_path / "a"), "owner_user_id": "user-1"}
fake = SimpleNamespace(poll=lambda: None)

def _popen(cmd, cwd, env, stdout, stderr):
assert env["DSH_HOME"] == str(Path(cwd) / ".dsh")
assert env["HOME"] == cwd
assert env["XDG_CONFIG_HOME"] == str(Path(cwd) / ".config")
assert env["APEMIND_USER_ID"] == "user-1"
assert env["DSH_HOME"] != os.path.expanduser("~/.dsh")
assert Path(env["DSH_HOME"]).is_dir()
ident = Path(cwd) / ".apemind" / "identity"
assert ident.read_text() == '{"user_id": "user-1"}'
assert ident.stat().st_mode & 0o777 == 0o600
return fake

with patch("apemind_computerd.subprocess.Popen", side_effect=_popen):
Expand All @@ -28,6 +32,11 @@ def _popen(cmd, cwd, env, stdout, stderr):
assert daemon._children["agt-a"] is fake


def test_shutdown_flag_is_not_the_stop_function():
assert daemon._shutdown is False
assert callable(daemon._stop_agent)


def test_next_backoff_doubles_then_caps():
assert daemon._next_backoff(5) == 10
assert daemon._next_backoff(40) == 60
Expand All @@ -47,7 +56,7 @@ def test_observe_reports_stopped_after_stop():
proc = SimpleNamespace(poll=lambda: None, terminate=lambda: None, wait=lambda timeout: None, kill=lambda: None)
daemon._children["agt-b"] = proc
daemon._ports["agt-b"] = 3081
daemon._stop("agt-b")
daemon._stop_agent("agt-b")
rows = daemon._observe({"agt-b"})
assert rows == [
{
Expand Down
Loading