agentgui 1.0.383 → 1.0.385
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/package.json +1 -1
- package/server.js +9 -573
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -12,7 +12,8 @@ import { OAuth2Client } from 'google-auth-library';
|
|
|
12
12
|
import express from 'express';
|
|
13
13
|
import Busboy from 'busboy';
|
|
14
14
|
import fsbrowse from 'fsbrowse';
|
|
15
|
-
import { queries } from './database.js';
|
|
15
|
+
import { queries, db, prepare } from './database.js';
|
|
16
|
+
import { createACPQueries } from './acp-queries.js';
|
|
16
17
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
17
18
|
import { initializeDescriptors, getAgentDescriptor } from './lib/agent-descriptors.js';
|
|
18
19
|
|
|
@@ -340,6 +341,8 @@ function discoverAgents() {
|
|
|
340
341
|
|
|
341
342
|
const discoveredAgents = discoverAgents();
|
|
342
343
|
initializeDescriptors(discoveredAgents);
|
|
344
|
+
const acpQueries = createACPQueries(db, prepare);
|
|
345
|
+
acpQueries.getAgentDescriptor = getAgentDescriptor;
|
|
343
346
|
|
|
344
347
|
const modelCache = new Map();
|
|
345
348
|
|
|
@@ -1512,7 +1515,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1512
1515
|
}
|
|
1513
1516
|
|
|
1514
1517
|
const oldRunCancelMatch = pathOnly.match(/^\/api\/runs\/([^/]+)\/cancel$/);
|
|
1515
|
-
if (
|
|
1518
|
+
if (oldRunCancelMatch && req.method === 'POST') {
|
|
1516
1519
|
const runId = oldRunCancelMatch[1];
|
|
1517
1520
|
const session = queries.getSession(runId);
|
|
1518
1521
|
|
|
@@ -2073,7 +2076,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
2073
2076
|
}
|
|
2074
2077
|
|
|
2075
2078
|
const oldRunCancelMatch1 = pathOnly.match(/^\/api\/runs\/([^/]+)\/cancel$/);
|
|
2076
|
-
if (
|
|
2079
|
+
if (oldRunCancelMatch1 && req.method === 'POST') {
|
|
2077
2080
|
const runId = oldRunCancelMatch1[1];
|
|
2078
2081
|
try {
|
|
2079
2082
|
const run = acpQueries.cancelRun(runId);
|
|
@@ -2323,573 +2326,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
2323
2326
|
return;
|
|
2324
2327
|
}
|
|
2325
2328
|
|
|
2326
|
-
const threadsMatch = pathOnly.match(/^\/api\/threads$/);
|
|
2327
|
-
if (threadsMatch && req.method === 'POST') {
|
|
2328
|
-
let body = '';
|
|
2329
|
-
for await (const chunk of req) { body += chunk; }
|
|
2330
|
-
let parsed = {};
|
|
2331
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2332
|
-
|
|
2333
|
-
const thread = queries.createConversation(parsed.agentId || 'claude-code', parsed.title || 'New Thread', parsed.workingDirectory || STARTUP_CWD);
|
|
2334
|
-
sendJSON(req, res, 200, {
|
|
2335
|
-
id: thread.id,
|
|
2336
|
-
agentId: thread.agentId,
|
|
2337
|
-
title: thread.title,
|
|
2338
|
-
created_at: thread.created_at,
|
|
2339
|
-
status: thread.status,
|
|
2340
|
-
state: null
|
|
2341
|
-
});
|
|
2342
|
-
return;
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
const threadsSearchMatch = pathOnly.match(/^\/api\/threads\/search$/);
|
|
2346
|
-
if (threadsSearchMatch && req.method === 'POST') {
|
|
2347
|
-
const conversations = queries.getConversations();
|
|
2348
|
-
const threads = conversations.map(c => ({
|
|
2349
|
-
id: c.id,
|
|
2350
|
-
agentId: c.agentId,
|
|
2351
|
-
title: c.title,
|
|
2352
|
-
created_at: c.created_at,
|
|
2353
|
-
updated_at: c.updated_at,
|
|
2354
|
-
status: c.status,
|
|
2355
|
-
state: null
|
|
2356
|
-
}));
|
|
2357
|
-
sendJSON(req, res, 200, threads);
|
|
2358
|
-
return;
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
const oldThreadByIdMatch = pathOnly.match(/^\/api\/threads\/([^/]+)$/);
|
|
2362
|
-
if (oldThreadByIdMatch) {
|
|
2363
|
-
const threadId = oldThreadByIdMatch[1];
|
|
2364
|
-
const conv = queries.getConversation(threadId);
|
|
2365
|
-
|
|
2366
|
-
if (!conv) {
|
|
2367
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2368
|
-
return;
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
if (req.method === 'GET') {
|
|
2372
|
-
sendJSON(req, res, 200, {
|
|
2373
|
-
id: conv.id,
|
|
2374
|
-
agentId: conv.agentId,
|
|
2375
|
-
title: conv.title,
|
|
2376
|
-
created_at: conv.created_at,
|
|
2377
|
-
updated_at: conv.updated_at,
|
|
2378
|
-
status: conv.status,
|
|
2379
|
-
state: null
|
|
2380
|
-
});
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
if (req.method === 'DELETE') {
|
|
2385
|
-
const activeEntry = activeExecutions.get(threadId);
|
|
2386
|
-
if (activeEntry) {
|
|
2387
|
-
sendJSON(req, res, 409, { error: 'Thread has an active run, cannot delete' });
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
2390
|
-
queries.deleteConversation(threadId);
|
|
2391
|
-
sendJSON(req, res, 204, {});
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
if (req.method === 'PATCH') {
|
|
2396
|
-
let body = '';
|
|
2397
|
-
for await (const chunk of req) { body += chunk; }
|
|
2398
|
-
let parsed = {};
|
|
2399
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2400
|
-
|
|
2401
|
-
const updates = {};
|
|
2402
|
-
if (parsed.title !== undefined) updates.title = parsed.title;
|
|
2403
|
-
if (parsed.state !== undefined) updates.state = parsed.state;
|
|
2404
|
-
|
|
2405
|
-
if (Object.keys(updates).length > 0) {
|
|
2406
|
-
queries.updateConversation(threadId, updates);
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
const updated = queries.getConversation(threadId);
|
|
2410
|
-
sendJSON(req, res, 200, {
|
|
2411
|
-
id: updated.id,
|
|
2412
|
-
agentId: updated.agentId,
|
|
2413
|
-
title: updated.title,
|
|
2414
|
-
created_at: updated.created_at,
|
|
2415
|
-
updated_at: updated.updated_at,
|
|
2416
|
-
status: updated.status,
|
|
2417
|
-
state: updated.state
|
|
2418
|
-
});
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
|
-
const oldThreadHistoryMatch = pathOnly.match(/^\/api\/threads\/([^/]+)\/history$/);
|
|
2424
|
-
if (threadHistoryMatch && req.method === 'GET') {
|
|
2425
|
-
const threadId = oldThreadHistoryMatch[1];
|
|
2426
|
-
const conv = queries.getConversation(threadId);
|
|
2427
|
-
|
|
2428
|
-
if (!conv) {
|
|
2429
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
const limit = parseInt(new URL(req.url, 'http://localhost').searchParams.get('limit') || '10', 10);
|
|
2434
|
-
const sessions = queries.getSessionsByConversation(threadId, limit);
|
|
2435
|
-
|
|
2436
|
-
const history = sessions.map(s => ({
|
|
2437
|
-
checkpoint: s.id,
|
|
2438
|
-
state: null,
|
|
2439
|
-
created_at: s.started_at,
|
|
2440
|
-
runId: s.id,
|
|
2441
|
-
status: s.status
|
|
2442
|
-
})).reverse();
|
|
2443
|
-
|
|
2444
|
-
sendJSON(req, res, 200, history);
|
|
2445
|
-
return;
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
const oldThreadCopyMatch = pathOnly.match(/^\/api\/threads\/([^/]+)\/copy$/);
|
|
2449
|
-
if (threadCopyMatch && req.method === 'POST') {
|
|
2450
|
-
const threadId = threadCopyMatch[1];
|
|
2451
|
-
const original = queries.getConversation(threadId);
|
|
2452
|
-
|
|
2453
|
-
if (!original) {
|
|
2454
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
const newThread = queries.createConversation(original.agentId, original.title + ' (copy)', original.workingDirectory);
|
|
2459
|
-
|
|
2460
|
-
const messages = queries.getMessages(threadId, 1000, 0);
|
|
2461
|
-
for (const msg of messages) {
|
|
2462
|
-
queries.createMessage(newThread.id, msg.role, msg.content);
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
sendJSON(req, res, 200, {
|
|
2466
|
-
id: newThread.id,
|
|
2467
|
-
agentId: newThread.agentId,
|
|
2468
|
-
title: newThread.title,
|
|
2469
|
-
created_at: newThread.created_at,
|
|
2470
|
-
status: newThread.status,
|
|
2471
|
-
state: null
|
|
2472
|
-
});
|
|
2473
|
-
return;
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
const threadRunsMatch = pathOnly.match(/^\/api\/threads\/([^/]+)\/runs$/);
|
|
2477
|
-
if (threadRunsMatch) {
|
|
2478
|
-
const threadId = threadRunsMatch[1];
|
|
2479
|
-
const conv = queries.getConversation(threadId);
|
|
2480
|
-
|
|
2481
|
-
if (!conv) {
|
|
2482
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2483
|
-
return;
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
if (req.method === 'GET') {
|
|
2487
|
-
const limit = parseInt(new URL(req.url, 'http://localhost').searchParams.get('limit') || '10', 10);
|
|
2488
|
-
const offset = parseInt(new URL(req.url, 'http://localhost').searchParams.get('offset') || '0', 10);
|
|
2489
|
-
const sessions = queries.getSessionsByConversation(threadId, limit, offset);
|
|
2490
|
-
|
|
2491
|
-
const runs = sessions.map(s => ({
|
|
2492
|
-
id: s.id,
|
|
2493
|
-
threadId: s.conversationId,
|
|
2494
|
-
status: s.status,
|
|
2495
|
-
started_at: s.started_at,
|
|
2496
|
-
completed_at: s.completed_at,
|
|
2497
|
-
agentId: s.agentId,
|
|
2498
|
-
input: null,
|
|
2499
|
-
output: null
|
|
2500
|
-
}));
|
|
2501
|
-
|
|
2502
|
-
sendJSON(req, res, 200, runs);
|
|
2503
|
-
return;
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
if (req.method === 'POST') {
|
|
2507
|
-
const activeEntry = activeExecutions.get(threadId);
|
|
2508
|
-
if (activeEntry) {
|
|
2509
|
-
sendJSON(req, res, 409, { error: 'Thread already has an active run' });
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
let body = '';
|
|
2514
|
-
for await (const chunk of req) { body += chunk; }
|
|
2515
|
-
let parsed = {};
|
|
2516
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2517
|
-
|
|
2518
|
-
const { input, agentId, webhook } = parsed;
|
|
2519
|
-
if (!input) {
|
|
2520
|
-
sendJSON(req, res, 400, { error: 'Missing input in request body' });
|
|
2521
|
-
return;
|
|
2522
|
-
}
|
|
2523
|
-
|
|
2524
|
-
const resolvedAgentId = agentId || conv.agentId || 'claude-code';
|
|
2525
|
-
const resolvedModel = parsed.model || conv.model || null;
|
|
2526
|
-
const cwd = conv.workingDirectory || STARTUP_CWD;
|
|
2527
|
-
|
|
2528
|
-
const session = queries.createSession(threadId, resolvedAgentId, 'pending');
|
|
2529
|
-
const message = queries.createMessage(threadId, 'user', typeof input === 'string' ? input : JSON.stringify(input));
|
|
2530
|
-
|
|
2531
|
-
processMessageWithStreaming(threadId, message.id, session.id, typeof input === 'string' ? input : JSON.stringify(input), resolvedAgentId, resolvedModel);
|
|
2532
|
-
|
|
2533
|
-
sendJSON(req, res, 200, {
|
|
2534
|
-
id: session.id,
|
|
2535
|
-
threadId: threadId,
|
|
2536
|
-
status: 'pending',
|
|
2537
|
-
started_at: session.started_at,
|
|
2538
|
-
agentId: resolvedAgentId
|
|
2539
|
-
});
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
const threadRunsStreamMatch = pathOnly.match(/^\/api\/threads\/([^\/]+)\/runs\/stream$/);
|
|
2545
|
-
if (threadRunsStreamMatch && req.method === 'POST') {
|
|
2546
|
-
const threadId = threadRunsStreamMatch[1];
|
|
2547
|
-
const conv = queries.getConversation(threadId);
|
|
2548
|
-
|
|
2549
|
-
if (!conv) {
|
|
2550
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2551
|
-
return;
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
|
-
const activeEntry = activeExecutions.get(threadId);
|
|
2555
|
-
if (activeEntry) {
|
|
2556
|
-
sendJSON(req, res, 409, { error: 'Thread already has an active run' });
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
let body = '';
|
|
2561
|
-
for await (const chunk of req) { body += chunk; }
|
|
2562
|
-
let parsed = {};
|
|
2563
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2564
|
-
|
|
2565
|
-
const { input, agentId } = parsed;
|
|
2566
|
-
if (!input) {
|
|
2567
|
-
sendJSON(req, res, 400, { error: 'Missing input in request body' });
|
|
2568
|
-
return;
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
const resolvedAgentId = agentId || conv.agentId || 'claude-code';
|
|
2572
|
-
const resolvedModel = parsed.model || conv.model || null;
|
|
2573
|
-
|
|
2574
|
-
const session = queries.createSession(threadId, resolvedAgentId, 'pending');
|
|
2575
|
-
const message = queries.createMessage(threadId, 'user', typeof input === 'string' ? input : JSON.stringify(input));
|
|
2576
|
-
|
|
2577
|
-
res.writeHead(200, {
|
|
2578
|
-
'Content-Type': 'text/event-stream',
|
|
2579
|
-
'Cache-Control': 'no-cache',
|
|
2580
|
-
'Connection': 'keep-alive'
|
|
2581
|
-
});
|
|
2582
|
-
|
|
2583
|
-
const eventListeners = [];
|
|
2584
|
-
const broadcastListener = (event) => {
|
|
2585
|
-
if (event.sessionId === session.id) {
|
|
2586
|
-
if (event.type === 'streaming_progress') {
|
|
2587
|
-
res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`);
|
|
2588
|
-
} else if (event.type === 'streaming_complete') {
|
|
2589
|
-
res.write(`event: done\ndata: ${JSON.stringify({ status: 'completed' })}\n\n`);
|
|
2590
|
-
res.end();
|
|
2591
|
-
} else if (event.type === 'streaming_error') {
|
|
2592
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: event.error })}\n\n`);
|
|
2593
|
-
res.end();
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
};
|
|
2597
|
-
eventListeners.push(broadcastListener);
|
|
2598
|
-
|
|
2599
|
-
req.on('close', () => {
|
|
2600
|
-
eventListeners.length = 0;
|
|
2601
|
-
});
|
|
2602
|
-
|
|
2603
|
-
processMessageWithStreaming(threadId, message.id, session.id, typeof input === 'string' ? input : JSON.stringify(input), resolvedAgentId, resolvedModel);
|
|
2604
|
-
return;
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
const threadRunsWaitMatch = pathOnly.match(/^\/api\/threads\/([^\/]+)\/runs\/wait$/);
|
|
2608
|
-
if (threadRunsWaitMatch && req.method === 'POST') {
|
|
2609
|
-
const threadId = threadRunsWaitMatch[1];
|
|
2610
|
-
const conv = queries.getConversation(threadId);
|
|
2611
|
-
|
|
2612
|
-
if (!conv) {
|
|
2613
|
-
sendJSON(req, res, 404, { error: 'Thread not found' });
|
|
2614
|
-
return;
|
|
2615
|
-
}
|
|
2616
|
-
|
|
2617
|
-
const activeEntry = activeExecutions.get(threadId);
|
|
2618
|
-
if (activeEntry) {
|
|
2619
|
-
sendJSON(req, res, 409, { error: 'Thread already has an active run' });
|
|
2620
|
-
return;
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
let body = '';
|
|
2624
|
-
for await (const chunk of req) { body += chunk; }
|
|
2625
|
-
let parsed = {};
|
|
2626
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2627
|
-
|
|
2628
|
-
const { input, agentId } = parsed;
|
|
2629
|
-
if (!input) {
|
|
2630
|
-
sendJSON(req, res, 400, { error: 'Missing input in request body' });
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
const resolvedAgentId = agentId || conv.agentId || 'claude-code';
|
|
2635
|
-
const resolvedModel = parsed.model || conv.model || null;
|
|
2636
|
-
|
|
2637
|
-
const session = queries.createSession(threadId, resolvedAgentId, 'pending');
|
|
2638
|
-
const message = queries.createMessage(threadId, 'user', typeof input === 'string' ? input : JSON.stringify(input));
|
|
2639
|
-
|
|
2640
|
-
const waitPromise = new Promise((resolve, reject) => {
|
|
2641
|
-
const checkInterval = setInterval(() => {
|
|
2642
|
-
const updatedSession = queries.getSession(session.id);
|
|
2643
|
-
if (!updatedSession) {
|
|
2644
|
-
clearInterval(checkInterval);
|
|
2645
|
-
reject(new Error('Session not found'));
|
|
2646
|
-
return;
|
|
2647
|
-
}
|
|
2648
|
-
if (['success', 'error', 'interrupted', 'cancelled'].includes(updatedSession.status)) {
|
|
2649
|
-
clearInterval(checkInterval);
|
|
2650
|
-
resolve(updatedSession);
|
|
2651
|
-
}
|
|
2652
|
-
}, 500);
|
|
2653
|
-
|
|
2654
|
-
setTimeout(() => {
|
|
2655
|
-
clearInterval(checkInterval);
|
|
2656
|
-
const updatedSession = queries.getSession(session.id);
|
|
2657
|
-
resolve(updatedSession || session);
|
|
2658
|
-
}, 300000);
|
|
2659
|
-
});
|
|
2660
|
-
|
|
2661
|
-
processMessageWithStreaming(threadId, message.id, session.id, typeof input === 'string' ? input : JSON.stringify(input), resolvedAgentId, resolvedModel);
|
|
2662
|
-
|
|
2663
|
-
try {
|
|
2664
|
-
const completedSession = await waitPromise;
|
|
2665
|
-
sendJSON(req, res, 200, {
|
|
2666
|
-
id: completedSession.id,
|
|
2667
|
-
threadId: threadId,
|
|
2668
|
-
status: completedSession.status,
|
|
2669
|
-
started_at: completedSession.started_at,
|
|
2670
|
-
completed_at: completedSession.completed_at,
|
|
2671
|
-
agentId: resolvedAgentId,
|
|
2672
|
-
output: completedSession.response || null
|
|
2673
|
-
});
|
|
2674
|
-
} catch (err) {
|
|
2675
|
-
sendJSON(req, res, 500, { error: err.message });
|
|
2676
|
-
}
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
const threadRunByIdMatch = pathOnly.match(/^\/api\/threads\/([^/]+)\/runs\/([^/]+)$/);
|
|
2682
|
-
if (threadRunByIdMatch) {
|
|
2683
|
-
const threadId = threadRunByIdMatch[1];
|
|
2684
|
-
const runId = threadRunByIdMatch[2];
|
|
2685
|
-
const session = queries.getSession(runId);
|
|
2686
|
-
|
|
2687
|
-
if (!session || session.conversationId !== threadId) {
|
|
2688
|
-
sendJSON(req, res, 404, { error: 'Run not found' });
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
|
|
2692
|
-
if (req.method === 'GET') {
|
|
2693
|
-
sendJSON(req, res, 200, {
|
|
2694
|
-
id: session.id,
|
|
2695
|
-
threadId: session.conversationId,
|
|
2696
|
-
status: session.status,
|
|
2697
|
-
started_at: session.started_at,
|
|
2698
|
-
completed_at: session.completed_at,
|
|
2699
|
-
agentId: session.agentId,
|
|
2700
|
-
input: null,
|
|
2701
|
-
output: null
|
|
2702
|
-
});
|
|
2703
|
-
return;
|
|
2704
|
-
}
|
|
2705
|
-
|
|
2706
|
-
if (req.method === 'DELETE') {
|
|
2707
|
-
queries.deleteSession(runId);
|
|
2708
|
-
sendJSON(req, res, 204, {});
|
|
2709
|
-
return;
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
if (req.method === 'POST') {
|
|
2713
|
-
if (session.status !== 'interrupted') {
|
|
2714
|
-
sendJSON(req, res, 409, { error: 'Can only resume interrupted runs' });
|
|
2715
|
-
return;
|
|
2716
|
-
}
|
|
2717
|
-
|
|
2718
|
-
let body = '';
|
|
2719
|
-
for await (const chunk of req) { body += chunk; }
|
|
2720
|
-
let parsed = {};
|
|
2721
|
-
try { parsed = body ? JSON.parse(body) : {}; } catch {}
|
|
2722
|
-
|
|
2723
|
-
const { input } = parsed;
|
|
2724
|
-
if (!input) {
|
|
2725
|
-
sendJSON(req, res, 400, { error: 'Missing input in request body' });
|
|
2726
|
-
return;
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
const conv = queries.getConversation(threadId);
|
|
2730
|
-
const resolvedAgentId = session.agentId || conv.agentId || 'claude-code';
|
|
2731
|
-
const resolvedModel = conv?.model || null;
|
|
2732
|
-
const cwd = conv?.workingDirectory || STARTUP_CWD;
|
|
2733
|
-
|
|
2734
|
-
queries.updateSession(runId, { status: 'pending' });
|
|
2735
|
-
|
|
2736
|
-
const message = queries.createMessage(threadId, 'user', typeof input === 'string' ? input : JSON.stringify(input));
|
|
2737
|
-
|
|
2738
|
-
processMessageWithStreaming(threadId, message.id, runId, typeof input === 'string' ? input : JSON.stringify(input), resolvedAgentId, resolvedModel);
|
|
2739
|
-
|
|
2740
|
-
sendJSON(req, res, 200, {
|
|
2741
|
-
id: session.id,
|
|
2742
|
-
threadId: threadId,
|
|
2743
|
-
status: 'pending',
|
|
2744
|
-
started_at: session.started_at,
|
|
2745
|
-
agentId: resolvedAgentId
|
|
2746
|
-
});
|
|
2747
|
-
return;
|
|
2748
|
-
}
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
const threadRunWaitMatch = pathOnly.match(/^\/api\/threads\/([^\/]+)\/runs\/([^\/]+)\/wait$/);
|
|
2752
|
-
if (threadRunWaitMatch && req.method === 'GET') {
|
|
2753
|
-
const threadId = threadRunWaitMatch[1];
|
|
2754
|
-
const runId = threadRunWaitMatch[2];
|
|
2755
|
-
const session = queries.getSession(runId);
|
|
2756
|
-
|
|
2757
|
-
if (!session || session.conversationId !== threadId) {
|
|
2758
|
-
sendJSON(req, res, 404, { error: 'Run not found' });
|
|
2759
|
-
return;
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
const waitPromise = new Promise((resolve) => {
|
|
2763
|
-
const checkInterval = setInterval(() => {
|
|
2764
|
-
const updatedSession = queries.getSession(runId);
|
|
2765
|
-
if (!updatedSession) {
|
|
2766
|
-
clearInterval(checkInterval);
|
|
2767
|
-
resolve(session);
|
|
2768
|
-
return;
|
|
2769
|
-
}
|
|
2770
|
-
if (['success', 'error', 'interrupted', 'cancelled'].includes(updatedSession.status)) {
|
|
2771
|
-
clearInterval(checkInterval);
|
|
2772
|
-
resolve(updatedSession);
|
|
2773
|
-
}
|
|
2774
|
-
}, 500);
|
|
2775
|
-
|
|
2776
|
-
setTimeout(() => {
|
|
2777
|
-
clearInterval(checkInterval);
|
|
2778
|
-
const updatedSession = queries.getSession(runId) || session;
|
|
2779
|
-
resolve(updatedSession);
|
|
2780
|
-
}, 30000);
|
|
2781
|
-
});
|
|
2782
|
-
|
|
2783
|
-
try {
|
|
2784
|
-
const completedSession = await waitPromise;
|
|
2785
|
-
sendJSON(req, res, 200, {
|
|
2786
|
-
id: completedSession.id,
|
|
2787
|
-
threadId: threadId,
|
|
2788
|
-
status: completedSession.status,
|
|
2789
|
-
started_at: completedSession.started_at,
|
|
2790
|
-
completed_at: completedSession.completed_at,
|
|
2791
|
-
agentId: completedSession.agentId,
|
|
2792
|
-
output: completedSession.response || null
|
|
2793
|
-
});
|
|
2794
|
-
} catch (err) {
|
|
2795
|
-
sendJSON(req, res, 500, { error: err.message });
|
|
2796
|
-
}
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
|
|
2800
|
-
const threadRunStreamMatch = pathOnly.match(/^\/api\/threads\/([^\/]+)\/runs\/([^\/]+)\/stream$/);
|
|
2801
|
-
if (threadRunStreamMatch && req.method === 'GET') {
|
|
2802
|
-
const threadId = threadRunStreamMatch[1];
|
|
2803
|
-
const runId = threadRunStreamMatch[2];
|
|
2804
|
-
const session = queries.getSession(runId);
|
|
2805
|
-
|
|
2806
|
-
if (!session || session.conversationId !== threadId) {
|
|
2807
|
-
sendJSON(req, res, 404, { error: 'Run not found' });
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
res.writeHead(200, {
|
|
2812
|
-
'Content-Type': 'text/event-stream',
|
|
2813
|
-
'Cache-Control': 'no-cache',
|
|
2814
|
-
'Connection': 'keep-alive'
|
|
2815
|
-
});
|
|
2816
|
-
|
|
2817
|
-
const chunks = queries.getSessionChunks(runId, 0);
|
|
2818
|
-
for (const chunk of chunks) {
|
|
2819
|
-
res.write(`event: message\ndata: ${JSON.stringify(chunk)}\n\n`);
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
const eventListeners = [];
|
|
2823
|
-
const broadcastListener = (event) => {
|
|
2824
|
-
if (event.sessionId === runId) {
|
|
2825
|
-
if (event.type === 'streaming_progress') {
|
|
2826
|
-
res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`);
|
|
2827
|
-
} else if (event.type === 'streaming_complete') {
|
|
2828
|
-
res.write(`event: done\ndata: ${JSON.stringify({ status: 'completed' })}\n\n`);
|
|
2829
|
-
res.end();
|
|
2830
|
-
} else if (event.type === 'streaming_error') {
|
|
2831
|
-
res.write(`event: error\ndata: ${JSON.stringify({ error: event.error })}\n\n`);
|
|
2832
|
-
res.end();
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
};
|
|
2836
|
-
eventListeners.push(broadcastListener);
|
|
2837
|
-
|
|
2838
|
-
req.on('close', () => {
|
|
2839
|
-
eventListeners.length = 0;
|
|
2840
|
-
});
|
|
2841
|
-
|
|
2842
|
-
if (['success', 'error', 'interrupted', 'cancelled'].includes(session.status)) {
|
|
2843
|
-
res.write(`event: done\ndata: ${JSON.stringify({ status: session.status })}\n\n`);
|
|
2844
|
-
res.end();
|
|
2845
|
-
}
|
|
2846
|
-
|
|
2847
|
-
return;
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
const threadRunCancelMatch = pathOnly.match(/^\/api\/threads\/([^/]+)\/runs\/([^/]+)\/cancel$/);
|
|
2852
|
-
if (threadRunCancelMatch && req.method === 'POST') {
|
|
2853
|
-
const threadId = threadRunCancelMatch[1];
|
|
2854
|
-
const runId = threadRunCancelMatch[2];
|
|
2855
|
-
const session = queries.getSession(runId);
|
|
2856
|
-
|
|
2857
|
-
if (!session || session.conversationId !== threadId) {
|
|
2858
|
-
sendJSON(req, res, 404, { error: 'Run not found' });
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
const entry = activeExecutions.get(threadId);
|
|
2863
|
-
|
|
2864
|
-
if (entry && entry.sessionId === runId) {
|
|
2865
|
-
const { pid } = entry;
|
|
2866
|
-
if (pid) {
|
|
2867
|
-
try {
|
|
2868
|
-
process.kill(-pid, 'SIGKILL');
|
|
2869
|
-
} catch {
|
|
2870
|
-
try {
|
|
2871
|
-
process.kill(pid, 'SIGKILL');
|
|
2872
|
-
} catch (e) {}
|
|
2873
|
-
}
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
|
|
2877
|
-
queries.updateSession(runId, { status: 'interrupted', completed_at: Date.now() });
|
|
2878
|
-
queries.setIsStreaming(threadId, false);
|
|
2879
|
-
activeExecutions.delete(threadId);
|
|
2880
|
-
|
|
2881
|
-
broadcastSync({
|
|
2882
|
-
type: 'streaming_complete',
|
|
2883
|
-
sessionId: runId,
|
|
2884
|
-
conversationId: threadId,
|
|
2885
|
-
interrupted: true,
|
|
2886
|
-
timestamp: Date.now()
|
|
2887
|
-
});
|
|
2888
|
-
|
|
2889
|
-
sendJSON(req, res, 204, {});
|
|
2890
|
-
return;
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
2329
|
if (pathOnly === '/api/stt' && req.method === 'POST') {
|
|
2894
2330
|
try {
|
|
2895
2331
|
const chunks = [];
|
|
@@ -3198,7 +2634,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
3198
2634
|
// GET /threads/{thread_id} - Get thread by ID
|
|
3199
2635
|
const acpThreadMatch = pathOnly.match(/^\/api\/threads\/([a-f0-9-]{36})$/);
|
|
3200
2636
|
if (acpThreadMatch && req.method === 'GET') {
|
|
3201
|
-
const threadId =
|
|
2637
|
+
const threadId = acpThreadMatch[1];
|
|
3202
2638
|
try {
|
|
3203
2639
|
const thread = queries.getThread(threadId);
|
|
3204
2640
|
if (!thread) {
|
|
@@ -3214,7 +2650,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
3214
2650
|
|
|
3215
2651
|
// PATCH /threads/{thread_id} - Update thread metadata
|
|
3216
2652
|
if (acpThreadMatch && req.method === 'PATCH') {
|
|
3217
|
-
const threadId =
|
|
2653
|
+
const threadId = acpThreadMatch[1];
|
|
3218
2654
|
try {
|
|
3219
2655
|
const body = await parseBody(req);
|
|
3220
2656
|
const thread = queries.patchThread(threadId, body);
|
|
@@ -3231,7 +2667,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
3231
2667
|
|
|
3232
2668
|
// DELETE /threads/{thread_id} - Delete thread (fail if pending runs exist)
|
|
3233
2669
|
if (acpThreadMatch && req.method === 'DELETE') {
|
|
3234
|
-
const threadId =
|
|
2670
|
+
const threadId = acpThreadMatch[1];
|
|
3235
2671
|
try {
|
|
3236
2672
|
queries.deleteThread(threadId);
|
|
3237
2673
|
res.writeHead(204);
|