code-exec ux fixes

code-exec ux fixes fix clickable paths in code-exec terminal

3clyp50 committed Jan 19, 2026 at 03:38 UTC d5889fa42005674df02d863dae499b92c9799acd
2 files changed +66 -23
webui/components/messages/process-group/process-group.css
+34 -17
@@ -446,7 +446,6 @@
446 transition: grid-template-rows 0.2s ease-out, opacity 0.15s ease-out, margin-top 0.2s ease-out;
447 overflow: hidden;
448 -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */
449 - scroll-behavior: smooth;
449 scrollbar-width: none; /* Firefox */
450 -ms-overflow-style: none; /* IE/Edge */
451 overscroll-behavior: contain; /* avoid scroll chaining */
@@ -488,7 +487,7 @@
487 }
488
489 .process-step.step-expanded > .process-step-detail > .process-step-detail-content {
491 - max-height: 300px;
490 + max-height: 350px;
491 overflow-y: auto;
492 }
493
@@ -500,10 +499,8 @@
499 background-color: transparent;
500 font-size: 0.7rem;
501 line-height: 1.5;
503 - max-height: 300px;
502 overflow-y: auto;
503 -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */
506 - scroll-behavior: smooth;
504 scrollbar-width: none; /* Firefox */
505 -ms-overflow-style: none; /* IE/Edge */
506 overscroll-behavior: contain; /* avoid scroll chaining */
@@ -656,29 +653,49 @@
653 margin: var(--spacing-xs) 0 0 0;
654 padding: var(--spacing-xs);
655 background: rgba(0, 0, 0, 0.8);
659 - border-radius: 4px;
656 + border-radius: 8px;
657 color: #c9d1d9;
661 - white-space: pre-wrap;
662 - word-break: break-word;
663 - max-height: 200px;
664 - overflow-y: auto;
665 - -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */
666 - scroll-behavior: smooth;
667 - scrollbar-width: none; /* Firefox */
668 - -ms-overflow-style: none; /* IE/Edge */
669 - overscroll-behavior: contain; /* avoid scroll chaining */
658 + white-space: pre;
659 + max-width: fit-content;
660 + max-height: 300px;
661 + overflow: auto;
662 + -webkit-overflow-scrolling: touch;
663 + scrollbar-width: thin;
664 + scrollbar-color: rgba(255,255,255,0.2) transparent;
665 + overscroll-behavior: contain;
666 }
667
668 .process-step-detail-content .terminal-output::-webkit-scrollbar {
673 - display: none;
674 - width: 0;
675 - height: 0;
669 + width: 4px;
670 + height: 4px;
671 +}
672 +
673 +.process-step-detail-content .terminal-output::-webkit-scrollbar-track {
674 + background: transparent;
675 +}
676 +
677 +.process-step-detail-content .terminal-output::-webkit-scrollbar-thumb {
678 + background: rgba(255, 255, 255, 0.2);
679 + border-radius: 2px;
680 +}
681 +
682 +.process-step-detail-content .terminal-output::-webkit-scrollbar-thumb:hover {
683 + background: rgba(255, 255, 255, 0.35);
684 }
685
686 /* Light mode terminal */
687 .light-mode .process-step-detail-content .terminal-output {
688 background: rgba(0, 0, 0, 0.05);
689 color: black;
690 + scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
691 +}
692 +
693 +.light-mode .process-step-detail-content .terminal-output::-webkit-scrollbar-thumb {
694 + background: rgba(0, 0, 0, 0.15);
695 +}
696 +
697 +.light-mode .process-step-detail-content .terminal-output::-webkit-scrollbar-thumb:hover {
698 + background: rgba(0, 0, 0, 0.3);
699 }
700
701 /* Light mode adjustments */
webui/js/messages.js
+32 -6
@@ -1351,6 +1351,11 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1351 // Explicitly add or remove the class based on state
1352 if (newState) {
1353 step.classList.add("step-expanded");
1354 + // Scroll terminal for newly expanded steps
1355 + requestAnimationFrame(() => {
1356 + const terminal = step.querySelector(".terminal-output");
1357 + if (terminal) terminal.scrollTop = terminal.scrollHeight;
1358 + });
1359 } else {
1360 step.classList.remove("step-expanded");
1361 }
@@ -1371,6 +1376,14 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1376 detail.appendChild(detailContent);
1377 step.appendChild(detail);
1378
1379 + // Scroll terminal for already expanded steps
1380 + if (isStepExpanded) {
1381 + requestAnimationFrame(() => {
1382 + const terminal = step.querySelector(".terminal-output");
1383 + if (terminal) terminal.scrollTop = terminal.scrollHeight;
1384 + });
1385 + }
1386 +
1387 // Track delegation steps for nesting
1388 if (toolNameToUse === "call_subordinate") {
1389 currentDelegationSteps[agentNumber] = step;
@@ -1467,9 +1480,17 @@ function updateProcessStep(stepElement, id, type, heading, content, kvps, durati
1480 * Get a concise title for a process step
1481 */
1482 function getStepTitle(heading, kvps, type) {
1483 + // code_exe: show command when finished
1484 + const showCommand = type === "code_exe" && kvps?.code &&
1485 + /done_all|code_execution_tool/.test(heading || "");
1486 + if (showCommand) {
1487 + const s = kvps.session ?? kvps.Session;
1488 + return `${s != null ? `[${s}] ` : ""}${kvps.runtime || "bash"}> ${kvps.code.trim()}`;
1489 + }
1490 +
1491 // Try to get a meaningful title from heading or kvps
1492 if (heading && heading.trim()) {
1472 - return cleanStepTitle(heading, 80);
1493 + return cleanStepTitle(heading, 100);
1494 }
1495
1496 // For warnings/errors without heading, use content preview as title
@@ -1485,13 +1506,13 @@ function getStepTitle(heading, kvps, type) {
1506 return `${kvps.tool_name}${headline ? ': ' + headline : ''}`;
1507 }
1508 if (kvps.headline) {
1488 - return cleanStepTitle(kvps.headline, 80);
1509 + return cleanStepTitle(kvps.headline, 100);
1510 }
1511 if (kvps.query) {
1491 - return truncateText(kvps.query, 80);
1512 + return truncateText(kvps.query, 100);
1513 }
1514 if (kvps.thoughts) {
1494 - return truncateText(String(kvps.thoughts), 80);
1515 + return truncateText(String(kvps.thoughts), 100);
1516 }
1517 }
1518
@@ -1569,12 +1590,17 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1590 const terminalDiv = document.createElement("div");
1591 terminalDiv.classList.add("step-terminal");
1592
1572 - // Show output if present
1593 + // Show output if present (no truncation - CSS handles max-height)
1594 if (output && output.trim()) {
1595 const outputPre = document.createElement("pre");
1596 outputPre.classList.add("terminal-output");
1576 - outputPre.textContent = truncateText(output, 1000);
1597 + // Escape HTML first, then convert paths to clickable links
1598 + let processedOutput = escapeHTML(output);
1599 + processedOutput = convertPathsToLinks(processedOutput);
1600 + outputPre.innerHTML = processedOutput;
1601 terminalDiv.appendChild(outputPre);
1602 + // Scroll terminal to bottom
1603 + outputPre.scrollTop = outputPre.scrollHeight;
1604 }
1605
1606 container.appendChild(terminalDiv);