agentgui 1.0.414 → 1.0.416

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.
@@ -97,7 +97,20 @@ export function register(router, deps) {
97
97
  return data;
98
98
  });
99
99
 
100
- router.handle('agent.ls', () => ({ agents: discoveredAgents }));
100
+ router.handle('agent.ls', () => {
101
+ // Get local agents only (avoid external HTTP calls in WebSocket to prevent recursion)
102
+ const localAgents = discoveredAgents.map(agent => ({
103
+ id: agent.id,
104
+ name: agent.name,
105
+ icon: agent.icon,
106
+ path: agent.path,
107
+ protocol: agent.protocol || 'unknown',
108
+ description: agent.description || '',
109
+ status: 'available'
110
+ }));
111
+
112
+ return { agents: localAgents };
113
+ });
101
114
 
102
115
  router.handle('agent.get', (p) => {
103
116
  const a = discoveredAgents.find(x => x.id === p.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.414",
3
+ "version": "1.0.416",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -342,31 +342,96 @@ function findCommand(cmd) {
342
342
  }
343
343
  }
344
344
 
345
+ async function queryACPServerAgents(baseUrl) {
346
+ try {
347
+ // Ensure correct endpoint format (handle both /api and /api/ cases)
348
+ const endpoint = baseUrl.endsWith('/') ? `${baseUrl}agents/search` : `${baseUrl}/agents/search`;
349
+ const response = await fetch(endpoint, {
350
+ method: 'POST',
351
+ headers: {
352
+ 'Content-Type': 'application/json',
353
+ },
354
+ body: JSON.stringify({}),
355
+ });
356
+
357
+ if (!response.ok) {
358
+ console.error(`Failed to query ACP agents from ${baseUrl}: ${response.status}`);
359
+ return [];
360
+ }
361
+
362
+ const data = await response.json();
363
+ if (!data.agents || !Array.isArray(data.agents)) {
364
+ console.error(`Invalid agents response from ${baseUrl}`);
365
+ return [];
366
+ }
367
+
368
+ // Convert ACP agent format to our internal format
369
+ return data.agents.map(agent => ({
370
+ id: agent.agent_id || agent.id,
371
+ name: agent.metadata?.ref?.name || agent.name || 'Unknown Agent',
372
+ icon: agent.metadata?.ref?.name?.charAt(0) || 'A',
373
+ path: baseUrl,
374
+ protocol: 'acp',
375
+ description: agent.metadata?.description || '',
376
+ }));
377
+ } catch (error) {
378
+ console.error(`Error querying ACP server ${baseUrl}:`, error.message);
379
+ return [];
380
+ }
381
+ }
382
+
345
383
  function discoverAgents() {
346
384
  const agents = [];
347
385
  const binaries = [
348
- { cmd: 'claude', id: 'claude-code', name: 'Claude Code', icon: 'C' },
349
- { cmd: 'opencode', id: 'opencode', name: 'OpenCode', icon: 'O' },
350
- { cmd: 'gemini', id: 'gemini', name: 'Gemini CLI', icon: 'G' },
351
- { cmd: 'kilo', id: 'kilo', name: 'Kilo Code', icon: 'K' },
352
- { cmd: 'goose', id: 'goose', name: 'Goose', icon: 'g' },
353
- { cmd: 'openhands', id: 'openhands', name: 'OpenHands', icon: 'H' },
354
- { cmd: 'augment', id: 'augment', name: 'Augment Code', icon: 'A' },
355
- { cmd: 'cline', id: 'cline', name: 'Cline', icon: 'c' },
356
- { cmd: 'kimi', id: 'kimi', name: 'Kimi CLI', icon: 'K' },
357
- { cmd: 'qwen-code', id: 'qwen', name: 'Qwen Code', icon: 'Q' },
358
- { cmd: 'codex', id: 'codex', name: 'Codex CLI', icon: 'X' },
359
- { cmd: 'mistral-vibe', id: 'mistral', name: 'Mistral Vibe', icon: 'M' },
360
- { cmd: 'kiro', id: 'kiro', name: 'Kiro CLI', icon: 'k' },
361
- { cmd: 'fast-agent', id: 'fast-agent', name: 'fast-agent', icon: 'F' },
386
+ { cmd: 'claude', id: 'claude-code', name: 'Claude Code', icon: 'C', protocol: 'cli' },
387
+ { cmd: 'opencode', id: 'opencode', name: 'OpenCode', icon: 'O', protocol: 'acp' },
388
+ { cmd: 'gemini', id: 'gemini', name: 'Gemini CLI', icon: 'G', protocol: 'acp' },
389
+ { cmd: 'kilo', id: 'kilo', name: 'Kilo Code', icon: 'K', protocol: 'acp' },
390
+ { cmd: 'goose', id: 'goose', name: 'Goose', icon: 'g', protocol: 'acp' },
391
+ { cmd: 'openhands', id: 'openhands', name: 'OpenHands', icon: 'H', protocol: 'acp' },
392
+ { cmd: 'augment', id: 'augment', name: 'Augment Code', icon: 'A', protocol: 'acp' },
393
+ { cmd: 'cline', id: 'cline', name: 'Cline', icon: 'c', protocol: 'acp' },
394
+ { cmd: 'kimi', id: 'kimi', name: 'Kimi CLI', icon: 'K', protocol: 'acp' },
395
+ { cmd: 'qwen-code', id: 'qwen', name: 'Qwen Code', icon: 'Q', protocol: 'acp' },
396
+ { cmd: 'codex', id: 'codex', name: 'Codex CLI', icon: 'X', protocol: 'acp' },
397
+ { cmd: 'mistral-vibe', id: 'mistral', name: 'Mistral Vibe', icon: 'M', protocol: 'acp' },
398
+ { cmd: 'kiro', id: 'kiro', name: 'Kiro CLI', icon: 'k', protocol: 'acp' },
399
+ { cmd: 'fast-agent', id: 'fast-agent', name: 'fast-agent', icon: 'F', protocol: 'acp' },
362
400
  ];
363
401
  for (const bin of binaries) {
364
402
  const result = findCommand(bin.cmd);
365
- if (result) agents.push({ id: bin.id, name: bin.name, icon: bin.icon, path: result });
403
+ if (result) agents.push({
404
+ id: bin.id,
405
+ name: bin.name,
406
+ icon: bin.icon,
407
+ path: result,
408
+ protocol: bin.protocol
409
+ });
366
410
  }
367
411
  return agents;
368
412
  }
369
413
 
414
+ // Function to discover agents from external ACP servers
415
+ async function discoverExternalACPServers() {
416
+ // Default ACP servers to query (excluding local server to prevent recursion)
417
+ const acpServers = [
418
+ 'http://localhost:8080', // Common default ACP port
419
+ ];
420
+
421
+ const externalAgents = [];
422
+ for (const serverUrl of acpServers) {
423
+ try {
424
+ console.log(`Querying ACP agents from: ${serverUrl}`);
425
+ const agents = await queryACPServerAgents(serverUrl);
426
+ externalAgents.push(...agents);
427
+ } catch (error) {
428
+ console.error(`Failed to query ${serverUrl}:`, error.message);
429
+ }
430
+ }
431
+
432
+ return externalAgents;
433
+ }
434
+
370
435
  const discoveredAgents = discoverAgents();
371
436
  initializeDescriptors(discoveredAgents);
372
437
 
@@ -1947,8 +2012,31 @@ const server = http.createServer(async (req, res) => {
1947
2012
 
1948
2013
  if (pathOnly === '/api/agents/search' && req.method === 'POST') {
1949
2014
  const body = await parseBody(req);
1950
- const result = queries.searchAgents(discoveredAgents, body);
1951
- sendJSON(req, res, 200, result);
2015
+ try {
2016
+ // Get local agents
2017
+ const localResult = queries.searchAgents(discoveredAgents, body);
2018
+
2019
+ // Get external agents from ACP servers
2020
+ const externalAgents = await discoverExternalACPServers();
2021
+ const externalResult = queries.searchAgents(externalAgents, body);
2022
+
2023
+ // Combine results
2024
+ const combinedAgents = [...localResult.agents, ...externalResult.agents];
2025
+ const total = localResult.total + externalResult.total;
2026
+ const hasMore = localResult.hasMore || externalResult.hasMore;
2027
+
2028
+ sendJSON(req, res, 200, {
2029
+ agents: combinedAgents,
2030
+ total,
2031
+ limit: body.limit || 50,
2032
+ offset: body.offset || 0,
2033
+ hasMore,
2034
+ });
2035
+ } catch (error) {
2036
+ console.error('Error searching agents:', error);
2037
+ const result = queries.searchAgents(discoveredAgents, body);
2038
+ sendJSON(req, res, 200, result);
2039
+ }
1952
2040
  return;
1953
2041
  }
1954
2042
 
package/static/index.html CHANGED
@@ -804,25 +804,29 @@
804
804
  align-items: flex-end;
805
805
  }
806
806
 
807
- .agent-selector {
808
- padding: 0.5rem;
809
- border: none;
810
- border-radius: 0.375rem;
811
- background-color: var(--color-bg-secondary);
812
- color: var(--color-text-primary);
813
- font-size: 0.8rem;
814
- cursor: pointer;
815
- flex-shrink: 0;
816
- width: auto;
817
- min-width: 80px;
818
- max-width: 200px;
819
- }
820
-
821
- .agent-selector:disabled, .model-selector:disabled {
822
- opacity: 0.5;
823
- cursor: not-allowed;
824
- background-color: var(--color-bg-secondary);
825
- }
807
+ .agent-selector {
808
+ padding: 0.5rem;
809
+ border: none;
810
+ border-radius: 0.375rem;
811
+ background-color: var(--color-bg-secondary);
812
+ color: var(--color-text-primary);
813
+ font-size: 0.8rem;
814
+ cursor: pointer;
815
+ flex-shrink: 0;
816
+ width: auto;
817
+ min-width: 80px;
818
+ max-width: 200px;
819
+ }
820
+
821
+
822
+
823
+
824
+
825
+ .agent-selector:disabled, .model-selector:disabled {
826
+ opacity: 0.5;
827
+ cursor: not-allowed;
828
+ background-color: var(--color-bg-secondary);
829
+ }
826
830
 
827
831
  .model-selector {
828
832
  padding: 0.5rem;
@@ -3167,17 +3171,17 @@
3167
3171
  </div>
3168
3172
 
3169
3173
  <!-- Input area: fixed at bottom -->
3170
- <div class="input-section">
3171
- <div class="input-wrapper">
3172
- <select class="agent-selector" data-agent-selector title="Select agent"></select>
3173
- <select class="model-selector" data-model-selector title="Select model" data-empty="true"></select>
3174
- <textarea
3175
- class="message-textarea"
3176
- data-message-input
3177
- placeholder="Message AgentGUI... (Ctrl+Enter to send)"
3178
- aria-label="Message input"
3179
- rows="1"
3180
- ></textarea>
3174
+ <div class="input-section">
3175
+ <div class="input-wrapper">
3176
+ <select class="agent-selector" data-agent-selector title="Select agent"></select>
3177
+ <select class="model-selector" data-model-selector title="Select model" data-empty="true"></select>
3178
+ <textarea
3179
+ class="message-textarea"
3180
+ data-message-input
3181
+ placeholder="Message AgentGUI... (Ctrl+Enter to send)"
3182
+ aria-label="Message input"
3183
+ rows="1"
3184
+ ></textarea>
3181
3185
  <button class="inject-btn" id="injectBtn" title="Inject instructions into running agent" aria-label="Inject instructions">
3182
3186
  <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3183
3187
  <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/>
@@ -355,6 +355,7 @@ class AgentGUIClient {
355
355
  this.ui.messageInput = document.querySelector('[data-message-input]');
356
356
  this.ui.sendButton = document.querySelector('[data-send-button]');
357
357
  this.ui.agentSelector = document.querySelector('[data-agent-selector]');
358
+ this.ui.agentSelector = document.querySelector('[data-agent-selector]');
358
359
  this.ui.modelSelector = document.querySelector('[data-model-selector]');
359
360
 
360
361
  if (this.ui.agentSelector) {
@@ -1248,7 +1249,7 @@ class AgentGUIClient {
1248
1249
  const prompt = this.ui.messageInput?.value || '';
1249
1250
  const conv = this.state.currentConversation;
1250
1251
  const isNewConversation = conv && !conv.messageCount && !this.state.streamingConversations.has(conv.id);
1251
- const agentId = (isNewConversation ? this.ui.agentSelector?.value : null) || conv?.agentType || this.ui.agentSelector?.value || 'claude-code';
1252
+ const agentId = (isNewConversation ? this.getCurrentAgent() : null) || conv?.agentType || this.getCurrentAgent() || 'claude-code';
1252
1253
  const model = this.ui.modelSelector?.value || null;
1253
1254
 
1254
1255
  if (!prompt.trim()) {
@@ -1822,10 +1823,34 @@ class AgentGUIClient {
1822
1823
  const { agents } = await window.wsClient.rpc('agent.ls');
1823
1824
  this.state.agents = agents;
1824
1825
 
1826
+ // Split agents by protocol
1827
+ const cliAgents = agents.filter(agent => agent.protocol === 'cli');
1828
+ const acpAgents = agents.filter(agent => agent.protocol === 'acp');
1829
+
1830
+ // Populate CLI agent selector
1825
1831
  if (this.ui.agentSelector) {
1826
- this.ui.agentSelector.innerHTML = agents
1827
- .map(agent => `<option value="${agent.id}">${agent.name}</option>`)
1828
- .join('');
1832
+ if (cliAgents.length > 0) {
1833
+ this.ui.agentSelector.innerHTML = cliAgents
1834
+ .map(agent => `<option value="${agent.id}">${agent.name}</option>`)
1835
+ .join('');
1836
+ this.ui.agentSelector.style.display = 'inline-block';
1837
+ } else {
1838
+ this.ui.agentSelector.innerHTML = '';
1839
+ this.ui.agentSelector.style.display = 'none';
1840
+ }
1841
+ }
1842
+
1843
+ // Populate ACP agent selector
1844
+ if (this.ui.agentSelector) {
1845
+ if (acpAgents.length > 0) {
1846
+ this.ui.agentSelector.innerHTML = acpAgents
1847
+ .map(agent => `<option value="${agent.id}">${agent.name}</option>`)
1848
+ .join('');
1849
+ this.ui.agentSelector.style.display = 'inline-block';
1850
+ } else {
1851
+ this.ui.agentSelector.innerHTML = '';
1852
+ this.ui.agentSelector.style.display = 'none';
1853
+ }
1829
1854
  }
1830
1855
 
1831
1856
  window.dispatchEvent(new CustomEvent('agents-loaded', { detail: { agents } }));
@@ -1892,10 +1917,34 @@ class AgentGUIClient {
1892
1917
 
1893
1918
  lockAgentAndModel(agentId, model) {
1894
1919
  this._agentLocked = true;
1895
- if (this.ui.agentSelector) {
1896
- this.ui.agentSelector.value = agentId;
1897
- this.ui.agentSelector.disabled = true;
1920
+
1921
+ // Find the agent to determine protocol
1922
+ const agent = this.state.agents.find(a => a.id === agentId);
1923
+
1924
+ if (agent) {
1925
+ if (agent.protocol === 'cli') {
1926
+ if (this.ui.agentSelector) {
1927
+ this.ui.agentSelector.value = agentId;
1928
+ this.ui.agentSelector.disabled = true;
1929
+ }
1930
+ if (this.ui.agentSelector) {
1931
+ this.ui.agentSelector.value = '';
1932
+ this.ui.agentSelector.disabled = true;
1933
+ this.ui.agentSelector.style.display = 'none';
1934
+ }
1935
+ } else {
1936
+ if (this.ui.agentSelector) {
1937
+ this.ui.agentSelector.value = agentId;
1938
+ this.ui.agentSelector.disabled = true;
1939
+ }
1940
+ if (this.ui.agentSelector) {
1941
+ this.ui.agentSelector.value = '';
1942
+ this.ui.agentSelector.disabled = true;
1943
+ this.ui.agentSelector.style.display = 'none';
1944
+ }
1945
+ }
1898
1946
  }
1947
+
1899
1948
  this.loadModelsForAgent(agentId).then(() => {
1900
1949
  if (this.ui.modelSelector && model) {
1901
1950
  this.ui.modelSelector.value = model;
@@ -1908,6 +1957,17 @@ class AgentGUIClient {
1908
1957
  if (this.ui.agentSelector) {
1909
1958
  this.ui.agentSelector.disabled = false;
1910
1959
  }
1960
+ if (this.ui.agentSelector) {
1961
+ this.ui.agentSelector.disabled = false;
1962
+ }
1963
+
1964
+ // Show both selectors again when unlocking
1965
+ if (this.ui.agentSelector && this.state.agents.some(a => a.protocol === 'cli')) {
1966
+ this.ui.agentSelector.style.display = 'inline-block';
1967
+ }
1968
+ if (this.ui.agentSelector && this.state.agents.some(a => a.protocol === 'acp')) {
1969
+ this.ui.agentSelector.style.display = 'inline-block';
1970
+ }
1911
1971
  }
1912
1972
 
1913
1973
  /**
@@ -1922,9 +1982,31 @@ class AgentGUIClient {
1922
1982
  this.lockAgentAndModel(agentId, model);
1923
1983
  } else {
1924
1984
  this.unlockAgentAndModel();
1925
- if (this.ui.agentSelector) {
1926
- this.ui.agentSelector.value = agentId;
1985
+ // Find the agent to determine protocol
1986
+ const agent = this.state.agents.find(a => a.id === agentId);
1987
+
1988
+ if (agent) {
1989
+ if (agent.protocol === 'cli') {
1990
+ if (this.ui.agentSelector) {
1991
+ this.ui.agentSelector.value = agentId;
1992
+ this.ui.agentSelector.style.display = 'inline-block';
1993
+ }
1994
+ if (this.ui.agentSelector) {
1995
+ this.ui.agentSelector.value = '';
1996
+ this.ui.agentSelector.style.display = 'none';
1997
+ }
1998
+ } else {
1999
+ if (this.ui.agentSelector) {
2000
+ this.ui.agentSelector.value = agentId;
2001
+ this.ui.agentSelector.style.display = 'inline-block';
2002
+ }
2003
+ if (this.ui.agentSelector) {
2004
+ this.ui.agentSelector.value = '';
2005
+ this.ui.agentSelector.style.display = 'none';
2006
+ }
2007
+ }
1927
2008
  }
2009
+
1928
2010
  this.loadModelsForAgent(agentId).then(() => {
1929
2011
  if (model && this.ui.modelSelector) {
1930
2012
  this.ui.modelSelector.value = model;
@@ -2494,10 +2576,23 @@ class AgentGUIClient {
2494
2576
  }
2495
2577
 
2496
2578
  /**
2497
- * Get application state
2579
+ * Get current selected agent
2580
+ */
2581
+ getCurrentAgent() {
2582
+ if (this.ui.agentSelector?.value) {
2583
+ return this.ui.agentSelector.value;
2584
+ }
2585
+ if (this.ui.agentSelector?.value) {
2586
+ return this.ui.agentSelector.value;
2587
+ }
2588
+ return 'claude-code';
2589
+ }
2590
+
2591
+ /**
2592
+ * Get current selected model
2498
2593
  */
2499
- getState() {
2500
- return { ...this.state };
2594
+ getCurrentModel() {
2595
+ return this.ui.modelSelector?.value || null;
2501
2596
  }
2502
2597
 
2503
2598
  /**