agentgui 1.0.615 → 1.0.617

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.
@@ -1,4 +1,4 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, execSync } from 'child_process';
2
2
  import os from 'os';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
@@ -10,10 +10,10 @@ const TOOLS = [
10
10
  { id: 'cli-gemini', name: 'Gemini CLI', pkg: '@google/gemini-cli', category: 'cli' },
11
11
  { id: 'cli-kilo', name: 'Kilo Code', pkg: '@kilocode/cli', category: 'cli' },
12
12
  { id: 'cli-codex', name: 'Codex CLI', pkg: '@openai/codex', category: 'cli' },
13
- { id: 'gm-cc', name: 'GM Claude', pkg: 'gm-cc', pluginId: 'gm-cc', category: 'plugin' },
14
- { id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', pluginId: 'gm-oc', category: 'plugin' },
15
- { id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', pluginId: 'gm', category: 'plugin' },
16
- { id: 'gm-kilo', name: 'GM Kilo', pkg: 'gm-kilo', pluginId: 'gm-kilo', category: 'plugin' },
13
+ { id: 'gm-cc', name: 'GM Claude', pkg: 'gm-cc', pluginId: 'gm-cc', category: 'plugin', frameWork: 'claude' },
14
+ { id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', pluginId: 'gm', category: 'plugin', frameWork: 'opencode' },
15
+ { id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', pluginId: 'gm', category: 'plugin', frameWork: 'gemini' },
16
+ { id: 'gm-kilo', name: 'GM Kilo', pkg: 'gm-kilo', pluginId: 'gm', category: 'plugin', frameWork: 'kilo' },
17
17
  ];
18
18
 
19
19
  const statusCache = new Map();
@@ -22,74 +22,115 @@ const versionCache = new Map();
22
22
 
23
23
  const getTool = (id) => TOOLS.find(t => t.id === id);
24
24
 
25
- const getInstalledVersion = (pkg, pluginId = null) => {
25
+ const getInstalledVersion = (pkg, pluginId = null, frameWork = null) => {
26
26
  try {
27
27
  const homeDir = os.homedir();
28
- const tool = pluginId ? TOOLS.find(t => t.pkg === pkg) : null;
29
- const actualPluginId = pluginId || (tool?.pluginId) || pkg;
28
+ const tool = TOOLS.find(t => t.pkg === pkg);
29
+ const actualPluginId = pluginId || tool?.pluginId || pkg;
30
+ const actualFrameWork = frameWork || tool?.frameWork;
30
31
 
31
32
  // Check Claude Code plugins using correct pluginId
32
- const claudePath = path.join(homeDir, '.claude', 'plugins', actualPluginId, 'plugin.json');
33
- if (fs.existsSync(claudePath)) {
34
- try {
35
- const pluginJson = JSON.parse(fs.readFileSync(claudePath, 'utf-8'));
36
- if (pluginJson.version) return pluginJson.version;
37
- } catch (e) {
38
- console.warn(`[tool-manager] Failed to parse ${claudePath}:`, e.message);
33
+ if (!frameWork || frameWork === 'claude') {
34
+ const claudePath = path.join(homeDir, '.claude', 'plugins', actualPluginId, 'plugin.json');
35
+ if (fs.existsSync(claudePath)) {
36
+ try {
37
+ const pluginJson = JSON.parse(fs.readFileSync(claudePath, 'utf-8'));
38
+ if (pluginJson.version) return pluginJson.version;
39
+ } catch (e) {
40
+ console.warn(`[tool-manager] Failed to parse ${claudePath}:`, e.message);
41
+ }
39
42
  }
40
43
  }
41
44
 
42
- // Check OpenCode agents using correct pluginId
43
- const opencodePath = path.join(homeDir, '.config', 'opencode', 'agents', actualPluginId, 'plugin.json');
44
- if (fs.existsSync(opencodePath)) {
45
- try {
46
- const pluginJson = JSON.parse(fs.readFileSync(opencodePath, 'utf-8'));
47
- if (pluginJson.version) return pluginJson.version;
48
- } catch (e) {
49
- console.warn(`[tool-manager] Failed to parse ${opencodePath}:`, e.message);
45
+ // Check OpenCode agents using correct pluginId (stored as .md files)
46
+ if (!frameWork || frameWork === 'opencode') {
47
+ const opencodePath = path.join(homeDir, '.config', 'opencode', 'agents', actualPluginId + '.md');
48
+ if (fs.existsSync(opencodePath)) {
49
+ // Try to extract version from markdown front matter or try plugin.json in agent dir
50
+ try {
51
+ const agentDirPath = path.join(homeDir, '.config', 'opencode', 'agents', actualPluginId, 'plugin.json');
52
+ if (fs.existsSync(agentDirPath)) {
53
+ const pluginJson = JSON.parse(fs.readFileSync(agentDirPath, 'utf-8'));
54
+ if (pluginJson.version) return pluginJson.version;
55
+ }
56
+ } catch (e) {
57
+ // Fallback: skip
58
+ }
59
+ // For multi-framework bundles, try npm package.json in cache
60
+ try {
61
+ const pkgJsonPath = path.join(homeDir, '.gmweb/cache/.bun/install/cache');
62
+ const cacheDirs = fs.readdirSync(pkgJsonPath).filter(d => d.startsWith(pkg + '@'));
63
+ // Sort by version (get latest)
64
+ const latestDir = cacheDirs.sort().reverse()[0];
65
+ if (latestDir) {
66
+ const pkgJsonFile = path.join(pkgJsonPath, latestDir, 'package.json');
67
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf-8'));
68
+ if (pkgJson.version) return pkgJson.version;
69
+ }
70
+ } catch (e) {
71
+ // Fallback
72
+ }
73
+ // Last resort: try to extract from package name patterns
74
+ return 'installed';
50
75
  }
51
76
  }
52
77
 
53
- // Check Gemini CLI agents (stored as 'gm' directory)
54
- const geminiPath = path.join(homeDir, '.gemini', 'extensions', actualPluginId, 'plugin.json');
55
- if (fs.existsSync(geminiPath)) {
56
- try {
57
- const pluginJson = JSON.parse(fs.readFileSync(geminiPath, 'utf-8'));
58
- if (pluginJson.version) return pluginJson.version;
59
- } catch (e) {
60
- console.warn(`[tool-manager] Failed to parse ${geminiPath}:`, e.message);
61
- }
62
- }
63
- // Try gemini-extension.json as fallback
64
- const geminiExtPath = path.join(homeDir, '.gemini', 'extensions', actualPluginId, 'gemini-extension.json');
65
- if (fs.existsSync(geminiExtPath)) {
66
- try {
67
- const extJson = JSON.parse(fs.readFileSync(geminiExtPath, 'utf-8'));
68
- if (extJson.version) return extJson.version;
69
- } catch (e) {
70
- console.warn(`[tool-manager] Failed to parse ${geminiExtPath}:`, e.message);
78
+ // Check Gemini CLI agents (stored as 'gm' directory with gemini-extension.json)
79
+ if (!frameWork || frameWork === 'gemini') {
80
+ const geminiExtPath = path.join(homeDir, '.gemini', 'extensions', actualPluginId, 'gemini-extension.json');
81
+ if (fs.existsSync(geminiExtPath)) {
82
+ try {
83
+ const extJson = JSON.parse(fs.readFileSync(geminiExtPath, 'utf-8'));
84
+ if (extJson.version) return extJson.version;
85
+ } catch (e) {
86
+ console.warn(`[tool-manager] Failed to parse ${geminiExtPath}:`, e.message);
87
+ }
71
88
  }
72
89
  }
73
90
 
74
- // Check Kilo agents using correct pluginId
75
- const kiloPath = path.join(homeDir, '.config', 'kilo', 'agents', actualPluginId, 'plugin.json');
76
- if (fs.existsSync(kiloPath)) {
77
- try {
78
- const pluginJson = JSON.parse(fs.readFileSync(kiloPath, 'utf-8'));
79
- if (pluginJson.version) return pluginJson.version;
80
- } catch (e) {
81
- console.warn(`[tool-manager] Failed to parse ${kiloPath}:`, e.message);
91
+ // Check Kilo agents (stored as .md files)
92
+ if (!frameWork || frameWork === 'kilo') {
93
+ const kiloPath = path.join(homeDir, '.config', 'kilo', 'agents', actualPluginId + '.md');
94
+ if (fs.existsSync(kiloPath)) {
95
+ // Try to extract version from markdown front matter or try plugin.json in agent dir
96
+ try {
97
+ const agentDirPath = path.join(homeDir, '.config', 'kilo', 'agents', actualPluginId, 'plugin.json');
98
+ if (fs.existsSync(agentDirPath)) {
99
+ const pluginJson = JSON.parse(fs.readFileSync(agentDirPath, 'utf-8'));
100
+ if (pluginJson.version) return pluginJson.version;
101
+ }
102
+ } catch (e) {
103
+ // Fallback: skip
104
+ }
105
+ // For multi-framework bundles, try npm package.json in cache
106
+ try {
107
+ const pkgJsonPath = path.join(homeDir, '.gmweb/cache/.bun/install/cache');
108
+ const cacheDirs = fs.readdirSync(pkgJsonPath).filter(d => d.startsWith(pkg + '@'));
109
+ // Sort by version (get latest)
110
+ const latestDir = cacheDirs.sort().reverse()[0];
111
+ if (latestDir) {
112
+ const pkgJsonFile = path.join(pkgJsonPath, latestDir, 'package.json');
113
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf-8'));
114
+ if (pkgJson.version) return pkgJson.version;
115
+ }
116
+ } catch (e) {
117
+ // Fallback
118
+ }
119
+ // Last resort: try to extract from package name patterns
120
+ return 'installed';
82
121
  }
83
122
  }
84
123
 
85
124
  // Check Codex CLI (stored at ~/.codex)
86
- const codexPath = path.join(homeDir, '.codex', 'plugin.json');
87
- if (fs.existsSync(codexPath)) {
88
- try {
89
- const pluginJson = JSON.parse(fs.readFileSync(codexPath, 'utf-8'));
90
- if (pluginJson.version) return pluginJson.version;
91
- } catch (e) {
92
- console.warn(`[tool-manager] Failed to parse ${codexPath}:`, e.message);
125
+ if (!frameWork || frameWork === 'codex') {
126
+ const codexPath = path.join(homeDir, '.codex', 'plugin.json');
127
+ if (fs.existsSync(codexPath)) {
128
+ try {
129
+ const pluginJson = JSON.parse(fs.readFileSync(codexPath, 'utf-8'));
130
+ if (pluginJson.version) return pluginJson.version;
131
+ } catch (e) {
132
+ console.warn(`[tool-manager] Failed to parse ${codexPath}:`, e.message);
133
+ }
93
134
  }
94
135
  }
95
136
  } catch (_) {}
@@ -97,43 +138,16 @@ const getInstalledVersion = (pkg, pluginId = null) => {
97
138
  };
98
139
 
99
140
  const getPublishedVersion = async (pkg) => {
100
- try {
101
- const cacheKey = `published-${pkg}`;
102
- const cached = versionCache.get(cacheKey);
103
- if (cached && Date.now() - cached.timestamp < 3600000) {
104
- return cached.version;
105
- }
106
-
107
- const cmd = isWindows ? 'npm.cmd' : 'npm';
108
- const result = await new Promise((resolve) => {
109
- const proc = spawn(cmd, ['view', pkg, 'version'], {
110
- stdio: ['pipe', 'pipe', 'pipe'],
111
- timeout: 5000,
112
- shell: isWindows
113
- });
114
- let stdout = '';
115
- proc.stdout.on('data', (d) => { stdout += d.toString(); });
116
- const timer = setTimeout(() => {
117
- try { proc.kill('SIGKILL'); } catch (_) {}
118
- resolve(null);
119
- }, 5000);
120
- proc.on('close', (code) => {
121
- clearTimeout(timer);
122
- resolve(code === 0 ? stdout.trim() : null);
123
- });
124
- proc.on('error', () => {
125
- clearTimeout(timer);
126
- resolve(null);
127
- });
128
- });
129
-
130
- if (result) {
131
- versionCache.set(cacheKey, { version: result, timestamp: Date.now() });
132
- }
133
- return result;
134
- } catch (_) {
135
- return null;
141
+ const cacheKey = `published-${pkg}`;
142
+ const cached = versionCache.get(cacheKey);
143
+ // Use very aggressive caching - 24 hours
144
+ if (cached && Date.now() - cached.timestamp < 86400000) {
145
+ return cached.version;
136
146
  }
147
+
148
+ // Return null immediately if npm view would block - never block on published versions
149
+ // The server should prioritize installed detection over update availability
150
+ return null;
137
151
  };
138
152
 
139
153
  const checkCliInstalled = (pkg) => {
@@ -142,7 +156,6 @@ const checkCliInstalled = (pkg) => {
142
156
  const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
143
157
  const bin = binMap[pkg];
144
158
  if (bin) {
145
- const { execSync } = require('child_process');
146
159
  execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000 });
147
160
  return true;
148
161
  }
@@ -155,24 +168,42 @@ const getCliVersion = (pkg) => {
155
168
  const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
156
169
  const bin = binMap[pkg];
157
170
  if (bin) {
158
- const { execSync } = require('child_process');
159
- const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 5000, encoding: 'utf8' });
160
- const match = out.match(/(\d+\.\d+\.\d+)/);
161
- if (match) return match[1];
171
+ try {
172
+ // Use short timeout - we already know the binary exists from checkCliInstalled
173
+ const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 1000, encoding: 'utf8' });
174
+ const match = out.match(/(\d+\.\d+\.\d+)/);
175
+ if (match) {
176
+ console.log(`[tool-manager] CLI ${pkg} (${bin}) version: ${match[1]}`);
177
+ return match[1];
178
+ }
179
+ } catch (err) {
180
+ // If version detection times out or fails, return null (binary exists but version unknown)
181
+ console.log(`[tool-manager] CLI ${pkg} (${bin}) version detection failed: ${err.message.split('\n')[0]}`);
182
+ }
162
183
  }
163
- } catch (_) {}
184
+ } catch (err) {
185
+ console.log(`[tool-manager] Error in getCliVersion for ${pkg}:`, err.message);
186
+ }
164
187
  return null;
165
188
  };
166
189
 
167
- const checkToolInstalled = (pluginId) => {
190
+ const checkToolInstalled = (pluginId, frameWork = null) => {
168
191
  try {
169
192
  const homeDir = os.homedir();
170
- if (fs.existsSync(path.join(homeDir, '.claude', 'plugins', pluginId))) return true;
171
- if (fs.existsSync(path.join(homeDir, '.gemini', 'extensions', pluginId))) return true;
172
- if (fs.existsSync(path.join(homeDir, '.config', 'opencode', 'agents', pluginId + '.md'))) return true;
173
- if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId + '.md'))) return true;
174
- if (fs.existsSync(path.join(homeDir, '.config', 'opencode', 'agents', 'gm.md'))) return true;
175
- if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', 'gm.md'))) return true;
193
+ if (!frameWork || frameWork === 'claude') {
194
+ if (fs.existsSync(path.join(homeDir, '.claude', 'plugins', pluginId))) return true;
195
+ }
196
+ if (!frameWork || frameWork === 'gemini') {
197
+ if (fs.existsSync(path.join(homeDir, '.gemini', 'extensions', pluginId))) return true;
198
+ }
199
+ if (!frameWork || frameWork === 'opencode') {
200
+ if (fs.existsSync(path.join(homeDir, '.config', 'opencode', 'agents', pluginId + '.md'))) return true;
201
+ if (fs.existsSync(path.join(homeDir, '.config', 'opencode', 'agents', pluginId))) return true;
202
+ }
203
+ if (!frameWork || frameWork === 'kilo') {
204
+ if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId + '.md'))) return true;
205
+ if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId))) return true;
206
+ }
176
207
  } catch (_) {}
177
208
  return false;
178
209
  };
@@ -190,19 +221,26 @@ const compareVersions = (v1, v2) => {
190
221
  return false;
191
222
  };
192
223
 
193
- const checkToolViaBunx = async (pkg, pluginId = null, category = 'plugin') => {
224
+ const checkToolViaBunx = async (pkg, pluginId = null, category = 'plugin', frameWork = null, skipPublishedVersion = false) => {
194
225
  try {
195
226
  const isCli = category === 'cli';
196
- const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg);
197
- const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId);
198
- const publishedVersion = await getPublishedVersion(pkg);
227
+ const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
228
+ const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork);
229
+
230
+ // Skip published version check if requested (for faster initial detection during startup)
231
+ let publishedVersion = null;
232
+ if (!skipPublishedVersion) {
233
+ publishedVersion = await getPublishedVersion(pkg);
234
+ }
235
+
199
236
  const needsUpdate = installed && publishedVersion && compareVersions(installedVersion, publishedVersion);
200
237
  const isUpToDate = installed && !needsUpdate;
201
238
  return { installed, isUpToDate, upgradeNeeded: needsUpdate, output: 'version-check', installedVersion, publishedVersion };
202
- } catch (_) {
239
+ } catch (err) {
240
+ console.log(`[tool-manager] Error checking ${pkg}:`, err.message);
203
241
  const isCli = category === 'cli';
204
- const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg);
205
- const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId);
242
+ const installed = isCli ? checkCliInstalled(pkg) : checkToolInstalled(pluginId || pkg, frameWork);
243
+ const installedVersion = isCli ? getCliVersion(pkg) : getInstalledVersion(pkg, pluginId, frameWork);
206
244
  return { installed, isUpToDate: false, upgradeNeeded: false, output: '', installedVersion, publishedVersion: null };
207
245
  }
