import type { FastifyInstance } from 'fastify'; import type { Sql } from '../db.js'; // v2.6 Phase 1-UX (design §9b): chat-scoped "resumed vs new session" indicator. // `agent_sessions` is keyed (chat_id, agent) — the tab/chat is the agent-context // unit (P1.5-b). The route param is a SESSION id, so we resolve every chat in the // session and return the union of their agent_sessions rows. A session with two // opencode tabs yields two rows (one per chat); the frontend keys the chip per // chat, but the wire shape is a flat per-(chat,agent) list. // // has_session = agent_session_id IS NOT NULL — i.e. a native backend session id // (opencode/ACP) was created and stored, so switching back resumes rather than // starts fresh. export interface AgentSessionRow { agent: string; status: string; has_session: boolean; last_active_at: string | null; // v2.6.8 per-(chat,agent) running token/cost totals (sampling-streamjson-tokens // #8). BIGINT columns arrive as strings over the wire; the frontend coerces. input_tokens: number; output_tokens: number; cost: number; } export function registerAgentSessionRoutes(app: FastifyInstance, sql: Sql): void { // GET /api/sessions/:sessionId/agent-sessions — list the agent-session rows for // every chat in the session (drives the AgentComposerBar resumed/new chip). app.get<{ Params: { sessionId: string } }>( '/api/sessions/:sessionId/agent-sessions', async (req, reply) => { const sessionId = req.params.sessionId; const session = await sql<{ id: string }[]>`SELECT id FROM sessions WHERE id = ${sessionId}`; if (session.length === 0) { reply.code(404); return { error: 'session not found' }; } // Join through chats so the session-scoped param resolves to its (chat,agent) // rows. last_active_at first → the frontend reads the freshest activity. const rows = await sql` SELECT a.agent AS agent, a.status AS status, (a.agent_session_id IS NOT NULL) AS has_session, a.last_active_at AS last_active_at, a.input_tokens AS input_tokens, a.output_tokens AS output_tokens, a.cost AS cost FROM agent_sessions a JOIN chats c ON c.id = a.chat_id WHERE c.session_id = ${sessionId} ORDER BY a.last_active_at DESC NULLS LAST, a.agent ASC `; return rows; }, ); }