continued log+rendering refactor

frdel committed Jan 23, 2026 at 22:08 UTC c4846e7e9d1ea157175f78842fdb07a65f230e60
15 files changed +217 -294
initialize.py
+1 -1
@@ -109,7 +109,7 @@ def initialize_agent(override_settings: dict | None = None):
109 # if first_context:
110 # (
111 # first_context.log
112 - # .log(type="warning", content=f"Failed to update MCP settings: {e}", temp=False)
112 + # .log(type="warning", content=f"Failed to update MCP settings: {e}")
113 # )
114 # (
115 # print_style_helper.PrintStyle(background_color="black", font_color="red", padding=True)
python/extensions/monologue_end/_50_memorize_fragments.py
-3
@@ -114,7 +114,6 @@ class MemorizeMemories(Extension):
114 # memory_log = self.agent.context.log.log(
115 # type="util",
116 # heading=f"Processing memory fragment: {txt[:50]}...",
117 - # temp=False,
117 # update_progress="none" # Don't affect status bar
118 # )
119
@@ -133,7 +132,6 @@ class MemorizeMemories(Extension):
132 memory_log.update(
133 result="Fragment processed successfully",
134 heading=f"Memory fragment completed: {txt[:50]}...",
136 - temp=False, # Show completion message
135 update_progress="none" # Show briefly then disappear
136 )
137 else:
@@ -141,7 +139,6 @@ class MemorizeMemories(Extension):
139 memory_log.update(
140 result="Fragment processing failed",
141 heading=f"Memory fragment failed: {txt[:50]}...",
144 - temp=False, # Show completion message
142 update_progress="none" # Show briefly then disappear
143 )
144 total_processed += 1
python/extensions/monologue_end/_51_memorize_solutions.py
-3
@@ -121,7 +121,6 @@ class MemorizeSolutions(Extension):
121 # solution_log = self.agent.context.log.log(
122 # type="util",
123 # heading=f"Processing solution: {txt[:50]}...",
124 - # temp=False,
124 # update_progress="none" # Don't affect status bar
125 # )
126
@@ -140,7 +139,6 @@ class MemorizeSolutions(Extension):
139 solution_log.update(
140 result="Solution processed successfully",
141 heading=f"Solution completed: {txt[:50]}...",
143 - temp=False, # Show completion message
142 update_progress="none" # Show briefly then disappear
143 )
144 else:
@@ -148,7 +146,6 @@ class MemorizeSolutions(Extension):
146 solution_log.update(
147 result="Solution processing failed",
148 heading=f"Solution failed: {txt[:50]}...",
151 - temp=False, # Show completion message
149 update_progress="none" # Show briefly then disappear
150 )
151 total_processed += 1
python/helpers/docker.py
+2 -2
@@ -73,7 +73,7 @@ class DockerContainerManager:
73 if existing_container:
74 if existing_container.status != 'running':
75 PrintStyle.standard(f"Starting existing container: {self.name} for safe code execution...")
76 - if self.logger: self.logger.log(type="info", content=f"Starting existing container: {self.name} for safe code execution...", temp=True)
76 + if self.logger: self.logger.log(type="info", content=f"Starting existing container: {self.name} for safe code execution...")
77
78 existing_container.start()
79 self.container = existing_container
@@ -84,7 +84,7 @@ class DockerContainerManager:
84 # PrintStyle.standard(f"Container with name '{self.name}' is already running with ID: {existing_container.id}")
85 else:
86 PrintStyle.standard(f"Initializing docker container {self.name} for safe code execution...")
87 - if self.logger: self.logger.log(type="info", content=f"Initializing docker container {self.name} for safe code execution...", temp=True)
87 + if self.logger: self.logger.log(type="info", content=f"Initializing docker container {self.name} for safe code execution...")
88
89 self.container = self.client.containers.run(
90 self.image,
python/helpers/fasta2a_server.py
-1
@@ -98,7 +98,6 @@ class AgentZeroWorker(Worker): # type: ignore[misc]
98 heading="Remote user message",
99 content=agent_message.message,
100 kvps={"from": "A2A"},
101 - temp=False,
101 )
102
103 # Process message through Agent Zero (includes response)
python/helpers/log.py
+5 -18
@@ -127,14 +127,12 @@ class LogItem:
127 type: Type
128 heading: str = ""
129 content: str = ""
130 - temp: bool = False
130 update_progress: Optional[ProgressUpdate] = "persistent"
131 kvps: Optional[OrderedDict] = None # Use OrderedDict for kvps
132 id: Optional[str] = None # Add id field
133 guid: str = ""
134 timestamp: float = 0.0
136 - duration_ms: Optional[int] = None
137 - agent_number: int = 0
135 + agentno: int = 0
136
137 def __post_init__(self):
138 self.guid = self.log.guid
@@ -146,7 +144,6 @@ class LogItem:
144 heading: str | None = None,
145 content: str | None = None,
146 kvps: dict | None = None,
149 - temp: bool | None = None,
147 update_progress: ProgressUpdate | None = None,
148 **kwargs,
149 ):
@@ -157,7 +154,6 @@ class LogItem:
154 heading=heading,
155 content=content,
156 kvps=kvps,
160 - temp=temp,
157 update_progress=update_progress,
158 **kwargs,
159 )
@@ -184,11 +180,9 @@ class LogItem:
180 "type": self.type,
181 "heading": self.heading,
182 "content": self.content,
187 - "temp": self.temp,
183 "kvps": self.kvps,
184 "timestamp": self.timestamp,
190 - "duration_ms": self.duration_ms,
191 - "agent_number": self.agent_number,
185 + "agentno": self.agentno,
186 }
187
188
@@ -207,7 +201,6 @@ class Log:
201 heading: str | None = None,
202 content: str | None = None,
203 kvps: dict | None = None,
210 - temp: bool | None = None,
204 update_progress: ProgressUpdate | None = None,
205 id: Optional[str] = None,
206 **kwargs,
@@ -215,20 +208,19 @@ class Log:
208
209 # add a minimal item to the log
210 # Determine agent number from streaming agent
218 - agent_number = 0
211 + agentno = 0
212 if self.context and self.context.streaming_agent:
220 - agent_number = self.context.streaming_agent.number
213 + agentno = self.context.streaming_agent.number
214
215 item = LogItem(
216 log=self,
217 no=len(self.logs),
218 type=type,
226 - agent_number=agent_number,
219 + agentno=agentno,
220 )
221 # Set duration on previous item and mark it as updated
222 if self.logs:
223 prev = self.logs[-1]
231 - prev.duration_ms = int((item.timestamp - prev.timestamp) * 1000)
224 self.updates += [prev.no]
225 self.logs.append(item)
226
@@ -239,7 +231,6 @@ class Log:
231 heading=heading,
232 content=content,
233 kvps=kvps,
242 - temp=temp,
234 update_progress=update_progress,
235 id=id,
236 **kwargs,
@@ -253,7 +244,6 @@ class Log:
244 heading: str | None = None,
245 content: str | None = None,
246 kvps: dict | None = None,
256 - temp: bool | None = None,
247 update_progress: ProgressUpdate | None = None,
248 id: Optional[str] = None,
249 **kwargs,
@@ -266,9 +256,6 @@ class Log:
256 if type is not None:
257 item.type = type
258
269 - if temp is not None:
270 - item.temp = temp
271 -
259 if update_progress is not None:
260 item.update_progress = update_progress
261
python/helpers/mcp_handler.py
-1
@@ -90,7 +90,6 @@ def initialize_mcp(mcp_servers_config: str):
90 AgentContext.log_to_all(
91 type="warning",
92 content=f"Failed to update MCP settings: {e}",
93 - temp=False,
93 )
94
95 PrintStyle(
python/helpers/memory_consolidation.py
-5
@@ -130,7 +130,6 @@ class MemoryConsolidator:
130 if log_item:
131 log_item.update(
132 progress="No similar memories found, inserting new memory",
133 - temp=True
133 )
134 try:
135 db = await Memory.get(self.agent)
@@ -153,7 +152,6 @@ class MemoryConsolidator:
152 if log_item:
153 log_item.update(
154 progress=f"Found {len(similar_memories)} similar memories, analyzing...",
156 - temp=True,
155 similar_memories_count=len(similar_memories)
156 )
157
@@ -174,7 +172,6 @@ class MemoryConsolidator:
172 if log_item:
173 log_item.update(
174 progress=f"Filtered out {deleted_count} deleted memories, {len(valid_similar_memories)} remain for analysis",
177 - temp=True,
175 race_condition_detected=True,
176 deleted_similar_memories_count=deleted_count
177 )
@@ -185,7 +182,6 @@ class MemoryConsolidator:
182 if log_item:
183 log_item.update(
184 progress="No valid similar memories remain, inserting new memory",
188 - temp=True
185 )
186 try:
187 db = await Memory.get(self.agent)
@@ -220,7 +216,6 @@ class MemoryConsolidator:
216 if log_item:
217 log_item.update(
218 progress="LLM analysis suggests skipping consolidation",
223 - temp=True
219 )
220 try:
221 db = await Memory.get(self.agent)
python/helpers/persist_chat.py
+2 -4
@@ -269,11 +269,9 @@ def _deserialize_log(data: dict[str, Any]) -> "Log":
269 heading=item_data.get("heading", ""),
270 content=item_data.get("content", ""),
271 kvps=OrderedDict(item_data["kvps"]) if item_data["kvps"] else None,
272 - temp=item_data.get("temp", False),
273 - # Pass metrics directly to constructor
272 timestamp=item_data.get("timestamp", 0.0),
275 - duration_ms=item_data.get("duration_ms"),
276 - agent_number=item_data.get("agent_number", 0),
273 + agentno=item_data.get("agentno", 0),
274 + id=item_data.get("id"),
275 ))
276 log.updates.append(i)
277 i += 1
python/helpers/shell_ssh.py
-1
@@ -82,7 +82,6 @@ class SSHInteractiveSession:
82 self.logger.log(
83 type="info",
84 content=f"SSH Connection attempt {errors}...",
85 - temp=True,
85 )
86 time.sleep(5)
87 else:
webui/components/messages/process-group/process-group.css
+11 -19
@@ -145,87 +145,83 @@
145 /* GEN - agent type (blue/cyan) */
146 .status-gen {
147 --step-accent: #38bdf8;
148 - color: var(--step-accent);
148 }
149
150 /* END - response/done type (green) */
151 .status-end {
152 --step-accent: #22c55e;
154 - color: var(--step-accent);
153 }
154
155 /* USE - tool usage (amber/yellow) */
156 .status-tool {
157 --step-accent: #fbbf24;
160 - color: var(--step-accent);
158 }
159
160 /* MCP - mcp type (amber/yellow) */
161 .status-mcp {
162 --step-accent: #fbbf24;
166 - color: var(--step-accent);
163 }
164
165 /* SUB - subagent type (teal) */
166 .status-sub {
167 --step-accent: #14b8a6;
172 - color: var(--step-accent);
168 }
169
170 /* EXE - code_exe type (magenta/purple) */
171 .status-exe {
172 --step-accent: #ba68c8;
178 - color: var(--step-accent);
173 }
174
175 /* WWW - browser type (indigo) */
176 .status-www {
177 --step-accent: #818cf8;
184 - color: var(--step-accent);
178 }
179
180 /* WAIT - progress type (slate) */
181 .status-wait {
182 --step-accent: #94a3b8;
190 - color: var(--step-accent);
183 }
184
185 /* INF - info type (gray) */
186 .status-inf {
187 --step-accent: #94a3b8;
196 - color: var(--step-accent);
188 }
189
190 /* HNT - hint type (yellow-green) */
191 .status-hnt {
192 --step-accent: #a3e635;
202 - color: var(--step-accent);
193 }
194
195 /* WRN - warning type (orange) */
196 .status-wrn {
197 --step-accent: #f97316;
208 - color: var(--step-accent);
198 }
199
200 /* ERR - error type (red) */
201 .status-err {
202 --step-accent: var(--color-error-text);
214 - color: var(--step-accent);
203 }
204
205 /* UTL - util type (gray-blue) */
206 .status-utl {
207 --step-accent: #64748b;
220 - color: var(--step-accent);
208 }
209
210 /* USR - user type (sky) */
211 .status-usr {
212 --step-accent: #38bdf8;
213 +}
214 +
215 +
216 +.process-step .status-badge{
217 + color: var(--step-accent);
218 +}
219 +
220 +.process-step .kvps-key{
221 color: var(--step-accent);
222 }
223
224 +
225 /* Completed process group styling */
226 .process-group-completed {
227 opacity: 0.95;
@@ -302,7 +298,7 @@
298
299 /* Process Group Content - Animated expand/collapse */
300 .process-group-content {
305 - display: grid;
301 + /* display: grid; */
302 grid-template-rows: 0fr;
303 opacity: 0;
304 margin-top: 0;
@@ -321,7 +317,7 @@
317 }
318
319 .process-group.expanded .process-group-content {
324 - grid-template-rows: 1fr;
320 + /* grid-template-rows: 1fr; */
321 opacity: 1;
322 margin-top: var(--spacing-xs);
323 padding-top: var(--spacing-xs);
@@ -791,10 +787,6 @@
787 display: none;
788 }
789
794 -.process-step.message-util.show-util {
795 - display: flex;
796 -}
797 -
790 /* Thoughts KVP row */
791 .step-kvp.msg-thoughts {
792 display: none;
webui/css/messages.css
+38 -40
@@ -17,7 +17,7 @@
17 scrollbar-color: #555 transparent;
18 padding-left: var(--spacing-sm);
19 padding-right: var(--spacing-sm);
20 - padding-bottom:5em;
20 + padding-bottom: 5em;
21 }
22
23 #chat-history > *:first-child {
@@ -70,7 +70,6 @@
70 margin: 0;
71 }
72
73 -
73 .message.message-user {
74 text-align: end;
75 margin-bottom: var(--spacing-md);
@@ -117,13 +116,11 @@
116 color: #2e2e2e;
117 } */
118
120 -
119 .message-center {
120 align-self: center;
121 /* border-bottom-left-radius: unset; */
122 }
123
126 -
124 .message-followup .message {
125 border-radius: 0;
126 /* border-top-left-radius: var(--spacing-xxs); */
@@ -333,7 +330,9 @@
330 border-width: 4px 0 4px 6px;
331 border-color: transparent transparent transparent #ef4444;
332 opacity: 0.7;
336 - transition: transform 0.2s ease, opacity 0.15s ease;
333 + transition:
334 + transform 0.2s ease,
335 + opacity 0.15s ease;
336 flex-shrink: 0;
337 font-size: 0;
338 margin-right: 2px;
@@ -375,10 +374,11 @@
374 opacity: 0;
375 margin-top: 0;
376 padding-top: 0;
378 - transition: grid-template-rows 0.25s ease-out,
379 - opacity 0.2s ease-out,
380 - margin-top 0.25s ease-out,
381 - padding-top 0.25s ease-out;
377 + transition:
378 + grid-template-rows 0.25s ease-out,
379 + opacity 0.2s ease-out,
380 + margin-top 0.25s ease-out,
381 + padding-top 0.25s ease-out;
382 overflow: hidden;
383 }
384
@@ -415,7 +415,6 @@
415 scrollbar-color: var(--color-error-text) transparent;
416 }
417
418 -
418 /* Terminal styling moved to new terminal block above */
419
420 /* Agent and AI Info */
@@ -433,21 +432,24 @@
432 width: 100%;
433 }
434
435 +.kvps-val{
436 + font-size: 0.75rem;
437 +}
438 +
439 .kvps-val pre {
440 white-space: pre-wrap; /* keep \n, collapse no spaces, allow wrapping */
441 word-break: break-word; /* optional – forces really long “words” to break */
442 font-family: var(--font-family-code);
443 font-optical-sizing: auto;
444 -webkit-font-optical-sizing: auto;
442 - font-size: 0.75rem;
445 }
446
447 .msg-kvps th,
448 .msg-kvps td {
447 - align-content: center;
449 + align-content: start;
450 padding: 0.25rem;
451 padding-left: 0;
450 - text-align: left;
452 + /* text-align: left; */
453 }
454
455 .msg-kvps th {
@@ -510,12 +512,19 @@
512 .kvps-key {
513 font-weight: 500;
514 font-size: var(--font-size-small);
513 - min-width: 7em;
515 + /* min-width: 7em; */
516 + max-width: 10em;
517 + text-align: right;
518 +}
519 +
520 +.kvps-key .material-symbols-outlined{
521 + font-size: var(--font-size-smaller);
522 }
523
524 .kvps-val {
525 /* margin: 0.65rem 0 0.65rem 0.4rem; */
526 white-space: pre-wrap;
527 + font-size: var(--font-size-small);
528 }
529
530 .kvps-img {
@@ -531,18 +540,6 @@
540 width: 100%;
541 }
542
534 -.msg-json {
535 - display: none;
536 -}
537 -
538 -.msg-thoughts {
539 - display: auto;
540 -}
541 -
542 -.msg-thoughts .kvps-val {
543 - max-height: 20em;
544 - overflow: auto;
545 -}
543
544 .msg-content {
545 margin-bottom: 0;
@@ -550,13 +547,6 @@
547 overflow: hidden;
548 }
549
553 -.message-temp {
554 - display: none;
555 -}
556 -
557 -.message-temp:not([style*="display: none"]):last-of-type {
558 - display: block; /* or any style you want for visibility */
559 -}
550
551 /* Math (KaTeX) */
552 .katex {
@@ -568,8 +558,14 @@
558 /* Chat width controlled by --chat-max-width CSS variable (set via preferences, default 55em) */
559 @media (min-width: 1025px) {
560 #chat-history {
571 - padding-left: max(var(--spacing-sm), calc((100% - var(--chat-max-width, 55em)) / 2)) !important;
572 - padding-right: max(var(--spacing-sm), calc((100% - var(--chat-max-width, 55em)) / 2)) !important;
561 + padding-left: max(
562 + var(--spacing-sm),
563 + calc((100% - var(--chat-max-width, 55em)) / 2)
564 + ) !important;
565 + padding-right: max(
566 + var(--spacing-sm),
567 + calc((100% - var(--chat-max-width, 55em)) / 2)
568 + ) !important;
569 }
570 }
571
@@ -628,7 +624,6 @@
624 border-bottom: none;
625 }
626
631 -
627 .light-mode .message-user {
628 color: #4e4e4e;
629 }
@@ -647,14 +642,17 @@
642 max-width: 25em;
643 max-height: 25em;
644 border-radius: var(--border-radius);
650 - background: repeating-linear-gradient(45deg, var(--color-panel) 0 10px, var(--color-chat-background) 10px 20px);
645 + background: repeating-linear-gradient(
646 + 45deg,
647 + var(--color-panel) 0 10px,
648 + var(--color-chat-background) 10px 20px
649 + );
650 }
651
653 -.message-agent-response .msg-content .message-markdown-image-wrap img:hover{
652 +.message-agent-response .msg-content .message-markdown-image-wrap img:hover {
653 transform: translateY(-2px);
654 }
655
657 -
656 .msg-content h1 {
657 font-size: 1.25em;
658 font-weight: 800;
@@ -767,7 +765,7 @@
765 width: 100%;
766 justify-content: end;
767 margin-top: 4em;
770 - margin-bottom:4em;
768 + margin-bottom: 4em;
769 }
770
771 .message-container {
webui/index.css
-39
@@ -1099,45 +1099,6 @@ input:checked + .slider:before {
1099 }
1100 }
1101
1102 -@media (max-width: 768px) {
1103 - /* .copy-button {
1104 - display: none !important;
1105 - } */
1106 -
1107 - /* .msg-content span,
1108 - .kvps-val,
1109 - .message-text span {
1110 - cursor: pointer;
1111 - position: relative;
1112 - } */
1113 -
1114 - /* .msg-thoughts span::after,
1115 - .msg-content span::after,
1116 - .kvps-val::after,
1117 - .message-text::after {
1118 - content: "Copied!";
1119 - position: absolute;
1120 - opacity: 0;
1121 - font-family: "Rubik", Arial, Helvetica, sans-serif;
1122 - font-size: 0.7rem;
1123 - padding: 6px var(--spacing-sm);
1124 - -webkit-transition: opacity var(--transition-speed) ease-in-out;
1125 - transition: opacity var(--transition-speed) ease-in-out;
1126 - right: 0px;
1127 - top: 0px;
1128 - background-color: var(--color-background);
1129 - border: none;
1130 - border-radius: 5px;
1131 - color: inherit;
1132 - } */
1133 -
1134 - /* .msg-thoughts span.copied::after,
1135 - .msg-content span.copied::after,
1136 - .kvps-val.copied::after,
1137 - .message-text.copied::after {
1138 - opacity: 1;
1139 - } */
1140 -}
1102
1103 @media (max-height: 600px) {
1104 /* consistent font sizing */
webui/index.js
+5 -17
@@ -66,9 +66,9 @@ export async function sendMessage() {
66 : "";
67
68 // Render user message with attachments
69 - setMessage(messageId, "user", heading, message, {
69 + setMessage({ id: messageId, type: "user", heading, content: message, kvps: {
70 // attachments: attachmentsWithUrls, // skip here, let the backend properly log them
71 - });
71 + }});
72
73 // sleep one frame to render the message before upload starts - better UX
74 sleep(0);
@@ -200,8 +200,8 @@ async function updateUserTime() {
200 updateUserTime();
201 setInterval(updateUserTime, 1000);
202
203 -function setMessage(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, /* tokensIn = 0, tokensOut = 0, */ agentNumber = 0) {
204 - const result = msgs.setMessage(id, type, heading, content, kvps, timestamp, durationMs, /* tokensIn, tokensOut, */ agentNumber);
203 +function setMessage(...params) {
204 + const result = msgs.setMessage(...params);
205 const chatHistoryEl = document.getElementById("chat-history");
206 if (preferencesStore.autoScroll && chatHistoryEl) {
207 chatHistoryEl.scrollTop = chatHistoryEl.scrollHeight;
@@ -317,19 +317,7 @@ export async function poll() {
317 if (lastLogVersion != response.log_version) {
318 updated = true;
319 for (const log of response.logs) {
320 - const messageId = log.id || log.no; // Use log.id if available
321 - setMessage(
322 - messageId,
323 - log.type,
324 - log.heading,
325 - log.content,
326 - log.kvps,
327 - log.timestamp,
328 - log.duration_ms,
329 - // log.tokens_in,
330 - // log.tokens_out,
331 - log.agent_number || 0 // Agent number for identifying main/subordinate agents
332 - );
320 + setMessage(log);
321 }
322 afterMessagesUpdate(response.logs);
323 applyModeSteps(preferencesStore.detailMode, preferencesStore.showUtils);
webui/js/messages.js
+153 -140
@@ -73,7 +73,7 @@ const TYPE_STATUS_CLASSES = {
73 done: "status-end"
74 };
75
76 -const chatHistory = document.getElementById("chat-history");
76 +let chatHistory = null;
77
78 // handlers for log message rendering
79 export function getMessageHandler(type) {
@@ -124,7 +124,7 @@ function setActiveProcessGroup(group) {
124 if (group.classList.contains("active")) return;
125
126 // Clear active + shiny from all other groups
127 - document.querySelectorAll(".process-group.active").forEach(g => {
127 + getChatHistoryEl().querySelectorAll(".process-group.active").forEach(g => {
128 if (g !== group) {
129 g.classList.remove("active");
130 g.querySelectorAll(".step-title.shiny-text").forEach(el => el.classList.remove("shiny-text"));
@@ -137,13 +137,14 @@ function setActiveProcessGroup(group) {
137
138 export function clearActiveStepShine() {
139 // Clear all shiny step titles in process steps
140 - document.querySelectorAll(".process-step .step-title.shiny-text").forEach((el) => {
140 + getChatHistoryEl().querySelectorAll(".process-step .step-title.shiny-text").forEach((el) => {
141 el.classList.remove("shiny-text");
142 });
143 }
144
145 function getChatHistoryEl() {
146 - return chatHistory || document.getElementById("chat-history");
146 + if(!chatHistory) chatHistory = document.getElementById("chat-history");
147 + return chatHistory;
148 }
149
150 function getLastMessageContainer() {
@@ -224,9 +225,11 @@ function updateBadgeText(badge, newCode) {
225
226
227 // entrypoint called from poll/WS communication, this is how all messages are rendered and updated
227 -export function setMessage(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
228 +// input is raw log format
229 +export function setMessage({ no, id, type, heading, content, kvps, timestamp, agentno, ...additional }) {
230 const handler = getMessageHandler(type);
229 - return handler(id, type, heading, content, kvps, timestamp, durationMs, agentNumber);
231 + // prefer log ID if set to match user message created on frontend with backend updates
232 + return handler({ id: id || no, type, heading, content, kvps, timestamp, agentno, ...additional });
233 }
234
235 function getOrCreateMessageContainer(id, position, containerClasses = [], forceNewGroup = false) {
@@ -287,8 +290,7 @@ function buildDetailPayload(stepData) {
290 content: stepData.content,
291 kvps: stepData.kvps,
292 timestamp: stepData.timestamp,
290 - durationMs: stepData.durationMs,
291 - agentNumber: stepData.agentNumber,
293 + agentno: stepData.agentno,
294 toolName: stepData.toolName,
295 statusCode: stepData.statusCode,
296 statusClass: stepData.statusClass
@@ -310,17 +312,24 @@ function buildStepCopyContent(stepData) {
312 return parts.join("\n\n");
313 }
314
313 -function drawProcessStep(id, title, statusClass, statusCode, kvps = null, detailHandler = null, copyContent = null, speakContent = null, options = {}) {
314 - const {
315 - type = "agent",
316 - heading = null,
317 - content = null,
318 - timestamp = null,
319 - durationMs = null,
320 - agentNumber = 0,
321 - toolName = null,
322 - detailPayload = null
323 - } = options;
315 +function drawProcessStep({
316 + id,
317 + title,
318 + statusClass,
319 + statusCode,
320 + kvps = null,
321 + detailHandler = null,
322 + copyContent = null,
323 + speakContent = null,
324 + type = "agent",
325 + heading = null,
326 + content = null,
327 + timestamp = null,
328 + agentno = 0,
329 + toolName = null,
330 + detailPayload = null,
331 + ...additional
332 +}) {
333
334 const group = getOrCreateProcessGroup(id);
335 const stepId = `process-step-${id}`;
@@ -332,8 +341,7 @@ function drawProcessStep(id, title, statusClass, statusCode, kvps = null, detail
341 content,
342 kvps,
343 timestamp,
335 - durationMs,
336 - agentNumber,
344 + agentno,
345 toolName,
346 statusCode,
347 statusClass
@@ -344,12 +352,10 @@ function drawProcessStep(id, title, statusClass, statusCode, kvps = null, detail
352 const copyText = copyContent ?? buildStepCopyContent(stepData);
353 const speakText = speakContent ?? copyText;
354
347 - if (step) {
355 + if (step)
356 updateProcessStep(step, stepData, detailData, copyText, speakText, detailHandler);
349 - return step;
350 - }
351 -
352 - step = addProcessStep(group, stepData, detailData, copyText, speakText, detailHandler);
357 + else
358 + step = addProcessStep(group, stepData, detailData, copyText, speakText, detailHandler);
359 return step;
360 }
361
@@ -537,7 +543,7 @@ export function addBlankTargetsToLinks(str) {
543 return doc.body.innerHTML;
544 }
545
540 -export function drawMessageDefault(id, type, heading, content, kvps = null) {
546 +export function drawMessageDefault({ id, heading, content, kvps = null, ...additional }) {
547 return drawStandaloneMessage(id, heading, content, {
548 position: "left",
549 containerClasses: ["ai-container"],
@@ -548,37 +554,44 @@ export function drawMessageDefault(id, type, heading, content, kvps = null) {
554 });
555 }
556
551 -export function drawMessageAgent(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
557 +export function drawMessageAgent({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
558 const title = getStepTitle(heading, kvps, type);
559 const statusCode = getStatusCode(type);
560 const statusClass = getStatusClass(type);
561 const toolName = kvps?.tool_name || null;
562 + let displayKvps = { "icon://lightbulb": kvps?.thoughts, "icon://step": heading, "icon school":"dummy" }
563
557 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
564 + return drawProcessStep({
565 + id,
566 + title,
567 + statusClass,
568 + statusCode,
569 + kvps: displayKvps,
570 type,
571 heading,
572 content,
561 - kvps,
573 timestamp,
563 - durationMs,
564 - agentNumber,
574 + agentno,
575 toolName
576 });
577 }
578
569 -export function drawMessageResponse(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
570 - if (agentNumber && agentNumber > 0) {
579 +export function drawMessageResponse({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
580 + if (agentno && agentno > 0) {
581 const title = getStepTitle(heading, kvps, type);
582 const statusCode = getStatusCode(type);
583 const statusClass = getStatusClass(type);
574 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
584 + return drawProcessStep({
585 + id,
586 + title,
587 + statusClass,
588 + statusCode,
589 + kvps,
590 type,
591 heading,
592 content,
578 - kvps,
593 timestamp,
580 - durationMs,
581 - agentNumber
594 + agentno
595 });
596 }
597
@@ -599,7 +612,7 @@ export function drawMessageResponse(id, type, heading, content, kvps = null, tim
612 }
613
614
602 -export function drawMessageUser(id, type, heading, content, kvps = null) {
615 +export function drawMessageUser({ id, heading, content, kvps = null, ...additional }) {
616 const messageContainer = getOrCreateMessageContainer(id, "right", ["user-container"], true);
617
618 // Find existing message div or create new one
@@ -707,92 +720,107 @@ export function drawMessageUser(id, type, heading, content, kvps = null) {
720 addActionButtonsToElement(messageDiv, { copyContent: content || "", speakContent: content || "" });
721 }
722
710 -export function drawMessageTool(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
723 +export function drawMessageTool({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
724 const toolName = kvps?.tool_name || null;
725 const title = getStepTitle(heading, kvps, type);
726 const statusCode = getStatusCode(type, toolName);
727 const statusClass = getStatusClass(type, toolName);
728
716 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
729 + return drawProcessStep({
730 + id,
731 + title,
732 + statusClass,
733 + statusCode,
734 + kvps,
735 type,
736 heading,
737 content,
720 - kvps,
738 timestamp,
722 - durationMs,
723 - agentNumber,
739 + agentno,
740 toolName
741 });
742 }
743
728 -export function drawMessageCodeExe(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
744 +export function drawMessageCodeExe({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
745 const title = getStepTitle(heading, kvps, type);
746 const statusCode = getStatusCode(type);
747 const statusClass = getStatusClass(type);
748
733 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
749 + return drawProcessStep({
750 + id,
751 + title,
752 + statusClass,
753 + statusCode,
754 + kvps,
755 type,
756 heading,
757 content,
737 - kvps,
758 timestamp,
739 - durationMs,
740 - agentNumber
759 + agentno
760 });
761 }
762
744 -export function drawMessageBrowser(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
763 +export function drawMessageBrowser({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
764 const title = getStepTitle(heading, kvps, type);
765 const statusCode = getStatusCode(type);
766 const statusClass = getStatusClass(type);
767
749 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
768 + return drawProcessStep({
769 + id,
770 + title,
771 + statusClass,
772 + statusCode,
773 + kvps,
774 type,
775 heading,
776 content,
753 - kvps,
777 timestamp,
755 - durationMs,
756 - agentNumber
778 + agentno
779 });
780 }
781
760 -export function drawMessageMcp(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
782 +export function drawMessageMcp({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
783 const toolName = kvps?.tool_name || null;
784 const title = getStepTitle(heading, kvps, type);
785 const statusCode = getStatusCode(type, toolName);
786 const statusClass = getStatusClass(type, toolName);
787
766 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
788 + return drawProcessStep({
789 + id,
790 + title,
791 + statusClass,
792 + statusCode,
793 + kvps,
794 type,
795 heading,
796 content,
770 - kvps,
797 timestamp,
772 - durationMs,
773 - agentNumber,
798 + agentno,
799 toolName
800 });
801 }
802
778 -export function drawMessageSubagent(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
803 +export function drawMessageSubagent({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
804 const title = getStepTitle(heading, kvps, type);
805 const statusCode = getStatusCode(type);
806 const statusClass = getStatusClass(type);
807
783 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
808 + return drawProcessStep({
809 + id,
810 + title,
811 + statusClass,
812 + statusCode,
813 + kvps,
814 type,
815 heading,
816 content,
787 - kvps,
817 timestamp,
789 - durationMs,
790 - agentNumber
818 + agentno
819 });
820 }
821
822
795 -export function drawMessageInfo(id, type, heading, content, kvps = null) {
823 +export function drawMessageInfo({ id, heading, content, kvps = null, ...additional }) {
824 return drawStandaloneMessage(id, heading, content, {
825 position: "mid",
826 containerClasses: ["ai-container", "center-container"],
@@ -801,55 +829,64 @@ export function drawMessageInfo(id, type, heading, content, kvps = null) {
829 });
830 }
831
804 -export function drawMessageUtil(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
832 +export function drawMessageUtil({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
833 const title = getStepTitle(heading, kvps, type);
834 const statusCode = getStatusCode(type);
835 const statusClass = getStatusClass(type);
836
809 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
837 + return drawProcessStep({
838 + id,
839 + title,
840 + statusClass,
841 + statusCode,
842 + kvps,
843 type,
844 heading,
845 content,
813 - kvps,
846 timestamp,
815 - durationMs,
816 - agentNumber
847 + agentno
848 });
849 }
850
820 -export function drawMessageHint(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
851 +export function drawMessageHint({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
852 const title = getStepTitle(heading, kvps, type);
853 const statusCode = getStatusCode(type);
854 const statusClass = getStatusClass(type);
855
825 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
856 + return drawProcessStep({
857 + id,
858 + title,
859 + statusClass,
860 + statusCode,
861 + kvps,
862 type,
863 heading,
864 content,
829 - kvps,
865 timestamp,
831 - durationMs,
832 - agentNumber
866 + agentno
867 });
868 }
869
836 -export function drawMessageProgress(id, type, heading, content, kvps = null, timestamp = null, durationMs = null, agentNumber = 0) {
870 +export function drawMessageProgress({ id, type, heading, content, kvps = null, timestamp = null, agentno = 0, ...additional }) {
871 const title = getStepTitle(heading, kvps, type);
872 const statusCode = getStatusCode(type);
873 const statusClass = getStatusClass(type);
874
841 - return drawProcessStep(id, title, statusClass, statusCode, kvps, null, null, null, {
875 + return drawProcessStep({
876 + id,
877 + title,
878 + statusClass,
879 + statusCode,
880 + kvps,
881 type,
882 heading,
883 content,
845 - kvps,
884 timestamp,
847 - durationMs,
848 - agentNumber
885 + agentno
886 });
887 }
888
852 -export function drawMessageWarning(id, type, heading, content, kvps = null) {
889 +export function drawMessageWarning({ id, heading, content, kvps = null, ...additional }) {
890 return drawStandaloneMessage(id, heading, content, {
891 position: "mid",
892 containerClasses: ["ai-container", "center-container"],
@@ -858,7 +895,7 @@ export function drawMessageWarning(id, type, heading, content, kvps = null) {
895 });
896 }
897
861 -export function drawMessageError(id, type, heading, content, kvps = null) {
898 +export function drawMessageError({ id, heading, content, kvps = null, ...additional }) {
899 const messageContainer = getOrCreateMessageContainer(id, "mid", ["ai-container", "center-container"]);
900
901 // Create or get the message div
@@ -1010,9 +1047,6 @@ function drawKvpsIncremental(container, kvps, latex) {
1047
1048 // Update row classes
1049 row.className = "kvps-row";
1013 - if (key === "thoughts") {
1014 - row.classList.add("msg-thoughts");
1015 - }
1050
1051 // Handle key cell
1052 let th = row.querySelector(".kvps-key");
@@ -1083,11 +1117,12 @@ function drawKvpsIncremental(container, kvps, latex) {
1117 imageViewerStore.open(imgElement.src, { refreshInterval: 1000 });
1118 });
1119 } else {
1086 - const pre = document.createElement("pre");
1120 + // const pre = document.createElement("pre");
1121 const span = document.createElement("span");
1122 span.innerHTML = convertHTML(value);
1089 - pre.appendChild(span);
1090 - tdiv.appendChild(pre);
1123 + // pre.appendChild(span);
1124 + // tdiv.appendChild(pre);
1125 + tdiv.appendChild(span)
1126
1127 // Add action buttons to the row
1128 // const row = tdiv.closest(".kvps-row");
@@ -1430,13 +1465,13 @@ function addStepCollapseInteractionHandlers(stepElement) {
1465 /**
1466 * Find parent delegation step for nested agents (DOM-first, reverse scan).
1467 */
1433 -function findParentDelegationStep(group, agentNumber) {
1434 - if (!group || agentNumber <= 0) return null;
1468 +function findParentDelegationStep(group, agentno) {
1469 + if (!group || agentno <= 0) return null;
1470 const steps = group.querySelectorAll(".process-step");
1471 for (let i = steps.length - 1; i >= 0; i -= 1) {
1472 const step = steps[i];
1473 const stepAgent = Number(step.getAttribute("data-agent-number"));
1439 - if (stepAgent === agentNumber - 1 && step.getAttribute("data-tool-name") === "call_subordinate") {
1474 + if (stepAgent === agentno - 1 && step.getAttribute("data-tool-name") === "call_subordinate") {
1475 return step;
1476 }
1477 }
@@ -1455,8 +1490,7 @@ function addProcessStep(group, stepData, detailPayload, copyContent, speakConten
1490 content,
1491 kvps,
1492 timestamp,
1458 - durationMs,
1459 - agentNumber,
1493 + agentno,
1494 toolName,
1495 statusCode,
1496 statusClass
@@ -1471,7 +1505,7 @@ function addProcessStep(group, stepData, detailPayload, copyContent, speakConten
1505 step.classList.add("process-step");
1506 step.setAttribute("data-type", type);
1507 step.setAttribute("data-step-id", id);
1474 - step.setAttribute("data-agent-number", agentNumber);
1508 + step.setAttribute("data-agent-number", agentno);
1509
1510 if (toolName) {
1511 step.setAttribute("data-tool-name", toolName);
@@ -1495,19 +1529,14 @@ function addProcessStep(group, stepData, detailPayload, copyContent, speakConten
1529 }
1530 }
1531
1498 - // Store duration from backend (used for final duration calculation)
1499 - if (durationMs != null) {
1500 - step.setAttribute("data-duration-ms", durationMs);
1501 - }
1502 -
1503 - // Add message-util class for utility/info types (controlled by showUtils preference)
1504 - if (type === "util" || type === "info" || type === "hint") {
1505 - step.classList.add("message-util");
1506 - // Apply current preference state
1507 - if (preferencesStore.showUtils) {
1508 - step.classList.add("show-util");
1509 - }
1510 - }
1532 + // // Add message-util class for utility/info types (controlled by showUtils preference)
1533 + // if (type === "util" || type === "info" || type === "hint") {
1534 + // step.classList.add("message-util");
1535 + // // Apply current preference state
1536 + // if (preferencesStore.showUtils) {
1537 + // step.classList.add("show-util");
1538 + // }
1539 + // }
1540
1541 // Determine if this new step should be expanded
1542 const detailMode = preferencesStore.detailMode;
@@ -1608,7 +1637,7 @@ function addProcessStep(group, stepData, detailPayload, copyContent, speakConten
1637 let appendTarget = stepsContainer;
1638
1639 // Check if this step belongs to a subordinate agent
1611 - const parentStep = findParentDelegationStep(group, agentNumber);
1640 + const parentStep = findParentDelegationStep(group, agentno);
1641 if (parentStep) {
1642 appendTarget = getNestedContainer(parentStep);
1643 step.classList.add("nested-step");
@@ -1655,8 +1684,7 @@ function updateProcessStep(stepElement, stepData, detailPayload, copyContent, sp
1684 content,
1685 kvps,
1686 timestamp,
1658 - durationMs,
1659 - agentNumber,
1687 + agentno,
1688 toolName,
1689 statusCode,
1690 statusClass
@@ -1672,12 +1700,8 @@ function updateProcessStep(stepElement, stepData, detailPayload, copyContent, sp
1700 stepElement.setAttribute("data-timestamp", timestamp);
1701 }
1702
1675 - if (durationMs != null) {
1676 - stepElement.setAttribute("data-duration-ms", durationMs);
1677 - }
1678 -
1679 - if (agentNumber !== undefined) {
1680 - stepElement.setAttribute("data-agent-number", agentNumber);
1703 + if (agentno !== undefined) {
1704 + stepElement.setAttribute("data-agent-number", agentno);
1705 }
1706
1707 const toolNameToUse = resolveToolName(type, kvps, stepElement) || toolName;
@@ -1837,6 +1861,8 @@ function cleanStepTitle(text, maxLength) {
1861 function renderStepDetailContent(container, content, kvps, type = null) {
1862 container.innerHTML = "";
1863
1864 + drawKvpsIncremental(container, kvps)
1865 +
1866 // Special handling for response type - show content as markdown (for subordinate responses)
1867 if (type === "response" && content && content.trim()) {
1868 const responseDiv = document.createElement("div");
@@ -2121,25 +2147,14 @@ function updateProcessGroupHeader(group) {
2147 // Update duration metric
2148 const durationMetricEl = metricsEl?.querySelector(".metric-duration .metric-value");
2149 if (durationMetricEl && steps.length > 0) {
2124 - // Calculate accumulated duration from backend data
2125 - let accumulatedMs = 0;
2126 - steps.forEach(step => {
2127 - accumulatedMs += parseInt(step.getAttribute("data-duration-ms") || "0", 10);
2128 - });
2129 -
2130 - // Check if last step is still in progress (no duration_ms set yet)
2150 + const firstTimestampMs = parseInt(steps[0]?.getAttribute("data-timestamp") || "0", 10);
2151 +
2152 const lastStep = steps[steps.length - 1];
2132 - const lastStepDuration = lastStep.getAttribute("data-duration-ms");
2133 - const lastStepTimestamp = lastStep.getAttribute("data-timestamp");
2134 -
2135 - if (lastStepDuration == null && lastStepTimestamp) {
2136 - // Last step is in progress - add live elapsed time for this step only
2137 - const lastStepStartMs = parseFloat(lastStepTimestamp) * 1000;
2138 - const liveElapsedMs = Math.max(0, Date.now() - lastStepStartMs);
2139 - accumulatedMs += liveElapsedMs;
2140 - }
2141 -
2142 - durationMetricEl.textContent = formatDuration(accumulatedMs);
2153 + const lastTimestampMs = parseInt(lastStep.getAttribute("data-timestamp") || "0", 10);
2154 +
2155 + const totalDurationMs = Math.max(0, lastTimestampMs - firstTimestampMs);
2156 +
2157 + durationMetricEl.textContent = formatDuration(totalDurationMs);
2158 }
2159
2160 if (steps.length > 0) {
@@ -2226,13 +2241,11 @@ function markProcessGroupComplete(group, responseTitle) {
2241 });
2242 }
2243
2229 - // Calculate final duration from backend data (sum of all step durations)
2244 + // Calculate final duration from backend data (difference between first and last timestamps)
2245 const steps = group.querySelectorAll(".process-step");
2231 - let totalDurationMs = 0;
2232 - steps.forEach(step => {
2233 - const durationMs = parseInt(step.getAttribute("data-duration-ms") || "0", 10);
2234 - totalDurationMs += durationMs;
2235 - });
2246 + const firstTimestampMs = parseInt(steps[0]?.getAttribute("data-timestamp") || "0", 10);
2247 + const lastTimestampMs = parseInt(steps[steps.length - 1]?.getAttribute("data-timestamp") || "0", 10);
2248 + const totalDurationMs = Math.max(0, lastTimestampMs - firstTimestampMs);
2249
2250 // Update duration metric with final value from backend
2251 const metricsEl = group.querySelector(".group-metrics");