agentgui 1.0.383 → 1.0.384

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +4 -569
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.383",
3
+ "version": "1.0.384",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -340,6 +340,8 @@ function discoverAgents() {
340
340
 
341
341
  const discoveredAgents = discoverAgents();
342
342
  initializeDescriptors(discoveredAgents);
343
+ const acpQueries = createACPQueries(db, prepare);
344
+ acpQueries.getAgentDescriptor = getAgentDescriptor;
343
345
 
344
346
  const modelCache = new Map();
345
347
 
@@ -1512,7 +1514,7 @@ const server = http.createServer(async (req, res) => {
1512
1514
  }
1513
1515
 
1514
1516
  const oldRunCancelMatch = pathOnly.match(/^\/api\/runs\/([^/]+)\/cancel$/);
1515
- if (runCancelMatch && req.method === 'POST') {
1517
+ if (oldRunCancelMatch && req.method === 'POST') {
1516
1518
  const runId = oldRunCancelMatch[1];
1517
1519
  const session = queries.getSession(runId);
1518
1520
 
@@ -2073,7 +2075,7 @@ const server = http.createServer(async (req, res) => {
2073
2075
  }
2074
2076
 
2075
2077
  const oldRunCancelMatch1 = pathOnly.match(/^\/api\/runs\/([^/]+)\/cancel$/);
2076
- if (runCancelMatch && req.method === 'POST') {
2078
+ if (oldRunCancelMatch1 && req.method === 'POST') {
2077
2079
  const runId = oldRunCancelMatch1[1];
2078
2080
  try {
2079
2081
  const run = acpQueries.cancelRun(runId);
@@ -2323,573 +2325,6 @@ const server = http.createServer(async (req, res) => {
2323
2325
  return;
2324
2326
  }
2325
2327
 
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
2328
  if (pathOnly === '/api/stt' && req.method === 'POST') {
2894
2329
  try {
2895
2330
  const chunks = [];