agentgui 1.0.742 → 1.0.743

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.
@@ -117,6 +117,24 @@ export function register(router, deps) {
117
117
  return { ok: true, chunks: result.chunks, total: result.total, hasMore: result.hasMore, limit: result.limit };
118
118
  });
119
119
 
120
+ router.handle('conv.export', (p) => {
121
+ const conv = queries.getConversation(p.id);
122
+ if (!conv) notFound();
123
+ const msgs = queries.getConversationMessages(p.id);
124
+ const format = p.format || 'markdown';
125
+ if (format === 'json') return { conversation: conv, messages: msgs };
126
+ let md = `# ${conv.title || 'Conversation'}\n\n`;
127
+ md += `Agent: ${conv.agentType || 'unknown'} | Created: ${new Date(conv.created_at).toISOString()}\n\n---\n\n`;
128
+ for (const m of msgs) {
129
+ const role = m.role === 'user' ? 'User' : 'Assistant';
130
+ md += `## ${role}\n\n`;
131
+ let content = m.content;
132
+ try { const parsed = JSON.parse(content); if (Array.isArray(parsed)) { content = parsed.map(b => b.text || b.content || JSON.stringify(b)).join('\n'); } } catch {}
133
+ md += content + '\n\n---\n\n';
134
+ }
135
+ return { markdown: md, title: conv.title };
136
+ });
137
+
120
138
  router.handle('conv.cancel', (p) => {
121
139
  if (!execMachine.isActive(p.id)) notFound('No active execution to cancel');
122
140
  const ctx = execMachine.getContext(p.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.742",
3
+ "version": "1.0.743",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
package/server.js CHANGED
@@ -3684,6 +3684,41 @@ const _assetCache = new LRUCache({ max: 200 });
3684
3684
  let _htmlCache = null;
3685
3685
  let _htmlCacheEtag = null;
3686
3686
 
3687
+ function warmAssetCache() {
3688
+ const dirs = ['js', 'css', 'lib', 'vendor'];
3689
+ let count = 0;
3690
+ for (const dir of dirs) {
3691
+ const full = path.join(staticDir, dir);
3692
+ if (!fs.existsSync(full)) continue;
3693
+ for (const file of fs.readdirSync(full)) {
3694
+ const filePath = path.join(full, file);
3695
+ try {
3696
+ const stats = fs.statSync(filePath);
3697
+ if (!stats.isFile()) continue;
3698
+ const etag = generateETag(stats);
3699
+ if (_assetCache.has(etag)) continue;
3700
+ const raw = fs.readFileSync(filePath);
3701
+ const entry = raw.length < 860 ? { raw, gz: null } : { raw, gz: zlib.gzipSync(raw, { level: 6 }) };
3702
+ _assetCache.set(etag, entry);
3703
+ count++;
3704
+ } catch (_) {}
3705
+ }
3706
+ }
3707
+ for (const file of ['app.js', 'theme.js']) {
3708
+ const filePath = path.join(staticDir, file);
3709
+ try {
3710
+ const stats = fs.statSync(filePath);
3711
+ const etag = generateETag(stats);
3712
+ if (!_assetCache.has(etag)) {
3713
+ const raw = fs.readFileSync(filePath);
3714
+ _assetCache.set(etag, raw.length < 860 ? { raw, gz: null } : { raw, gz: zlib.gzipSync(raw, { level: 6 }) });
3715
+ count++;
3716
+ }
3717
+ } catch (_) {}
3718
+ }
3719
+ if (count > 0) console.log(`[CACHE] Pre-warmed ${count} static assets`);
3720
+ }
3721
+
3687
3722
  function serveFile(filePath, res, req) {
3688
3723
  const ext = path.extname(filePath).toLowerCase();
3689
3724
  const contentType = MIME_TYPES[ext] || 'application/octet-stream';
@@ -5109,6 +5144,7 @@ function onServerReady() {
5109
5144
  }
5110
5145
 
5111
5146
  recoverStaleSessions();
5147
+ warmAssetCache();
5112
5148
 
5113
5149
  try {
5114
5150
  jsonlWatcher = new JsonlWatcher({ broadcastSync, queries, ownedSessionIds });
@@ -5120,6 +5156,20 @@ function onServerReady() {
5120
5156
 
5121
5157
  resumeInterruptedStreams().catch(err => console.error('[RESUME] Startup error:', err.message));
5122
5158
 
5159
+ setInterval(() => {
5160
+ try {
5161
+ const streaming = queries.getStreamingConversations();
5162
+ let cleared = 0;
5163
+ for (const c of streaming) {
5164
+ if (!activeExecutions.has(c.id)) {
5165
+ queries.setIsStreaming(c.id, false);
5166
+ cleared++;
5167
+ }
5168
+ }
5169
+ if (cleared > 0) debugLog(`[HEALTH] Cleared ${cleared} stale streaming flag(s)`);
5170
+ } catch (e) { debugLog(`[HEALTH] Error: ${e.message}`); }
5171
+ }, 5 * 60 * 1000);
5172
+
5123
5173
  installGMAgentConfigs().catch(err => console.error('[GM-CONFIG] Startup error:', err.message));
5124
5174
 
5125
5175
  startACPTools().then(() => {
@@ -591,6 +591,27 @@ class AgentGUIClient {
591
591
  });
592
592
  }
593
593
 
594
+ document.addEventListener('keydown', (e) => {
595
+ if (e.key === 'n' && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
596
+ e.preventDefault();
597
+ const newBtn = document.querySelector('[data-new-conversation], #newConversationBtn, .new-conversation-btn');
598
+ if (newBtn) newBtn.click();
599
+ }
600
+ if (e.key === 'b' && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
601
+ e.preventDefault();
602
+ const toggleBtn = document.querySelector('[data-sidebar-toggle]');
603
+ if (toggleBtn) toggleBtn.click();
604
+ }
605
+ if (e.key === 'Escape') {
606
+ const activeEl = document.activeElement;
607
+ if (activeEl && activeEl.tagName === 'TEXTAREA') { activeEl.blur(); return; }
608
+ if (this.state.isStreaming) {
609
+ const cancelBtn = document.querySelector('#cancelBtn, [data-cancel-btn]');
610
+ if (cancelBtn && cancelBtn.offsetParent !== null) cancelBtn.click();
611
+ }
612
+ }
613
+ });
614
+
594
615
  // Setup theme toggle
595
616
  const themeToggle = document.querySelector('[data-theme-toggle]');
596
617
  if (themeToggle) {
@@ -917,6 +938,9 @@ class AgentGUIClient {
917
938
  }
918
939
 
919
940
  handleStreamingProgress(data) {
941
+ try { return this._handleStreamingProgressInner(data); } catch (e) { console.error('[render-error] streaming progress:', e); }
942
+ }
943
+ _handleStreamingProgressInner(data) {
920
944
  if (!data.block || !data.sessionId) return;
921
945
 
922
946
  // Deduplicate by seq number to guarantee exactly-once rendering
@@ -2186,6 +2210,9 @@ class AgentGUIClient {
2186
2210
  }
2187
2211
 
2188
2212
  renderChunk(chunk) {
2213
+ try { return this._renderChunkInner(chunk); } catch (e) { console.error('[render-error] chunk:', e); }
2214
+ }
2215
+ _renderChunkInner(chunk) {
2189
2216
  if (!chunk || !chunk.block) return;
2190
2217
  const seq = chunk.sequence;
2191
2218
  if (seq !== undefined) {
@@ -781,6 +781,7 @@ class StreamingRenderer {
781
781
  const details = document.createElement('details');
782
782
  details.className = 'block-tool-use folded-tool';
783
783
  if (block.id) details.dataset.toolUseId = block.id;
784
+ details.dataset.startedAt = Date.now();
784
785
  details.classList.add(this._getBlockTypeClass('tool_use'));
785
786
  details.classList.add(this._getToolColorClass(toolName));
786
787
  const normalizedForOpen = toolName.replace(/^mcp__.*?__/, '');
@@ -2228,6 +2229,16 @@ class StreamingRenderer {
2228
2229
  statusSpan.className = 'folded-tool-status';
2229
2230
  statusSpan.innerHTML = statusSvg;
2230
2231
  summary.appendChild(statusSpan);
2232
+ const startedAt = parseInt(toolUseEl.dataset.startedAt);
2233
+ if (startedAt > 0) {
2234
+ const ms = Date.now() - startedAt;
2235
+ const label = ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's';
2236
+ const dur = document.createElement('span');
2237
+ dur.className = 'folded-tool-duration';
2238
+ dur.style.cssText = 'margin-left:0.375rem;font-size:0.6rem;opacity:0.45;font-weight:400';
2239
+ dur.textContent = label;
2240
+ summary.appendChild(dur);
2241
+ }
2231
2242
  }
2232
2243
 
2233
2244
  const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);