|
176 | 176 | .term .termfoot .skip { background: var(--vscode-button-secondaryBackground, rgba(127,127,127,.25)); color: var(--vscode-button-secondaryForeground, var(--vscode-foreground)); } |
177 | 177 | .term .verdict { font-size: 12px; color: var(--muted); } |
178 | 178 | .term.ran .verdict { color: var(--vscode-terminal-ansiBrightGreen, var(--vscode-terminal-ansiGreen, #7ee787)); } |
| 179 | + /* live terminal output (streamed stdout/stderr) — flows under the prompt like a real terminal, no box */ |
| 180 | + .termrun .termout { margin: 4px 0 0; padding: 0; border: none; border-radius: 0; background: none; min-height: 0; |
| 181 | + max-height: 320px; overflow-y: auto; white-space: pre-wrap; word-break: break-word; |
| 182 | + font-family: var(--vscode-editor-font-family, ui-monospace, monospace); font-size: 12.5px; line-height: 1.5; |
| 183 | + color: var(--vscode-terminal-foreground, var(--vscode-foreground)); } |
| 184 | + .termrun .termout:empty { display: none; margin: 0; } |
| 185 | + .termrun .termerr { color: var(--vscode-terminal-ansiRed, var(--vscode-errorForeground, #f14c4c)); } |
| 186 | + .termrun .termstatus { margin-top: 9px; font-size: 11.5px; color: var(--muted); display: flex; align-items: center; gap: 6px; } |
| 187 | + .termrun .termstatus.ok { color: var(--vscode-terminal-ansiBrightGreen, var(--vscode-terminal-ansiGreen, #7ee787)); } |
| 188 | + .termrun .termstatus.bad { color: var(--vscode-errorForeground, #f14c4c); } |
| 189 | + .termrun .termstatus .ci { width: 1em; height: 1em; } |
| 190 | + .termrun .termstop { margin-left: auto; cursor: pointer; border: 1px solid var(--border); border-radius: 5px; padding: 2px 9px; font-size: 11px; display: inline-flex; align-items: center; gap: 4px; background: var(--vscode-button-secondaryBackground, rgba(127,127,127,.22)); color: var(--vscode-foreground); } |
| 191 | + .termrun .termstop:hover { background: var(--vscode-errorForeground, #f14c4c); color: #fff; border-color: transparent; } |
| 192 | + .termrun .termstop[disabled] { opacity: .6; cursor: default; } |
| 193 | + .termrun .termstop .ci { width: 1em; height: 1em; } |
179 | 194 |
|
180 | 195 | /* ---- ask_user: clickable clarifying questions ---- */ |
181 | 196 | .questions { border: 1px solid var(--border); border-radius: 10px; margin: 6px 2px; padding: 10px 11px 11px; background: var(--field-bg); } |
|
577 | 592 | log.addEventListener('keydown', function(e){ if (e.key === 'Enter' || e.key === ' '){ const c = e.target.closest && e.target.closest('.filechip'); if (c){ e.preventDefault(); openChip(c); } } }); |
578 | 593 | const reviewBar = document.getElementById('reviewBar'); |
579 | 594 | const editCards = {}; // id -> applied-edit card element |
| 595 | + const termCards = {}; // id -> live terminal-output card element |
580 | 596 | reviewBar.querySelector('.keepall').onclick = () => vscode.postMessage({ type: 'reviewKeepAll' }); |
581 | 597 | reviewBar.querySelector('.undoall').onclick = () => vscode.postMessage({ type: 'reviewUndoAll' }); |
582 | 598 | const input = document.getElementById('input'); |
|
938 | 954 | vscode.postMessage({ type: 'approvalResponse', id: m.id, approved }); |
939 | 955 | card.classList.add(approved ? 'ran' : 'skipped'); |
940 | 956 | const caret = card.querySelector('.tcaret'); if (caret) { caret.remove(); } |
941 | | - card.querySelector('.termfoot').innerHTML = '<span class="verdict">' + (approved ? codicon('check') + ' Running in terminal…' : codicon('circle-slash') + ' Skipped') + '</span>'; |
| 957 | + card.querySelector('.termfoot').innerHTML = '<span class="verdict">' + (approved ? codicon('check') + ' Approved' : codicon('circle-slash') + ' Skipped') + '</span>'; |
942 | 958 | }; |
943 | 959 | card.querySelector('.approve').onclick = () => done(true); |
944 | 960 | card.querySelector('.skip').onclick = () => done(false); |
945 | 961 | } |
946 | 962 |
|
| 963 | + // Live terminal: an approved command streams its real stdout/stderr here as it runs. |
| 964 | + function addTermRun(m){ |
| 965 | + clearEmpty(); clearStatus(); agentBubble = null; |
| 966 | + const card = document.createElement('div'); card.className = 'approval term termrun'; |
| 967 | + card.innerHTML = |
| 968 | + '<div class="termbar"><span class="tdots"><i class="r"></i><i class="y"></i><i class="g"></i></span>' |
| 969 | + + '<span class="tbartitle">zsh — ' + esc(m.cwd || 'workspace') + '</span></div>' |
| 970 | + + '<div class="termbody">' |
| 971 | + + '<div class="termline"><span class="tprompt">➜</span> <span class="tcwd">' + esc(m.cwd || '') + '</span> ' |
| 972 | + + '<span class="tcmd">' + esc(m.command || '') + '</span></div>' |
| 973 | + + '<pre class="termout"></pre>' |
| 974 | + + '<div class="termstatus running"><span class="tcaret"></span> running… ' |
| 975 | + + '<button class="termstop" title="Stop this command">' + codicon('stop-circle') + ' Stop</button></div>' |
| 976 | + + '</div>'; |
| 977 | + log.appendChild(card); scrollIfStuck(); |
| 978 | + termCards[m.id] = card; |
| 979 | + const sb = card.querySelector('.termstop'); if (sb) { sb.onclick = () => { sb.disabled = true; sb.textContent = 'stopping…'; vscode.postMessage({ type: 'stopCommand', id: m.id }); }; } |
| 980 | + } |
| 981 | + function termOutputAppend(m){ |
| 982 | + const card = termCards[m.id]; if (!card) { return; } |
| 983 | + const out = card.querySelector('.termout'); if (!out) { return; } |
| 984 | + out.insertAdjacentHTML('beforeend', m.stream === 'stderr' ? '<span class="termerr">' + esc(m.chunk) + '</span>' : esc(m.chunk)); |
| 985 | + out.scrollTop = out.scrollHeight; // keep the latest line visible inside the card |
| 986 | + scrollIfStuck(); |
| 987 | + } |
| 988 | + function termExitFinish(m){ |
| 989 | + const card = termCards[m.id]; if (!card) { return; } |
| 990 | + const st = card.querySelector('.termstatus'); if (!st) { return; } |
| 991 | + const dur = (typeof m.ms === 'number') ? (m.ms >= 1000 ? (m.ms / 1000).toFixed(1) + 's' : m.ms + 'ms') : ''; |
| 992 | + if (m.how === 'stopped'){ st.innerHTML = codicon('circle-slash') + ' stopped' + (dur ? ' · ' + dur : ''); st.className = 'termstatus bad'; } |
| 993 | + else if (m.how === 'timeout'){ st.innerHTML = codicon('warning') + ' timed out' + (dur ? ' · ' + dur : ''); st.className = 'termstatus bad'; } |
| 994 | + else { const ok = m.code === 0; st.innerHTML = (ok ? codicon('check') : codicon('circle-slash')) + ' exit ' + m.code + (dur ? ' · ' + dur : ''); st.className = 'termstatus ' + (ok ? 'ok' : 'bad'); } |
| 995 | + delete termCards[m.id]; |
| 996 | + scrollIfStuck(); |
| 997 | + } |
| 998 | + |
947 | 999 | // Blocking clarifying questions (ask_user) — clickable numbered options the agent waits on. |
948 | 1000 | function addQuestions(m){ |
949 | 1001 | clearEmpty(); clearStatus(); agentBubble = null; |
|
1253 | 1305 | else if (m.type === 'agentTool'){ finishAgentBubble(); addAgentLine(m.icon, m.text); } |
1254 | 1306 | else if (m.type === 'agentApproval'){ addApproval(m); } |
1255 | 1307 | else if (m.type === 'agentQuestions'){ addQuestions(m); } |
| 1308 | + else if (m.type === 'termRun'){ addTermRun(m); } |
| 1309 | + else if (m.type === 'termOutput'){ termOutputAppend(m); } |
| 1310 | + else if (m.type === 'termExit'){ termExitFinish(m); } |
1256 | 1311 | else if (m.type === 'editApplied'){ addEditCard(m); } |
1257 | 1312 | else if (m.type === 'editResolved'){ resolveEditCard(m); } |
1258 | 1313 | else if (m.type === 'reviewState'){ updateReviewBar(m.count, m.add, m.del); } |
|
1268 | 1323 | else if (m.type === 'reset'){ |
1269 | 1324 | log.innerHTML = '<div id="empty"><div class="big">New chat</div>Ask about your code, or describe what to build.</div>'; |
1270 | 1325 | selLabel = null; ctxFiles = []; renderChips(); |
1271 | | - for (const k in editCards) { delete editCards[k]; } updateReviewBar(0); renderPlan([]); renderContext({ input: 0 }); |
| 1326 | + for (const k in editCards) { delete editCards[k]; } for (const k in termCards) { delete termCards[k]; } updateReviewBar(0); renderPlan([]); renderContext({ input: 0 }); |
1272 | 1327 | pending = ''; shown = ''; doneSignaled = false; flushAll = false; current = null; setStreaming(false); |
1273 | 1328 | forceStick(); |
1274 | 1329 | } |
|
0 commit comments