import { promises as fs } from 'node:fs' import path from 'node:path' import { NextRequest, NextResponse } from 'next/server' import { requireRole } from '@/lib/auth' import { logger } from '@/lib/logger' import { runCommand } from '@/lib/command' type ContinueKind = 'claude-code' | 'codex-cli' function sanitizePrompt(value: unknown): string { return typeof value === 'string' ? value.trim() : '' } /** * POST /api/sessions/continue * Body: { kind: 'claude-code'|'codex-cli', id: string, prompt: string } */ export async function POST(request: NextRequest) { const auth = requireRole(request, 'operator') if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) try { const body = await request.json().catch(() => ({})) const kind = body?.kind as ContinueKind const sessionId = typeof body?.id === 'string' ? body.id.trim() : '' const prompt = sanitizePrompt(body?.prompt) if (!sessionId || !/^[a-zA-Z0-9._:-]+$/.test(sessionId)) { return NextResponse.json({ error: 'Invalid session id' }, { status: 400 }) } if (kind !== 'claude-code' && kind !== 'codex-cli') { return NextResponse.json({ error: 'Invalid kind' }, { status: 400 }) } if (!prompt || prompt.length > 6000) { return NextResponse.json({ error: 'prompt is required (max 6000 chars)' }, { status: 400 }) } let reply = '' if (kind === 'claude-code') { const result = await runCommand('claude', ['--print', '--resume', sessionId, prompt], { timeoutMs: 180000, }) reply = (result.stdout || '').trim() || (result.stderr || '').trim() } else { const outputPath = path.join('/tmp', `mc-codex-last-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`) try { await runCommand('codex', ['exec', 'resume', sessionId, prompt, '--skip-git-repo-check', '-o', outputPath], { timeoutMs: 180000, }) } finally { // Read after run attempt either way for best-effort output } try { reply = (await fs.readFile(outputPath, 'utf-8')).trim() } catch { reply = '' } try { await fs.unlink(outputPath) } catch { // ignore } } if (!reply) { reply = 'Session continued, but no text response was returned.' } return NextResponse.json({ ok: true, reply }) } catch (error: any) { logger.error({ err: error }, 'POST /api/sessions/continue error') return NextResponse.json({ error: error?.message || 'Failed to continue session' }, { status: 500 }) } } export const dynamic = 'force-dynamic'