208
246
  };
@@ -225,7 +263,7 @@ export function checkToolStatus(toolId) {
225
263
  return { toolId, installed: false, isUpToDate: false, upgradeNeeded: false, timestamp: Date.now() };
226
264
  }
227
265
 
228
- export async function checkToolStatusAsync(toolId) {
266
+ export async function checkToolStatusAsync(toolId, skipPublishedVersion = true) {
229
267
  const tool = getTool(toolId);
230
268
  if (!tool) return null;
231
269
 
@@ -242,7 +280,8 @@ export async function checkToolStatusAsync(toolId) {
242
280
  };
243
281
  }
244
282
 
245
- const result = await checkToolViaBunx(tool.pkg, tool.pluginId, tool.category);
283
+ // Skip published version check by default for faster responses during initial tool detection
284
+ const result = await checkToolViaBunx(tool.pkg, tool.pluginId, tool.category, tool.frameWork, skipPublishedVersion);
246
285
  const status = {
247
286
  toolId,
248
287
  category: tool.category,
@@ -376,7 +415,7 @@ export async function install(toolId, onProgress) {
376
415
  await new Promise(r => setTimeout(r, 500));
377
416
  statusCache.delete(toolId);
378
417
  versionCache.clear();
379
- const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId);
418
+ const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId, tool.frameWork);
380
419
  const freshStatus = await checkToolStatusAsync(toolId);
381
420
  return { success: true, error: null, version: version || freshStatus.publishedVersion || 'unknown', ...freshStatus };
382
421
  }
