diff --git a/apps/agent-forge/src/screens/threads/ThreadsScreen.tsx b/apps/agent-forge/src/screens/threads/ThreadsScreen.tsx index 7f8d39a64..27e229945 100644 --- a/apps/agent-forge/src/screens/threads/ThreadsScreen.tsx +++ b/apps/agent-forge/src/screens/threads/ThreadsScreen.tsx @@ -34,8 +34,12 @@ import { type ThreadsUrlState, } from './threadsUrl'; -/** Session statuses the daemon writes: `active`, then `interrupted` when the agent's crash sweep closes it. */ -const STATUSES = ['active', 'interrupted']; +/** + * Session statuses the daemon writes: `active`; `ended` once its idle sweep sees + * nothing run for the idle window; `interrupted` when the agent's crash sweep + * closes it. New activity makes an ended or interrupted session `active` again. + */ +const STATUSES = ['active', 'ended', 'interrupted']; /** The session's span, start to its latest finished observation; `-` while nothing has finished. */ const span = (s: ObserveSessionRow): number | null => (s.ended_at === null ? null : s.ended_at - s.started_at); diff --git a/apps/agent-forge/src/screens/threads/__tests__/threads.test.tsx b/apps/agent-forge/src/screens/threads/__tests__/threads.test.tsx index 2e17d4ba0..bdf80bcfa 100644 --- a/apps/agent-forge/src/screens/threads/__tests__/threads.test.tsx +++ b/apps/agent-forge/src/screens/threads/__tests__/threads.test.tsx @@ -147,6 +147,15 @@ describe('ThreadsScreen list', () => { expect(custom?.getAttribute('aria-pressed')).toBe('true'); }); + it('offers the ended status the idle sweep writes', async () => { + const container = await mountAt('#/threads?status=ended'); + expect(listRequests().at(-1)!.get('status')).toBe('ended'); + const statusBtn = [...container.querySelectorAll('.facet-btn')].find((b) => b.textContent?.startsWith('Status'))!; + act(() => statusBtn.click()); + const options = [...container.querySelectorAll('.facet-pop .facet-opt')].map((o) => o.textContent); + expect(options).toEqual(['active', 'ended', 'interrupted']); + }); + it('opening a row navigates to #/threads/:sessionId', async () => { const container = await mountAt('#/threads?status=active'); const row = container.querySelector('tbody tr[data-row-index="0"]')!; diff --git a/docs/cli.md b/docs/cli.md index 591d8752c..f2c14ee16 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -36,7 +36,7 @@ lousho mcp [--http --port N --host H] lousho doctor [agent.yaml|json|agent-dir|agent.ts] [--json] [--ping] lousho eval [globs...] [--tag t] [--junit path] [--json path] [--strict] [--judge] [--record | --replay | --drift [--drift-usage]] [--url [--token ]] [--config vitest.config.ts] [--timeout ] lousho traces [traceId|prefix] [--dir D] [--limit N] [--json] [--content] -lousho observe [--port N] [--db path] [--host H] [--require-token] [--project X] [--webhook URL] [--retention-days N] [--older-than d] [--since d|h|m] [--agent X] [--out file] +lousho observe [--port N] [--db path] [--host H] [--require-token] [--project X] [--webhook URL] [--retention-days N] [--session-idle-minutes N] [--older-than d] [--since d|h|m] [--agent X] [--out file] lousho prompt |list|get |label