agentgui 1.0.463 → 1.0.465

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/.prd CHANGED
@@ -0,0 +1,61 @@
1
+ {
2
+ "project": "agentgui-tools-detection",
3
+ "created": "2026-03-01",
4
+ "objective": "Fix tools view to correctly detect, install, and upgrade CLI tools (OpenCode, Gemini CLI, Kilo, Claude Code)",
5
+ "problem": "Tools show 'Not installed' even after installation via npm/npx. Detection logic fails to recognize installed tools.",
6
+ "root_causes": [
7
+ "isInstalled() checks PATH but npm-installed binaries may not be in PATH immediately",
8
+ "Version detection uses --version flag which may not work for all tools",
9
+ "Marker directories are not created during installation, only checked",
10
+ "Race condition: binary checked before npm finishes fully writing files",
11
+ "No fallback detection method if which/where command fails"
12
+ ],
13
+ "items": [],
14
+ "completed": [
15
+ {
16
+ "id": "1",
17
+ "subject": "Understand current tool detection and installation flow",
18
+ "status": "completed"
19
+ },
20
+ {
21
+ "id": "2",
22
+ "subject": "Implement reliable tool installation detection with multiple fallbacks",
23
+ "status": "completed"
24
+ },
25
+ {
26
+ "id": "3",
27
+ "subject": "Improve version detection with multiple fallback strategies",
28
+ "status": "completed"
29
+ },
30
+ {
31
+ "id": "4",
32
+ "subject": "Add installation verification and marker directory creation",
33
+ "status": "completed"
34
+ },
35
+ {
36
+ "id": "5",
37
+ "subject": "Test tool installation and detection for all 4 tools",
38
+ "status": "completed"
39
+ },
40
+ {
41
+ "id": "6",
42
+ "subject": "Fix tools UI to show correct installation status",
43
+ "status": "completed"
44
+ },
45
+ {
46
+ "id": "7",
47
+ "subject": "Implement upgrade mechanism for installed tools",
48
+ "status": "completed"
49
+ },
50
+ {
51
+ "id": "8",
52
+ "subject": "Test upgrade path for each tool",
53
+ "status": "completed"
54
+ },
55
+ {
56
+ "id": "9",
57
+ "subject": "Verify git commit and all work is pushed",
58
+ "status": "completed"
59
+ }
60
+ ]
61
+ }
package/database.js CHANGED
@@ -1607,6 +1607,28 @@ export const queries = {
1607
1607
  return stmt.all();
1608
1608
  },
1609
1609
 
