status icons and formatting

3clyp50 committed Dec 30, 2025 at 01:15 UTC e275cab0c980dad4b70693734a9bdfba411bf3ae
7 files changed +285 -207
python/extensions/before_main_llm_call/_10_log_for_stream.py
+2 -2
@@ -19,8 +19,8 @@ class LogForStream(Extension):
19 )
20 )
21
22 -def build_heading(agent, text: str):
23 - return f"icon://network_intelligence {agent.agent_name}: {text}"
22 +def build_heading(agent, text: str, icon: str = "network_intelligence"):
23 + return f"icon://{icon} {text}"
24
25 def build_default_heading(agent):
26 return build_heading(agent, "Generating...")
\ No newline at end of file
python/extensions/response_stream/_10_log_from_stream.py
+2 -5
@@ -22,12 +22,9 @@ class LogFromStream(Extension):
22 if "headline" in parsed:
23 heading = build_heading(self.agent, parsed['headline'])
24 elif "tool_name" in parsed:
25 - heading = build_heading(self.agent, f"Using tool {parsed['tool_name']}") # if the llm skipped headline
25 + heading = build_heading(self.agent, f"Using {parsed['tool_name']}") # if the llm skipped headline
26 elif "thoughts" in parsed:
27 - # thought length indicator
28 - thoughts = "\n".join(parsed["thoughts"])
29 - pipes = "|" * math.ceil(math.sqrt(len(thoughts)))
30 - heading = build_heading(self.agent, f"Thinking... {pipes}")
27 + heading = build_default_heading(self.agent)
28
29 # create log message and store it in loop data temporary params
30 if "log_item_generating" not in loop_data.params_temporary:
python/extensions/response_stream/_20_live_response.py
+1 -1
@@ -30,7 +30,7 @@ class LiveResponse(Extension):
30 loop_data.params_temporary["log_item_response"] = (
31 self.agent.context.log.log(
32 type="response",
33 - heading=f"icon://chat {self.agent.agent_name}: Responding",
33 + heading=f"icon://chat Responding",
34 )
35 )
36
webui/components/messages/process-group/process-group-store.js
+35 -72
@@ -78,110 +78,73 @@ const model = {
78 this._persist();
79 },
80
81 - // Get icon for step type (Material Symbols)
81 + // Fallback icon for step type when backend doesn't provide one (Material Symbols)
82 + // Maps backend log types to appropriate icons
83 getStepIcon(type) {
84 const icons = {
84 - 'agent': 'neurology',
85 - 'tool': 'build',
85 + 'agent': 'network_intelligence',
86 + 'response': 'chat',
87 + 'tool': 'construction',
88 + 'mcp': 'api',
89 + 'subagent': 'communication',
90 'code_exe': 'terminal',
87 - 'browser': 'language',
91 + 'browser': 'captive_portal',
92 + 'progress': 'timer',
93 'info': 'info',
94 'hint': 'lightbulb',
90 - 'util': 'settings',
95 'warning': 'warning',
96 'error': 'error',
93 - 'mcp': 'api',
94 - 'memory': 'memory',
95 - 'done': 'check_circle',
96 - 'subagent': 'group'
97 + 'util': 'memory',
98 + 'user': 'person',
99 + 'done': 'done_all'
100 };
101 return icons[type] || 'circle';
102 },
103
101 - // Get 3-letter status code for step type
104 + // Status code (3-4 letter) for backend log types
105 getStepCode(type) {
106 const codes = {
107 'agent': 'GEN',
105 - 'tool': 'MCP',
108 + 'response': 'END',
109 + 'tool': 'TOOL',
110 + 'mcp': 'MCP',
111 + 'subagent': 'SUB',
112 'code_exe': 'EXE',
107 - 'browser': 'EXE',
113 + 'browser': 'BRW',
114 + 'progress': 'WAIT',
115 'info': 'INF',
109 - 'hint': 'INF',
110 - 'util': 'UTL',
116 + 'hint': 'HNT',
117 'warning': 'WRN',
118 'error': 'ERR',
113 - 'mcp': 'MCP',
114 - 'memory': 'MEM',
115 - 'done': 'END',
116 - 'response': 'END',
117 - 'subagent': 'SUB'
119 + 'util': 'UTL',
120 + 'user': 'USR',
121 + 'done': 'END'
122 };
119 - return codes[type] || 'GEN';
123 + return codes[type] || type?.toUpperCase()?.slice(0, 4) || 'GEN';
124 },
125
122 - // Get color class for status code
126 + // CSS color class for backend log types
127 getStatusColorClass(type) {
128 const colors = {
129 'agent': 'status-gen',
126 - 'tool': 'status-mcp',
130 + 'response': 'status-end',
131 + 'tool': 'status-tool',
132 + 'mcp': 'status-mcp',
133 + 'subagent': 'status-sub',
134 'code_exe': 'status-exe',
128 - 'browser': 'status-exe',
135 + 'browser': 'status-brw',
136 + 'progress': 'status-wait',
137 'info': 'status-inf',
130 - 'hint': 'status-inf',
131 - 'util': 'status-utl',
138 + 'hint': 'status-hnt',
139 'warning': 'status-wrn',
140 'error': 'status-err',
134 - 'mcp': 'status-mcp',
135 - 'memory': 'status-mem',
136 - 'done': 'status-end',
137 - 'response': 'status-end',
138 - 'subagent': 'status-sub'
141 + 'util': 'status-utl',
142 + 'user': 'status-usr',
143 + 'done': 'status-end'
144 };
145 return colors[type] || 'status-gen';
146 },
147
143 - // Get icon for badge display (Material Symbols)
144 - getStepBadgeIcon(type) {
145 - const icons = {
146 - 'agent': 'public', // Globe for GEN (network intelligence)
147 - 'tool': 'build', // Wrench for MCP
148 - 'code_exe': 'terminal', // Terminal for EXE
149 - 'browser': 'language', // Globe for browser
150 - 'info': 'info',
151 - 'hint': 'lightbulb',
152 - 'util': 'settings',
153 - 'warning': 'warning',
154 - 'error': 'error',
155 - 'mcp': 'build', // Wrench for MCP
156 - 'memory': 'memory',
157 - 'done': 'check_circle',
158 - 'response': 'check_circle',
159 - 'subagent': 'group'
160 - };
161 - return icons[type] || 'circle';
162 - },
163 -
164 - // Get label for step type (longer description)
165 - getStepLabel(type) {
166 - const labels = {
167 - 'agent': 'Generating',
168 - 'tool': 'MCP Call',
169 - 'code_exe': 'Executing',
170 - 'browser': 'Browser',
171 - 'info': 'Info',
172 - 'hint': 'Hint',
173 - 'util': 'Utility',
174 - 'warning': 'Warning',
175 - 'error': 'Error',
176 - 'mcp': 'MCP',
177 - 'memory': 'Memory',
178 - 'done': 'Done',
179 - 'response': 'Response',
180 - 'subagent': 'Subagent'
181 - };
182 - return labels[type] || 'Process';
183 - },
184 -
148 // Clear state for a specific context (when chat is reset)
149 clearContext(contextPrefix) {
150 // Clear groups matching the context
webui/components/messages/process-group/process-group.css
+147 -63
@@ -140,76 +140,106 @@
140 opacity: 0.9;
141 }
142
143 -/* GEN - Generating (blue/cyan) */
143 +/* Status colors - mapped from backend types via store */
144 +
145 +/* GEN - agent type (blue/cyan) */
146 .status-gen {
147 background-color: rgba(56, 189, 248, 0.15);
148 color: #38bdf8;
149 border: 1px solid rgba(56, 189, 248, 0.3);
150 }
151
150 -/* EXE - Executing (magenta/purple) */
151 -.status-exe {
152 - background-color: rgba(186, 104, 200, 0.15);
153 - color: #ba68c8;
154 - border: 1px solid rgba(186, 104, 200, 0.3);
152 +/* END - response/done type (green) */
153 +.status-end {
154 + background-color: rgba(34, 197, 94, 0.15);
155 + color: #22c55e;
156 + border: 1px solid rgba(34, 197, 94, 0.3);
157 }
158
157 -/* MCP - MCP Tool Call (amber/yellow) */
158 -.status-mcp {
159 +/* TOOL - tool type (amber/yellow) */
160 +.status-tool {
161 background-color: rgba(251, 191, 36, 0.15);
162 color: #fbbf24;
163 border: 1px solid rgba(251, 191, 36, 0.3);
164 }
165
164 -/* MEM - Memory (blue) */
165 -.status-mem {
166 - background-color: rgba(96, 165, 250, 0.15);
167 - color: #60a5fa;
168 - border: 1px solid rgba(96, 165, 250, 0.3);
166 +/* MCP - mcp type (amber/yellow) */
167 +.status-mcp {
168 + background-color: rgba(251, 191, 36, 0.15);
169 + color: #fbbf24;
170 + border: 1px solid rgba(251, 191, 36, 0.3);
171 }
172
171 -/* END - Done (green) */
172 -.status-end {
173 - background-color: rgba(34, 197, 94, 0.15);
174 - color: #22c55e;
175 - border: 1px solid rgba(34, 197, 94, 0.3);
173 +/* SUB - subagent type (teal) */
174 +.status-sub {
175 + background-color: rgba(20, 184, 166, 0.15);
176 + color: #14b8a6;
177 + border: 1px solid rgba(20, 184, 166, 0.3);
178 }
179
178 -/* ERR - Error (red) */
179 -.status-err {
180 - background-color: rgba(239, 68, 68, 0.15);
181 - color: #ef4444;
182 - border: 1px solid rgba(239, 68, 68, 0.3);
180 +/* EXE - code_exe type (magenta/purple) */
181 +.status-exe {
182 + background-color: rgba(186, 104, 200, 0.15);
183 + color: #ba68c8;
184 + border: 1px solid rgba(186, 104, 200, 0.3);
185 }
186
185 -/* WRN - Warning (orange) */
186 -.status-wrn {
187 - background-color: rgba(249, 115, 22, 0.15);
188 - color: #f97316;
189 - border: 1px solid rgba(249, 115, 22, 0.3);
187 +/* BRW - browser type (indigo) */
188 +.status-brw {
189 + background-color: rgba(129, 140, 248, 0.15);
190 + color: #818cf8;
191 + border: 1px solid rgba(129, 140, 248, 0.3);
192 }
193
192 -/* SUB - Subagent (teal) */
193 -.status-sub {
194 - background-color: rgba(20, 184, 166, 0.15);
195 - color: #14b8a6;
196 - border: 1px solid rgba(20, 184, 166, 0.3);
194 +/* WAIT - progress type (slate) */
195 +.status-wait {
196 + background-color: rgba(148, 163, 184, 0.15);
197 + color: #94a3b8;
198 + border: 1px solid rgba(148, 163, 184, 0.3);
199 }
200
199 -/* INF - Info (gray) */
201 +/* INF - info type (gray) */
202 .status-inf {
203 background-color: rgba(148, 163, 184, 0.15);
204 color: #94a3b8;
205 border: 1px solid rgba(148, 163, 184, 0.3);
206 }
207
206 -/* UTL - Utility (gray-blue) */
208 +/* HNT - hint type (yellow-green) */
209 +.status-hnt {
210 + background-color: rgba(163, 230, 53, 0.15);
211 + color: #a3e635;
212 + border: 1px solid rgba(163, 230, 53, 0.3);
213 +}
214 +
215 +/* WRN - warning type (orange) */
216 +.status-wrn {
217 + background-color: rgba(249, 115, 22, 0.15);
218 + color: #f97316;
219 + border: 1px solid rgba(249, 115, 22, 0.3);
220 +}
221 +
222 +/* ERR - error type (red) */
223 +.status-err {
224 + background-color: rgba(239, 68, 68, 0.15);
225 + color: #ef4444;
226 + border: 1px solid rgba(239, 68, 68, 0.3);
227 +}
228 +
229 +/* UTL - util type (gray-blue) */
230 .status-utl {
231 background-color: rgba(100, 116, 139, 0.15);
232 color: #64748b;
233 border: 1px solid rgba(100, 116, 139, 0.3);
234 }
235
236 +/* USR - user type (sky) */
237 +.status-usr {
238 + background-color: rgba(56, 189, 248, 0.15);
239 + color: #38bdf8;
240 + border: 1px solid rgba(56, 189, 248, 0.3);
241 +}
242 +
243 /* Animated spinner for active status (CSS-only, no Material Icons) */
244 .status-badge.status-active::after {
245 content: "";
@@ -235,7 +265,7 @@
265 display: none;
266 }
267
238 -/* END status with checkmark icon (like sketch) - use ::before only */
268 +/* END status with checkmark icon - use ::before only */
269 .status-end::before {
270 content: "✓";
271 margin-right: 2px;
@@ -477,8 +507,13 @@
507 .process-step-detail-content .step-kvp-key {
508 color: var(--color-primary);
509 font-weight: 500;
480 - min-width: 80px;
510 opacity: 0.8;
511 + display: flex;
512 + align-items: center;
513 +}
514 +
515 +.process-step-detail-content .step-kvp-key .material-symbols-outlined {
516 + font-size: 0.9rem;
517 }
518
519 .process-step-detail-content .step-kvp-value {
@@ -540,8 +575,14 @@
575 font-size: 0.68rem;
576 font-weight: 600;
577 color: var(--color-primary);
543 - min-width: 70px;
578 flex-shrink: 0;
579 + display: flex;
580 + align-items: center;
581 +}
582 +
583 +.process-step-detail-content .tool-arg-label .material-symbols-outlined {
584 + font-size: 0.9rem;
585 + opacity: 0.85;
586 }
587
588 .process-step-detail-content .tool-arg-value {
@@ -558,6 +599,25 @@
599 border-left-color: transparent;
600 }
601
602 +.process-step-detail-content .step-tool-header .tool-icon {
603 + font-size: 0.85rem;
604 + color: var(--color-warning);
605 + opacity: 0.85;
606 +}
607 +
608 +.process-step-detail-content .step-tool-header .tool-label {
609 + font-size: 0.68rem;
610 + font-weight: 600;
611 + color: var(--color-warning);
612 + opacity: 0.85;
613 +}
614 +
615 +.process-step-detail-content .step-tool-header .tool-name {
616 + font-size: 0.72rem;
617 + color: var(--color-text);
618 + opacity: 0.85;
619 +}
620 +
621 /* Terminal-style output for code_exe */
622 .process-step-detail-content .step-terminal {
623 margin: var(--spacing-xs) 0;
@@ -658,46 +718,46 @@
718 border-color: rgba(14, 165, 233, 0.25);
719 }
720
661 -.light-mode .status-exe {
662 - background-color: rgba(147, 51, 234, 0.12);
663 - color: #7c3aed;
664 - border-color: rgba(147, 51, 234, 0.25);
721 +.light-mode .status-end {
722 + background-color: rgba(22, 163, 74, 0.12);
723 + color: #15803d;
724 + border-color: rgba(22, 163, 74, 0.25);
725 }
726
667 -.light-mode .status-mcp {
727 +.light-mode .status-tool {
728 background-color: rgba(217, 119, 6, 0.12);
729 color: #b45309;
730 border-color: rgba(217, 119, 6, 0.25);
731 }
732
673 -.light-mode .status-mem {
674 - background-color: rgba(37, 99, 235, 0.12);
675 - color: #1d4ed8;
676 - border-color: rgba(37, 99, 235, 0.25);
733 +.light-mode .status-mcp {
734 + background-color: rgba(217, 119, 6, 0.12);
735 + color: #b45309;
736 + border-color: rgba(217, 119, 6, 0.25);
737 }
738
679 -.light-mode .status-end {
680 - background-color: rgba(22, 163, 74, 0.12);
681 - color: #15803d;
682 - border-color: rgba(22, 163, 74, 0.25);
739 +.light-mode .status-sub {
740 + background-color: rgba(13, 148, 136, 0.12);
741 + color: #0f766e;
742 + border-color: rgba(13, 148, 136, 0.25);
743 }
744
685 -.light-mode .status-err {
686 - background-color: rgba(220, 38, 38, 0.12);
687 - color: #b91c1c;
688 - border-color: rgba(220, 38, 38, 0.25);
745 +.light-mode .status-exe {
746 + background-color: rgba(147, 51, 234, 0.12);
747 + color: #7c3aed;
748 + border-color: rgba(147, 51, 234, 0.25);
749 }
750
691 -.light-mode .status-wrn {
692 - background-color: rgba(234, 88, 12, 0.12);
693 - color: #c2410c;
694 - border-color: rgba(234, 88, 12, 0.25);
751 +.light-mode .status-brw {
752 + background-color: rgba(99, 102, 241, 0.12);
753 + color: #4f46e5;
754 + border-color: rgba(99, 102, 241, 0.25);
755 }
756
697 -.light-mode .status-sub {
698 - background-color: rgba(13, 148, 136, 0.12);
699 - color: #0f766e;
700 - border-color: rgba(13, 148, 136, 0.25);
757 +.light-mode .status-wait {
758 + background-color: rgba(100, 116, 139, 0.12);
759 + color: #475569;
760 + border-color: rgba(100, 116, 139, 0.25);
761 }
762
763 .light-mode .status-inf {
@@ -706,12 +766,36 @@
766 border-color: rgba(100, 116, 139, 0.25);
767 }
768
769 +.light-mode .status-hnt {
770 + background-color: rgba(132, 204, 22, 0.12);
771 + color: #65a30d;
772 + border-color: rgba(132, 204, 22, 0.25);
773 +}
774 +
775 +.light-mode .status-wrn {
776 + background-color: rgba(234, 88, 12, 0.12);
777 + color: #c2410c;
778 + border-color: rgba(234, 88, 12, 0.25);
779 +}
780 +
781 +.light-mode .status-err {
782 + background-color: rgba(220, 38, 38, 0.12);
783 + color: #b91c1c;
784 + border-color: rgba(220, 38, 38, 0.25);
785 +}
786 +
787 .light-mode .status-utl {
788 background-color: rgba(71, 85, 105, 0.12);
789 color: #334155;
790 border-color: rgba(71, 85, 105, 0.25);
791 }
792
793 +.light-mode .status-usr {
794 + background-color: rgba(14, 165, 233, 0.12);
795 + color: #0284c7;
796 + border-color: rgba(14, 165, 233, 0.25);
797 +}
798 +
799 /* Animation for loading state */
800 @keyframes pulse-step {
801 0%, 100% { opacity: 0.5; }
webui/css/messages.css
-6
@@ -78,13 +78,7 @@
78 }
79
80 .message-user {
81 - background-color: #4a4a4a;
82 - border-radius: var(--border-radius) !important;
83 - /* border-bottom-right-radius: var(--spacing-xxs); */
84 - /* min-width: 195px; */
81 text-align: end;
86 - box-shadow: inset 0 2rem 2rem -2rem rgba(0, 0, 0, 0.3),
87 - inset 0 -2rem 2rem -2rem rgba(0, 0, 0, 0.1);
82 }
83
84 .message-user > div {
webui/js/messages.js
+98 -58
@@ -218,42 +218,6 @@ export function _drawMessage(
218 // Update message classes
219 messageDiv.className = `message ${mainClass} ${messageClasses.join(" ")}`;
220
221 - // Handle heading
222 - if (heading) {
223 - let headingElement = messageDiv.querySelector(".msg-heading");
224 - if (!headingElement) {
225 - headingElement = document.createElement("div");
226 - headingElement.classList.add("msg-heading");
227 - messageDiv.insertBefore(headingElement, messageDiv.firstChild);
228 - }
229 -
230 - let headingH4 = headingElement.querySelector("h4");
231 - if (!headingH4) {
232 - headingH4 = document.createElement("h4");
233 - headingElement.appendChild(headingH4);
234 - }
235 - headingH4.innerHTML = convertIcons(escapeHTML(heading));
236 -
237 - if (resizeBtns) {
238 - let minMaxBtn = headingElement.querySelector(".msg-min-max-btns");
239 - if (!minMaxBtn) {
240 - minMaxBtn = document.createElement("div");
241 - minMaxBtn.classList.add("msg-min-max-btns");
242 - minMaxBtn.innerHTML = `
243 - <a href="#" class="msg-min-max-btn" @click.prevent="$store.messageResize.minimizeMessageClass('${mainClass}', $event)"><span class="material-symbols-outlined" x-text="$store.messageResize.getSetting('${mainClass}').minimized ? 'expand_content' : 'minimize'"></span></a>
244 - <a href="#" class="msg-min-max-btn" x-show="!$store.messageResize.getSetting('${mainClass}').minimized" @click.prevent="$store.messageResize.maximizeMessageClass('${mainClass}', $event)"><span class="material-symbols-outlined" x-text="$store.messageResize.getSetting('${mainClass}').maximized ? 'expand' : 'expand_all'"></span></a>
245 - `;
246 - headingElement.appendChild(minMaxBtn);
247 - }
248 - }
249 - } else {
250 - // Remove heading if it exists but heading is null
251 - const existingHeading = messageDiv.querySelector(".msg-heading");
252 - if (existingHeading) {
253 - existingHeading.remove();
254 - }
255 - }
256 -
221 // Find existing body div or create new one
222 let bodyDiv = messageDiv.querySelector(".message-body");
223 if (!bodyDiv) {
@@ -1162,8 +1126,8 @@ function createProcessGroup(id) {
1126 header.classList.add("process-group-header");
1127 header.innerHTML = `
1128 <span class="expand-icon"></span>
1165 - <span class="status-badge status-gen status-active group-status"><span class="badge-icon material-symbols-outlined">public</span>GEN</span>
1129 <span class="group-title">Processing...</span>
1130 + <span class="status-badge status-gen status-active group-status"><span class="badge-icon material-symbols-outlined">public</span>GEN</span>
1131 <span class="group-metrics">
1132 <span class="metric-time" title="Start time"><span class="metric-value">--:--</span></span>
1133 <span class="metric-steps" title="Steps"><span class="metric-value">0</span></span>
@@ -1201,6 +1165,7 @@ function createProcessGroup(id) {
1165 function addProcessStep(group, id, type, heading, content, kvps, timestamp = null, durationMs = null, tokensIn = 0, tokensOut = 0) {
1166 const groupId = group.getAttribute("data-group-id");
1167 const stepsContainer = group.querySelector(".process-steps");
1168 + const isGroupCompleted = group.classList.contains("process-group-completed");
1169
1170 // Create step element
1171 const step = document.createElement("div");
@@ -1235,9 +1200,7 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1200 }
1201 }
1202
1238 - // Get step info
1239 - const icon = processGroupStore.getStepIcon(type);
1240 - const label = processGroupStore.getStepLabel(type);
1203 + // Get step info from heading (single source of truth: backend)
1204 const title = getStepTitle(heading, kvps, type);
1205
1206 // Check if step should be expanded
@@ -1250,16 +1213,17 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1213 const stepHeader = document.createElement("div");
1214 stepHeader.classList.add("process-step-header");
1215
1253 - // Get 3-letter status code and color class
1216 + // Status code and color class from store (maps backend types)
1217 const statusCode = processGroupStore.getStepCode(type);
1218 const statusColorClass = processGroupStore.getStatusColorClass(type);
1219
1257 - // Get icon for step type
1258 - const stepIcon = processGroupStore.getStepBadgeIcon(type);
1220 + // Icon extracted from heading (backend sends icon://xxx), fallback to type-based
1221 + const stepIcon = extractIconFromHeading(heading) || processGroupStore.getStepIcon(type);
1222
1223 + const activeClass = isGroupCompleted ? "" : " status-active";
1224 stepHeader.innerHTML = `
1225 <span class="step-expand-icon"></span>
1262 - <span class="status-badge ${statusColorClass} status-active"><span class="badge-icon material-symbols-outlined">${stepIcon}</span>${statusCode}</span>
1226 + <span class="status-badge ${statusColorClass}${activeClass}"><span class="badge-icon material-symbols-outlined">${stepIcon}</span>${statusCode}</span>
1227 <span class="step-title">${escapeHTML(title)}</span>
1228 `;
1229
@@ -1355,12 +1319,23 @@ function getStepTitle(heading, kvps, type) {
1319 }
1320 }
1321
1358 - return processGroupStore.getStepLabel(type);
1322 + // Fallback: capitalize type (backend is source of truth)
1323 + return type ? type.charAt(0).toUpperCase() + type.slice(1).replace(/_/g, ' ') : 'Process';
1324 +}
1325 +
1326 +/**
1327 + * Extract icon name from heading with icon:// prefix
1328 + * Returns the icon name (e.g., "terminal") or null if no prefix found
1329 + */
1330 +function extractIconFromHeading(heading) {
1331 + if (!heading) return null;
1332 + const match = String(heading).match(/^icon:\/\/([a-zA-Z0-9_]+)/);
1333 + return match ? match[1] : null;
1334 }
1335
1336 /**
1362 - * Clean step title by removing icon:// prefixes
1363 - * Preserves agent markers (A0:, A1:, etc.) so users can see which agent is executing
1337 + * Clean step title by removing icon:// prefixes and status phrases
1338 + * Preserves agent markers (A1:, A2:, etc.) so users can see which subordinate agent is executing
1339 */
1340 function cleanStepTitle(text, maxLength) {
1341 if (!text) return "";
@@ -1369,10 +1344,6 @@ function cleanStepTitle(text, maxLength) {
1344 // Remove icon:// patterns (e.g., "icon://network_intelligence")
1345 cleaned = cleaned.replace(/icon:\/\/[a-zA-Z0-9_]+\s*/g, "");
1346
1372 - // Keep agent markers (A0:, A1:, etc.) - users need to see which agent is executing
1373 - // Only remove "A0:" as it's the main agent (implied)
1374 - cleaned = cleaned.replace(/^A0:\s*/i, "");
1375 -
1347 // Trim whitespace
1348 cleaned = cleaned.trim();
1349
@@ -1440,6 +1411,12 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1411
1412 const lowerKey = key.toLowerCase();
1413
1414 + // Skip headline and tool_name - they're shown elsewhere
1415 + if (lowerKey === "headline" || lowerKey === "tool_name") continue;
1416 +
1417 + // Skip query in agent steps - it's shown in the tool call step
1418 + if (type === "agent" && lowerKey === "query") continue;
1419 +
1420 // Special handling for thoughts/reasoning - render with single lightbulb icon
1421 if (lowerKey === "thoughts" || lowerKey === "thinking" || lowerKey === "reflection" || lowerKey === "reasoning") {
1422 const thoughtsDiv = document.createElement("div");
@@ -1472,18 +1449,45 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1449 continue;
1450 }
1451
1475 - // Special handling for tool_args - render as structured labeled parameters
1476 - if (lowerKey === "tool_args" && typeof value === "object" && value !== null) {
1452 + // Special handling for tool_args - render only for tool/mcp types (skip for agent)
1453 + if (lowerKey === "tool_args") {
1454 + // Skip tool_args for agent steps - it's shown in the tool call step
1455 + if (type === "agent") continue;
1456 +
1457 + if (typeof value !== "object" || value === null) continue;
1458 const argsDiv = document.createElement("div");
1459 argsDiv.classList.add("step-tool-args");
1460
1461 + // Icon mapping for common tool arguments
1462 + const argIcons = {
1463 + 'query': 'search',
1464 + 'url': 'link',
1465 + 'path': 'folder',
1466 + 'file': 'description',
1467 + 'code': 'code',
1468 + 'command': 'terminal',
1469 + 'message': 'chat',
1470 + 'text': 'notes',
1471 + 'content': 'article',
1472 + 'name': 'label',
1473 + 'id': 'tag',
1474 + 'type': 'category'
1475 + };
1476 +
1477 for (const [argKey, argValue] of Object.entries(value)) {
1478 const argRow = document.createElement("div");
1479 argRow.classList.add("tool-arg-row");
1480
1481 const argLabel = document.createElement("span");
1482 argLabel.classList.add("tool-arg-label");
1486 - argLabel.textContent = convertToTitleCase(argKey) + ":";
1483 +
1484 + // Use icon if available, otherwise use text label
1485 + const lowerArgKey = argKey.toLowerCase();
1486 + if (argIcons[lowerArgKey]) {
1487 + argLabel.innerHTML = `<span class="material-symbols-outlined">${argIcons[lowerArgKey]}</span>`;
1488 + } else {
1489 + argLabel.textContent = convertToTitleCase(argKey) + ":";
1490 + }
1491
1492 const argVal = document.createElement("span");
1493 argVal.classList.add("tool-arg-value");
@@ -1508,7 +1512,31 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1512
1513 const keySpan = document.createElement("span");
1514 keySpan.classList.add("step-kvp-key");
1511 - keySpan.textContent = convertToTitleCase(key) + ":";
1515 +
1516 + // Icon mapping for common kvp keys
1517 + const kvpIcons = {
1518 + 'query': 'search',
1519 + 'url': 'link',
1520 + 'path': 'folder',
1521 + 'file': 'description',
1522 + 'code': 'code',
1523 + 'command': 'terminal',
1524 + 'message': 'chat',
1525 + 'text': 'notes',
1526 + 'content': 'article',
1527 + 'name': 'label',
1528 + 'id': 'tag',
1529 + 'type': 'category',
1530 + 'runtime': 'memory',
1531 + 'result': 'output'
1532 + };
1533 +
1534 + // lowerKey already defined above
1535 + if (kvpIcons[lowerKey]) {
1536 + keySpan.innerHTML = `<span class="material-symbols-outlined">${kvpIcons[lowerKey]}</span>`;
1537 + } else {
1538 + keySpan.textContent = convertToTitleCase(key) + ":";
1539 + }
1540
1541 const valueSpan = document.createElement("span");
1542 valueSpan.classList.add("step-kvp-value");
@@ -1578,6 +1606,7 @@ function updateProcessGroupHeader(group) {
1606 const titleEl = group.querySelector(".group-title");
1607 const statusEl = group.querySelector(".group-status");
1608 const metricsEl = group.querySelector(".group-metrics");
1609 + const isCompleted = group.classList.contains("process-group-completed");
1610
1611 // Update step count in metrics
1612 const stepsMetricEl = metricsEl?.querySelector(".metric-steps .metric-value");
@@ -1626,6 +1655,14 @@ function updateProcessGroupHeader(group) {
1655 tokensMetricEl.textContent = "--";
1656 }
1657 }
1658 +
1659 + // Once a group is completed, never re-enable any loading spinners (status-active).
1660 + // This prevents late util/tool messages from making a completed group look "running".
1661 + if (isCompleted) {
1662 + const activeBadges = group.querySelectorAll(".status-badge.status-active");
1663 + activeBadges.forEach(badge => badge.classList.remove("status-active"));
1664 + return;
1665 + }
1666
1667 if (steps.length > 0) {
1668 // Get the last step's type for status
@@ -1635,9 +1672,12 @@ function updateProcessGroupHeader(group) {
1672
1673 // Update status badge with icon (keep status-active during execution)
1674 if (statusEl) {
1675 + // Status code and color class from store (maps backend types)
1676 const statusCode = processGroupStore.getStepCode(lastType);
1677 const statusColorClass = processGroupStore.getStatusColorClass(lastType);
1640 - const badgeIcon = processGroupStore.getStepBadgeIcon(lastType);
1678 + // Get icon from the last step's badge, fallback to type-based
1679 + const lastStepBadge = lastStep.querySelector(".badge-icon");
1680 + const badgeIcon = lastStepBadge?.textContent || processGroupStore.getStepIcon(lastType);
1681 statusEl.innerHTML = `<span class="badge-icon material-symbols-outlined">${badgeIcon}</span>${statusCode}`;
1682 statusEl.className = `status-badge ${statusColorClass} status-active group-status`;
1683 }
@@ -1683,8 +1723,8 @@ function markProcessGroupComplete(group, responseTitle) {
1723 // Update status badge to END (remove status-active)
1724 const statusEl = group.querySelector(".group-status");
1725 if (statusEl) {
1686 - statusEl.textContent = "END";
1687 - statusEl.className = "status-badge status-end group-status"; // No status-active
1726 + statusEl.innerHTML = '<span class="badge-icon material-symbols-outlined">check_circle</span>END';
1727 + statusEl.className = "status-badge status-response group-status"; // No status-active
1728 }
1729
1730 // Remove status-active from all step badges (stop spinners)