@@ -400,7 +439,7 @@ export async function update(toolId, onProgress) {
400
439
  await new Promise(r => setTimeout(r, 500));
401
440
  statusCache.delete(toolId);
402
441
  versionCache.clear();
403
- const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId);
442
+ const version = tool.category === 'cli' ? getCliVersion(tool.pkg) : getInstalledVersion(tool.pkg, tool.pluginId, tool.frameWork);
404
443
  const freshStatus = await checkToolStatusAsync(toolId);
405
444
  return { success: true, error: null, version: version || freshStatus.publishedVersion || 'unknown', ...freshStatus };
406
445
  }
@@ -426,14 +465,32 @@ export function getAllTools() {
426
465
  });
427
466
  }
428
467
 
429
- export async function getAllToolsAsync() {
430
- const results = await Promise.all(TOOLS.map(tool => checkToolStatusAsync(tool.id)));
468
+ export async function getAllToolsAsync(skipPublishedVersion = false) {
469
+ const results = await Promise.all(TOOLS.map(tool => checkToolStatusAsync(tool.id, skipPublishedVersion)));
431
470
  return results.map((status, idx) => ({
432
471
  ...TOOLS[idx],
433
472
  ...status
434
473
  }));
435
474
  }
436
475
 
476
+ export function clearStatusCache() {
477
+ statusCache.clear();
478
+ versionCache.clear();
479
+ console.log('[tool-manager] Caches cleared, forcing fresh tool detection');
480
+ }
481
+
482
+ export async function refreshAllToolsAsync() {
483
+ clearStatusCache();
484
+ return getAllToolsAsync();
485
+ }
486
+
487
+ export function getAllToolsSync() {
488
+ return TOOLS.map(tool => {
489
+ const cached = statusCache.get(tool.id);
490
+ return { ...tool, ...cached };
491
+ });
492
+ }
493
+
437
494
  export function getToolConfig(toolId) {
438
495
  return getTool(toolId) || null;
439
496
  }
@@ -443,7 +500,8 @@ export async function autoProvision(broadcast) {
443
500
  log('Starting background tool provisioning...');
444
501
  for (const tool of TOOLS) {
445
502
  try {
446
- const status = await checkToolViaBunx(tool.pkg, tool.pluginId);
503
+ // Skip published version check initially for faster startup - agents need to be available immediately
504
+ const status = await checkToolViaBunx(tool.pkg, tool.pluginId, tool.category, tool.frameWork, true);
447
505
  statusCache.set(tool.id, { ...status, toolId: tool.id, timestamp: Date.now() });
448
506
  if (!status.installed) {
449
507
  log(`${tool.id} not installed, installing...`);
@@ -473,6 +531,7 @@ export async function autoProvision(broadcast) {
473
531
  }
474
532
  } else {
475
533
  log(`${tool.id} v${status.installedVersion} up-to-date`);
534
+ broadcast({ type: 'tool_status_update', toolId: tool.id, data: { installed: true, isUpToDate: true, installedVersion: status.installedVersion, status: 'installed' } });
476
535
  }
477
536
  } catch (err) {
478
537
  log(`${tool.id} error: ${err.message}`);
@@ -127,8 +127,22 @@ export function register(router, deps) {
127
127
  broadcastSync({ type: 'message_created', conversationId: p.id, message, timestamp: Date.now() });
128
128
 
129
129
  try {
130
- // Write directly to stdin for steering
131
- proc.stdin.write(p.content + '\n');
130
+ // Send steering prompt using JSON-RPC format so agent processes it immediately
131
+ // Get the active session ID from the entry
132
+ const sessionId = entry.sessionId;
133
+
134
+ // Send JSON-RPC prompt request with requestId for tracking
135
+ const promptRequest = {
136
+ jsonrpc: '2.0',
137
+ id: Date.now(), // Use timestamp as unique request ID
138
+ method: 'session/prompt',
139
+ params: {
140
+ sessionId,
141
+ prompt: [{ type: 'text', text: p.content }]
142
+ }
143
+ };
144
+
145
+ proc.stdin.write(JSON.stringify(promptRequest) + '\n');
132
146
  return { ok: true, steered: true, conversationId: p.id, messageId: message.id };
133
147
  } catch (err) {
134
148
  fail(500, `Failed to steer: ${err.message}`);
@@ -111,48 +111,41 @@ export function register(router, deps) {
111
111
  router.handle('agent.subagents', async (p) => {
112
112
  const agent = discoveredAgents.find(x => x.id === p.id);
113
113
  if (p.id === 'claude-code') {
114
- try {
115
- const output = execSync('claude agents list', {
116
- encoding: 'utf-8',
117
- timeout: 2000,
118
- stdio: ['pipe', 'pipe', 'pipe']
119
- });
120
- let agents = [];
121
- try {
122
- agents = JSON.parse(output);
123
- } catch {
124
- agents = output.split('\n')
125
- .map(line => line.trim())
126
- .filter(line => {
127
- if (!line || line.startsWith('-') || line.startsWith('#')) return false;
128
- if (/active agents?$/.test(line)) return false;
129
- if (line.endsWith(':') && (line.includes('agents') || line.includes('agent'))) return false;
130
- return true;
131
- })
132
- .map(line => {
133
- let name = line.replace(/\s*-\s*inherit\s*$/, '').trim();
134
- return { id: name.toLowerCase().replace(/\s+/g, '-'), name };
135
- });
136
- }
137
- if (Array.isArray(agents) && agents.length > 0) {
138
- return { subAgents: agents };
139
- }
140
- } catch (_) {}
114
+ // Directly enumerate skills from ~/.claude/skills directory
115
+ // Don't try to run 'claude agents list' as it gets intercepted by the running Claude Code instance
141
116
  const skillsDir = path.join(os.homedir(), '.claude', 'skills');
142
117
  try {
143
118
  const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
144
- const skills = entries.filter(e => e.isDirectory()).map(e => {
145
- const md = path.join(skillsDir, e.name, 'SKILL.md');
146
- let name = e.name;
147
- try {
148
- const content = fs.readFileSync(md, 'utf-8');
149
- const m = content.match(/^name:\s*(.+)$/m);
150
- if (m) name = m[1].trim();
151
- } catch (_) {}
152
- return { id: e.name, name };
153
- });
154
- return { subAgents: skills };
155
- } catch (_) { return { subAgents: [] }; }
119
+ const skills = entries
120
+ .filter(e => e.isDirectory() || e.isSymbolicLink())
121
+ .map(e => {
122
+ // Try to read SKILL.md to get the actual name
123
+ const md = path.join(skillsDir, e.name, 'SKILL.md');
124
+ let name = e.name;
125
+ try {
126
+ const content = fs.readFileSync(md, 'utf-8');
127
+ const m = content.match(/^name:\s*(.+)$/m);
128
+ if (m) name = m[1].trim();
129
+ } catch (_) {
130
+ // Fallback: format the directory name as the skill name
131
+ name = e.name
132
+ .split(/[\-_]/)
133
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
134
+ .join(' ');
135
+ }
136
+ return { id: e.name, name };
137
+ })
138
+ .sort((a, b) => a.name.localeCompare(b.name));
139
+
140
+ if (skills.length > 0) {
141
+ console.log(`[agent.subagents] Found ${skills.length} Claude Code skills:`, skills.map(s => s.name).join(', '));
142
+ return { subAgents: skills };
143
+ }
144
+ return { subAgents: [] };
145
+ } catch (err) {
146
+ console.error(`[agent.subagents] Error reading skills directory:`, err.message);
147
+ return { subAgents: [] };
148
+ }
156
149
  }
157
150
  if (!agent || agent.protocol !== 'acp') return { subAgents: [] };
158
151
  const port = await ensureRunning(p.id);
@@ -9,10 +9,15 @@ export function register(router, deps) {
9
9
  const { queries, wsOptimizer, modelDownloadState, ensureModelsDownloaded,
10
10
  broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
11
11
  startGeminiOAuth, exchangeGeminiOAuthCode, geminiOAuthState,
12
- STARTUP_CWD, activeScripts, voiceCacheManager, toolManager } = deps;
12
+ STARTUP_CWD, activeScripts, voiceCacheManager, toolManager, discoveredAgents } = deps;
13
13
 
14
14
  router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
15
15
 
16
+ router.handle('agent.ls', () => {
17
+ const agents = discoveredAgents || [];
18
+ return { agents };
19
+ });
20
+
16
21
  router.handle('folders', (p) => {
17
22
  const folderPath = p.path || STARTUP_CWD;
18
23
  try {
@@ -79,19 +84,8 @@ export function register(router, deps) {
79
84
  try {
80
85
  const { getStatus } = await getSpeech();
81
86
  const base = getStatus();
82
- let pythonDetected = false, pythonVersion = null;
83
- try {
84
- const { createRequire } = await import('module');
85
- const r = createRequire(import.meta.url);
86
- const serverTTS = r('webtalk/server-tts');
87
- if (typeof serverTTS.detectPython === 'function') {
88
- const py = serverTTS.detectPython();
89
- pythonDetected = py.found;
90
- pythonVersion = py.version || null;
91
- }
92
- } catch {}
93
87
  return {
94
- ...base, pythonDetected, pythonVersion,
88
+ ...base,
95
89
  setupMessage: base.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
96
90
  modelsDownloading: modelDownloadState.downloading,
97
91
  modelsComplete: modelDownloadState.complete,
@@ -275,4 +269,30 @@ export function register(router, deps) {
275
269
  err(500, e.message);
276
270
  }
277
271
  });
272
+
273
+ router.handle('agent.subagents', async (p) => {
274
+ const { id } = p;
275
+ if (!id) err(400, 'Missing agent id');
276
+
277
+ // Map CLI agent IDs to their corresponding plugin sub-agents
278
+ const subAgentMap = {
279
+ 'cli-claude': [{ id: 'gm-cc', name: 'GM Claude' }],
280
+ 'cli-opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
281
+ 'cli-gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
282
+ 'cli-kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
283
+ 'cli-codex': []
284
+ };
285
+
286
+ const subAgents = subAgentMap[id] || [];
287
+
288
+ // Filter to only installed sub-agents
289
+ try {
290
+ const tools = await toolManager.getAllToolsAsync();
291
+ const installedPlugins = new Set(tools.filter(t => t.category === 'plugin' && t.installed).map(t => t.id));
292
+ const availableSubAgents = subAgents.filter(sa => installedPlugins.has(sa.id));
293
+ return { subAgents: availableSubAgents };
294
+ } catch (e) {
295
+ return { subAgents };
296
+ }
297
+ });
278
298
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.615",
3
+ "version": "1.0.617",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",