agentgui 1.0.332 → 1.0.334

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.332",
3
+ "version": "1.0.334",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1756,6 +1756,71 @@ const server = http.createServer(async (req, res) => {
1756
1756
  return;
1757
1757
  }
1758
1758
 
1759
+ if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
1760
+ const statuses = discoveredAgents.map(agent => {
1761
+ const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
1762
+ try {
1763
+ if (agent.id === 'claude-code') {
1764
+ const credFile = path.join(os.homedir(), '.claude', '.credentials.json');
1765
+ if (fs.existsSync(credFile)) {
1766
+ const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
1767
+ if (creds.claudeAiOauth && creds.claudeAiOauth.expiresAt > Date.now()) {
1768
+ status.authenticated = true;
1769
+ status.detail = creds.claudeAiOauth.subscriptionType || 'authenticated';
1770
+ } else {
1771
+ status.detail = 'expired';
1772
+ }
1773
+ } else {
1774
+ status.detail = 'no credentials';
1775
+ }
1776
+ } else if (agent.id === 'gemini') {
1777
+ const oauthFile = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
1778
+ const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
1779
+ let hasOAuth = false;
1780
+ if (fs.existsSync(oauthFile)) {
1781
+ try {
1782
+ const creds = JSON.parse(fs.readFileSync(oauthFile, 'utf-8'));
1783
+ if (creds.refresh_token || creds.access_token) hasOAuth = true;
1784
+ } catch (_) {}
1785
+ }
1786
+ if (fs.existsSync(acctFile)) {
1787
+ const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
1788
+ if (accts.active) {
1789
+ status.authenticated = true;
1790
+ status.detail = accts.active;
1791
+ } else if (hasOAuth) {
1792
+ status.authenticated = true;
1793
+ status.detail = 'oauth';
1794
+ } else {
1795
+ status.detail = 'logged out';
1796
+ }
1797
+ } else if (hasOAuth) {
1798
+ status.authenticated = true;
1799
+ status.detail = 'oauth';
1800
+ } else {
1801
+ status.detail = 'no credentials';
1802
+ }
1803
+ } else if (agent.id === 'opencode') {
1804
+ const out = execSync('opencode auth list 2>&1', { encoding: 'utf-8', timeout: 5000 });
1805
+ const countMatch = out.match(/(\d+)\s+credentials?/);
1806
+ if (countMatch && parseInt(countMatch[1], 10) > 0) {
1807
+ status.authenticated = true;
1808
+ status.detail = countMatch[1] + ' credential(s)';
1809
+ } else {
1810
+ status.detail = 'no credentials';
1811
+ }
1812
+ } else {
1813
+ status.detail = 'unknown';
1814
+ }
1815
+ } catch (e) {
1816
+ status.detail = 'check failed';
1817
+ }
1818
+ return status;
1819
+ });
1820
+ sendJSON(req, res, 200, { agents: statuses });
1821
+ return;
1822
+ }
1823
+
1759
1824
  const agentByIdMatch = pathOnly.match(/^\/api\/agents\/([^/]+)$/);
