agentgui 1.0.740 → 1.0.742

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.740",
3
+ "version": "1.0.742",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -90,14 +90,18 @@ class AgentGUIClient {
90
90
  currentConversationId: null,
91
91
  currentSessionId: null
92
92
  };
93
+
94
+ this._debug = typeof localStorage !== 'undefined' && localStorage.getItem('debug') === '1';
93
95
  }
94
96
 
97
+ _dbg(...args) { if (this._debug) this._dbg(...args); }
98
+
95
99
  /**
96
100
  * Initialize the client
97
101
  */
98
102
  async init() {
99
103
  try {
100
- console.log('Initializing AgentGUI client');
104
+ this._dbg('Initializing AgentGUI client');
101
105
 
102
106
  // Initialize renderer
103
107
  this.renderer.init(this.config.outputContainerId, this.config.scrollContainerId);
@@ -105,7 +109,7 @@ class AgentGUIClient {
105
109
  // Initialize image loader
106
110
  if (typeof ImageLoader !== 'undefined') {
107
111
  window.imageLoader = new ImageLoader();
108
- console.log('Image loader initialized');
112
+ this._dbg('Image loader initialized');
109
113
  }
110
114
 
111
115
  // Setup event listeners
@@ -137,7 +141,7 @@ class AgentGUIClient {
137
141
  this.emit('initialized');
138
142
  this._setupDebugHooks();
139
143
 
140
- console.log('AgentGUI client initialized');
144
+ this._dbg('AgentGUI client initialized');
141
145
  return this;
142
146
  } catch (error) {
143
147
  console.error('Client initialization error:', error);
@@ -151,7 +155,7 @@ class AgentGUIClient {
151
155
  */
152
156
  setupWebSocketListeners() {
153
157
  this.wsManager.on('connected', () => {
154
- console.log('WebSocket connected');
158
+ this._dbg('WebSocket connected');
155
159
  this.updateConnectionStatus('connected');
156
160
  this._subscribeToConversationUpdates();
157
161
  // On reconnect (not initial connect), invalidate current conversation's DOM
@@ -170,7 +174,7 @@ class AgentGUIClient {
170
174
  if (window.__SERVER_VERSION) {
171
175
  fetch((window.__BASE_URL || '') + '/api/version').then(r => r.json()).then(d => {
172
176
  if (d.version && d.version !== window.__SERVER_VERSION) {
173
- console.log(`Server updated ${window.__SERVER_VERSION} → ${d.version}, reloading`);
177
+ this._dbg(`Server updated ${window.__SERVER_VERSION} → ${d.version}, reloading`);
174
178
  window.location.reload();
175
179
  }
176
180
  }).catch(() => {});
@@ -178,7 +182,7 @@ class AgentGUIClient {
178
182
  });
179
183
 
180
184
  this.wsManager.on('disconnected', () => {
181
- console.log('WebSocket disconnected');
185
+ this._dbg('WebSocket disconnected');
182
186
  this.updateConnectionStatus('disconnected');
183
187
  this.updateSendButtonState();
184
188
  this.disablePromptArea();
@@ -186,7 +190,7 @@ class AgentGUIClient {
186
190
  });
187
191
 
188
192
  this.wsManager.on('reconnecting', (data) => {
189
- console.log('WebSocket reconnecting:', data);
193
+ this._dbg('WebSocket reconnecting:', data);
190
194
  this.updateConnectionStatus('reconnecting');
191
195
  });
192
196
 
@@ -266,7 +270,7 @@ class AgentGUIClient {
266
270
  */
267
271
  setupRendererListeners() {
268
272
  this.renderer.on('batch:complete', (data) => {
269
- console.log('Batch rendered:', data);
273
+ this._dbg('Batch rendered:', data);
270
274
  this.updateMetrics(data.metrics);
271
275
  });
272
276
 
@@ -293,7 +297,7 @@ class AgentGUIClient {
293
297
  if (sessionId && this.isValidId(sessionId)) {
294
298
  this.routerState.currentSessionId = sessionId;
295
299
  }
296
- console.log('Restoring conversation from URL:', conversationId);
300
+ this._dbg('Restoring conversation from URL:', conversationId);
297
301
  this._isLoadingConversation = true;
298
302
  if (window.conversationManager) {
299
303
  window.conversationManager.select(conversationId);
@@ -303,7 +307,7 @@ class AgentGUIClient {
303
307
  // If the URL conversation doesn't exist, try loading the most recent conversation
304
308
  if (this.state.conversations && this.state.conversations.length > 0) {
305
309
  const latestConv = this.state.conversations[0];
306
- console.log('Loading latest conversation instead:', latestConv.id);
310
+ this._dbg('Loading latest conversation instead:', latestConv.id);
307
311
  return this.loadConversationMessages(latestConv.id);
308
312
  } else {
309
313
  // No conversations available - show welcome screen
@@ -510,7 +514,7 @@ class AgentGUIClient {
510
514
  if (!this.state.currentConversation) return;
511
515
  try {
512
516
  const data = await window.wsClient.rpc('conv.cancel', { id: this.state.currentConversation.id });
513
- console.log('Stop response:', data);
517
+ this._dbg('Stop response:', data);
514
518
  } catch (err) {
515
519
  console.error('Failed to stop:', err);
516
520
  }
@@ -561,7 +565,7 @@ class AgentGUIClient {
561
565
  try {
562
566
  // Queue uses msg.send which will enqueue if streaming is active
563
567
  const data = await window.wsClient.rpc('msg.send', { id: this.state.currentConversation.id, content: message });
564
- console.log('Queue response:', data);
568
+ this._dbg('Queue response:', data);
565
569
  if (this.ui.messageInput) {
566
570
  this.ui.messageInput.value = '';
567
571
  this.ui.messageInput.style.height = 'auto';
@@ -788,7 +792,7 @@ class AgentGUIClient {
788
792
  }
789
793
 
790
794
  async handleStreamingStart(data) {
791
- console.log('Streaming started:', data);
795
+ this._dbg('Streaming started:', data);
792
796
  if (window.promptMachineAPI) window.promptMachineAPI.send({ type: 'STREAMING', conversationId: data.conversationId });
793
797
  this._clearThinkingCountdown();
794
798
  if (this._lastSendTime > 0) {
@@ -812,9 +816,9 @@ class AgentGUIClient {
812
816
  // If this streaming event is for a different conversation than what we are viewing,
813
817
  // just track the state but do not modify the DOM or start polling
814
818
  if (this.state.currentConversation?.id !== data.conversationId) {
815
- console.log('Streaming started for non-active conversation:', data.conversationId);
819
+ this._dbg('Streaming started for non-active conversation:', data.conversationId);
816
820
  this._setConvStreaming(data.conversationId, true, data.sessionId, data.agentId);
817
- console.log('[SYNC] streaming_start - non-active conv:', { convId: data.conversationId, sessionId: data.sessionId, streamingCount: this.state.streamingConversations.size });
821
+ this._dbg('[SYNC] streaming_start - non-active conv:', { convId: data.conversationId, sessionId: data.sessionId, streamingCount: this.state.streamingConversations.size });
818
822
  this.updateBusyPromptArea(data.conversationId);
819
823
  this.emit('streaming:start', data);
820
824
 
@@ -900,7 +904,7 @@ class AgentGUIClient {
900
904
  }
901
905
 
902
906
  async handleStreamingResumed(data) {
903
- console.log('Streaming resumed:', data);
907
+ this._dbg('Streaming resumed:', data);
904
908
  const conv = this.state.currentConversation || { id: data.conversationId };
905
909
  await this.handleStreamingStart({
906
910
  type: 'streaming_start',
@@ -956,6 +960,52 @@ class AgentGUIClient {
956
960
  const blocksEl = streamingEl.querySelector('.streaming-blocks');
957
961
  if (!blocksEl) return;
958
962
 
963
+ if (block.type === 'tool_result') {
964
+ const tid = block.tool_use_id;
965
+ const toolUseEl = (tid && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`))
966
+ || (blocksEl.lastElementChild?.classList?.contains('block-tool-use') ? blocksEl.lastElementChild : null);
967
+ if (toolUseEl) {
968
+ this.renderer.mergeResultIntoToolUse(toolUseEl, block);
969
+ this.scrollToBottom();
970
+ return;
971
+ }
972
+ }
973
+
974
+ if (block.type === 'tool_status') {
975
+ const tid = block.tool_use_id;
976
+ const toolUseEl = tid && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`);
977
+ if (toolUseEl) {
978
+ const summary = toolUseEl.querySelector(':scope > summary');
979
+ if (summary) {
980
+ let badge = summary.querySelector('.folded-tool-status-live');
981
+ if (!badge) { badge = document.createElement('span'); badge.className = 'folded-tool-status-live'; summary.appendChild(badge); }
982
+ const isRunning = block.status === 'in_progress';
983
+ badge.style.cssText = 'margin-left:auto;font-size:0.65rem;opacity:0.7;display:inline-flex;align-items:center;gap:0.25rem';
984
+ badge.innerHTML = (isRunning ? '<span class="animate-spin" style="display:inline-block;width:0.6rem;height:0.6rem;border:1.5px solid var(--color-border);border-top-color:var(--color-info);border-radius:50%"></span>' : '')
985
+ + '<span>' + (isRunning ? 'Running' : (block.status || '')) + '</span>';
986
+ }
987
+ this.scrollToBottom();
988
+ return;
989
+ }
990
+ }
991
+
992
+ if (block.type === 'hook_progress') {
993
+ const hookEvent = (block.hookEvent || '').split(':')[0];
994
+ if (hookEvent === 'PreToolUse' || hookEvent === 'PostToolUse') {
995
+ const lastTool = blocksEl.querySelector('.block-tool-use:last-of-type') || blocksEl.lastElementChild;
996
+ if (lastTool?.classList?.contains('block-tool-use')) {
997
+ const summary = lastTool.querySelector(':scope > summary');
998
+ if (summary) {
999
+ let hookBadge = summary.querySelector('.folded-tool-hook');
1000
+ if (!hookBadge) { hookBadge = document.createElement('span'); hookBadge.className = 'folded-tool-hook'; hookBadge.style.cssText = 'margin-left:0.375rem;font-size:0.6rem;opacity:0.4;font-weight:400'; summary.appendChild(hookBadge); }
1001
+ hookBadge.textContent = block.hookEvent?.split(':').pop() || hookEvent;
1002
+ }
1003
+ return;
1004
+ }
1005
+ }
1006
+ return;
1007
+ }
1008
+
959
1009
  const el = this.renderer.renderBlock(block, data, blocksEl);
960
1010
  if (el) {
961
1011
  blocksEl.appendChild(el);
@@ -1052,7 +1102,7 @@ class AgentGUIClient {
1052
1102
 
1053
1103
  // If this event is for a conversation we are NOT currently viewing, just track state
1054
1104
  if (conversationId && this.state.currentConversation?.id !== conversationId) {
1055
- console.log('Streaming error for non-active conversation:', conversationId);
1105
+ this._dbg('Streaming error for non-active conversation:', conversationId);
1056
1106
  this._setConvStreaming(conversationId, false);
1057
1107
  this.updateBusyPromptArea(conversationId);
1058
1108
  this.emit('streaming:error', data);
@@ -1117,7 +1167,7 @@ class AgentGUIClient {
1117
1167
  }
1118
1168
 
1119
1169
  handleStreamingComplete(data) {
1120
- console.log('Streaming completed:', data);
1170
+ this._dbg('Streaming completed:', data);
1121
1171
  if (window.promptMachineAPI) window.promptMachineAPI.send({ type: 'READY' });
1122
1172
  this._clearThinkingCountdown();
1123
1173
 
@@ -1125,16 +1175,16 @@ class AgentGUIClient {
1125
1175
  if (conversationId) this.invalidateCache(conversationId);
1126
1176
 
1127
1177
  if (conversationId && this.state.currentConversation?.id !== conversationId) {
1128
- console.log('Streaming completed for non-active conversation:', conversationId);
1178
+ this._dbg('Streaming completed for non-active conversation:', conversationId);
1129
1179
  this._setConvStreaming(conversationId, false);
1130
- console.log('[SYNC] streaming_complete - non-active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size });
1180
+ this._dbg('[SYNC] streaming_complete - non-active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size });
1131
1181
  this.updateBusyPromptArea(conversationId);
1132
1182
  this.emit('streaming:complete', data);
1133
1183
  return;
1134
1184
  }
1135
1185
 
1136
1186
  this._setConvStreaming(conversationId, false);
1137
- console.log('[SYNC] streaming_complete - active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size, interrupted: data.interrupted });
1187
+ this._dbg('[SYNC] streaming_complete - active conv:', { convId: conversationId, streamingCount: this.state.streamingConversations.size, interrupted: data.interrupted });
1138
1188
  this.updateBusyPromptArea(conversationId);
1139
1189
 
1140
1190
  const sessionId = data.sessionId || this.state.currentSession?.id;
@@ -1222,7 +1272,7 @@ class AgentGUIClient {
1222
1272
  return;
1223
1273
  }
1224
1274
 
1225
- console.log('[SYNC] message_created:', { msgId: data.message.id, role: data.message.role, convId: data.conversationId });
1275
+ this._dbg('[SYNC] message_created:', { msgId: data.message.id, role: data.message.role, convId: data.conversationId });
1226
1276
 
1227
1277
  // Update messageCount in current conversation state for user messages
1228
1278
  if (data.message.role === 'user' && this.state.currentConversation) {
@@ -2036,7 +2086,7 @@ class AgentGUIClient {
2036
2086
  }
2037
2087
 
2038
2088
  if (result.queued) {
2039
- console.log('Message queued, position:', result.queuePosition);
2089
+ this._dbg('Message queued, position:', result.queuePosition);
2040
2090
  this.enableControls();
2041
2091
  return;
2042
2092
  }
@@ -2083,7 +2133,8 @@ class AgentGUIClient {
2083
2133
  const deferred = [];
2084
2134
  for (const chunk of list) {
2085
2135
  if (!chunk.block?.type) continue;
2086
- if (chunk.block.type === 'tool_result') { deferred.push(chunk); continue; }
2136
+ const bt = chunk.block.type;
2137
+ if (bt === 'tool_result' || bt === 'tool_status' || bt === 'hook_progress') { deferred.push(chunk); continue; }
2087
2138
  const el = this.renderer.renderBlock(chunk.block, chunk, blockFrag);
2088
2139
  if (!el) continue;
2089
2140
  el.classList.add('block-loaded');
@@ -2091,10 +2142,23 @@ class AgentGUIClient {
2091
2142
  }
2092
2143
  blocksEl.appendChild(blockFrag);
2093
2144
  for (const chunk of deferred) {
2094
- const tid = chunk.block.tool_use_id;
2095
- const toolUseEl = (tid ? blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`) : null)
2096
- || (blocksEl.lastElementChild?.classList.contains('block-tool-use') ? blocksEl.lastElementChild : null);
2097
- if (toolUseEl) this.renderer.mergeResultIntoToolUse(toolUseEl, chunk.block);
2145
+ const b = chunk.block;
2146
+ if (b.type === 'tool_result') {
2147
+ const tid = b.tool_use_id;
2148
+ const toolUseEl = (tid ? blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`) : null)
2149
+ || (blocksEl.lastElementChild?.classList.contains('block-tool-use') ? blocksEl.lastElementChild : null);
2150
+ if (toolUseEl) this.renderer.mergeResultIntoToolUse(toolUseEl, b);
2151
+ } else if (b.type === 'tool_status') {
2152
+ const tid = b.tool_use_id;
2153
+ const toolUseEl = tid && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${tid}"]`);
2154
+ if (toolUseEl) {
2155
+ const isError = b.status === 'failed';
2156
+ const isDone = b.status === 'completed';
2157
+ if (isDone || isError) {
2158
+ toolUseEl.classList.add(isError ? 'tool-result-error' : 'tool-result-success');
2159
+ }
2160
+ }
2161
+ }
2098
2162
  }
2099
2163
  if (isActive) {
2100
2164
  const ind = document.createElement('div');
@@ -2143,6 +2207,7 @@ class AgentGUIClient {
2143
2207
  return;
2144
2208
  }
2145
2209
  }
2210
+ if (chunk.block.type === 'tool_status' || chunk.block.type === 'hook_progress') return;
2146
2211
  const element = this.renderer.renderBlock(chunk.block, chunk, blocksEl);
2147
2212
  if (!element) { this.scrollToBottom(); return; }
2148
2213
  blocksEl.appendChild(element);
@@ -2199,13 +2264,13 @@ class AgentGUIClient {
2199
2264
  .map(a => `<option value="${a.id}">${a.name.split(/[\s\-]+/)[0]}</option>`)
2200
2265
  .join('');
2201
2266
  this.ui.agentSelector.style.display = 'inline-block';
2202
- console.log(`[Agent Selector] Loaded ${subAgents.length} sub-agents for ${cliAgentId}`);
2267
+ this._dbg(`[Agent Selector] Loaded ${subAgents.length} sub-agents for ${cliAgentId}`);
2203
2268
  // Auto-select first sub-agent and load its models
2204
2269
  const firstSubAgentId = subAgents[0].id;
2205
2270
  this.ui.agentSelector.value = firstSubAgentId;
2206
2271
  this.loadModelsForAgent(cliAgentId); // models keyed to parent agent
2207
2272
  } else {
2208
- console.log(`[Agent Selector] No sub-agents found for ${cliAgentId}`);
2273
+ this._dbg(`[Agent Selector] No sub-agents found for ${cliAgentId}`);
2209
2274
  // Load models for the CLI agent itself (fallback for agents without sub-agents)
2210
2275
  const cliToAcpMap = {
2211
2276
  'cli-opencode': 'opencode',
@@ -2447,7 +2512,7 @@ class AgentGUIClient {
2447
2512
  }
2448
2513
  if (progress.done || progress.status === 'completed') {
2449
2514
  this._modelDownloadInProgress = false;
2450
- console.log('[Models] Download complete');
2515
+ this._dbg('[Models] Download complete');
2451
2516
  this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
2452
2517
  return;
2453
2518
  }
@@ -2459,7 +2524,7 @@ class AgentGUIClient {
2459
2524
 
2460
2525
  _handleTTSSetupProgress(data) {
2461
2526
  if (data.step && data.status) {
2462
- console.log('[TTS Setup]', data.step, ':', data.status, data.message || '');
2527
+ this._dbg('[TTS Setup]', data.step, ':', data.status, data.message || '');
2463
2528
  }
2464
2529
  }
2465
2530
 
@@ -2716,7 +2781,7 @@ class AgentGUIClient {
2716
2781
  if (window.conversationManager) window.conversationManager.loadConversations();
2717
2782
  const fallbackConv = prevConversationId ? prevConversationId : availableFallback?.id;
2718
2783
  if (fallbackConv && fallbackConv !== conversationId) {
2719
- console.log('Resuming from fallback conversation:', fallbackConv);
2784
+ this._dbg('Resuming from fallback conversation:', fallbackConv);
2720
2785
  this.showError('Conversation not found. Resuming previous conversation.');
2721
2786
  await this.loadConversationMessages(fallbackConv);
2722
2787
  } else {
@@ -2897,7 +2962,7 @@ class AgentGUIClient {
2897
2962
  // Resume from last successful conversation if available, or fall back to any available conversation
2898
2963
  const fallbackConv = prevConversationId ? prevConversationId : availableFallback?.id;
2899
2964
  if (fallbackConv && fallbackConv !== conversationId) {
2900
- console.log('Resuming from fallback conversation due to error:', fallbackConv);
2965
+ this._dbg('Resuming from fallback conversation due to error:', fallbackConv);
2901
2966
  this.showError('Failed to load conversation. Resuming previous conversation.');
2902
2967
  try {
2903
2968
  await this.loadConversationMessages(fallbackConv);
@@ -3293,7 +3358,7 @@ document.addEventListener('DOMContentLoaded', async () => {
3293
3358
  agentGUIClient = new AgentGUIClient();
3294
3359
  window.agentGuiClient = agentGUIClient;
3295
3360
  await agentGUIClient.init();
3296
- console.log('AgentGUI ready');
3361
+ this._dbg('AgentGUI ready');
3297
3362
  } catch (error) {
3298
3363
  console.error('Failed to initialize AgentGUI:', error);
3299
3364
  }
@@ -403,6 +403,8 @@ class StreamingRenderer {
403
403
  return this.renderBlockPlan(block, context);
404
404
  case 'premature':
405
405
  return this.renderBlockPremature(block, context);
406
+ case 'hook_progress':
407
+ return this.renderBlockHookProgress(block, context);
406
408
  default:
407
409
  return this.renderBlockGeneric(block, context);
408
410
  }
@@ -540,23 +542,28 @@ class StreamingRenderer {
540
542
  * Render thinking block (expandable), signature-aware
541
543
  */
542
544
  renderBlockThinking(block, context) {
545
+ const thinking = block.thinking || '';
546
+ const hasSignature = !!block.signature;
543
547
  const div = document.createElement('div');
544
548
  div.className = 'block-thinking';
545
549
  div.classList.add(this._getBlockTypeClass('thinking'));
546
550
 
547
- const thinking = block.thinking || '';
548
- const hasSignature = !!block.signature;
549
- div.innerHTML = `
550
- <details>
551
- <summary>
552
- <svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>
553
- <span>Thinking Process</span>
554
- ${hasSignature ? '<span style="margin-left:0.5rem;font-size:0.65rem;opacity:0.5;font-weight:400">verified</span>' : ''}
555
- </summary>
556
- <div class="thinking-content">${this.escapeHtml(thinking)}</div>
557
- </details>
558
- `;
559
-
551
+ if (!thinking) {
552
+ div.style.cssText = 'display:flex;align-items:center;gap:0.375rem;padding:0.25rem 0;font-size:0.7rem;color:var(--color-text-secondary);opacity:0.6';
553
+ div.innerHTML = '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.875rem;height:0.875rem"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/></svg>'
554
+ + '<span>Thinking</span>'
555
+ + (hasSignature ? '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem;color:#3b82f6"><path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>' : '');
556
+ return div;
557
+ }
558
+
559
+ div.innerHTML = '<details>'
560
+ + '<summary>'
561
+ + '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/></svg>'
562
+ + '<span>Thinking Process</span>'
563
+ + (hasSignature ? '<span style="margin-left:0.5rem;font-size:0.65rem;opacity:0.5;font-weight:400">verified</span>' : '')
564
+ + '</summary>'
565
+ + '<div class="thinking-content">' + this.escapeHtml(thinking) + '</div>'
566
+ + '</details>';
560
567
  return div;
561
568
  }
562
569
 
@@ -1437,6 +1444,23 @@ class StreamingRenderer {
1437
1444
  return div;
1438
1445
  }
1439
1446
 
1447
+ renderBlockHookProgress(block, context) {
1448
+ const hookEvent = block.hookEvent || block.hookName || '';
1449
+ const hookIcons = {
1450
+ PreToolUse: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10.666a1 1 0 11-1.64-1.118L9.687 10H5a1 1 0 01-.82-1.573l7-10.666a1 1 0 011.12-.373z" clip-rule="evenodd"/></svg>',
1451
+ PostToolUse: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>',
1452
+ Stop: '<svg viewBox="0 0 20 20" fill="currentColor" style="width:0.75rem;height:0.75rem"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z" clip-rule="evenodd"/></svg>'
1453
+ };
1454
+ const icon = hookIcons[hookEvent.split(':')[0]] || hookIcons.PreToolUse;
1455
+ const label = hookEvent.includes(':') ? hookEvent.split(':').pop() : hookEvent;
1456
+ const div = document.createElement('div');
1457
+ div.className = 'block-hook-progress';
1458
+ div.dataset.hookEvent = hookEvent;
1459
+ div.style.cssText = 'display:none';
1460
+ div.innerHTML = '<span class="hook-badge" style="display:inline-flex;align-items:center;gap:0.25rem;padding:0.125rem 0.5rem;font-size:0.65rem;color:var(--color-text-secondary);opacity:0.5">' + icon + '<span>' + this.escapeHtml(label) + '</span></span>';
1461
+ return div;
1462
+ }
1463
+
1440
1464
  /**
1441
1465
  * Render usage block (ACP usage updates)
1442
1466
  */