agentgui 1.0.63 → 1.0.65

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/acp-launcher.js CHANGED
@@ -8,16 +8,56 @@ import { query } from '@anthropic-ai/claude-code';
8
8
  * - Actual filesystem operations
9
9
  * - Streaming responses with onUpdate callbacks
10
10
  * - No subprocess spawning needed (SDK handles it)
11
+ * - Integrated glootie-cc MCP servers for full capabilities
11
12
  */
12
13
  export default class ACPConnection {
13
14
  constructor() {
14
15
  this.sessionId = null;
15
16
  this.onUpdate = null;
16
17
  this.cwd = process.cwd();
18
+ this.mcpServers = this.buildMcpServers();
19
+ }
20
+
21
+ buildMcpServers() {
22
+ const mcpServers = {};
23
+
24
+ // Add glootie-cc MCP servers for full execution capabilities
25
+ if (process.env.CLAUDE_PLUGIN_ROOT) {
26
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
27
+ mcpServers['dev'] = {
28
+ type: 'stdio',
29
+ command: 'bunx',
30
+ args: ['mcp-glootie@latest'],
31
+ timeout: 360000
32
+ };
33
+ mcpServers['code-search'] = {
34
+ type: 'stdio',
35
+ command: 'bunx',
36
+ args: ['codebasesearch@latest'],
37
+ timeout: 360000
38
+ };
39
+ } else {
40
+ // Fallback to standard MCP configuration
41
+ mcpServers['dev'] = {
42
+ type: 'stdio',
43
+ command: 'bunx',
44
+ args: ['mcp-glootie@latest'],
45
+ timeout: 360000
46
+ };
47
+ mcpServers['code-search'] = {
48
+ type: 'stdio',
49
+ command: 'bunx',
50
+ args: ['codebasesearch@latest'],
51
+ timeout: 360000
52
+ };
53
+ }
54
+
55
+ return mcpServers;
17
56
  }
18
57
 
19
58
  async connect(agentType, cwd) {
20
59
  console.log(`[ACP] Using @anthropic-ai/claude-code SDK (${agentType})`);
60
+ console.log(`[ACP] MCP servers configured: ${Object.keys(this.mcpServers).join(', ')}`);
21
61
  if (cwd) {
22
62
  this.cwd = cwd;
23
63
  }
@@ -46,7 +86,7 @@ export default class ACPConnection {
46
86
  }
47
87
 
48
88
  async injectSystemContext() {
49
- return { context: 'Using Claude Code SDK with real plugins' };
89
+ return { context: 'Using Claude Code SDK with glootie-cc MCP integration' };
50
90
  }
51
91
 
52
92
  async sendPrompt(prompt) {
@@ -55,12 +95,42 @@ export default class ACPConnection {
55
95
  try {
56
96
  console.log(`[ACP] Sending prompt (${promptText.length} chars) in ${this.cwd}`);
57
97
 
98
+ // Build environment with proper permissions and working directory setup
99
+ const env = {
100
+ ...process.env,
101
+ HOME: process.env.HOME || '/config',
102
+ USER: process.env.USER || 'abc',
103
+ PATH: process.env.PATH || '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
104
+ // Ensure file operations are fully enabled
105
+ CLAUDE_CODE_ALLOW_ALL: 'true',
106
+ CLAUDE_CODE_BYPASS_PERMISSIONS: 'true'
107
+ };
108
+
109
+ // Build permission updates to allow directory access
110
+ const permissionUpdates = [
111
+ {
112
+ type: 'addDirectories',
113
+ directories: ['/tmp/test-projects', this.cwd, '/tmp', '/config'],
114
+ destination: 'session'
115
+ },
116
+ {
117
+ type: 'addRules',
118
+ rules: ['*'],
119
+ behavior: 'allow',
120
+ destination: 'session'
121
+ }
122
+ ];
123
+
58
124
  // Use the SDK directly to execute the prompt
59
125
  // The SDK handles plugins, system prompt, and all real execution
60
126
  const session = await query({
61
127
  prompt: promptText,
62
128
  options: {
63
- cwd: this.cwd
129
+ cwd: this.cwd,
130
+ env: env,
131
+ mcpServers: this.mcpServers,
132
+ permissionMode: 'acceptEdits',
133
+ additionalDirectories: ['/tmp/test-projects', this.cwd, '/tmp', '/config']
64
134
  }
65
135
  });
66
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.63",
3
+ "version": "1.0.65",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -199,7 +199,7 @@ const server = http.createServer(async (req, res) => {
199
199
  const idempotencyKey = body.idempotencyKey || null;
200
200
  const message = queries.createMessage(conversationId, 'user', body.content, idempotencyKey);
201
201
  queries.createEvent('message.created', { role: 'user', messageId: message.id }, conversationId);
202
- broadcastSync({ type: 'message_created', conversationId, message });
202
+ broadcastSync({ type: 'message_created', conversationId, message, timestamp: Date.now() });
203
203
  const session = queries.createSession(conversationId);
204
204
  queries.createEvent('session.created', { messageId: message.id, sessionId: session.id }, conversationId, session.id);
205
205
  res.writeHead(201, { 'Content-Type': 'application/json' });
@@ -475,12 +475,14 @@ async function processMessage(conversationId, messageId, sessionId, content, age
475
475
  });
476
476
  queries.createEvent('session.completed', { messageId: assistantMessage.id }, conversationId, sessionId);
477
477
 
478
- // Broadcast final consolidated response
478
+ // Broadcast final consolidated response with full message content
479
479
  broadcastSync({
480
480
  type: 'session_updated',
481
481
  sessionId,
482
+ conversationId,
482
483
  status: 'completed',
483
- message: assistantMessage
484
+ message: assistantMessage,
485
+ timestamp: Date.now()
484
486
  });
485
487
 
486
488
  // STATE: PROCESSING → COMPLETED
@@ -510,10 +512,10 @@ async function processMessage(conversationId, messageId, sessionId, content, age
510
512
 
511
513
  // Save error to database
512
514
  const errorMsg = `ACP Error: ${acpError.message}`;
513
- queries.createMessage(conversationId, 'assistant', errorMsg);
515
+ const errorMessage = queries.createMessage(conversationId, 'assistant', errorMsg);
514
516
  queries.updateSession(sessionId, { status: 'error', error: acpError.message, completed_at: Date.now() });
515
517
  queries.createEvent('session.error', { error: acpError.message, stack: acpError.stack }, conversationId, sessionId);
516
- broadcastSync({ type: 'session_updated', sessionId, status: 'error', error: acpError.message });
518
+ broadcastSync({ type: 'session_updated', sessionId, conversationId, status: 'error', error: acpError.message, message: errorMessage, timestamp: Date.now() });
517
519
 
518
520
  // Clean up ACP connection on error
519
521
  acpPool.delete(actualAgentId);
package/static/app.js CHANGED
@@ -265,14 +265,14 @@ class GMGUIApp {
265
265
 
266
266
  handleSyncEvent(event, fromBroadcast = false) {
267
267
  // CRITICAL: Server is the authoritative source of truth
268
- // On ANY event, fetch fresh state from server to ensure consistency
269
- // Never rely on event data alone - always verify with server
270
-
268
+ // Real-time WebSocket events for messages arrive immediately
269
+ // Subscribe to conversation to receive message updates
270
+
271
271
  console.log('[STATE SYNC] Event received:', event.type);
272
-
272
+
273
273
  switch (event.type) {
274
274
  case 'sync_connected':
275
- console.log('[STATE SYNC] Connected to sync bus - fetching full state');
275
+ console.log('[STATE SYNC] Connected to sync bus - subscribing to all active sessions');
276
276
  // On connection, always do a full state refresh
277
277
  this.fetchConversations().then(() => this.renderChatHistory());
278
278
  break;
@@ -325,32 +325,54 @@ class GMGUIApp {
325
325
  break;
326
326
 
327
327
  case 'message_created':
328
- console.log('[STATE SYNC] Message created, fetching full state');
329
- // A message was created - refresh everything to see updated timestamps
330
- this.fetchConversations().then(() => {
331
- this.renderChatHistory();
332
- // If we're viewing this conversation, refresh it
333
- if (this.currentConversation === event.conversationId) {
334
- this.displayConversation(event.conversationId);
328
+ console.log('[STATE SYNC] Message created via WebSocket - real-time push');
329
+ // User message was created - add it immediately without polling
330
+ if (this.currentConversation === event.conversationId && event.message) {
331
+ console.log('[STATE SYNC] Adding user message to display immediately');
332
+ // Stop any existing polling for this conversation
333
+ this.stopPollingMessages();
334
+ // Add message directly to display
335
+ this.addMessageToDisplay(event.message);
336
+ // Update conversation metadata
337
+ this.fetchConversations().then(() => this.renderChatHistory());
338
+ // Auto-scroll to new message
339
+ if (this.settings.autoScroll) {
340
+ setTimeout(() => {
341
+ const div = document.getElementById('chatMessages');
342
+ if (div) div.scrollTop = div.scrollHeight;
343
+ }, 50);
335
344
  }
336
- });
345
+ } else {
346
+ // Not viewing this conversation, just update timestamps
347
+ this.fetchConversations().then(() => this.renderChatHistory());
348
+ }
337
349
  if (!fromBroadcast && this.broadcastChannel) {
338
350
  this.broadcastChannel.postMessage(event);
339
351
  }
340
352
  break;
341
353
 
342
354
  case 'session_updated':
343
- console.log('[STATE SYNC] Session updated:', event.status, '- fetching full state');
344
- // Session completed with a message - ALWAYS fetch fresh state
345
- // This ensures the conversation's updated_at timestamp is synced
346
- this.fetchConversations().then(() => {
347
- this.renderChatHistory(); // Update sidebar with new timestamps
348
-
349
- // If viewing this conversation, show the message
350
- if (this.currentConversation === event.conversationId) {
351
- this.displayConversation(event.conversationId);
355
+ console.log('[STATE SYNC] Session updated via WebSocket:', event.status, '- real-time push');
356
+ // Session completed - agent response arrived via WebSocket push (no polling!)
357
+ if (this.currentConversation === event.conversationId && event.message) {
358
+ console.log('[STATE SYNC] Adding assistant message to display immediately (real-time push)');
359
+ // Stop polling immediately
360
+ this.stopPollingMessages();
361
+ // Add message directly to display
362
+ this.addMessageToDisplay(event.message);
363
+ // Update conversation metadata
364
+ this.fetchConversations().then(() => this.renderChatHistory());
365
+ // Auto-scroll to new message
366
+ if (this.settings.autoScroll) {
367
+ setTimeout(() => {
368
+ const div = document.getElementById('chatMessages');
369
+ if (div) div.scrollTop = div.scrollHeight;
370
+ }, 50);
352
371
  }
353
- });
372
+ } else {
373
+ // Not viewing this conversation, just update timestamps
374
+ this.fetchConversations().then(() => this.renderChatHistory());
375
+ }
354
376
  if (!fromBroadcast && this.broadcastChannel) {
355
377
  this.broadcastChannel.postMessage(event);
356
378
  }
@@ -1350,56 +1372,20 @@ class GMGUIApp {
1350
1372
  this.addMessageToDisplay({ role: 'system', content: text });
1351
1373
  }
1352
1374
 
1353
- startPollingMessages(conversationId) {
1354
- if (this.pollingInterval) clearInterval(this.pollingInterval);
1355
-
1356
- let pollCount = 0;
1357
- const maxNoResponsePolls = 60;
1358
- let lastKnownIds = new Set(
1359
- Array.from(document.querySelectorAll('#chatMessages [data-message-id]'))
1360
- .map(el => el.dataset.messageId)
1361
- .filter(id => id && !id.startsWith('pending-'))
1362
- );
1363
-
1364
- this.pollingInterval = setInterval(async () => {
1365
- try {
1366
- const res = await this.apiFetch(`${BASE_URL}/api/conversations/${conversationId}/messages`);
1367
- const data = await res.json();
1368
- const messages = data.messages || [];
1369
-
1370
- let added = false;
1371
- messages.forEach(msg => {
1372
- if (msg.id && !lastKnownIds.has(msg.id)) {
1373
- const existingEl = document.querySelector(`[data-message-id="${msg.id}"]`);
1374
- if (!existingEl) {
1375
- this.addMessageToDisplay(msg);
1376
- added = true;
1377
- }
1378
- lastKnownIds.add(msg.id);
1379
- }
1380
- });
1381
- if (added) {
1382
- pollCount = 0;
1383
-
1384
- if (this.settings.autoScroll) {
1385
- const div = document.getElementById('chatMessages');
1386
- if (div) div.scrollTop = div.scrollHeight;
1387
- }
1388
- } else {
1389
- pollCount++;
1390
- }
1375
+ stopPollingMessages() {
1376
+ if (this.pollingInterval) {
1377
+ clearInterval(this.pollingInterval);
1378
+ this.pollingInterval = null;
1379
+ console.log('[POLLING] Polling stopped - using WebSocket push instead');
1380
+ }
1381
+ }
1391
1382
 
1392
- // Stop polling if no changes for a while
1393
- if (pollCount > maxNoResponsePolls) {
1394
- clearInterval(this.pollingInterval);
1395
- this.pollingInterval = null;
1396
- }
1397
- } catch (e) {
1398
- console.error('Polling error:', e);
1399
- clearInterval(this.pollingInterval);
1400
- this.pollingInterval = null;
1401
- }
1402
- }, 500); // Poll every 500ms
1383
+ startPollingMessages(conversationId) {
1384
+ // DEPRECATED: Polling mechanism replaced with WebSocket push
1385
+ // This method is kept for backwards compatibility but does nothing
1386
+ // Messages now arrive via WebSocket in real-time via handleSyncEvent
1387
+ console.log('[POLLING] Polling requested but disabled - WebSocket handles real-time updates');
1388
+ this.stopPollingMessages();
1403
1389
  }
1404
1390
 
1405
1391
  createThoughtBlock() {