feat(cli): hide terminal sessions in `ao session ls` by default (#1319)

* feat(cli): hide terminal session rows by default in session ls

- Filter with TERMINAL_STATUSES unless --include-terminated
- Hint when completed sessions are hidden; docs/CLI.md updated

Made-with: Cursor

* fix(cli): keep session ls JSON inventory stable

Address review concerns by preserving terminal sessions in `ao session ls --json` output while keeping terminal rows hidden by default in text output. Use the core terminal-session predicate, improve hidden-row messaging, and add regression tests for mixed/hinted and JSON behavior.

Made-with: Cursor

* fix(cli): resolve rebase conflicts with latest session ls behavior

Align session command and CLI docs with the current mainline JSON contract (`data` + `meta.hiddenTerminatedCount`) while preserving terminal-filter defaults and updated regression coverage.

Made-with: Cursor
This commit is contained in:
Chirag Arora 2026-04-21 11:35:54 +05:30 committed by GitHub
parent e518562a62
commit f09cc72dc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
"@aoagents/ao-cli": patch
---
`ao session ls` hides terminal sessions in text output by default; use `--include-terminated` for the full text list.

View File

@ -370,6 +370,23 @@ describe("session ls", () => {
});
});
it("filters terminal sessions from JSON by default and reports hidden count", async () => {
writeFileSync(join(sessionsDir, "app-1"), "branch=main\nstatus=working\n");
writeFileSync(join(sessionsDir, "app-done"), "branch=main\nstatus=merged\nactivity=exited\n");
mockTmux.mockResolvedValue(null);
mockGit.mockResolvedValue(null);
await program.parseAsync(["node", "test", "session", "ls", "--json"]);
const parsed = JSON.parse(String(consoleSpy.mock.calls[0][0])) as {
data: Array<{ id: string }>;
meta: { hiddenTerminatedCount: number };
};
expect(parsed.data.map((entry) => entry.id)).toEqual(["app-1"]);
expect(parsed.meta.hiddenTerminatedCount).toBe(1);
});
it("marks metadata-based orchestrators correctly in JSON output", async () => {
writeFileSync(
join(sessionsDir, "app-control"),
@ -573,6 +590,71 @@ describe("session ls", () => {
expect(parsed.data.map((e: { id: string }) => e.id)).toEqual(["app-1"]);
expect(parsed.meta.hiddenTerminatedCount).toBe(3);
});
it("hides terminal-status sessions by default", async () => {
writeFileSync(join(sessionsDir, "app-1"), "branch=main\nstatus=working\n");
writeFileSync(join(sessionsDir, "app-done"), "branch=main\nstatus=merged\n");
mockTmux.mockImplementation(async (...args: string[]) => {
if (args[0] === "list-sessions") return "app-1\napp-done";
if (args[0] === "display-message") {
return String(Math.floor(Date.now() / 1000) - 60);
}
return null;
});
mockGit.mockResolvedValue(null);
await program.parseAsync(["node", "test", "session", "ls"]);
const output = consoleSpy.mock.calls.map((c) => String(c[0])).join("\n");
expect(output).toContain("app-1");
expect(output).not.toContain("app-done");
});
it("lists terminal sessions when --include-terminated is set", async () => {
writeFileSync(join(sessionsDir, "app-done"), "branch=main\nstatus=merged\n");
mockTmux.mockImplementation(async (...args: string[]) => {
if (args[0] === "list-sessions") return "app-done";
return null;
});
mockGit.mockResolvedValue(null);
await program.parseAsync(["node", "test", "session", "ls", "--include-terminated"]);
const output = consoleSpy.mock.calls.map((c) => String(c[0])).join("\n");
expect(output).toContain("app-done");
});
it("prints a hint when only terminal sessions exist", async () => {
writeFileSync(join(sessionsDir, "app-done"), "branch=main\nstatus=merged\n");
mockTmux.mockImplementation(async (...args: string[]) => {
if (args[0] === "list-sessions") return "app-done";
return null;
});
await program.parseAsync(["node", "test", "session", "ls"]);
const output = consoleSpy.mock.calls.map((c) => String(c[0])).join("\n");
expect(output).toContain("--include-terminated");
expect(output).not.toContain("app-done");
});
it("prints a hint when terminal sessions are hidden alongside active ones", async () => {
writeFileSync(join(sessionsDir, "app-1"), "branch=main\nstatus=working\n");
writeFileSync(join(sessionsDir, "app-done"), "branch=main\nstatus=merged\n");
mockTmux.mockResolvedValue(null);
mockGit.mockResolvedValue(null);
await program.parseAsync(["node", "test", "session", "ls"]);
const output = consoleSpy.mock.calls.map((c) => String(c[0])).join("\n");
expect(output).toContain("app-1");
expect(output).toContain("terminated session");
expect(output).toContain("--include-terminated");
});
});
describe("session kill", () => {