1610
+ insertToolInstallation(toolId, data) {
1611
+ const now = Date.now();
1612
+ const stmt = prep(`
1613
+ INSERT OR IGNORE INTO tool_installations
1614
+ (id, tool_id, version, installed_at, status, last_check_at, error_message, update_available, latest_version, created_at, updated_at)
1615
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1616
+ `);
1617
+ stmt.run(
1618
+ generateId('ti'),
1619
+ toolId,
1620
+ data.version || null,
1621
+ data.installed_at || null,
1622
+ data.status || 'not_installed',
1623
+ now,
1624
+ data.error_message || null,
1625
+ 0,
1626
+ null,
1627
+ now,
1628
+ now
1629
+ );
1630
+ },
1631
+
1610
1632
  updateToolStatus(toolId, data) {
1611
1633
  const now = Date.now();
1612
1634
  const stmt = prep(`
@@ -22,7 +22,33 @@ const isInstalled = (tool) => {
22
22
  if (fs.existsSync(path.join(process.cwd(), 'node_modules', '.bin', tool.binary + ext))) return true;
23
23
  try { execSync(`${isWindows ? 'where' : 'which'} ${tool.binary}`, { stdio: 'pipe', timeout: 2000 }); return true; } catch (_) { return false; }
24
24
  };
25
- const detectVersion = (binary) => { try { const o = execSync(`${binary} --version 2>&1 || ${binary} -v 2>&1`, { timeout: 3000, encoding: 'utf8', stdio: 'pipe' }); return o.match(/(\d+\.\d+\.\d+)/)?.[1] || null; } catch (_) { return null; } };
25
+ const detectVersion = (binary) => {
26
+ const versionPatterns = ['--version', '-v', '-V', '--help'];
27
+ for (const flag of versionPatterns) {
28
+ try {
29
+ const o = execSync(`${binary} ${flag} 2>&1`, { timeout: 2000, encoding: 'utf8', stdio: 'pipe' });
30
+ const match = o.match(/(\d+\.\d+\.\d+)/);
31
+ if (match?.[1]) return match[1];
32
+ } catch (_) {}
33
+ }
34
+ try {
35
+ const binPath = execSync(`${isWindows ? 'where' : 'which'} ${binary}`, { timeout: 2000, encoding: 'utf8', stdio: 'pipe' }).trim();
36
+ let pkgDir = path.dirname(binPath);
37
+ for (let i = 0; i < 5; i++) {
38
+ const pkgPath = path.join(pkgDir, 'package.json');
39
+ if (fs.existsSync(pkgPath)) {
40
+ try {
41
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
42
+ if (pkg.version && pkg.version.match(/\d+\.\d+\.\d+/)) {
43
+ return pkg.version.match(/(\d+\.\d+\.\d+)/)[1];
44
+ }
45
+ } catch (_) {}
46
+ }
47
+ pkgDir = path.dirname(pkgDir);
48
+ }
49
+ } catch (_) {}
50
+ return null;
51
+ };
26
52
  const cmpVer = (v1, v2) => { const [a,b] = [v1?.split('.')?.map(Number) || [], v2?.split('.')?.map(Number) || []]; for(let i=0;i<3;i++) { const n1=a[i]||0, n2=b[i]||0; if(n1<n2)return true; if(n1>n2)return false; } return false; };
27
53
 
28
54
  export function checkToolStatus(toolId) {
@@ -52,13 +78,39 @@ export async function checkForUpdates(toolId, currentVersion) {
52
78
  } catch (_) { return { hasUpdate: false, latestVersion: null }; }
53
79
  }
54
80
 
81
+ const createMarkerDir = (tool) => {
82
+ try {
83
+ const markerDir = path.dirname(tool.marker);
84
+ if (!fs.existsSync(markerDir)) {
85
+ fs.mkdirSync(markerDir, { recursive: true });
86
+ }
87
+ if (!fs.existsSync(tool.marker)) {
88
+ fs.mkdirSync(tool.marker, { recursive: true });
89
+ }
90
+ } catch (_) {}
91
+ };
92
+
55
93
  const spawnProc = (toolId, tool, pkg, onProgress) => new Promise((resolve) => {
56
94
  const proc = spawn(isWindows ? 'npx.cmd' : 'npx', ['--yes', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
57
95
  let completed = false, stderr = '';
58
96
  const timer = setTimeout(() => { if (!completed) { completed = true; try { proc.kill('SIGKILL'); } catch (_) {} resolve({ success: false, error: 'Timeout (5min)' }); }}, 300000);
59
97
  proc.stdout.on('data', (d) => { if (onProgress) onProgress({ type: 'progress', data: d.toString() }); });
60
98
  proc.stderr.on('data', (d) => { stderr += d.toString(); if (onProgress) onProgress({ type: 'error', data: d.toString() }); });
61
- proc.on('close', (code) => { clearTimeout(timer); if (completed) return; completed = true; if (code === 0) { const s = checkToolStatus(toolId); resolve(s?.installed ? { success: true, error: null, version: s.version } : { success: false, error: 'Tool not detected' }); } else { resolve({ success: false, error: stderr.substring(0, 1000) || 'Failed' }); } });
99
+ proc.on('close', (code) => {
100
+ clearTimeout(timer);
101
+ if (completed) return;
102
+ completed = true;
103
+ if (code === 0) {
104
+ createMarkerDir(tool);
105
+ let s = checkToolStatus(toolId);
106
+ if (!s?.installed) {
107
+ setTimeout(() => { s = checkToolStatus(toolId); }, 500);
108
+ }
109
+ resolve(s?.installed ? { success: true, error: null, version: s.version } : { success: false, error: 'Tool not detected' });
110
+ } else {
111
+ resolve({ success: false, error: stderr.substring(0, 1000) || 'Failed' });
112
+ }
113
+ });
62
114
  proc.on('error', (err) => { clearTimeout(timer); if (!completed) { completed = true; resolve({ success: false, error: err.message }); }});
63
115
  });
64
116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.463",
3
+ "version": "1.0.465",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1784,12 +1784,28 @@ const server = http.createServer(async (req, res) => {
1784
1784
  if (pathOnly === '/api/tools' && req.method === 'GET') {
1785
1785
  console.log('[TOOLS-API] Handling GET /api/tools');
1786
1786
  const tools = toolManager.getAllTools();
1787
- const toolsWithUpdates = await Promise.all(tools.map(async (t) => {
1788
- if (t.installed) {
1787
+ const toolsWithStatus = tools.map((t) => {
1788
+ const dbStatus = queries.getToolStatus(t.id);
1789
+ const status = dbStatus?.status || (t.installed ? 'installed' : 'not_installed');
1790
+ return {
1791
+ id: t.id,
1792
+ name: t.name,
1793
+ pkg: t.pkg,
1794
+ binary: t.binary,
1795
+ installed: t.installed,
1796
+ version: dbStatus?.version || t.version,
1797
+ status: status,
1798
+ error_message: dbStatus?.error_message,
1799
+ hasUpdate: false,
1800
+ latestVersion: null
1801
+ };
1802
+ });
1803
+ const toolsWithUpdates = await Promise.all(toolsWithStatus.map(async (t) => {
1804
+ if (t.installed && t.version) {
1789
1805
  const updates = await toolManager.checkForUpdates(t.id, t.version);
1790
1806
  return { ...t, hasUpdate: updates.hasUpdate, latestVersion: updates.latestVersion };
1791
1807
  }
1792
- return { ...t, hasUpdate: false, latestVersion: null };
1808
+ return t;
1793
1809
  }));
1794
1810
  sendJSON(req, res, 200, { tools: toolsWithUpdates });
1795
1811
  return;
@@ -1818,6 +1834,10 @@ const server = http.createServer(async (req, res) => {
1818
1834
  sendJSON(req, res, 404, { error: 'Tool not found' });
1819
1835
  return;
1820
1836
  }
1837
+ const existing = queries.getToolStatus(toolId);
1838
+ if (!existing) {
1839
+ queries.insertToolInstallation(toolId, { status: 'not_installed' });
1840
+ }
1821
1841
  queries.updateToolStatus(toolId, { status: 'installing' });
1822
1842
  sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
1823
1843
  toolManager.install(toolId, (msg) => {