241 lines
8.9 KiB
JavaScript
241 lines
8.9 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
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 fs = __importStar(require("fs"));
|
|
const path = __importStar(require("path"));
|
|
const child_process_1 = require("child_process");
|
|
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 ensureWorkspace(repo) {
|
|
const base = process.env.WORKSPACE_BASE || '/workspaces';
|
|
if (!repo) {
|
|
const dir = path.join(base, 'default');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
return dir;
|
|
}
|
|
const dir = path.join(base, repo.replace('/', '_'));
|
|
const gitea = {
|
|
apiUrl: process.env.GITEA_API_URL || '',
|
|
apiToken: process.env.GITEA_API_TOKEN || '',
|
|
username: process.env.GITEA_USERNAME || ''
|
|
};
|
|
if (!fs.existsSync(path.join(dir, '.git'))) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
const authedUrl = `${gitea.apiUrl}/${repo}.git`
|
|
.replace('https://', `https://${gitea.username}:${gitea.apiToken}@`);
|
|
try {
|
|
(0, child_process_1.execSync)(`git clone "${authedUrl}" "${dir}"`, { stdio: 'pipe' });
|
|
}
|
|
catch {
|
|
// Repo may not exist yet — just init
|
|
(0, child_process_1.execSync)(`git init`, { cwd: dir, stdio: 'pipe' });
|
|
(0, child_process_1.execSync)(`git remote add origin "${authedUrl}"`, { cwd: dir, stdio: 'pipe' });
|
|
}
|
|
}
|
|
return dir;
|
|
}
|
|
function buildContext(repo) {
|
|
const workspaceRoot = ensureWorkspace(repo);
|
|
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');
|
|
}
|
|
});
|