apteva 0.4.57 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +12 -79
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
package/src/routes/api/agents.ts
DELETED
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
import { existsSync, rmSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { json, isDev } from "./helpers";
|
|
4
|
-
import {
|
|
5
|
-
agentFetch,
|
|
6
|
-
toApiAgent, toApiAgentsBatch,
|
|
7
|
-
checkPortFree,
|
|
8
|
-
startAgentProcess,
|
|
9
|
-
buildAgentConfig,
|
|
10
|
-
pushConfigToAgent,
|
|
11
|
-
pushSkillsToAgent,
|
|
12
|
-
fetchFromAgent,
|
|
13
|
-
AGENTS_DATA_DIR,
|
|
14
|
-
META_AGENT_ID,
|
|
15
|
-
setAgentStatus,
|
|
16
|
-
} from "./agent-utils";
|
|
17
|
-
import { AgentDB, McpServerDB, SkillDB, TelemetryDB, generateId, getMultiAgentConfig, type Agent } from "../../db";
|
|
18
|
-
import { ProviderKeys } from "../../providers";
|
|
19
|
-
import { agentProcesses } from "../../server";
|
|
20
|
-
import type { AuthContext } from "../../auth/middleware";
|
|
21
|
-
|
|
22
|
-
export async function handleAgentRoutes(
|
|
23
|
-
req: Request,
|
|
24
|
-
path: string,
|
|
25
|
-
method: string,
|
|
26
|
-
authContext?: AuthContext,
|
|
27
|
-
): Promise<Response | null> {
|
|
28
|
-
// ==================== AGENT CRUD ====================
|
|
29
|
-
|
|
30
|
-
// GET /api/agents - List agents (excludes meta agent), optionally filtered by project
|
|
31
|
-
if (path === "/api/agents" && method === "GET") {
|
|
32
|
-
const url = new URL(req.url);
|
|
33
|
-
const projectId = url.searchParams.get("project_id");
|
|
34
|
-
|
|
35
|
-
let agents;
|
|
36
|
-
if (projectId === "unassigned") {
|
|
37
|
-
// Agents with no project
|
|
38
|
-
agents = AgentDB.findByProject(null);
|
|
39
|
-
} else if (projectId) {
|
|
40
|
-
agents = AgentDB.findByProject(projectId);
|
|
41
|
-
} else {
|
|
42
|
-
agents = AgentDB.findAll();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
agents = agents.filter(a => a.id !== META_AGENT_ID);
|
|
46
|
-
return json({ agents: toApiAgentsBatch(agents) });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// POST /api/agents - Create a new agent
|
|
50
|
-
if (path === "/api/agents" && method === "POST") {
|
|
51
|
-
try {
|
|
52
|
-
const body = await req.json();
|
|
53
|
-
const { name, model, provider, systemPrompt, features, projectId } = body;
|
|
54
|
-
|
|
55
|
-
if (!name) {
|
|
56
|
-
return json({ error: "Name is required" }, 400);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Import DEFAULT_FEATURES from db.ts
|
|
60
|
-
const { DEFAULT_FEATURES } = await import("../../db");
|
|
61
|
-
|
|
62
|
-
const agent = AgentDB.create({
|
|
63
|
-
id: generateId(),
|
|
64
|
-
name,
|
|
65
|
-
model: model || "claude-sonnet-4-6",
|
|
66
|
-
provider: provider || "anthropic",
|
|
67
|
-
system_prompt: systemPrompt || "You are a helpful assistant.",
|
|
68
|
-
features: features || DEFAULT_FEATURES,
|
|
69
|
-
mcp_servers: body.mcpServers || [],
|
|
70
|
-
skills: body.skills || [],
|
|
71
|
-
project_id: projectId || null,
|
|
72
|
-
} as any);
|
|
73
|
-
|
|
74
|
-
return json({ agent: toApiAgent(agent) }, 201);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error("Create agent error:", e);
|
|
77
|
-
return json({ error: "Invalid request body" }, 400);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// GET /api/agents/:id - Get a specific agent
|
|
82
|
-
const agentMatch = path.match(/^\/api\/agents\/([^/]+)$/);
|
|
83
|
-
if (agentMatch && method === "GET") {
|
|
84
|
-
const agent = AgentDB.findById(agentMatch[1]);
|
|
85
|
-
if (!agent) {
|
|
86
|
-
return json({ error: "Agent not found" }, 404);
|
|
87
|
-
}
|
|
88
|
-
return json({ agent: toApiAgent(agent) });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// PUT /api/agents/:id - Update an agent
|
|
92
|
-
if (agentMatch && method === "PUT") {
|
|
93
|
-
const agent = AgentDB.findById(agentMatch[1]);
|
|
94
|
-
if (!agent) {
|
|
95
|
-
return json({ error: "Agent not found" }, 404);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const body = await req.json();
|
|
100
|
-
const updates: Partial<Agent> = {};
|
|
101
|
-
|
|
102
|
-
if (body.name !== undefined) updates.name = body.name;
|
|
103
|
-
if (body.model !== undefined) updates.model = body.model;
|
|
104
|
-
if (body.provider !== undefined) updates.provider = body.provider;
|
|
105
|
-
if (body.systemPrompt !== undefined) updates.system_prompt = body.systemPrompt;
|
|
106
|
-
if (body.features !== undefined) updates.features = body.features;
|
|
107
|
-
if (body.mcpServers !== undefined) updates.mcp_servers = body.mcpServers;
|
|
108
|
-
if (body.skills !== undefined) updates.skills = body.skills;
|
|
109
|
-
if (body.projectId !== undefined) updates.project_id = body.projectId;
|
|
110
|
-
|
|
111
|
-
const updated = AgentDB.update(agentMatch[1], updates);
|
|
112
|
-
|
|
113
|
-
// If agent is running, handle config update
|
|
114
|
-
if (updated && updated.status === "running" && updated.port) {
|
|
115
|
-
const providerChanged = body.provider !== undefined && body.provider !== agent.provider;
|
|
116
|
-
|
|
117
|
-
if (providerChanged) {
|
|
118
|
-
// Provider changed — must restart to get new API key in env
|
|
119
|
-
console.log(`Provider changed for ${updated.name} (${agent.provider} -> ${updated.provider}), restarting...`);
|
|
120
|
-
const agentProc = agentProcesses.get(updated.id);
|
|
121
|
-
if (agentProc) {
|
|
122
|
-
// Graceful shutdown
|
|
123
|
-
try {
|
|
124
|
-
await fetch(`http://localhost:${updated.port}/shutdown`, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
signal: AbortSignal.timeout(2000),
|
|
127
|
-
});
|
|
128
|
-
await new Promise(r => setTimeout(r, 500));
|
|
129
|
-
} catch {}
|
|
130
|
-
try { agentProc.proc.kill(); } catch {}
|
|
131
|
-
agentProcesses.delete(updated.id);
|
|
132
|
-
}
|
|
133
|
-
setAgentStatus(updated.id, "stopped", "provider_changed");
|
|
134
|
-
// Start with new provider
|
|
135
|
-
const startResult = await startAgentProcess(updated, { silent: true });
|
|
136
|
-
if (!startResult.success) {
|
|
137
|
-
console.error(`Failed to restart agent after provider change: ${startResult.error}`);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
// Same provider — just push updated config
|
|
141
|
-
const providerKey = ProviderKeys.getDecrypted(updated.provider);
|
|
142
|
-
if (providerKey) {
|
|
143
|
-
const config = buildAgentConfig(updated, providerKey);
|
|
144
|
-
const configResult = await pushConfigToAgent(updated.id, updated.port, config);
|
|
145
|
-
if (!configResult.success) {
|
|
146
|
-
console.error(`Failed to push config to running agent: ${configResult.error}`);
|
|
147
|
-
}
|
|
148
|
-
// Push skills via /skills endpoint
|
|
149
|
-
if (config.skills?.definitions?.length > 0) {
|
|
150
|
-
const skillsResult = await pushSkillsToAgent(updated.id, updated.port, config.skills.definitions);
|
|
151
|
-
if (!skillsResult.success) {
|
|
152
|
-
console.error(`Failed to push skills to running agent: ${skillsResult.error}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return json({ agent: updated ? toApiAgent(updated) : null });
|
|
160
|
-
} catch (e) {
|
|
161
|
-
return json({ error: "Invalid request body" }, 400);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// DELETE /api/agents/:id - Delete an agent
|
|
166
|
-
if (agentMatch && method === "DELETE") {
|
|
167
|
-
const agentId = agentMatch[1];
|
|
168
|
-
const agent = AgentDB.findById(agentId);
|
|
169
|
-
if (!agent) {
|
|
170
|
-
return json({ error: "Agent not found" }, 404);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Stop the agent if running
|
|
174
|
-
const agentProc = agentProcesses.get(agentId);
|
|
175
|
-
const port = agent.port;
|
|
176
|
-
|
|
177
|
-
if (agentProc) {
|
|
178
|
-
// Try graceful shutdown first
|
|
179
|
-
if (port) {
|
|
180
|
-
try {
|
|
181
|
-
await fetch(`http://localhost:${port}/shutdown`, {
|
|
182
|
-
method: "POST",
|
|
183
|
-
signal: AbortSignal.timeout(2000),
|
|
184
|
-
});
|
|
185
|
-
await new Promise(r => setTimeout(r, 500));
|
|
186
|
-
} catch {
|
|
187
|
-
// Graceful shutdown failed
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
agentProc.proc.kill();
|
|
193
|
-
} catch {
|
|
194
|
-
// Already dead
|
|
195
|
-
}
|
|
196
|
-
agentProcesses.delete(agentId);
|
|
197
|
-
|
|
198
|
-
// Ensure port is freed
|
|
199
|
-
if (port) {
|
|
200
|
-
const isFree = await checkPortFree(port);
|
|
201
|
-
if (!isFree) {
|
|
202
|
-
try {
|
|
203
|
-
const { execSync } = await import("child_process");
|
|
204
|
-
execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, { stdio: "ignore" });
|
|
205
|
-
} catch {
|
|
206
|
-
// Ignore
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Delete agent's telemetry data
|
|
213
|
-
TelemetryDB.deleteByAgent(agentId);
|
|
214
|
-
|
|
215
|
-
// Delete agent's data directory (contains threads, messages, etc.)
|
|
216
|
-
const agentDataDir = join(AGENTS_DATA_DIR, agentId);
|
|
217
|
-
if (existsSync(agentDataDir)) {
|
|
218
|
-
try {
|
|
219
|
-
rmSync(agentDataDir, { recursive: true, force: true });
|
|
220
|
-
console.log(`Deleted agent data directory: ${agentDataDir}`);
|
|
221
|
-
} catch (err) {
|
|
222
|
-
console.error(`Failed to delete agent data directory: ${err}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
AgentDB.delete(agentId);
|
|
227
|
-
return json({ success: true });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ==================== AGENT API KEY ====================
|
|
231
|
-
|
|
232
|
-
// GET /api/agents/:id/api-key - Get the agent's API key (masked)
|
|
233
|
-
const apiKeyMatch = path.match(/^\/api\/agents\/([^/]+)\/api-key$/);
|
|
234
|
-
if (apiKeyMatch && method === "GET") {
|
|
235
|
-
const agent = AgentDB.findById(apiKeyMatch[1]);
|
|
236
|
-
if (!agent) {
|
|
237
|
-
return json({ error: "Agent not found" }, 404);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const apiKey = AgentDB.getApiKey(agent.id);
|
|
241
|
-
if (!apiKey) {
|
|
242
|
-
return json({ error: "No API key found for this agent" }, 404);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Return masked key + full key (full key only shown on demand by frontend)
|
|
246
|
-
const masked = apiKey.substring(0, 8) + "..." + apiKey.substring(apiKey.length - 4);
|
|
247
|
-
return json({
|
|
248
|
-
apiKey: masked,
|
|
249
|
-
fullKey: apiKey,
|
|
250
|
-
hasKey: true,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// POST /api/agents/:id/api-key - Regenerate the agent's API key
|
|
255
|
-
if (apiKeyMatch && method === "POST") {
|
|
256
|
-
const agent = AgentDB.findById(apiKeyMatch[1]);
|
|
257
|
-
if (!agent) {
|
|
258
|
-
return json({ error: "Agent not found" }, 404);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const newKey = AgentDB.regenerateApiKey(agent.id);
|
|
262
|
-
if (!newKey) {
|
|
263
|
-
return json({ error: "Failed to regenerate API key" }, 500);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Return the full new key (only time it's fully visible)
|
|
267
|
-
return json({
|
|
268
|
-
apiKey: newKey,
|
|
269
|
-
message: "API key regenerated. This is the only time the full key will be shown.",
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// ==================== SHARE LINK ====================
|
|
274
|
-
|
|
275
|
-
// GET /api/agents/:id/share-token - Get the share token for this agent
|
|
276
|
-
const shareTokenMatch = path.match(/^\/api\/agents\/([^/]+)\/share-token$/);
|
|
277
|
-
if (shareTokenMatch && method === "GET") {
|
|
278
|
-
const agent = AgentDB.findById(shareTokenMatch[1]);
|
|
279
|
-
if (!agent) {
|
|
280
|
-
return json({ error: "Agent not found" }, 404);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const { getShareToken } = await import("../share");
|
|
284
|
-
const token = getShareToken(agent.id);
|
|
285
|
-
if (!token) {
|
|
286
|
-
return json({ error: "Could not generate share token" }, 500);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return json({ token });
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// ==================== AGENT LIFECYCLE ====================
|
|
293
|
-
|
|
294
|
-
// POST /api/agents/:id/start - Start an agent
|
|
295
|
-
const startMatch = path.match(/^\/api\/agents\/([^/]+)\/start$/);
|
|
296
|
-
if (startMatch && method === "POST") {
|
|
297
|
-
const agent = AgentDB.findById(startMatch[1]);
|
|
298
|
-
if (!agent) {
|
|
299
|
-
return json({ error: "Agent not found" }, 404);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const result = await startAgentProcess(agent);
|
|
303
|
-
if (!result.success) {
|
|
304
|
-
return json({ error: result.error }, 400);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const updated = AgentDB.findById(agent.id);
|
|
308
|
-
return json({ agent: updated ? toApiAgent(updated) : null, message: `Agent started on port ${result.port}` });
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// POST /api/agents/:id/stop - Stop an agent
|
|
312
|
-
const stopMatch = path.match(/^\/api\/agents\/([^/]+)\/stop$/);
|
|
313
|
-
if (stopMatch && method === "POST") {
|
|
314
|
-
const agent = AgentDB.findById(stopMatch[1]);
|
|
315
|
-
if (!agent) {
|
|
316
|
-
return json({ error: "Agent not found" }, 404);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const agentProc = agentProcesses.get(agent.id);
|
|
320
|
-
const port = agent.port;
|
|
321
|
-
|
|
322
|
-
if (agentProc) {
|
|
323
|
-
console.log(`Stopping agent ${agent.name} (pid: ${agentProc.proc.pid})...`);
|
|
324
|
-
|
|
325
|
-
// Try graceful shutdown first
|
|
326
|
-
if (port) {
|
|
327
|
-
try {
|
|
328
|
-
await fetch(`http://localhost:${port}/shutdown`, {
|
|
329
|
-
method: "POST",
|
|
330
|
-
signal: AbortSignal.timeout(2000),
|
|
331
|
-
});
|
|
332
|
-
await new Promise(r => setTimeout(r, 500)); // Wait for graceful shutdown
|
|
333
|
-
} catch {
|
|
334
|
-
// Graceful shutdown failed or timed out
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Force kill if still running
|
|
339
|
-
try {
|
|
340
|
-
agentProc.proc.kill();
|
|
341
|
-
} catch {
|
|
342
|
-
// Already dead
|
|
343
|
-
}
|
|
344
|
-
agentProcesses.delete(agent.id);
|
|
345
|
-
|
|
346
|
-
// Ensure port is freed
|
|
347
|
-
if (port) {
|
|
348
|
-
const isFree = await checkPortFree(port);
|
|
349
|
-
if (!isFree) {
|
|
350
|
-
// Force kill by port
|
|
351
|
-
try {
|
|
352
|
-
const { execSync } = await import("child_process");
|
|
353
|
-
execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, { stdio: "ignore" });
|
|
354
|
-
} catch {
|
|
355
|
-
// Ignore
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const updated = setAgentStatus(agent.id, "stopped", "user_stopped");
|
|
362
|
-
return json({ agent: updated ? toApiAgent(updated) : null, message: "Agent stopped" });
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// POST /api/agents/:id/chat - Proxy chat to agent binary with streaming
|
|
366
|
-
const chatMatch = path.match(/^\/api\/agents\/([^/]+)\/chat$/);
|
|
367
|
-
if (chatMatch && method === "POST") {
|
|
368
|
-
const agent = AgentDB.findById(chatMatch[1]);
|
|
369
|
-
if (!agent) {
|
|
370
|
-
return json({ error: "Agent not found" }, 404);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (agent.status !== "running" || !agent.port) {
|
|
374
|
-
return json({ error: "Agent is not running" }, 400);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
const body = await req.json();
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
// Proxy to the agent's /chat endpoint with authentication
|
|
382
|
-
const response = await agentFetch(agent.id, agent.port, "/chat", {
|
|
383
|
-
method: "POST",
|
|
384
|
-
headers: { "Content-Type": "application/json" },
|
|
385
|
-
body: JSON.stringify(body),
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// Stream the response back
|
|
389
|
-
if (!response.ok) {
|
|
390
|
-
const errorText = await response.text();
|
|
391
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Return streaming response with proper headers
|
|
395
|
-
return new Response(response.body, {
|
|
396
|
-
status: 200,
|
|
397
|
-
headers: {
|
|
398
|
-
"Content-Type": response.headers.get("Content-Type") || "text/event-stream",
|
|
399
|
-
"Cache-Control": "no-cache",
|
|
400
|
-
"Connection": "keep-alive",
|
|
401
|
-
},
|
|
402
|
-
});
|
|
403
|
-
} catch (err) {
|
|
404
|
-
console.error(`Chat proxy error: ${err}`);
|
|
405
|
-
return json({ error: `Failed to proxy chat: ${err}` }, 500);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ==================== WEBHOOK ENDPOINT ====================
|
|
410
|
-
|
|
411
|
-
// POST /api/agents/:id/webhook - Receive external trigger events and forward to agent chat
|
|
412
|
-
const webhookMatch = path.match(/^\/api\/agents\/([^/]+)\/webhook$/);
|
|
413
|
-
if (webhookMatch && method === "POST") {
|
|
414
|
-
const agent = AgentDB.findById(webhookMatch[1]);
|
|
415
|
-
if (!agent) {
|
|
416
|
-
return json({ error: "Agent not found" }, 404);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (agent.status !== "running" || !agent.port) {
|
|
420
|
-
return json({ error: "Agent is not running" }, 400);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
const body = await req.json();
|
|
425
|
-
|
|
426
|
-
// Format the webhook payload as a chat message
|
|
427
|
-
const triggerSlug = body.trigger_name || body.type || "unknown_trigger";
|
|
428
|
-
const eventPayload = body.payload || body.data || body;
|
|
429
|
-
|
|
430
|
-
const triggerName = String(triggerSlug).replace(/_/g, " ");
|
|
431
|
-
const message = [
|
|
432
|
-
`[Trigger: ${triggerName}]`,
|
|
433
|
-
"",
|
|
434
|
-
"```json",
|
|
435
|
-
JSON.stringify(eventPayload, null, 2),
|
|
436
|
-
"```",
|
|
437
|
-
"",
|
|
438
|
-
"Process this event and take appropriate action.",
|
|
439
|
-
].join("\n");
|
|
440
|
-
|
|
441
|
-
// Forward to agent's /chat endpoint
|
|
442
|
-
const response = await agentFetch(agent.id, agent.port, "/chat", {
|
|
443
|
-
method: "POST",
|
|
444
|
-
headers: { "Content-Type": "application/json" },
|
|
445
|
-
body: JSON.stringify({ message }),
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Consume the streaming response (we don't need the agent's reply)
|
|
449
|
-
if (response.body) {
|
|
450
|
-
try {
|
|
451
|
-
const reader = response.body.getReader();
|
|
452
|
-
while (true) {
|
|
453
|
-
const { done } = await reader.read();
|
|
454
|
-
if (done) break;
|
|
455
|
-
}
|
|
456
|
-
} catch {
|
|
457
|
-
// Ignore read errors
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (!response.ok) {
|
|
462
|
-
return json({ error: "Agent failed to process webhook" }, 502);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return json({ received: true, agent_id: agent.id, trigger: triggerSlug });
|
|
466
|
-
} catch (err) {
|
|
467
|
-
console.error(`Webhook proxy error for agent ${webhookMatch[1]}:`, err);
|
|
468
|
-
return json({ error: `Failed to process webhook: ${err}` }, 500);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// ==================== THREAD & MESSAGE PROXY ====================
|
|
473
|
-
|
|
474
|
-
// GET /api/threads - Consolidated threads from all running agents
|
|
475
|
-
if (path === "/api/threads" && method === "GET") {
|
|
476
|
-
const url = new URL(req.url);
|
|
477
|
-
const projectId = url.searchParams.get("project_id");
|
|
478
|
-
|
|
479
|
-
let agents;
|
|
480
|
-
if (projectId === "unassigned") {
|
|
481
|
-
agents = AgentDB.findByProject(null);
|
|
482
|
-
} else if (projectId) {
|
|
483
|
-
agents = AgentDB.findByProject(projectId);
|
|
484
|
-
} else {
|
|
485
|
-
agents = AgentDB.findAll();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Only query running agents (excluding meta agent)
|
|
489
|
-
const runningAgents = agents.filter(a => a.id !== META_AGENT_ID && a.status === "running" && a.port);
|
|
490
|
-
|
|
491
|
-
const results = await Promise.allSettled(
|
|
492
|
-
runningAgents.map(async (agent) => {
|
|
493
|
-
const response = await agentFetch(agent.id, agent.port!, "/threads", {
|
|
494
|
-
method: "GET",
|
|
495
|
-
headers: { "Accept": "application/json" },
|
|
496
|
-
signal: AbortSignal.timeout(3000),
|
|
497
|
-
});
|
|
498
|
-
if (!response.ok) return [];
|
|
499
|
-
const data = await response.json() as any;
|
|
500
|
-
const threads = Array.isArray(data) ? data : (data.threads ?? []);
|
|
501
|
-
return threads.map((t: any) => ({
|
|
502
|
-
...t,
|
|
503
|
-
agent_id: agent.id,
|
|
504
|
-
agent_name: agent.name,
|
|
505
|
-
}));
|
|
506
|
-
})
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
const allThreads = results
|
|
510
|
-
.filter((r): r is PromiseFulfilledResult<any[]> => r.status === "fulfilled")
|
|
511
|
-
.flatMap(r => r.value)
|
|
512
|
-
.filter((t: any) => !t.parent_id);
|
|
513
|
-
|
|
514
|
-
// Sort by most recent first
|
|
515
|
-
allThreads.sort((a, b) => {
|
|
516
|
-
const ta = a.updated_at || a.created_at || "";
|
|
517
|
-
const tb = b.updated_at || b.created_at || "";
|
|
518
|
-
return tb.localeCompare(ta);
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
return json({ threads: allThreads });
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// GET/POST /api/agents/:id/threads
|
|
525
|
-
const threadsListMatch = path.match(/^\/api\/agents\/([^/]+)\/threads$/);
|
|
526
|
-
if (threadsListMatch && method === "GET") {
|
|
527
|
-
const agent = AgentDB.findById(threadsListMatch[1]);
|
|
528
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
529
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
530
|
-
|
|
531
|
-
try {
|
|
532
|
-
const response = await agentFetch(agent.id, agent.port, "/threads", {
|
|
533
|
-
method: "GET",
|
|
534
|
-
headers: { "Accept": "application/json" },
|
|
535
|
-
});
|
|
536
|
-
if (!response.ok) {
|
|
537
|
-
const errorText = await response.text();
|
|
538
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
539
|
-
}
|
|
540
|
-
const data = await response.json();
|
|
541
|
-
return json(data);
|
|
542
|
-
} catch (err) {
|
|
543
|
-
console.error(`Threads list proxy error: ${err}`);
|
|
544
|
-
return json({ error: `Failed to fetch threads: ${err}` }, 500);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (threadsListMatch && method === "POST") {
|
|
549
|
-
const agent = AgentDB.findById(threadsListMatch[1]);
|
|
550
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
551
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
552
|
-
|
|
553
|
-
try {
|
|
554
|
-
const body = await req.json().catch(() => ({}));
|
|
555
|
-
const response = await agentFetch(agent.id, agent.port, "/threads", {
|
|
556
|
-
method: "POST",
|
|
557
|
-
headers: { "Content-Type": "application/json" },
|
|
558
|
-
body: JSON.stringify(body),
|
|
559
|
-
});
|
|
560
|
-
if (!response.ok) {
|
|
561
|
-
const errorText = await response.text();
|
|
562
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
563
|
-
}
|
|
564
|
-
const data = await response.json();
|
|
565
|
-
return json(data, 201);
|
|
566
|
-
} catch (err) {
|
|
567
|
-
console.error(`Thread create proxy error: ${err}`);
|
|
568
|
-
return json({ error: `Failed to create thread: ${err}` }, 500);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// GET/DELETE /api/agents/:id/threads/:threadId
|
|
573
|
-
const threadDetailMatch = path.match(/^\/api\/agents\/([^/]+)\/threads\/([^/]+)$/);
|
|
574
|
-
if (threadDetailMatch && method === "GET") {
|
|
575
|
-
const agent = AgentDB.findById(threadDetailMatch[1]);
|
|
576
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
577
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
578
|
-
|
|
579
|
-
try {
|
|
580
|
-
const threadId = threadDetailMatch[2];
|
|
581
|
-
const response = await agentFetch(agent.id, agent.port, `/threads/${threadId}`, {
|
|
582
|
-
method: "GET",
|
|
583
|
-
headers: { "Accept": "application/json" },
|
|
584
|
-
});
|
|
585
|
-
if (!response.ok) {
|
|
586
|
-
const errorText = await response.text();
|
|
587
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
588
|
-
}
|
|
589
|
-
const data = await response.json();
|
|
590
|
-
return json(data);
|
|
591
|
-
} catch (err) {
|
|
592
|
-
console.error(`Thread detail proxy error: ${err}`);
|
|
593
|
-
return json({ error: `Failed to fetch thread: ${err}` }, 500);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (threadDetailMatch && method === "DELETE") {
|
|
598
|
-
const agent = AgentDB.findById(threadDetailMatch[1]);
|
|
599
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
600
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
601
|
-
|
|
602
|
-
try {
|
|
603
|
-
const threadId = threadDetailMatch[2];
|
|
604
|
-
const response = await agentFetch(agent.id, agent.port, `/threads/${threadId}`, { method: "DELETE" });
|
|
605
|
-
if (!response.ok) {
|
|
606
|
-
const errorText = await response.text();
|
|
607
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
608
|
-
}
|
|
609
|
-
return json({ success: true });
|
|
610
|
-
} catch (err) {
|
|
611
|
-
console.error(`Thread delete proxy error: ${err}`);
|
|
612
|
-
return json({ error: `Failed to delete thread: ${err}` }, 500);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// GET /api/agents/:id/threads/:threadId/messages
|
|
617
|
-
const threadMessagesMatch = path.match(/^\/api\/agents\/([^/]+)\/threads\/([^/]+)\/messages$/);
|
|
618
|
-
if (threadMessagesMatch && method === "GET") {
|
|
619
|
-
const agent = AgentDB.findById(threadMessagesMatch[1]);
|
|
620
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
621
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
622
|
-
|
|
623
|
-
try {
|
|
624
|
-
const threadId = threadMessagesMatch[2];
|
|
625
|
-
const response = await agentFetch(agent.id, agent.port, `/threads/${threadId}/messages`, {
|
|
626
|
-
method: "GET",
|
|
627
|
-
headers: { "Accept": "application/json" },
|
|
628
|
-
});
|
|
629
|
-
if (!response.ok) {
|
|
630
|
-
const errorText = await response.text();
|
|
631
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
632
|
-
}
|
|
633
|
-
const data = await response.json();
|
|
634
|
-
return json(data);
|
|
635
|
-
} catch (err) {
|
|
636
|
-
console.error(`Thread messages proxy error: ${err}`);
|
|
637
|
-
return json({ error: `Failed to fetch messages: ${err}` }, 500);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ==================== MEMORY PROXY ====================
|
|
642
|
-
|
|
643
|
-
const memoriesMatch = path.match(/^\/api\/agents\/([^/]+)\/memories$/);
|
|
644
|
-
if (memoriesMatch && method === "GET") {
|
|
645
|
-
const agent = AgentDB.findById(memoriesMatch[1]);
|
|
646
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
647
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
648
|
-
|
|
649
|
-
try {
|
|
650
|
-
const url = new URL(req.url);
|
|
651
|
-
const threadId = url.searchParams.get("thread_id") || "";
|
|
652
|
-
const endpoint = `/memories${threadId ? `?thread_id=${threadId}` : ""}`;
|
|
653
|
-
const response = await agentFetch(agent.id, agent.port, endpoint, {
|
|
654
|
-
method: "GET",
|
|
655
|
-
headers: { "Accept": "application/json" },
|
|
656
|
-
});
|
|
657
|
-
if (!response.ok) {
|
|
658
|
-
const errorText = await response.text();
|
|
659
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
660
|
-
}
|
|
661
|
-
const data = await response.json();
|
|
662
|
-
return json(data);
|
|
663
|
-
} catch (err) {
|
|
664
|
-
console.error(`Memories list proxy error: ${err}`);
|
|
665
|
-
return json({ error: `Failed to fetch memories: ${err}` }, 500);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (memoriesMatch && method === "DELETE") {
|
|
670
|
-
const agent = AgentDB.findById(memoriesMatch[1]);
|
|
671
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
672
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
673
|
-
|
|
674
|
-
try {
|
|
675
|
-
const response = await agentFetch(agent.id, agent.port, "/memories", { method: "DELETE" });
|
|
676
|
-
if (!response.ok) {
|
|
677
|
-
const errorText = await response.text();
|
|
678
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
679
|
-
}
|
|
680
|
-
return json({ success: true });
|
|
681
|
-
} catch (err) {
|
|
682
|
-
console.error(`Memories clear proxy error: ${err}`);
|
|
683
|
-
return json({ error: `Failed to clear memories: ${err}` }, 500);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
const memoryDeleteMatch = path.match(/^\/api\/agents\/([^/]+)\/memories\/([^/]+)$/);
|
|
688
|
-
if (memoryDeleteMatch && method === "DELETE") {
|
|
689
|
-
const agent = AgentDB.findById(memoryDeleteMatch[1]);
|
|
690
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
691
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
692
|
-
|
|
693
|
-
try {
|
|
694
|
-
const memoryId = memoryDeleteMatch[2];
|
|
695
|
-
const response = await agentFetch(agent.id, agent.port, `/memories/${memoryId}`, { method: "DELETE" });
|
|
696
|
-
if (!response.ok) {
|
|
697
|
-
const errorText = await response.text();
|
|
698
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
699
|
-
}
|
|
700
|
-
return json({ success: true });
|
|
701
|
-
} catch (err) {
|
|
702
|
-
console.error(`Memory delete proxy error: ${err}`);
|
|
703
|
-
return json({ error: `Failed to delete memory: ${err}` }, 500);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// ==================== FILES PROXY ====================
|
|
708
|
-
|
|
709
|
-
const filesMatch = path.match(/^\/api\/agents\/([^/]+)\/files$/);
|
|
710
|
-
if (filesMatch && method === "POST") {
|
|
711
|
-
const agent = AgentDB.findById(filesMatch[1]);
|
|
712
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
713
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
714
|
-
|
|
715
|
-
try {
|
|
716
|
-
const contentType = req.headers.get("content-type") || "";
|
|
717
|
-
const body = await req.arrayBuffer();
|
|
718
|
-
const response = await agentFetch(agent.id, agent.port, "/files", {
|
|
719
|
-
method: "POST",
|
|
720
|
-
headers: { "Content-Type": contentType },
|
|
721
|
-
body: body,
|
|
722
|
-
});
|
|
723
|
-
if (!response.ok) {
|
|
724
|
-
const errorText = await response.text();
|
|
725
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
726
|
-
}
|
|
727
|
-
const data = await response.json();
|
|
728
|
-
return json(data);
|
|
729
|
-
} catch (err) {
|
|
730
|
-
console.error(`File upload proxy error: ${err}`);
|
|
731
|
-
return json({ error: `Failed to upload file: ${err}` }, 500);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (filesMatch && method === "GET") {
|
|
736
|
-
const agent = AgentDB.findById(filesMatch[1]);
|
|
737
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
738
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
739
|
-
|
|
740
|
-
try {
|
|
741
|
-
const url = new URL(req.url);
|
|
742
|
-
const params = new URLSearchParams();
|
|
743
|
-
if (url.searchParams.get("thread_id")) params.set("thread_id", url.searchParams.get("thread_id")!);
|
|
744
|
-
if (url.searchParams.get("limit")) params.set("limit", url.searchParams.get("limit")!);
|
|
745
|
-
|
|
746
|
-
const endpoint = `/files${params.toString() ? `?${params}` : ""}`;
|
|
747
|
-
const response = await agentFetch(agent.id, agent.port, endpoint, {
|
|
748
|
-
method: "GET",
|
|
749
|
-
headers: { "Accept": "application/json" },
|
|
750
|
-
});
|
|
751
|
-
if (!response.ok) {
|
|
752
|
-
const errorText = await response.text();
|
|
753
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
754
|
-
}
|
|
755
|
-
const data = await response.json();
|
|
756
|
-
return json(data);
|
|
757
|
-
} catch (err) {
|
|
758
|
-
console.error(`Files list proxy error: ${err}`);
|
|
759
|
-
return json({ error: `Failed to fetch files: ${err}` }, 500);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// GET/DELETE /api/agents/:id/files/:fileId/download and /api/agents/:id/files/:fileId
|
|
764
|
-
const fileDownloadMatch = path.match(/^\/api\/agents\/([^/]+)\/files\/([^/]+)\/download$/);
|
|
765
|
-
if (fileDownloadMatch && method === "GET") {
|
|
766
|
-
const agent = AgentDB.findById(fileDownloadMatch[1]);
|
|
767
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
768
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
769
|
-
|
|
770
|
-
try {
|
|
771
|
-
const fileId = fileDownloadMatch[2];
|
|
772
|
-
const response = await agentFetch(agent.id, agent.port, `/files/${fileId}/download`);
|
|
773
|
-
if (!response.ok) {
|
|
774
|
-
const errorText = await response.text();
|
|
775
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
776
|
-
}
|
|
777
|
-
return new Response(response.body, {
|
|
778
|
-
status: response.status,
|
|
779
|
-
headers: {
|
|
780
|
-
"Content-Type": response.headers.get("Content-Type") || "application/octet-stream",
|
|
781
|
-
"Content-Disposition": response.headers.get("Content-Disposition") || "attachment",
|
|
782
|
-
"Content-Length": response.headers.get("Content-Length") || "",
|
|
783
|
-
},
|
|
784
|
-
});
|
|
785
|
-
} catch (err) {
|
|
786
|
-
console.error(`File download proxy error: ${err}`);
|
|
787
|
-
return json({ error: `Failed to download file: ${err}` }, 500);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
const fileGetMatch = path.match(/^\/api\/agents\/([^/]+)\/files\/([^/]+)$/);
|
|
792
|
-
if (fileGetMatch && method === "GET") {
|
|
793
|
-
const agent = AgentDB.findById(fileGetMatch[1]);
|
|
794
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
795
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
796
|
-
|
|
797
|
-
try {
|
|
798
|
-
const fileId = fileGetMatch[2];
|
|
799
|
-
const response = await agentFetch(agent.id, agent.port, `/files/${fileId}`, {
|
|
800
|
-
method: "GET",
|
|
801
|
-
headers: { "Accept": "application/json" },
|
|
802
|
-
});
|
|
803
|
-
if (!response.ok) {
|
|
804
|
-
const errorText = await response.text();
|
|
805
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
806
|
-
}
|
|
807
|
-
const data = await response.json();
|
|
808
|
-
return json(data);
|
|
809
|
-
} catch (err) {
|
|
810
|
-
console.error(`File get proxy error: ${err}`);
|
|
811
|
-
return json({ error: `Failed to fetch file: ${err}` }, 500);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
if (fileGetMatch && method === "DELETE") {
|
|
816
|
-
const agent = AgentDB.findById(fileGetMatch[1]);
|
|
817
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
818
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
819
|
-
|
|
820
|
-
try {
|
|
821
|
-
const fileId = fileGetMatch[2];
|
|
822
|
-
const response = await agentFetch(agent.id, agent.port, `/files/${fileId}`, { method: "DELETE" });
|
|
823
|
-
if (!response.ok) {
|
|
824
|
-
const errorText = await response.text();
|
|
825
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
826
|
-
}
|
|
827
|
-
return json({ success: true });
|
|
828
|
-
} catch (err) {
|
|
829
|
-
console.error(`File delete proxy error: ${err}`);
|
|
830
|
-
return json({ error: `Failed to delete file: ${err}` }, 500);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// ==================== DISCOVERY/PEERS PROXY ====================
|
|
835
|
-
|
|
836
|
-
// GET /api/discovery/agents - Central discovery endpoint for agents to find peers
|
|
837
|
-
if (path === "/api/discovery/agents" && method === "GET") {
|
|
838
|
-
const url = new URL(req.url); // BUG FIX: was missing url declaration
|
|
839
|
-
const group = url.searchParams.get("group");
|
|
840
|
-
const excludeId = url.searchParams.get("exclude") || req.headers.get("X-Agent-ID");
|
|
841
|
-
|
|
842
|
-
// Find all running agents in the same group
|
|
843
|
-
const allAgents = AgentDB.findAll();
|
|
844
|
-
const peers = allAgents
|
|
845
|
-
.filter(a => {
|
|
846
|
-
if (a.status !== "running" || !a.port) return false;
|
|
847
|
-
if (excludeId && a.id === excludeId) return false;
|
|
848
|
-
const agentConfig = getMultiAgentConfig(a.features, a.project_id);
|
|
849
|
-
if (!agentConfig.enabled) return false;
|
|
850
|
-
if (group) {
|
|
851
|
-
const peerGroup = agentConfig.group || a.project_id;
|
|
852
|
-
if (peerGroup !== group) return false;
|
|
853
|
-
}
|
|
854
|
-
return true;
|
|
855
|
-
})
|
|
856
|
-
.map(a => {
|
|
857
|
-
const agentConfig = getMultiAgentConfig(a.features, a.project_id);
|
|
858
|
-
return {
|
|
859
|
-
id: a.id,
|
|
860
|
-
name: a.name,
|
|
861
|
-
url: `http://localhost:${a.port}`,
|
|
862
|
-
group: agentConfig.group || a.project_id,
|
|
863
|
-
};
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
return json({ agents: peers });
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// GET /api/agents/:id/peers - Get discovered peer agents
|
|
870
|
-
const peersMatch = path.match(/^\/api\/agents\/([^/]+)\/peers$/);
|
|
871
|
-
if (peersMatch && method === "GET") {
|
|
872
|
-
const agent = AgentDB.findById(peersMatch[1]);
|
|
873
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
874
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
875
|
-
|
|
876
|
-
try {
|
|
877
|
-
const response = await agentFetch(agent.id, agent.port, "/discovery/agents", {
|
|
878
|
-
method: "GET",
|
|
879
|
-
headers: { "Accept": "application/json" },
|
|
880
|
-
});
|
|
881
|
-
if (!response.ok) {
|
|
882
|
-
const errorText = await response.text();
|
|
883
|
-
return json({ error: `Agent error: ${errorText}` }, response.status);
|
|
884
|
-
}
|
|
885
|
-
const data = await response.json();
|
|
886
|
-
return json(data);
|
|
887
|
-
} catch (err) {
|
|
888
|
-
console.error(`Peers list proxy error: ${err}`);
|
|
889
|
-
return json({ error: `Failed to fetch peers: ${err}` }, 500);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// ==================== AGENT TASKS ====================
|
|
894
|
-
|
|
895
|
-
// GET /api/agents/:id/tasks - Get tasks from a specific agent
|
|
896
|
-
const agentTasksMatch = path.match(/^\/api\/agents\/([^/]+)\/tasks$/);
|
|
897
|
-
if (agentTasksMatch && method === "GET") {
|
|
898
|
-
const agentId = agentTasksMatch[1];
|
|
899
|
-
const agent = AgentDB.findById(agentId);
|
|
900
|
-
|
|
901
|
-
if (!agent) return json({ error: "Agent not found" }, 404);
|
|
902
|
-
if (agent.status !== "running" || !agent.port) return json({ error: "Agent is not running" }, 400);
|
|
903
|
-
|
|
904
|
-
const url = new URL(req.url);
|
|
905
|
-
const status = url.searchParams.get("status") || "all";
|
|
906
|
-
|
|
907
|
-
const data = await fetchFromAgent(agent.id, agent.port, `/tasks?status=${status}`);
|
|
908
|
-
if (!data) {
|
|
909
|
-
return json({ error: "Failed to fetch tasks from agent" }, 500);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
return json(data);
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
return null;
|
|
916
|
-
}
|