1760
1825
  if (agentByIdMatch && req.method === 'GET') {
1761
1826
  const agentId = agentByIdMatch[1];
@@ -1826,71 +1891,6 @@ const server = http.createServer(async (req, res) => {
1826
1891
  return;
1827
1892
  }
1828
1893
 
1829
- if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
1830
- const statuses = discoveredAgents.map(agent => {
1831
- const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
1832
- try {
1833
- if (agent.id === 'claude-code') {
1834
- const credFile = path.join(os.homedir(), '.claude', '.credentials.json');
1835
- if (fs.existsSync(credFile)) {
1836
- const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
1837
- if (creds.claudeAiOauth && creds.claudeAiOauth.expiresAt > Date.now()) {
1838
- status.authenticated = true;
1839
- status.detail = creds.claudeAiOauth.subscriptionType || 'authenticated';
1840
- } else {
1841
- status.detail = 'expired';
1842
- }
1843
- } else {
1844
- status.detail = 'no credentials';
1845
- }
1846
- } else if (agent.id === 'gemini') {
1847
- const oauthFile = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
1848
- const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
1849
- let hasOAuth = false;
1850
- if (fs.existsSync(oauthFile)) {
1851
- try {
1852
- const creds = JSON.parse(fs.readFileSync(oauthFile, 'utf-8'));
1853
- if (creds.refresh_token || creds.access_token) hasOAuth = true;
1854
- } catch (_) {}
1855
- }
1856
- if (fs.existsSync(acctFile)) {
1857
- const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
1858
- if (accts.active) {
1859
- status.authenticated = true;
1860
- status.detail = accts.active;
1861
- } else if (hasOAuth) {
1862
- status.authenticated = true;
1863
- status.detail = 'oauth';
1864
- } else {
1865
- status.detail = 'logged out';
1866
- }
1867
- } else if (hasOAuth) {
1868
- status.authenticated = true;
1869
- status.detail = 'oauth';
1870
- } else {
1871
- status.detail = 'no credentials';
1872
- }
1873
- } else if (agent.id === 'opencode') {
1874
- const out = execSync('opencode auth list 2>&1', { encoding: 'utf-8', timeout: 5000 });
1875
- const countMatch = out.match(/(\d+)\s+credentials?/);
1876
- if (countMatch && parseInt(countMatch[1], 10) > 0) {
1877
- status.authenticated = true;
1878
- status.detail = countMatch[1] + ' credential(s)';
1879
- } else {
1880
- status.detail = 'no credentials';
1881
- }
1882
- } else {
1883
- status.detail = 'unknown';
1884
- }
1885
- } catch (e) {
1886
- status.detail = 'check failed';
1887
- }
1888
- return status;
1889
- });
1890
- sendJSON(req, res, 200, { agents: statuses });
1891
- return;
1892
- }
1893
-
1894
1894
  if (pathOnly === '/api/gemini-oauth/start' && req.method === 'POST') {
1895
1895
  try {
1896
1896
  const result = await startGeminiOAuth(req);
package/static/index.html CHANGED
@@ -46,6 +46,8 @@
46
46
  --color-text-primary: #f9fafb;
47
47
  --color-text-secondary: #d1d5db;
48
48
  --color-border: #374151;
49
+ --color-primary: #6b7280;
50
+ --color-primary-dark: #4b5563;
49
51
  }
50
52
 
51
53
  html, body {
@@ -496,6 +498,26 @@
496
498
  }
497
499
  .terminal-output { flex: 1; overflow: hidden; }
498
500
 
501
+ .models-container {
502
+ flex: 1; display: flex; flex-direction: column; overflow-y: auto;
503
+ align-items: flex-start; padding: 2rem;
504
+ }
505
+ .models-panel {
506
+ display: flex; flex-direction: column; gap: 1rem;
507
+ background: var(--color-bg-secondary); border-radius: 0.5rem;
508
+ padding: 1.5rem; min-width: 280px;
509
+ }
510
+ .models-panel h3 { margin: 0 0 0.5rem; font-size: 1rem; font-weight: 600; }
511
+ .models-panel label {
512
+ display: flex; flex-direction: column; gap: 0.25rem;
513
+ font-size: 0.8rem; color: var(--color-text-secondary); font-weight: 500;
514
+ }
515
+ .models-panel select {
516
+ padding: 0.5rem; border: 1px solid var(--color-border);
517
+ border-radius: 0.375rem; background: var(--color-bg-primary);
518
+ color: var(--color-text-primary); font-size: 0.875rem; cursor: pointer;
519
+ }
520
+
499
521
  /* --- View toggle bar --- */
500
522
  .view-toggle-bar {
501
523
  display: flex;
@@ -3024,6 +3046,7 @@
3024
3046
  <button class="view-toggle-btn active" data-view="chat">Chat</button>
3025
3047
  <button class="view-toggle-btn" data-view="files">Files</button>
3026
3048
  <button class="view-toggle-btn" data-view="voice" style="display:none;">Voice</button>
3049
+ <button class="view-toggle-btn" data-view="models">Models</button>
3027
3050
  <button class="view-toggle-btn" data-view="terminal" id="terminalTabBtn" style="display:none;">Terminal</button>
3028
3051
  </div>
3029
3052
 
@@ -3045,6 +3068,15 @@
3045
3068
  <iframe id="fileBrowserIframe" class="file-browser-iframe"></iframe>
3046
3069
  </div>
3047
3070
 
3071
+ <!-- Models/Agent selector view -->
3072
+ <div id="modelsContainer" class="models-container" style="display:none;">
3073
+ <div class="models-panel">
3074
+ <h3>Agent &amp; Model</h3>
3075
+ <label>Agent<select class="agent-selector" data-agent-selector-models title="Select agent"></select></label>
3076
+ <label>Model<select class="model-selector" data-model-selector-models title="Select model" data-empty="true"></select></label>
3077
+ </div>
3078
+ </div>
3079
+
3048
3080
  <!-- Terminal output view -->
3049
3081
  <div id="terminalContainer" class="terminal-container" style="display:none;">
3050
3082
  <div id="terminalOutput" class="terminal-output"></div>
@@ -346,6 +346,7 @@
346
346
  var fileIframe = document.getElementById('fileBrowserIframe');
347
347
  var voiceContainer = document.getElementById('voiceContainer');
348
348
  var terminalContainer = document.getElementById('terminalContainer');
349
+ var modelsContainer = document.getElementById('modelsContainer');
349
350
 
350
351
  if (!bar) return;
351
352
 
@@ -358,6 +359,7 @@
358
359
  if (fileBrowser) fileBrowser.style.display = view === 'files' ? 'flex' : 'none';
359
360
  if (voiceContainer) voiceContainer.style.display = view === 'voice' ? 'flex' : 'none';
360
361
  if (terminalContainer) terminalContainer.style.display = view === 'terminal' ? 'flex' : 'none';
362
+ if (modelsContainer) modelsContainer.style.display = view === 'models' ? 'flex' : 'none';
361
363
 
362
364
  if (view === 'files' && fileIframe && currentConversation) {
363
365
  var src = BASE + '/files/' + currentConversation + '/';
@@ -372,9 +374,29 @@
372
374
  window.voiceModule.deactivate();
373
375
  }
374
376
 
377
+ if (view === 'models') syncModelsPanelFromMain();
378
+
375
379
  window.dispatchEvent(new CustomEvent('view-switched', { detail: { view: view } }));
376
380
  }
377
381
 
382
+ function syncModelsPanelFromMain() {
383
+ var mainAgent = document.querySelector('[data-agent-selector]');
384
+ var mainModel = document.querySelector('[data-model-selector]');
385
+ var panelAgent = document.querySelector('[data-agent-selector-models]');
386
+ var panelModel = document.querySelector('[data-model-selector-models]');
387
+ if (!panelAgent || !panelModel) return;
388
+ if (mainAgent && panelAgent.innerHTML !== mainAgent.innerHTML) {
389
+ panelAgent.innerHTML = mainAgent.innerHTML;
390
+ panelAgent.value = mainAgent.value;
391
+ }
392
+ if (mainModel && panelModel.innerHTML !== mainModel.innerHTML) {
393
+ panelModel.innerHTML = mainModel.innerHTML;
394
+ panelModel.value = mainModel.value;
395
+ }
396
+ panelAgent.onchange = function() { if (mainAgent) { mainAgent.value = panelAgent.value; mainAgent.dispatchEvent(new Event('change')); } };
397
+ panelModel.onchange = function() { if (mainModel) { mainModel.value = panelModel.value; mainModel.dispatchEvent(new Event('change')); } };
398
+ }
399
+
378
400
  function updateViewToggleVisibility() {
379
401
  var bar = document.getElementById('viewToggleBar');
380
402
  if (!bar) return;
@@ -1213,6 +1213,7 @@ class StreamingRenderer {
1213
1213
  const details = document.createElement('details');
1214
1214
  details.className = 'folded-tool' + (isError ? ' folded-tool-error' : ' folded-tool-success');
1215
1215
  details.dataset.eventType = 'tool_result';
1216
+ if (!isError) details.open = true;
1216
1217
  if (block.tool_use_id) details.dataset.toolUseId = block.tool_use_id;
1217
1218
  details.classList.add(this._getBlockTypeClass('tool_result'));
1218
1219