diff --git a/.changeset/session-list-daemon-status.md b/.changeset/session-list-daemon-status.md new file mode 100644 index 000000000..3ceb7e599 --- /dev/null +++ b/.changeset/session-list-daemon-status.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": patch +--- + +Report whether the session daemon is reachable in `hunk session list --json` so callers can tell a down daemon from an empty session set. diff --git a/src/session/agent/commands.daemon.test.ts b/src/session/agent/commands.daemon.test.ts index a067f7d85..afbb90202 100644 --- a/src/session/agent/commands.daemon.test.ts +++ b/src/session/agent/commands.daemon.test.ts @@ -56,7 +56,7 @@ describe("resolveDaemonAvailability with no daemon listening", () => { action: "list", output: "json", } satisfies SessionCommandInput); - expect(JSON.parse(output)).toEqual({ sessions: [] }); + expect(JSON.parse(output)).toEqual({ sessions: [], daemon: { available: false } }); }); probeTest( diff --git a/src/session/agent/commands.test.ts b/src/session/agent/commands.test.ts index 3618f6810..e04222f49 100644 --- a/src/session/agent/commands.test.ts +++ b/src/session/agent/commands.test.ts @@ -951,6 +951,50 @@ describe("session command compatibility checks", () => { expect(output).toBe("No active Hunk sessions.\n"); }); + // Intent: the JSON list value must distinguish a down daemon (available: false) from a healthy + // daemon with zero sessions (available: true). Text output stays unchanged in both states. + test("list reports daemon unavailable in JSON when no daemon is present", async () => { + setSessionCommandTestHooks({ + createClient: () => { + throw new Error("list should not create a client without a daemon"); + }, + resolveDaemonAvailability: async () => false, + }); + + const output = await runSessionCommand({ + kind: "session", + action: "list", + output: "json", + } satisfies SessionCommandInput); + + expect(JSON.parse(output)).toEqual({ + sessions: [], + daemon: { available: false }, + }); + }); + + test("list reports daemon available in JSON when a daemon is present", async () => { + setSessionCommandTestHooks({ + createClient: () => + createClient({ + listSessions: async () => [createTestListedSession("session-1")], + }), + resolveDaemonAvailability: async () => true, + }); + + const output = await runSessionCommand({ + kind: "session", + action: "list", + output: "json", + } satisfies SessionCommandInput); + + const parsed = JSON.parse(output); + expect(parsed.daemon).toEqual({ available: true }); + expect(parsed).toMatchObject({ + sessions: [{ sessionId: "session-1" }], + }); + }); + // Intent: remaining command branches dispatch to the daemon and keep text output stable. test("routes remaining session actions through the daemon and formats text output", async () => { const selector: SessionSelectorInput = { sessionId: "session-1" }; diff --git a/src/session/agent/commands.ts b/src/session/agent/commands.ts index 9a994f07b..f55d835f9 100644 --- a/src/session/agent/commands.ts +++ b/src/session/agent/commands.ts @@ -176,7 +176,11 @@ export async function runSessionCommand(input: SessionCommandInput) { input.action, ) ?? resolveDaemonAvailability(input.action)); if (!daemonAvailable && input.action === "list") { - return renderOutput(input.output, { sessions: [] }, () => formatListOutput([])); + // Report daemon reachability in the JSON value so callers can tell a down daemon from a + // healthy daemon with zero sessions — both otherwise render an empty session list. + return renderOutput(input.output, { sessions: [], daemon: { available: false } }, () => + formatListOutput([]), + ); } const normalizedSelector = "selector" in input ? normalizeSessionSelector(input.selector) : null; @@ -188,7 +192,9 @@ export async function runSessionCommand(input: SessionCommandInput) { switch (input.action) { case "list": { const sessions = await client.listSessions(); - return renderOutput(input.output, { sessions }, () => formatListOutput(sessions)); + return renderOutput(input.output, { sessions, daemon: { available: true } }, () => + formatListOutput(sessions), + ); } case "get": { const session = await client.getSession(normalizedSelector!);