diff --git a/apps/desktop-tauri/scripts/proof-scenarios.json b/apps/desktop-tauri/scripts/proof-scenarios.json
index c576456a44..99ca76f18d 100644
--- a/apps/desktop-tauri/scripts/proof-scenarios.json
+++ b/apps/desktop-tauri/scripts/proof-scenarios.json
@@ -736,6 +736,16 @@
{ "expectText": "%" },
{ "screenshot": "floatbar" }
]
+ },
+ {
+ "name": "tray-agent-sessions-hosts",
+ "query": "window=main&fixture=agent-sessions-hosts",
+ "size": [310, 1032],
+ "steps": [
+ { "expectText": ["WORKSTATION-A — 2", "BUILD-BOX — 1", "LAB-HOST — unreachable", "ssh: connection timed out", "example-project"] },
+ { "expectNoText": "LAB-HOST — 0" },
+ { "screenshot": "tray-agent-sessions-hosts", "clip": "panel" }
+ ]
}
]
}
diff --git a/apps/desktop-tauri/src/components/AgentSessions.test.tsx b/apps/desktop-tauri/src/components/AgentSessions.test.tsx
index 9d4940bc5e..4db581081b 100644
--- a/apps/desktop-tauri/src/components/AgentSessions.test.tsx
+++ b/apps/desktop-tauri/src/components/AgentSessions.test.tsx
@@ -105,4 +105,66 @@ describe("AgentSessions", () => {
expect(await screen.findByText("AgentSessionsProviderPi")).toBeTruthy();
});
+
+ it("groups sessions under one header per host with its session count", async () => {
+ api.listAgentSessions.mockResolvedValue({
+ status: "hosts",
+ hosts: [
+ {
+ host: "DESKTOP",
+ sessions: [
+ session({ id: "1", host: "DESKTOP", workspace: { cwd: null, projectName: "alpha" } }),
+ session({ id: "2", host: "DESKTOP", workspace: { cwd: null, projectName: "beta" } }),
+ ],
+ error: null,
+ },
+ {
+ host: "LAPTOP",
+ sessions: [session({ id: "3", host: "LAPTOP", workspace: { cwd: null, projectName: "gamma" } })],
+ error: null,
+ },
+ ],
+ });
+ render();
+
+ const desktopHeader = await screen.findByText("DESKTOP — 2");
+ const laptopHeader = await screen.findByText("LAPTOP — 1");
+ const alpha = screen.getByText("alpha");
+ const gamma = screen.getByText("gamma");
+ // Each host's rows follow its own header, and the hosts stay in order.
+ expect(desktopHeader.compareDocumentPosition(alpha) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(alpha.compareDocumentPosition(laptopHeader) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(laptopHeader.compareDocumentPosition(gamma) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ });
+
+ it("shows an unreachable host as its own row with the error", async () => {
+ api.listAgentSessions.mockResolvedValue({
+ status: "hosts",
+ hosts: [
+ {
+ host: "DESKTOP",
+ sessions: [session({ id: "1", workspace: { cwd: null, projectName: "alpha" } })],
+ error: null,
+ },
+ { host: "BUILD-BOX", sessions: [], error: "ssh: connect timed out" },
+ ],
+ });
+ render();
+
+ expect(await screen.findByText("BUILD-BOX — AgentSessionsHostUnreachable")).toBeTruthy();
+ expect(screen.getByText("ssh: connect timed out").className).toContain("agent-sessions__error");
+ expect(screen.queryByText("BUILD-BOX — 0")).toBeNull();
+ expect(screen.getByText("DESKTOP — 1")).toBeTruthy();
+ });
+
+ it("keeps a reachable host with no sessions visible as a zero-count header", async () => {
+ api.listAgentSessions.mockResolvedValue({
+ status: "hosts",
+ hosts: [{ host: "IDLE-BOX", sessions: [], error: null }],
+ });
+ render();
+
+ expect(await screen.findByText("IDLE-BOX — 0")).toBeTruthy();
+ expect(screen.getByText("AgentSessionsEmpty")).toBeTruthy();
+ });
});
diff --git a/apps/desktop-tauri/src/components/AgentSessions.tsx b/apps/desktop-tauri/src/components/AgentSessions.tsx
index 73f8b336ca..d6ef9761c1 100644
--- a/apps/desktop-tauri/src/components/AgentSessions.tsx
+++ b/apps/desktop-tauri/src/components/AgentSessions.tsx
@@ -33,9 +33,6 @@ export default function AgentSessions() {
if (result?.status === "disabled") return null;
const hosts = result?.status === "hosts" ? result.hosts : [];
const sessions = hosts.flatMap((host) => host.sessions);
- const hostErrors = hosts.flatMap((host) =>
- host.error ? [`${host.host}: ${host.error}`] : [],
- );
const focus = (session: AgentSession) => {
void focusAgentSession(session).then((focusResult) => {
@@ -53,26 +50,41 @@ export default function AgentSessions() {
{result && sessions.length === 0 && (
{t("AgentSessionsEmpty")}
)}
- {sessions.map((session) => (
-