feat: Paseo-like orchestrator Phase 1-2 — trace system, session persistence, timeline, run_command, auto-fix loop
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
This commit is contained in:
51
apps/server/src/services/session-snapshots.ts
Normal file
51
apps/server/src/services/session-snapshots.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user