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
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import type { Sql } from '../db.js';
|
|
|
|
export interface AgentSnapshot {
|
|
id: string;
|
|
session_id: string;
|
|
chat_id: string;
|
|
model: string;
|
|
agent: string | null;
|
|
mode: string | null;
|
|
turn_number: number;
|
|
messages: unknown[];
|
|
tool_states: unknown[];
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
/** Save or update the agent snapshot for a chat (UPSERT). */
|
|
export async function saveAgentSnapshot(sql: Sql, chatId: string, data: {
|
|
session_id: string;
|
|
model: string;
|
|
agent?: string | null;
|
|
mode?: string | null;
|
|
turn_number: number;
|
|
messages: unknown[];
|
|
tool_states?: unknown[];
|
|
}): Promise<void> {
|
|
await sql`
|
|
INSERT INTO agent_snapshots (session_id, chat_id, model, agent, mode, turn_number, messages, tool_states, updated_at)
|
|
VALUES (${data.session_id}, ${chatId}, ${data.model}, ${data.agent ?? null}, ${data.mode ?? null}, ${data.turn_number}, ${sql.json(data.messages as never)}, ${sql.json((data.tool_states ?? []) as never)}, clock_timestamp())
|
|
ON CONFLICT (chat_id)
|
|
DO UPDATE SET
|
|
model = EXCLUDED.model,
|
|
agent = EXCLUDED.agent,
|
|
mode = EXCLUDED.mode,
|
|
turn_number = EXCLUDED.turn_number,
|
|
messages = EXCLUDED.messages,
|
|
tool_states = EXCLUDED.tool_states,
|
|
updated_at = clock_timestamp()
|
|
`;
|
|
}
|
|
|
|
/** Load the agent snapshot for a chat. Returns null if no snapshot exists. */
|
|
export async function loadAgentSnapshot(sql: Sql, chatId: string): Promise<AgentSnapshot | null> {
|
|
const rows = await sql<AgentSnapshot[]>`SELECT * FROM agent_snapshots WHERE chat_id = ${chatId}`;
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
/** Delete the agent snapshot for a chat (call when session ends). */
|
|
export async function deleteAgentSnapshot(sql: Sql, chatId: string): Promise<void> {
|
|
await sql`DELETE FROM agent_snapshots WHERE chat_id = ${chatId}`;
|
|
}
|