Phase 1: Trace System + Observability - tool_traces DB table + insert/update service - tool_trace_start/tool_trace_finish WS frames (contracts + FE types) - Instrumented tool-phase.ts with timing around every tool call - GET /api/chats/:id/traces paginated endpoint - Trace viewer frontend (collapsible panel with timing bars + token breakdown) Phase 2: Session Persistence + Resume - agent_snapshots table (UPSERT per chat, persisted on turn boundaries) - save/load/delete service functions - Agent snapshot sent on WS reconnect - Session timeline view (vertical timeline with scroll-to + restore) Tooling: - run_command tool (execFile, 30s timeout, 32KB cap, path-guarded) - Auto-fix loop: after write tools, runs pnpm build, injects errors into next turn
39 lines
1.2 KiB
TypeScript
39 lines
1.2 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import type { Sql } from '../db.js';
|
|
import type { ToolTrace } from '../services/tool-traces.js';
|
|
|
|
export function registerTraceRoutes(app: FastifyInstance, sql: Sql): void {
|
|
app.get<{ Params: { id: string }; Querystring: { limit?: string; offset?: string } }>(
|
|
'/api/chats/:id/traces',
|
|
async (req, reply) => {
|
|
const chat = await sql`SELECT id FROM chats WHERE id = ${req.params.id}`;
|
|
if (chat.length === 0) {
|
|
reply.code(404);
|
|
return { error: 'chat not found' };
|
|
}
|
|
|
|
const limit = Math.min(Math.max(Number(req.query.limit) || 50, 1), 200);
|
|
const offset = Math.max(Number(req.query.offset) || 0, 0);
|
|
|
|
const rows = await sql<ToolTrace[]>`
|
|
SELECT * FROM tool_traces
|
|
WHERE chat_id = ${req.params.id}
|
|
ORDER BY started_at ASC
|
|
LIMIT ${limit}
|
|
OFFSET ${offset}
|
|
`;
|
|
|
|
const [countRow] = await sql<{ count: number }[]>`
|
|
SELECT count(*)::int AS count FROM tool_traces WHERE chat_id = ${req.params.id}
|
|
`;
|
|
|
|
return {
|
|
data: rows,
|
|
total: countRow?.count ?? 0,
|
|
limit,
|
|
offset,
|
|
};
|
|
},
|
|
);
|
|
}
|