/** * Agent Sessions API * * POST /api/projects/[projectId]/agent/sessions * Create a new agent session and kick it off via vibn-agent-runner. * Body: { appName, appPath, task } * * GET /api/projects/[projectId]/agent/sessions * List all sessions for a project, newest first. */ import { NextResponse } from "next/server"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth/authOptions"; import { query } from "@/lib/db-postgres"; const AGENT_RUNNER_URL = process.env.AGENT_RUNNER_URL ?? "http://localhost:3333"; // Ensure the agent_sessions table exists (idempotent). async function ensureTable() { await query(` CREATE TABLE IF NOT EXISTS agent_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), project_id UUID NOT NULL, app_name TEXT NOT NULL, app_path TEXT NOT NULL, task TEXT NOT NULL, plan JSONB, status TEXT NOT NULL DEFAULT 'pending', output JSONB NOT NULL DEFAULT '[]'::jsonb, changed_files JSONB NOT NULL DEFAULT '[]'::jsonb, error TEXT, started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS agent_sessions_project_idx ON agent_sessions(project_id, created_at DESC); `, []); } // ── POST — create session ──────────────────────────────────────────────────── export async function POST( req: Request, { params }: { params: Promise<{ projectId: string }> } ) { try { const { projectId } = await params; const session = await getServerSession(authOptions); if (!session?.user?.email) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const body = await req.json(); const { appName, appPath, task } = body as { appName: string; appPath: string; task: string; }; if (!appName || !appPath || !task?.trim()) { return NextResponse.json({ error: "appName, appPath and task are required" }, { status: 400 }); } await ensureTable(); // Verify ownership const owns = await query<{ id: string }>( `SELECT p.id FROM fs_projects p JOIN fs_users u ON u.id = p.user_id WHERE p.id = $1 AND u.data->>'email' = $2 LIMIT 1`, [projectId, session.user.email] ); if (owns.length === 0) { return NextResponse.json({ error: "Project not found" }, { status: 404 }); } // Create the session row const rows = await query<{ id: string }>( `INSERT INTO agent_sessions (project_id, app_name, app_path, task, status, started_at) VALUES ($1, $2, $3, $4, 'running', now()) RETURNING id`, [projectId, appName, appPath, task.trim()] ); const sessionId = rows[0].id; // Fire-and-forget: call agent-runner to start the execution loop // The agent runner is responsible for updating the session row as it works. fetch(`${AGENT_RUNNER_URL}/agent/execute`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId, projectId, appName, appPath, task: task.trim(), }), }).catch(err => { // Agent runner may not be wired yet — log but don't fail console.warn("[agent] runner not reachable:", err.message); // Mark session as failed if runner unreachable query( `UPDATE agent_sessions SET status = 'failed', error = 'Agent runner not reachable', completed_at = now(), output = jsonb_build_array(jsonb_build_object( 'ts', now()::text, 'type', 'error', 'text', 'Agent runner service is not connected yet. Phase 2 implementation pending.' )) WHERE id = $1`, [sessionId] ).catch(() => {}); }); return NextResponse.json({ sessionId }, { status: 201 }); } catch (err) { console.error("[agent/sessions POST]", err); return NextResponse.json({ error: "Failed to create session" }, { status: 500 }); } } // ── GET — list sessions ────────────────────────────────────────────────────── export async function GET( req: Request, { params }: { params: Promise<{ projectId: string }> } ) { try { const { projectId } = await params; const session = await getServerSession(authOptions); if (!session?.user?.email) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } await ensureTable(); const sessions = await query<{ id: string; app_name: string; task: string; status: string; created_at: string; started_at: string | null; completed_at: string | null; output: Array<{ ts: string; type: string; text: string }>; changed_files: Array<{ path: string; status: string }>; error: string | null; }>( `SELECT s.id, s.app_name, s.task, s.status, s.created_at, s.started_at, s.completed_at, s.output, s.changed_files, s.error FROM agent_sessions s JOIN fs_projects p ON p.id = s.project_id JOIN fs_users u ON u.id = p.user_id WHERE s.project_id = $1 AND u.data->>'email' = $2 ORDER BY s.created_at DESC LIMIT 50`, [projectId, session.user.email] ); return NextResponse.json({ sessions }); } catch (err) { console.error("[agent/sessions GET]", err); return NextResponse.json({ error: "Failed to list sessions" }, { status: 500 }); } }