|
293 | 293 | #ctxBar .ctxnew { margin-left: 5px; cursor: pointer; border: none; border-radius: 5px; padding: 3px 10px; font-size: 11px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); } |
294 | 294 | #ctxBar .ctxnew:hover { background: var(--vscode-button-hoverBackground); } |
295 | 295 | /* sticky active-plan bar (sits just above the review bar) — opaque so the log can't show behind it */ |
| 296 | + /* Background tasks tray — collapsed by default; lists commands started with background:true */ |
| 297 | + #bgTasksBar { flex: 0 0 auto; margin: 0 8px 6px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; |
| 298 | + background: var(--vscode-editorWidget-background, rgba(127,127,127,.08)); font-size: 12px; } |
| 299 | + .bgt-head { display: flex; align-items: center; gap: 7px; padding: 8px 11px; cursor: pointer; user-select: none; color: var(--vscode-foreground); } |
| 300 | + .bgt-head:hover { background: var(--vscode-toolbar-hoverBackground, rgba(127,127,127,.12)); } |
| 301 | + .bgt-livedot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--vscode-terminal-ansiBrightGreen, #7ee787); animation: bgtpulse 1.8s infinite; } |
| 302 | + @keyframes bgtpulse { 0%{ box-shadow: 0 0 0 0 rgba(126,231,135,.5); } 70%{ box-shadow: 0 0 0 5px rgba(126,231,135,0); } 100%{ box-shadow: 0 0 0 0 rgba(126,231,135,0); } } |
| 303 | + .bgt-caret { display: inline-flex; transition: transform .15s; color: var(--muted); } |
| 304 | + .bgt-caret.open { transform: rotate(90deg); } |
| 305 | + .bgt-caret svg { width: 11px; height: 11px; display: block; } |
| 306 | + .bgt-title { font-weight: 600; } |
| 307 | + .bgt-count { color: var(--muted); } |
| 308 | + .bgt-list { border-top: 1px solid var(--border); } |
| 309 | + .bgt-row { display: flex; align-items: center; gap: 9px; padding: 7px 11px; border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent); } |
| 310 | + .bgt-row:last-child { border-bottom: none; } |
| 311 | + .bgt-cmd { flex: 1 1 auto; min-width: 0; font-family: var(--vscode-editor-font-family, monospace); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| 312 | + .bgt-status { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums; } |
| 313 | + .bgt-status .port { color: var(--vscode-terminal-ansiBrightGreen, var(--vscode-terminal-ansiGreen, #7ee787)); } |
| 314 | + .bgt-stop { flex: 0 0 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; |
| 315 | + background: var(--vscode-button-secondaryBackground, rgba(127,127,127,.22)); color: var(--vscode-foreground); } |
| 316 | + .bgt-stop:hover { background: var(--vscode-errorForeground, #f14c4c); color: #fff; border-color: transparent; } |
| 317 | + .bgt-stop[disabled] { opacity: .6; cursor: default; } |
| 318 | + .bgt-stop svg { width: 1em; height: 1em; } |
296 | 319 | #planBar { flex: 0 0 auto; padding: 8px 8px 6px; max-height: 38vh; overflow-y: auto; scrollbar-gutter: stable; |
297 | 320 | background: var(--vscode-sideBar-background, var(--vscode-editor-background, #1e1e1e)); |
298 | 321 | border-top: 1px solid var(--border); box-shadow: 0 -8px 14px -8px rgba(0,0,0,.4); } |
|
509 | 532 | <button id="jumpLatest" title="Jump to latest" aria-label="Jump to latest">↓</button> |
510 | 533 |
|
511 | 534 | <!-- context-window usage meter (appears as the conversation fills the model's window) --> |
| 535 | + <div id="bgTasksBar" style="display:none"></div> |
512 | 536 | <div id="ctxBar" style="display:none"></div> |
513 | 537 |
|
514 | 538 | <!-- active agent plan — sticky, just above the Keep all / Undo all bar --> |
|
1266 | 1290 | // 3) live-update the breakdown popover if it's open |
1267 | 1291 | renderCtxCard(); |
1268 | 1292 | } |
| 1293 | + // Background tasks tray: aggregates commands started with run_command background:true. Collapsed by |
| 1294 | + // default; lives across turns; each row has Stop. Fed by 'bgTask' events from the host. |
| 1295 | + const bgTasksBar = document.getElementById('bgTasksBar'); |
| 1296 | + const bgTasks = {}; // id -> { id, command, status, port, ready } |
| 1297 | + let bgTasksCollapsed = true; |
| 1298 | + const CARET = '<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M6 4l5 4-5 4z"/></svg>'; |
| 1299 | + function renderBgTasks(){ |
| 1300 | + const running = Object.keys(bgTasks).map(k => bgTasks[k]).filter(t => t.status === 'running'); |
| 1301 | + if (!running.length){ bgTasksBar.style.display = 'none'; bgTasksBar.innerHTML = ''; return; } |
| 1302 | + let html = '<div class="bgt-head"><span class="bgt-livedot"></span>' |
| 1303 | + + '<span class="bgt-caret' + (bgTasksCollapsed ? '' : ' open') + '">' + CARET + '</span>' |
| 1304 | + + '<span class="bgt-title">Background tasks</span><span class="bgt-count">· ' + running.length + ' running</span></div>'; |
| 1305 | + if (!bgTasksCollapsed){ |
| 1306 | + html += '<div class="bgt-list">' + running.map(t => { |
| 1307 | + const status = t.port ? '<span class="port">running on :' + esc(t.port) + '</span>' : (t.ready ? 'ready' : 'running…'); |
| 1308 | + return '<div class="bgt-row"><span class="bgt-cmd" title="' + esc(t.command) + '">' + esc(t.command) + '</span>' |
| 1309 | + + '<span class="bgt-status">' + status + '</span>' |
| 1310 | + + '<button class="bgt-stop" data-id="' + esc(t.id) + '">' + codicon('stop-circle') + ' Stop</button></div>'; |
| 1311 | + }).join('') + '</div>'; |
| 1312 | + } |
| 1313 | + bgTasksBar.innerHTML = html; |
| 1314 | + bgTasksBar.style.display = 'block'; |
| 1315 | + const head = bgTasksBar.querySelector('.bgt-head'); if (head){ head.onclick = () => { bgTasksCollapsed = !bgTasksCollapsed; renderBgTasks(); }; } |
| 1316 | + bgTasksBar.querySelectorAll('.bgt-stop').forEach(b => { b.onclick = (e) => { e.stopPropagation(); b.disabled = true; b.textContent = 'stopping…'; vscode.postMessage({ type: 'stopCommand', id: b.getAttribute('data-id') }); }; }); |
| 1317 | + } |
| 1318 | + function applyBgTask(m){ |
| 1319 | + if (!m.id){ return; } |
| 1320 | + const t = bgTasks[m.id] || (bgTasks[m.id] = { id: m.id, command: '', status: 'running', port: null, ready: false }); |
| 1321 | + if (m.command != null){ t.command = m.command; } |
| 1322 | + if (m.status){ t.status = m.status; } |
| 1323 | + if (m.port){ t.port = m.port; } |
| 1324 | + if (m.ready){ t.ready = true; } |
| 1325 | + if (m.done){ delete bgTasks[m.id]; } // finished → drop from the running tray |
| 1326 | + renderBgTasks(); |
| 1327 | + } |
1269 | 1328 | const planBar = document.getElementById('planBar'); |
1270 | 1329 | function renderPlan(todos){ |
1271 | 1330 | if (!todos || !todos.length){ planBar.style.display = 'none'; planBar.innerHTML = ''; return; } |
|
1475 | 1534 | else if (m.type === 'termRun'){ addTermRun(m); } |
1476 | 1535 | else if (m.type === 'termOutput'){ termOutputAppend(m); } |
1477 | 1536 | else if (m.type === 'termExit'){ termExitFinish(m); } |
| 1537 | + else if (m.type === 'bgTask'){ applyBgTask(m); } |
1478 | 1538 | else if (m.type === 'verifyRun'){ addVerifyRun(m); } |
1479 | 1539 | else if (m.type === 'verifyOutput'){ verifyOutputAppend(m); } |
1480 | 1540 | else if (m.type === 'verifyDone'){ verifyDoneFinish(m); } |
|
1493 | 1553 | else if (m.type === 'reset'){ |
1494 | 1554 | log.innerHTML = '<div id="empty"><div class="big">New chat</div>Ask about your code, or describe what to build.</div>'; |
1495 | 1555 | selLabel = null; ctxFiles = []; renderChips(); |
1496 | | - for (const k in editCards) { delete editCards[k]; } for (const k in termCards) { delete termCards[k]; } for (const k in verifyCards) { delete verifyCards[k]; } updateReviewBar(0); renderPlan([]); renderContext({ input: 0 }); |
| 1556 | + for (const k in editCards) { delete editCards[k]; } for (const k in termCards) { delete termCards[k]; } for (const k in verifyCards) { delete verifyCards[k]; } for (const k in bgTasks) { delete bgTasks[k]; } renderBgTasks(); updateReviewBar(0); renderPlan([]); renderContext({ input: 0 }); |
1497 | 1557 | pending = ''; shown = ''; doneSignaled = false; flushAll = false; current = null; setStreaming(false); |
1498 | 1558 | forceStick(); |
1499 | 1559 | } |
|
0 commit comments