CLI: include agentId in aggregated sessions output

This commit is contained in:
Gustavo Madeira Santana
2026-02-23 13:26:56 -05:00
parent d0e94f8e55
commit afc3b9baab
3 changed files with 89 additions and 1 deletions

View File

@@ -24,6 +24,27 @@ Scope selection:
- `--all-agents`: aggregate all configured agent stores
- `--store <path>`: explicit store path (cannot be combined with `--agent` or `--all-agents`)
JSON examples:
`openclaw sessions --all-agents --json`:
```json
{
"path": null,
"stores": [
{ "agentId": "main", "path": "/home/user/.openclaw/agents/main/sessions/sessions.json" },
{ "agentId": "work", "path": "/home/user/.openclaw/agents/work/sessions/sessions.json" }
],
"allAgents": true,
"count": 2,
"activeMinutes": null,
"sessions": [
{ "agentId": "main", "key": "agent:main:main", "model": "gpt-5" },
{ "agentId": "work", "key": "agent:work:main", "model": "claude-opus-4-5" }
]
}
```
## Cleanup maintenance
Run maintenance now (instead of waiting for the next write cycle):
@@ -48,6 +69,34 @@ openclaw sessions cleanup --json
- `--store <path>`: run against a specific `sessions.json` file.
- `--json`: print a JSON summary. With `--all-agents`, output includes one summary per store.
`openclaw sessions cleanup --all-agents --dry-run --json`:
```json
{
"allAgents": true,
"mode": "warn",
"dryRun": true,
"stores": [
{
"agentId": "main",
"storePath": "/home/user/.openclaw/agents/main/sessions/sessions.json",
"beforeCount": 120,
"afterCount": 80,
"pruned": 40,
"capped": 0
},
{
"agentId": "work",
"storePath": "/home/user/.openclaw/agents/work/sessions/sessions.json",
"beforeCount": 18,
"afterCount": 18,
"pruned": 0,
"capped": 0
}
]
}
```
Related:
- Session config: [Configuration reference](/gateway/configuration-reference#session)

View File

@@ -25,6 +25,7 @@ const resolveStorePathMock = vi.hoisted(() =>
return `/tmp/sessions-${opts?.agentId ?? "missing"}.json`;
}),
);
const loadSessionStoreMock = vi.hoisted(() => vi.fn(() => ({})));
vi.mock("../config/config.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../config/config.js")>();
@@ -39,7 +40,7 @@ vi.mock("../config/sessions.js", async (importOriginal) => {
return {
...actual,
resolveStorePath: resolveStorePathMock,
loadSessionStore: vi.fn(() => ({})),
loadSessionStore: loadSessionStoreMock,
};
});
@@ -58,6 +59,29 @@ function createRuntime(): { runtime: RuntimeEnv; logs: string[] } {
}
describe("sessionsCommand default store agent selection", () => {
it("includes agentId on sessions rows for --all-agents JSON output", async () => {
resolveStorePathMock.mockClear();
loadSessionStoreMock.mockReset();
loadSessionStoreMock
.mockReturnValueOnce({
main_row: { sessionId: "s1", updatedAt: Date.now() - 60_000, model: "pi:opus" },
})
.mockReturnValueOnce({
voice_row: { sessionId: "s2", updatedAt: Date.now() - 120_000, model: "pi:opus" },
});
const { runtime, logs } = createRuntime();
await sessionsCommand({ allAgents: true, json: true }, runtime);
const payload = JSON.parse(logs[0] ?? "{}") as {
allAgents?: boolean;
sessions?: Array<{ key: string; agentId?: string }>;
};
expect(payload.allAgents).toBe(true);
expect(payload.sessions?.map((session) => session.agentId)).toContain("main");
expect(payload.sessions?.map((session) => session.agentId)).toContain("voice");
});
it("uses configured default agent id when resolving implicit session store path", async () => {
resolveStorePathMock.mockClear();
const { runtime, logs } = createRuntime();
@@ -72,6 +96,12 @@ describe("sessionsCommand default store agent selection", () => {
it("uses all configured agent stores with --all-agents", async () => {
resolveStorePathMock.mockClear();
loadSessionStoreMock.mockReset();
loadSessionStoreMock
.mockReturnValueOnce({
main_row: { sessionId: "s1", updatedAt: Date.now() - 60_000, model: "pi:opus" },
})
.mockReturnValueOnce({});
const { runtime, logs } = createRuntime();
await sessionsCommand({ allAgents: true }, runtime);
@@ -83,5 +113,6 @@ describe("sessionsCommand default store agent selection", () => {
agentId: "voice",
});
expect(logs[0]).toContain("Session stores: 2 (main, voice)");
expect(logs[2]).toContain("Agent");
});
});

View File

@@ -22,9 +22,11 @@ import {
} from "./sessions-table.js";
type SessionRow = SessionDisplayRow & {
agentId: string;
kind: "direct" | "group" | "global" | "unknown";
};
const AGENT_PAD = 10;
const KIND_PAD = 6;
const TOKENS_PAD = 20;
@@ -120,6 +122,7 @@ export async function sessionsCommand(
const store = loadSessionStore(target.storePath);
return toSessionDisplayRows(store).map((row) => ({
...row,
agentId: target.agentId,
kind: classifySessionKey(row.key, store[row.key]),
}));
})
@@ -186,7 +189,9 @@ export async function sessionsCommand(
}
const rich = isRich();
const showAgentColumn = targets.length > 1;
const header = [
...(showAgentColumn ? ["Agent".padEnd(AGENT_PAD)] : []),
"Kind".padEnd(KIND_PAD),
"Key".padEnd(SESSION_KEY_PAD),
"Age".padEnd(SESSION_AGE_PAD),
@@ -203,6 +208,9 @@ export async function sessionsCommand(
const total = resolveFreshSessionTotalTokens(row);
const line = [
...(showAgentColumn
? [rich ? theme.accentBright(row.agentId.padEnd(AGENT_PAD)) : row.agentId.padEnd(AGENT_PAD)]
: []),
formatKindCell(row.kind, rich),
formatSessionKeyCell(row.key, rich),
formatSessionAgeCell(row.updatedAt, rich),