feat(@projects/@clare): add remote control session registration

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-20 18:46:20 -07:00
parent da2b27e7b8
commit d698cd6430
3 changed files with 164 additions and 25 deletions

View file

@ -295,10 +295,10 @@ def _send_kick(*, rcl: Rclaude, cwd: str) -> None:
"""
rcl.send(
text=(
"[bootstrap] You are Clare's orchestrator. Read CLAUDE.md in your "
"cwd, confirm your MCP tools (clare:*) are available via `/mcp`, "
"then run `/remote-control clare` so you appear in claude.ai/code "
"as the central agent. Reply with a single word: ready."
"[bootstrap] You are Clare's orchestrator. Read CLAUDE.md in "
"your cwd — it defines your role and per-turn workflow. Your "
"clare:* MCP tools are already wired. Reply with a single "
"word: ready."
),
match=_cwd_slug(cwd),
yes=True,
@ -306,6 +306,24 @@ def _send_kick(*, rcl: Rclaude, cwd: str) -> None:
)
def _register_remote_control(*, rcl: Rclaude, cwd: str) -> None:
"""Register the orchestrator session with claude.ai/code.
`/remote-control` is a Claude *slash command* it only takes effect
delivered as literal input, never from prose. The bootstrap kick is a
prose message (Claude cannot self-invoke a slash command from message
text), so the command itself is delivered as a separate send. This is
what makes the orchestrator appear as a remote session the user can
drive from the browser / claude.ai/code.
"""
rcl.send(
text="/remote-control clare",
match=_cwd_slug(cwd),
yes=True,
dry_run=False,
)
def _stage_remote_workspace(local_staging: Path, host: str, remote_cwd: str) -> None:
"""rsync the local staging dir to `host:remote_cwd`. Idempotent."""
import subprocess
@ -473,6 +491,13 @@ def ensure_running(
write_session_uuid(
new_uuid, host=cfg.orchestrator.host, config_path=config_path,
)
# Register with claude.ai/code so the user can drive the orchestrator
# from the browser. Non-fatal — it still works via Clare's chat
# regardless of whether remote-control registration succeeds.
try:
_register_remote_control(rcl=rcl, cwd=effective_cwd)
except RclaudeError:
pass
return new_uuid

View file

@ -17,6 +17,7 @@ from __future__ import annotations
import re as _re
import sqlite3
from collections import Counter
from dataclasses import dataclass
from datetime import datetime, timezone
from uuid import UUID
@ -128,11 +129,18 @@ def pull(
)
sessions_observed += 1
# --- Liveness: mark sessions whose host+cwd has no live tmux pane ----
# Without this, agent_status / list_fleet show stale rows for sessions
# whose tmux died long ago. The on-disk JSONL persists forever so
# session UUIDs alone don't tell us what's actually running. Compare
# the current rclaude tmux roster against the projection.
# --- Liveness: mark sessions backed by a live tmux pane --------------
# The on-disk JSONL persists forever, so a session UUID says nothing
# about what's running. We match against the live tmux roster.
#
# tmux names carry only the cwd slug (`claude-<user>-<slug>-<epoch>`),
# never the session UUID — so a pane can't be mapped to an exact
# session. The old code marked EVERY session at a live cwd as alive,
# which over-counted badly: a busy workspace with one live pane and
# 100 historical JSONLs reported 100 "alive". Instead, per
# (host, cwd-slug) with live pane(s), mark only the N most-recently
# touched sessions alive, where N = the number of live panes there.
# `sessions_alive` then tracks the real count of running panes.
try:
tmux_rows = rclaude.list_tmux()
except RclaudeError as exc:
@ -151,29 +159,40 @@ def pull(
m = _TMUX_NAME.match(name)
return m.group(1) if m else ""
alive_keys: set[tuple[str, str]] = {
# How many live panes exist per (host, cwd-slug).
live_pane_counts: Counter[tuple[str, str]] = Counter(
(r.host, _tmux_cwd_slug(r.session_name))
for r in tmux_rows
if _tmux_cwd_slug(r.session_name)
}
)
db_sessions = conn.execute(
"SELECT uuid, host, cwd FROM sessions"
"SELECT uuid, host, cwd, last_seen_mtime FROM sessions"
).fetchall()
# Group sessions by the workspace key their tmux pane would carry.
by_workspace: dict[tuple[str, str], list[sqlite3.Row]] = {}
for row in db_sessions:
by_workspace.setdefault(
(row["host"], _slug(row["cwd"] or "")), []
).append(row)
sessions_closed = 0
sessions_alive = 0
for row in db_sessions:
slug = _slug(row["cwd"] or "")
# Exact match against an extracted tmux cwd slug on the same host.
is_alive = slug != "" and (row["host"], slug) in alive_keys
new_liveness = "alive" if is_alive else "closed"
conn.execute(
"UPDATE sessions SET liveness = ? WHERE uuid = ? AND liveness != ?",
(new_liveness, row["uuid"], new_liveness),
)
if is_alive:
sessions_alive += 1
else:
sessions_closed += 1
for (host, slug), rows in by_workspace.items():
n_live = live_pane_counts.get((host, slug), 0) if slug else 0
# Freshest-first: the N newest sessions at a live workspace are the
# ones plausibly attached to its N live panes.
rows.sort(key=lambda r: r["last_seen_mtime"] or "", reverse=True)
for idx, row in enumerate(rows):
is_alive = idx < n_live
new_liveness = "alive" if is_alive else "closed"
conn.execute(
"UPDATE sessions SET liveness = ? WHERE uuid = ? AND liveness != ?",
(new_liveness, row["uuid"], new_liveness),
)
if is_alive:
sessions_alive += 1
else:
sessions_closed += 1
# --- Triage -----------------------------------------------------------
try:

View file

@ -0,0 +1,95 @@
"""Liveness pass: `sessions_alive` tracks live tmux panes, not disk JSONLs.
Regression for the over-count bug the old pass marked *every* session at
a cwd with any live pane as `alive`, so a busy workspace with one live pane
and N historical session JSONLs reported N "alive". The fix ranks sessions
by recency per (host, cwd-slug) and marks only the freshest N, where N is
the number of live panes at that workspace.
"""
from __future__ import annotations
from uuid import UUID
from clare.pull import pull
from clare.rclaude import SessionRow, TmuxRow
_CWD = "/var/home/lilith/Code/@projects/@clare"
_SLUG = "var-home-lilith-Code--projects--clare"
class _FakeRclaude:
def __init__(self, sessions: list[SessionRow], tmux: list[TmuxRow]):
self._sessions = sessions
self._tmux = tmux
def list_sessions(self) -> list[SessionRow]:
return list(self._sessions)
def list_tmux(self) -> list[TmuxRow]:
return list(self._tmux)
def triage(self) -> list:
return []
def _session(uuid_hex: str, mtime: int) -> SessionRow:
return SessionRow(
host="apricot", uuid=UUID(uuid_hex), snippet="x", cwd=_CWD, mtime_epoch=mtime,
)
def _liveness(conn, uuid_hex: str) -> str:
return conn.execute(
"SELECT liveness FROM sessions WHERE uuid = ?", (uuid_hex,)
).fetchone()["liveness"]
def test_one_live_pane_marks_only_freshest_session_alive(conn, gen) -> None:
"""3 disk sessions at one workspace, 1 live pane → only the newest alive."""
old = "11111111-1111-1111-1111-111111111111"
mid = "22222222-2222-2222-2222-222222222222"
new = "33333333-3333-3333-3333-333333333333"
fake = _FakeRclaude(
sessions=[
_session(old, 1_700_000_000),
_session(mid, 1_700_000_500),
_session(new, 1_700_001_000),
],
tmux=[TmuxRow(host="apricot", session_name=f"claude-natalie-{_SLUG}-1779326883", detail="")],
)
stats = pull(conn, gen, rclaude=fake)
assert stats.sessions_alive == 1
assert stats.sessions_closed == 2
assert _liveness(conn, new) == "alive"
assert _liveness(conn, mid) == "closed"
assert _liveness(conn, old) == "closed"
def test_alive_count_equals_live_pane_count(conn, gen) -> None:
"""4 disk sessions, 2 live panes → the 2 freshest are alive."""
uuids = [f"{i}{i}{i}{i}{i}{i}{i}{i}-0000-0000-0000-000000000000"[:36] for i in range(4)]
fake = _FakeRclaude(
sessions=[_session(u, 1_700_000_000 + i * 100) for i, u in enumerate(uuids)],
tmux=[
TmuxRow(host="apricot", session_name=f"claude-natalie-{_SLUG}-1779320000", detail=""),
TmuxRow(host="apricot", session_name=f"claude-natalie-{_SLUG}-1779320001", detail=""),
],
)
stats = pull(conn, gen, rclaude=fake)
assert stats.sessions_alive == 2
assert stats.sessions_closed == 2
# Freshest two (indices 3, 2) alive; oldest two (1, 0) closed.
assert _liveness(conn, uuids[3]) == "alive"
assert _liveness(conn, uuids[2]) == "alive"
assert _liveness(conn, uuids[1]) == "closed"
assert _liveness(conn, uuids[0]) == "closed"
def test_no_live_pane_marks_all_closed(conn, gen) -> None:
"""A workspace with disk sessions but no live pane → all closed."""
u = "44444444-4444-4444-4444-444444444444"
fake = _FakeRclaude(sessions=[_session(u, 1_700_000_000)], tmux=[])
stats = pull(conn, gen, rclaude=fake)
assert stats.sessions_alive == 0
assert _liveness(conn, u) == "closed"