"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const job_store_1 = require("./job-store"); const agent_runner_1 = require("./agent-runner"); const agents_1 = require("./agents"); const app = (0, express_1.default)(); app.use((0, cors_1.default)()); app.use(express_1.default.json()); const PORT = process.env.PORT || 3333; // --------------------------------------------------------------------------- // Build ToolContext from environment variables // --------------------------------------------------------------------------- function buildContext(repo) { const workspaceRoot = repo ? `${process.env.WORKSPACE_BASE || '/workspaces'}/${repo.replace('/', '_')}` : (process.env.WORKSPACE_BASE || '/workspaces/default'); return { workspaceRoot, gitea: { apiUrl: process.env.GITEA_API_URL || '', apiToken: process.env.GITEA_API_TOKEN || '', username: process.env.GITEA_USERNAME || '' }, coolify: { apiUrl: process.env.COOLIFY_API_URL || '', apiToken: process.env.COOLIFY_API_TOKEN || '' } }; } // --------------------------------------------------------------------------- // Routes // --------------------------------------------------------------------------- // Health check app.get('/health', (_req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // List available agents app.get('/api/agents', (_req, res) => { const agents = Object.values(agents_1.AGENTS).map(a => ({ name: a.name, description: a.description, tools: a.tools.map(t => t.name) })); res.json(agents); }); // Submit a new job app.post('/api/agent/run', async (req, res) => { const { agent: agentName, task, repo } = req.body; if (!agentName || !task) { res.status(400).json({ error: '"agent" and "task" are required' }); return; } const agentConfig = agents_1.AGENTS[agentName]; if (!agentConfig) { const available = Object.keys(agents_1.AGENTS).join(', '); res.status(400).json({ error: `Unknown agent "${agentName}". Available: ${available}` }); return; } const job = (0, job_store_1.createJob)(agentName, task, repo); res.status(202).json({ jobId: job.id, status: job.status }); // Run agent asynchronously const ctx = buildContext(repo); (0, agent_runner_1.runAgent)(job, agentConfig, task, ctx) .then(result => { (0, job_store_1.updateJob)(job.id, { status: 'completed', result: result.finalText, progress: `Done — ${result.turns} turns, ${result.toolCallCount} tool calls` }); }) .catch(err => { (0, job_store_1.updateJob)(job.id, { status: 'failed', error: err instanceof Error ? err.message : String(err), progress: 'Agent failed' }); }); }); // Check job status app.get('/api/jobs/:id', (req, res) => { const job = (0, job_store_1.getJob)(req.params.id); if (!job) { res.status(404).json({ error: 'Job not found' }); return; } res.json(job); }); // List recent jobs app.get('/api/jobs', (req, res) => { const limit = parseInt(req.query.limit || '20', 10); res.json((0, job_store_1.listJobs)(limit)); }); // Gitea webhook endpoint — triggers agent from a push/issue event app.post('/webhook/gitea', (req, res) => { const event = req.headers['x-gitea-event']; const body = req.body; // Verify secret if configured const webhookSecret = process.env.WEBHOOK_SECRET; if (webhookSecret) { const sig = req.headers['x-gitea-signature']; if (!sig || sig !== webhookSecret) { res.status(401).json({ error: 'Invalid webhook signature' }); return; } } let task = null; let agentName = 'Coder'; let repo; if (event === 'issues' && body.action === 'opened') { const issue = body.issue; repo = `${body.repository?.owner?.login}/${body.repository?.name}`; const labels = (issue.labels || []).map((l) => l.name); if (labels.includes('agent:pm')) { agentName = 'PM'; } else if (labels.includes('agent:marketing')) { agentName = 'Marketing'; } else { agentName = 'Coder'; } task = `Resolve this GitHub issue:\n\nTitle: ${issue.title}\n\nDescription:\n${issue.body || '(no description)'}`; } else if (event === 'push') { // Optionally trigger on push — useful for CI-style automation res.json({ ignored: true, reason: 'push events not auto-processed' }); return; } else { res.json({ ignored: true, event }); return; } if (!task) { res.json({ ignored: true }); return; } const agentConfig = agents_1.AGENTS[agentName]; const job = (0, job_store_1.createJob)(agentName, task, repo); res.status(202).json({ jobId: job.id, agent: agentName, event }); const ctx = buildContext(repo); (0, agent_runner_1.runAgent)(job, agentConfig, task, ctx) .then(result => { (0, job_store_1.updateJob)(job.id, { status: 'completed', result: result.finalText, progress: `Done — ${result.turns} turns, ${result.toolCallCount} tool calls` }); }) .catch(err => { (0, job_store_1.updateJob)(job.id, { status: 'failed', error: err instanceof Error ? err.message : String(err), progress: 'Agent failed' }); }); }); // --------------------------------------------------------------------------- // Error handler // --------------------------------------------------------------------------- app.use((err, _req, res, _next) => { console.error(err.stack); res.status(500).json({ error: err.message }); }); // --------------------------------------------------------------------------- // Start // --------------------------------------------------------------------------- app.listen(PORT, () => { console.log(`AgentRunner listening on port ${PORT}`); console.log(`Agents available: ${Object.keys(agents_1.AGENTS).join(', ')}`); if (!process.env.GOOGLE_API_KEY) { console.warn('WARNING: GOOGLE_API_KEY is not set — agents will fail'); } });