agentgui 1.0.639 → 1.0.640

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.
@@ -5,7 +5,7 @@ function notFound(msg = 'Not found') { fail(404, msg); }
5
5
 
6
6
  export function register(router, deps) {
7
7
  const { queries, activeExecutions, messageQueues, rateLimitState,
8
- broadcastSync, processMessageWithStreaming, activeProcessesByConvId } = deps;
8
+ broadcastSync, processMessageWithStreaming, activeProcessesByConvId, steeringTimeouts } = deps;
9
9
 
10
10
  router.handle('conv.ls', () => {
11
11
  const conversations = queries.getConversationsList();
@@ -118,7 +118,11 @@ export function register(router, deps) {
118
118
  if (!conv) notFound('Conversation not found');
119
119
  if (!p.content) fail(400, 'Missing content');
120
120
  const entry = activeExecutions.get(p.id);
121
- if (!entry) fail(409, 'No active execution to steer');
121
+ if (!entry) {
122
+ // Allow steering if we still have a process, even if execution entry is gone
123
+ const proc = activeProcessesByConvId.get(p.id);
124
+ if (!proc || !proc.stdin) fail(409, 'No active execution to steer');
125
+ }
122
126
  const proc = activeProcessesByConvId.get(p.id);
123
127
  if (!proc || !proc.stdin) fail(409, 'Process not available for steering');
124
128
 
@@ -129,7 +133,7 @@ export function register(router, deps) {
129
133
  try {
130
134
  // Send steering prompt using JSON-RPC format so agent processes it immediately
131
135
  // Get the active session ID from the entry
132
- const sessionId = entry.sessionId;
136
+ const sessionId = entry?.sessionId || queries.getConversation(p.id)?.sessionId;
133
137
 
134
138
  // Send JSON-RPC prompt request with requestId for tracking
135
139
  const promptRequest = {
@@ -143,6 +147,19 @@ export function register(router, deps) {
143
147
  };
144
148
 
145
149
  proc.stdin.write(JSON.stringify(promptRequest) + '\n');
150
+
151
+ // Clear the steering cleanup timeout so process stays alive longer
152
+ const timeout = steeringTimeouts.get(p.id);
153
+ if (timeout) {
154
+ clearTimeout(timeout);
155
+ // Set a new timeout (another 30 seconds for follow-up steers)
156
+ const newTimeout = setTimeout(() => {
157
+ activeProcessesByConvId.delete(p.id);
158
+ steeringTimeouts.delete(p.id);
159
+ }, 30000);
160
+ steeringTimeouts.set(p.id, newTimeout);
161
+ }
162
+
146
163
  return { ok: true, steered: true, conversationId: p.id, messageId: message.id };
147
164
  } catch (err) {
148
165
  fail(500, `Failed to steer: ${err.message}`);
@@ -74,6 +74,7 @@ export function register(router, deps) {
74
74
  const { db, discoveredAgents, modelCache,
75
75
  getAgentDescriptor, activeScripts, broadcastSync, startGeminiOAuth,
76
76
  geminiOAuthState } = deps;
77
+ console.log('[ws-handlers-session] register() called with discoveredAgents.length:', discoveredAgents.length);
77
78
 
78
79
  router.handle('sess.get', (p) => {
79
80
  const sess = db.getSession(p.id);
@@ -101,12 +102,16 @@ export function register(router, deps) {
101
102
  return { sessionId: p.id, events: [], total: 0, limit, offset, hasMore: false, metadata: { status: 'pending', startTime: Date.now(), duration: 0, eventCount: 0 } };
102
103
  });
103
104
 
104
- router.handle('agent.ls', () => ({
105
- agents: discoveredAgents.map(a => ({
106
- id: a.id, name: a.name, icon: a.icon, path: a.path,
107
- protocol: a.protocol || 'unknown', description: a.description || '', status: 'available'
108
- }))
109
- }));
105
+ router.handle('agent.ls', () => {
106
+ console.log('[agent.ls] discoveredAgents.length:', discoveredAgents.length);
107
+ console.log('[agent.ls] agent IDs:', discoveredAgents.map(a => a.id).join(', '));
108
+ return {
109
+ agents: discoveredAgents.map(a => ({
110
+ id: a.id, name: a.name, icon: a.icon, path: a.path,
111
+ protocol: a.protocol || 'unknown', description: a.description || '', status: 'available'
112
+ }))
113
+ };
114
+ });
110
115
  // Note: agent.subagents is handled by ws-handlers-util.js which is registered after this file
111
116
  // Keeping this note to avoid duplicate handler registration
112
117
 
@@ -13,10 +13,8 @@ export function register(router, 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
- });
16
+ // NOTE: agent.ls is handled by ws-handlers-session.js to ensure proper agent mapping
17
+ // Do not re-register here as it will override the session handler
20
18
 
21
19
  router.handle('folders', (p) => {
22
20
  const folderPath = p.path || STARTUP_CWD;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.639",
3
+ "version": "1.0.640",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -300,6 +300,7 @@ const messageQueues = new Map();
300
300
  const rateLimitState = new Map();
301
301
  const activeProcessesByRunId = new Map();
302
302
  const activeProcessesByConvId = new Map(); // Store process handles by conversationId for steering
303
+ const steeringTimeouts = new Map(); // Track timeout handles for process cleanup
303
304
  const acpQueries = queries;
304
305
  const checkpointManager = new CheckpointManager(queries);
305
306
  const STUCK_AGENT_THRESHOLD_MS = 600000;
@@ -512,6 +513,29 @@ function discoverAgents() {
512
513
  agents.push({ id: bin.id, name: bin.name, icon: bin.icon, path: null, protocol: bin.protocol, npxPackage: '@anthropic-ai/claude-code', npxLaunchable: true });
513
514
  }
514
515
  }
516
+
517
+ // Add CLI tool wrappers for ACP agents (these map CLI commands to plugin sub-agents)
518
+ // These allow users to select "OpenCode", "Gemini", etc. and then pick a model/variant
519
+ const cliWrappers = [
520
+ { id: 'cli-opencode', name: 'OpenCode', icon: 'O', protocol: 'cli-wrapper', acpId: 'opencode' },
521
+ { id: 'cli-gemini', name: 'Gemini', icon: 'G', protocol: 'cli-wrapper', acpId: 'gemini' },
522
+ { id: 'cli-kilo', name: 'Kilo', icon: 'K', protocol: 'cli-wrapper', acpId: 'kilo' },
523
+ { id: 'cli-codex', name: 'Codex', icon: 'X', protocol: 'cli-wrapper', acpId: 'codex' },
524
+ ];
525
+
526
+ // Only add CLI wrappers for agents that are already discovered
527
+ console.log('[discoverAgents] Found agents:', agents.map(a => a.id).join(', '));
528
+ for (const wrapper of cliWrappers) {
529
+ if (agents.some(a => a.id === wrapper.acpId)) {
530
+ console.log(`[discoverAgents] Adding CLI wrapper for ${wrapper.id}`);
531
+ agents.push(wrapper);
532
+ console.log(`[discoverAgents] After push, agents.length = ${agents.length}, includes ${wrapper.id}? ${agents.some(a => a.id === wrapper.id)}`);
533
+ } else {
534
+ console.log(`[discoverAgents] Skipping CLI wrapper ${wrapper.id} (ACP agent ${wrapper.acpId} not found)`);
535
+ }
536
+ }
537
+ console.log('[discoverAgents] Final agent count:', agents.length, 'Agent IDs:', agents.map(a => a.id).join(', '));
538
+
515
539
  return agents;
516
540
  }
517
541
 
@@ -533,16 +557,23 @@ initializeDescriptors(discoveredAgents);
533
557
  // Agent discovery happens asynchronously in background to not block startup
534
558
  async function initializeAgentDiscovery() {
535
559
  try {
536
- discoveredAgents = discoverAgents();
560
+ const agents = discoverAgents();
561
+ // Mutate the existing array instead of reassigning to preserve closure references in handlers
562
+ discoveredAgents.length = 0;
563
+ discoveredAgents.push(...agents);
537
564
  initializeDescriptors(discoveredAgents);
538
- console.log('[AGENTS] Discovered:', discoveredAgents.map(a => ({ id: a.id, found: !!a.path })));
565
+ console.log('[AGENTS] Discovered:', discoveredAgents.map(a => ({ id: a.id, found: !!a.path, protocol: a.protocol })));
566
+ console.log('[AGENTS] Total count:', discoveredAgents.length);
539
567
  } catch (err) {
540
568
  console.error('[AGENTS] Discovery error:', err.message);
541
569
  }
542
570
  }
543
571
 
544
572
  // Start immediately but don't wait for it
545
- initializeAgentDiscovery().catch(() => {});
573
+ const startTime = Date.now();
574
+ initializeAgentDiscovery().then(() => {
575
+ console.log('[INIT] initializeAgentDiscovery completed in', Date.now() - startTime, 'ms');
576
+ }).catch(() => {});
546
577
 
547
578
  const modelCache = new Map();
548
579
 
@@ -1878,6 +1909,7 @@ const server = http.createServer(async (req, res) => {
1878
1909
  }
1879
1910
 
1880
1911
  if (pathOnly === '/api/agents' && req.method === 'GET') {
1912
+ console.log(`[API /api/agents] Returning ${discoveredAgents.length} agents:`, discoveredAgents.map(a => a.id).join(', '));
1881
1913
  sendJSON(req, res, 200, { agents: discoveredAgents });
1882
1914
  return;
1883
1915
  }
@@ -3936,7 +3968,12 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
3936
3968
  }
3937
3969
 
3938
3970
  activeExecutions.delete(conversationId);
3939
- activeProcessesByConvId.delete(conversationId);
3971
+ // Keep process alive for steering for up to 30 seconds after execution completes
3972
+ const steeringTimeout = setTimeout(() => {
3973
+ activeProcessesByConvId.delete(conversationId);
3974
+ steeringTimeouts.delete(conversationId);
3975
+ }, 30000);
3976
+ steeringTimeouts.set(conversationId, steeringTimeout);
3940
3977
  batcher.drain();
3941
3978
  debugLog(`[stream] Claude returned ${outputs.length} outputs, sessionId=${claudeSessionId}`);
3942
3979
 
@@ -4281,14 +4318,16 @@ const wsRouter = new WsRouter();
4281
4318
 
4282
4319
  registerConvHandlers(wsRouter, {
4283
4320
  queries, activeExecutions, messageQueues, rateLimitState,
4284
- broadcastSync, processMessageWithStreaming, activeProcessesByConvId
4321
+ broadcastSync, processMessageWithStreaming, activeProcessesByConvId, steeringTimeouts
4285
4322
  });
4286
4323
 
4324
+ console.log('[INIT] About to call registerSessionHandlers, discoveredAgents.length:', discoveredAgents.length);
4287
4325
  registerSessionHandlers(wsRouter, {
4288
4326
  db: queries, discoveredAgents, modelCache,
4289
4327
  getAgentDescriptor, activeScripts, broadcastSync,
4290
4328
  startGeminiOAuth, geminiOAuthState: () => geminiOAuthState
4291
4329
  });
4330
+ console.log('[INIT] registerSessionHandlers completed');
4292
4331
 
4293
4332
  registerRunHandlers(wsRouter, {
4294
4333
  queries, discoveredAgents, activeExecutions, activeProcessesByRunId,
@@ -442,6 +442,11 @@ class AgentGUIClient {
442
442
 
443
443
  if (this.ui.agentSelector) {
444
444
  this.ui.agentSelector.addEventListener('change', () => {
445
+ // Load models for the selected sub-agent
446
+ const selectedSubAgentId = this.ui.agentSelector.value;
447
+ if (selectedSubAgentId) {
448
+ this.loadModelsForAgent(selectedSubAgentId);
449
+ }
445
450
  if (!this._agentLocked) {
446
451
  this.saveAgentAndModelToConversation();
447
452
  }
@@ -2068,13 +2073,34 @@ class AgentGUIClient {
2068
2073
  .join('');
2069
2074
  this.ui.agentSelector.style.display = 'inline-block';
2070
2075
  console.log(`[Agent Selector] Loaded ${subAgents.length} sub-agents for ${cliAgentId}`);
2071
- this.loadModelsForAgent(cliAgentId);
2076
+ // Auto-select first sub-agent and load its models
2077
+ const firstSubAgentId = subAgents[0].id;
2078
+ this.ui.agentSelector.value = firstSubAgentId;
2079
+ this.loadModelsForAgent(firstSubAgentId);
2072
2080
  } else {
2073
2081
  console.log(`[Agent Selector] No sub-agents found for ${cliAgentId}`);
2082
+ // Load models for the CLI agent itself (fallback for agents without sub-agents)
2083
+ const cliToAcpMap = {
2084
+ 'cli-opencode': 'opencode',
2085
+ 'cli-gemini': 'gemini',
2086
+ 'cli-kilo': 'kilo',
2087
+ 'cli-codex': 'codex'
2088
+ };
2089
+ const acpAgentId = cliToAcpMap[cliAgentId] || cliAgentId;
2090
+ this.loadModelsForAgent(acpAgentId);
2074
2091
  }
2075
2092
  } catch (err) {
2076
2093
  // No sub-agents available for this CLI tool — keep hidden
2077
2094
  console.warn(`[Agent Selector] Failed to load sub-agents for ${cliAgentId}:`, err.message);
2095
+ // Fallback: load models for the corresponding ACP agent
2096
+ const cliToAcpMap = {
2097
+ 'cli-opencode': 'opencode',
2098
+ 'cli-gemini': 'gemini',
2099
+ 'cli-kilo': 'kilo',
2100
+ 'cli-codex': 'codex'
2101
+ };
2102
+ const acpAgentId = cliToAcpMap[cliAgentId] || cliAgentId;
2103
+ this.loadModelsForAgent(acpAgentId);
2078
2104
  }
2079
2105
  }
2080
2106