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
38 changes: 38 additions & 0 deletions src/herdr_workflow/workflows/inbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,38 @@
from herdr_workflow.herdr import ops
from herdr_workflow.herdr.agents import start_agent
from herdr_workflow.herdr.client import HerdrClient
from herdr_workflow.protocol.messages import Snapshot

ROUTER_TAB = "router"


def _bootstrap_workspace_id(snapshot: Snapshot, home: Path) -> str | None:
"""Return Herdr's untouched startup workspace, if it is still present.

Herdr creates a ``~`` workspace with one tab named ``1`` when a session starts. Once
the inbox exists that shell is just clutter. Match the complete pristine shape so an
intentional home-directory workspace is never closed merely because it is named ``~``.
"""
for workspace in snapshot.workspaces:
if workspace.label != "~" or workspace.tab_count != 1 or workspace.pane_count != 1:
continue
tabs = [tab for tab in snapshot.tabs if tab.workspace_id == workspace.workspace_id]
panes = [pane for pane in snapshot.panes if pane.workspace_id == workspace.workspace_id]
agents = [
agent for agent in snapshot.agents if agent.workspace_id == workspace.workspace_id
]
if (
len(tabs) == 1
and tabs[0].label == "1"
and len(panes) == 1
and panes[0].cwd == str(home)
and panes[0].revision == 0
and not agents
):
return workspace.workspace_id
return None


@dataclass(frozen=True)
class UpResult:
workspace_id: str
Expand All @@ -46,6 +74,7 @@ async def inbox_workspace_id(client: HerdrClient, config: Config) -> str:
async def up(client: HerdrClient, config: Config, home: Path) -> UpResult:
snapshot = await client.snapshot()
workspace = ops.workspace_by_label(snapshot, config.herdr.inbox_label)
bootstrap_workspace_id = _bootstrap_workspace_id(snapshot, home)

created_workspace = False
if workspace is None:
Expand Down Expand Up @@ -78,6 +107,15 @@ async def up(client: HerdrClient, config: Config, home: Path) -> UpResult:
await ops.workspace_focus(client, workspace_id)
await ops.pane_focus(client, pane_id)

# A fresh Herdr session starts with an otherwise unused ``~`` workspace. Close it only
# after the inbox is ready and focused, so the session is never left without a workspace.
if bootstrap_workspace_id is not None and bootstrap_workspace_id != workspace_id:
# Agent startup takes long enough for a person to begin using the shell. Recheck
# before closing so a workspace changed since the first snapshot is preserved.
current = await client.snapshot()
if _bootstrap_workspace_id(current, home) == bootstrap_workspace_id:
await ops.workspace_close(client, bootstrap_workspace_id)

return UpResult(
workspace_id=workspace_id,
pane_id=pane_id,
Expand Down
77 changes: 75 additions & 2 deletions tests/unit/test_inbox_and_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,25 @@ def _tab(tab_id: str, ws_id: str, label: str) -> dict[str, Any]:
}


def _pane(pane_id: str, ws_id: str, tab_id: str, label: str | None = None) -> dict[str, Any]:
def _pane(
pane_id: str,
ws_id: str,
tab_id: str,
label: str | None = None,
*,
cwd: str | None = None,
revision: int = 1,
) -> dict[str, Any]:
return {
"pane_id": pane_id,
"terminal_id": f"t-{pane_id}",
"workspace_id": ws_id,
"tab_id": tab_id,
"focused": False,
"agent_status": "unknown",
"revision": 1,
"revision": revision,
"label": label,
"cwd": cwd,
}


Expand Down Expand Up @@ -112,6 +121,70 @@ async def test_up_creates_the_workspace_when_absent(
assert fake.calls("tab.rename")[0]["params"]["label"] == "router"


async def test_up_closes_herdrs_pristine_home_workspace(
client: HerdrClient, fake: FakeHerdr, tmp_path: Path
) -> None:
fake.on(
"session.snapshot",
_snapshot(
workspaces=[_ws("~", "w0")],
tabs=[_tab("w0:t1", "w0", "1")],
panes=[_pane("w0:p1", "w0", "w0:t1", cwd=str(tmp_path), revision=0)],
),
)
fake.on(
"workspace.create",
{
"type": "workspace_created",
"workspace": _ws("inbox", "w1"),
"tab": _tab("w1:t1", "w1", "inbox"),
"root_pane": _pane("w1:p1", "w1", "w1:t1"),
},
)
fake.on("agent.list", {"type": "agent_list", "agents": []})
_ok(
fake,
"tab.rename",
"agent.start",
"pane.rename",
"workspace.focus",
"pane.focus",
"workspace.close",
)

await inbox.up(client, Config(), tmp_path)

assert fake.calls("workspace.close")[0]["params"]["workspace_id"] == "w0"


async def test_up_keeps_a_home_workspace_that_is_not_pristine(
client: HerdrClient, fake: FakeHerdr, tmp_path: Path
) -> None:
fake.on(
"session.snapshot",
_snapshot(
workspaces=[_ws("~", "w0")],
tabs=[_tab("w0:t1", "w0", "shell")],
panes=[_pane("w0:p1", "w0", "w0:t1", cwd=str(tmp_path), revision=0)],
),
)
fake.on(
"workspace.create",
{
"type": "workspace_created",
"workspace": _ws("inbox", "w1"),
"tab": _tab("w1:t1", "w1", "inbox"),
"root_pane": _pane("w1:p1", "w1", "w1:t1"),
},
)
fake.on("agent.list", {"type": "agent_list", "agents": []})
_ok(fake, "tab.rename", "agent.start", "pane.rename", "workspace.focus", "pane.focus")

await inbox.up(client, Config(), tmp_path)

assert fake.calls("workspace.close") == []


async def test_up_is_idempotent_when_the_router_is_already_running(
client: HerdrClient, fake: FakeHerdr, tmp_path: Path
) -> None:
Expand Down