agentgui 1.0.688 → 1.0.690

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.
@@ -88,6 +88,10 @@ class AgentRunner {
88
88
  onRateLimit = null
89
89
  } = config;
90
90
 
91
+ if (process.env.DEBUG === '1') {
92
+ const sp = config.systemPrompt;
93
+ console.error(`[prompt-trace] convId=${config.conversationId} promptType=${typeof prompt} promptLen=${String(prompt).length} prompt0=${String(prompt).slice(0, 100)} sysLen=${sp ? String(sp).length : 0}`);
94
+ }
91
95
  const args = this.buildArgs(prompt, config);
92
96
  const spawnOpts = getSpawnOptions(cwd);
93
97
  if (Object.keys(this.spawnEnv).length > 0) {
@@ -7,7 +7,7 @@ function expandTilde(p) { return p && p.startsWith('~') ? path.join(os.homedir()
7
7
 
8
8
  export function register(router, deps) {
9
9
  const { queries, activeExecutions, messageQueues, rateLimitState,
10
- broadcastSync, processMessageWithStreaming, cleanupExecution } = deps;
10
+ broadcastSync, processMessageWithStreaming, cleanupExecution, logError = () => {} } = deps;
11
11
 
12
12
  // Per-conversation queue seq counter for event ordering
13
13
  const queueSeqByConv = new Map();
@@ -179,7 +179,7 @@ export function register(router, deps) {
179
179
  activeExecutions.set(convId, { pid: null, startTime: Date.now(), sessionId: session.id, lastActivity: Date.now() });
180
180
  queries.setIsStreaming(convId, true);
181
181
  broadcastSync({ type: 'streaming_start', sessionId: session.id, conversationId: convId, messageId: message.id, agentId, timestamp: Date.now() });
182
- processMessageWithStreaming(convId, message.id, session.id, content, agentId, model, subAgent).catch(() => {});
182
+ processMessageWithStreaming(convId, message.id, session.id, content, agentId, model, subAgent).catch(e => logError('startExecution', e, { convId }));
183
183
  return session;
184
184
  }
185
185
 
@@ -199,6 +199,10 @@ export function register(router, deps) {
199
199
  const subAgent = p.subAgent || conv.subAgent || null;
200
200
  const idempotencyKey = p.idempotencyKey || null;
201
201
  const rawContent = p.content;
202
+ if (rawContent !== undefined && typeof rawContent !== 'string') {
203
+ console.warn(`[ws-validation] msg.send content is ${typeof rawContent}: ${JSON.stringify(rawContent).slice(0, 200)}`);
204
+ logError('ws-content-type', new TypeError(`non-string content in msg.send`), { method: 'msg.send', type: typeof rawContent });
205
+ }
202
206
  p.content = typeof rawContent === 'string' ? rawContent : (rawContent ? JSON.stringify(rawContent) : '');
203
207
  const message = queries.createMessage(p.id, 'user', p.content, idempotencyKey);
204
208
  queries.createEvent('message.created', { role: 'user', messageId: message.id }, p.id);
@@ -225,6 +229,10 @@ export function register(router, deps) {
225
229
  const conv = queries.getConversation(p.id);
226
230
  if (!conv) notFound('Conversation not found');
227
231
  const rawContent = p.content || p.message;
232
+ if (rawContent !== undefined && typeof rawContent !== 'string') {
233
+ console.warn(`[ws-validation] msg.stream content is ${typeof rawContent}: ${JSON.stringify(rawContent).slice(0, 200)}`);
234
+ logError('ws-content-type', new TypeError(`non-string content in msg.stream`), { method: 'msg.stream', type: typeof rawContent });
235
+ }
228
236
  const prompt = typeof rawContent === 'string' ? rawContent : (rawContent ? JSON.stringify(rawContent) : '');
229
237
  const agentId = p.agentId || conv.agentType || conv.agentId || 'claude-code';
230
238
  const model = p.model || conv.model || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.688",
3
+ "version": "1.0.690",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -309,6 +309,14 @@ const debugLog = (msg) => {
309
309
  console.error(`[${timestamp}] ${msg}`);
310
310
  };
311
311
 
312
+ const _errLogPath = path.join(os.homedir(), 'logs', 'agentgui-errors.log');
313
+ function logError(op, err, ctx = {}) {
314
+ try {
315
+ const line = JSON.stringify({ ts: new Date().toISOString(), op, msg: err?.message, stack: err?.stack, ...ctx }) + '\n';
316
+ fs.appendFile(_errLogPath, line, () => {});
317
+ } catch (_) {}
318
+ }
319
+
312
320
  // Atomic cleanup function - ensures consistent state across all data structures
313
321
  function cleanupExecution(conversationId, broadcastCompletion = false) {
314
322
  debugLog(`[cleanup] Starting cleanup for ${conversationId}`);
@@ -586,6 +594,7 @@ async function initializeAgentDiscovery() {
586
594
  console.log('[AGENTS] Total count:', discoveredAgents.length);
587
595
  } catch (err) {
588
596
  console.error('[AGENTS] Discovery error:', err.message);
597
+ logError('initializeAgentDiscovery', err);
589
598
  }
590
599
  }
591
600
 
@@ -1269,6 +1278,7 @@ const server = http.createServer(async (req, res) => {
1269
1278
  .catch(err => {
1270
1279
  console.error(`[messages] Uncaught error for conv ${conversationId}:`, err.message);
1271
1280
  debugLog(`[messages] Uncaught error: ${err.message}`);
1281
+ logError('processMessageWithStreaming', err, { convId: conversationId });
1272
1282
  });
1273
1283
  return;
1274
1284
  }
@@ -1777,6 +1787,40 @@ const server = http.createServer(async (req, res) => {
1777
1787
  return;
1778
1788
  }
1779
1789
 
1790
+ if (pathOnly === '/api/health' && req.method === 'GET') {
1791
+ let dbStatus = { ok: true };
1792
+ try { queries._db.prepare('SELECT 1').get(); } catch (e) { dbStatus = { ok: false, error: e.message }; }
1793
+ const queueSizes = {};
1794
+ for (const [k, v] of messageQueues) queueSizes[k] = v.length;
1795
+ sendJSON(req, res, 200, {
1796
+ uptime: process.uptime(),
1797
+ db: dbStatus,
1798
+ activeExecutionCount: activeExecutions.size,
1799
+ queueSizes,
1800
+ wsClientCount: syncClients.size,
1801
+ memory: process.memoryUsage()
1802
+ });
1803
+ return;
1804
+ }
1805
+
1806
+ if (pathOnly === '/api/debug' && req.method === 'GET') {
1807
+ const execSnap = {};
1808
+ for (const [k, v] of activeExecutions) execSnap[k] = { pid: v.pid, startTime: v.startTime, sessionId: v.sessionId, lastActivity: v.lastActivity };
1809
+ const queueSnap = {};
1810
+ for (const [k, v] of messageQueues) queueSnap[k] = { length: v.length, messageIds: v.map(m => m.messageId) };
1811
+ let streamingConvs = [];
1812
+ try { streamingConvs = queries.getConversationsList().filter(c => c.isStreaming).map(c => ({ id: c.id, isStreaming: c.isStreaming })); } catch (_) {}
1813
+ const clientSubs = [];
1814
+ for (const ws of syncClients) clientSubs.push({ clientId: ws.clientId, subs: ws.subscriptions ? [...ws.subscriptions] : [] });
1815
+ let recentErrors = [];
1816
+ try {
1817
+ const raw = fs.readFileSync(_errLogPath, 'utf8');
1818
+ recentErrors = raw.trim().split('\n').slice(-20).map(l => { try { return JSON.parse(l); } catch { return l; } });
1819
+ } catch (_) {}
1820
+ sendJSON(req, res, 200, { activeExecutions: execSnap, messageQueues: queueSnap, streamingConversations: streamingConvs, wsClients: clientSubs, recentErrors });
1821
+ return;
1822
+ }
1823
+
1780
1824
  if (pathOnly === '/api/tools' && req.method === 'GET') {
1781
1825
  console.log('[TOOLS-API] Handling GET /api/tools');
1782
1826
  try {
@@ -1899,6 +1943,7 @@ const server = http.createServer(async (req, res) => {
1899
1943
  installCompleted = true;
1900
1944
  const error = err?.message || 'Unknown error';
1901
1945
  console.error(`[TOOLS-API] Install error for ${toolId}:`, error);
1946
+ logError('toolInstall', err, { toolId });
1902
1947
  queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1903
1948
  broadcastSync({ type: 'tool_install_failed', toolId, data: { success: false, error } });
1904
1949
  queries.addToolInstallHistory(toolId, 'install', 'failed', error);
@@ -1958,6 +2003,7 @@ const server = http.createServer(async (req, res) => {
1958
2003
  updateCompleted = true;
1959
2004
  const error = err?.message || 'Unknown error';
1960
2005
  console.error(`[TOOLS-API] Update error for ${toolId}:`, error);
2006
+ logError('toolUpdate', err, { toolId });
1961
2007
  queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1962
2008
  broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error } });
1963
2009
  queries.addToolInstallHistory(toolId, 'update', 'failed', error);
@@ -4095,7 +4141,7 @@ const wsRouter = new WsRouter();
4095
4141
 
4096
4142
  registerConvHandlers(wsRouter, {
4097
4143
  queries, activeExecutions, messageQueues, rateLimitState,
4098
- broadcastSync, processMessageWithStreaming, cleanupExecution
4144
+ broadcastSync, processMessageWithStreaming, cleanupExecution, logError
4099
4145
  });
4100
4146
 
4101
4147
  console.log('[INIT] About to call registerSessionHandlers, discoveredAgents.length:', discoveredAgents.length);
@@ -285,6 +285,9 @@ class StreamingRenderer {
285
285
  if (nodeCount > 0) {
286
286
  this.outputContainer.appendChild(fragment);
287
287
  this.domNodeCount += nodeCount;
288
+
289
+ // Nest tool result blocks inside their corresponding tool use blocks
290
+ this.nestToolResultsInToolUses();
288
291
  }
289
292
 
290
293
  // Auto-scroll to bottom
@@ -757,7 +760,7 @@ class StreamingRenderer {
757
760
  const input = block.input || {};
758
761
 
759
762
  const details = document.createElement('details');
760
- details.className = 'block-tool-use folded-tool permanently-expanded';
763
+ details.className = 'block-tool-use folded-tool';
761
764
  if (block.id) details.dataset.toolUseId = block.id;
762
765
  details.classList.add(this._getBlockTypeClass('tool_use'));
763
766
  details.classList.add(this._getToolColorClass(toolName));
@@ -1249,7 +1252,7 @@ class StreamingRenderer {
1249
1252
  const details = document.createElement('details');
1250
1253
  details.className = 'folded-tool' + (isError ? ' folded-tool-error' : ' folded-tool-success');
1251
1254
  details.dataset.eventType = 'tool_result';
1252
- // Only open by default if the content is an image
1255
+ // Only open by default if the content is an image and it's not an error
1253
1256
  const isImageContent = contentStr.includes('data:image/') || (block.content && block.content.type === 'base64');
1254
1257
  if (!isError && isImageContent) details.open = true;
1255
1258
  if (block.tool_use_id) details.dataset.toolUseId = block.tool_use_id;
@@ -2070,6 +2073,26 @@ class StreamingRenderer {
2070
2073
  return div;
2071
2074
  }
2072
2075
 
2076
+ /**
2077
+ * Nest tool result blocks inside their corresponding tool use blocks
2078
+ */
2079
+ nestToolResultsInToolUses() {
2080
+ if (!this.outputContainer) return;
2081
+
2082
+ const toolUseBlocks = this.outputContainer.querySelectorAll('details.block-tool-use[data-tool-use-id]');
2083
+ const toolResultBlocks = this.outputContainer.querySelectorAll('details[data-event-type="tool_result"][data-tool-use-id]');
2084
+
2085
+ toolResultBlocks.forEach(resultBlock => {
2086
+ const toolUseId = resultBlock.dataset.toolUseId;
2087
+ const toolUseBlock = Array.from(toolUseBlocks).find(b => b.dataset.toolUseId === toolUseId);
2088
+
2089
+ if (toolUseBlock && toolUseBlock.parentElement === resultBlock.parentElement) {
2090
+ // Result is a sibling of the tool use block, move it inside
2091
+ toolUseBlock.appendChild(resultBlock);
2092
+ }
2093
+ });
2094
+ }
2095
+
2073
2096
  /**
2074
2097
  * Auto-scroll to bottom of container
2075
2098
  */