diff --git a/desktop/package.json b/desktop/package.json index a6c7fcc..4096fb8 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,7 +1,7 @@ { "name": "codbash-desktop", "productName": "codbash", - "version": "7.14.3", + "version": "7.14.4", "private": true, "description": "Desktop shell (Electron) for codbash — wraps the codbash server in a native window.", "main": "main.js", diff --git a/package.json b/package.json index 9326ee4..651c53f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codbash-app", - "version": "7.14.3", + "version": "7.14.4", "description": "Dashboard + CLI for AI coding agents — Claude Code, Codex, Cursor, OpenCode, Kiro. View, search, resume, convert, sync sessions.", "bin": { "codbash": "./bin/cli.js", diff --git a/src/changelog.js b/src/changelog.js index f64ca3d..50e6f47 100644 --- a/src/changelog.js +++ b/src/changelog.js @@ -1,6 +1,16 @@ 'use strict'; const CHANGELOG = [ + { + version: '7.14.4', + date: '2026-07-20', + title: 'Layout restore fixed · agent-aware running tree · cleaner Terminal view', + changes: [ + 'Saved layouts now restore each pane in its original project folder and re-issue its command (including resume commands prefilled from session cards) — previously the folder and prefilled command were dropped on save', + 'The sidebar running tree is now driven by real agent detection: it groups running agents by their actual project folder and labels each by agent name (Claude, Codex, …) with a live/idle dot, so it works even when an agent cd\'d elsewhere or was started outside the app', + 'Hid the session toolbar (search / group / select / AI titles / count) in the Terminal and Overview views, where it has no list to act on', + ], + }, { version: '7.14.3', date: '2026-07-20', diff --git a/src/frontend/app.js b/src/frontend/app.js index 37f8a67..eec57c8 100644 --- a/src/frontend/app.js +++ b/src/frontend/app.js @@ -1177,8 +1177,10 @@ async function pollActiveSessions() { activeSessions = newActive; - // Reflect the new active-agent count on the Overview landing. + // Reflect the new active-agent count on the Overview landing and refresh + // the sidebar running-agents tree (which is driven by activeSessions now). if (typeof _ovRefreshIfCurrent === 'function') _ovRefreshIfCurrent(); + if (typeof _wsRenderRunningTree === 'function') _wsRenderRunningTree(); // Only touch cards that changed document.querySelectorAll('.card').forEach(function(card) { @@ -1638,6 +1640,11 @@ function render() { var stats = document.getElementById('stats'); if (!content) return; + // Reflect the active view on
so CSS can hide chrome that only makes + // sense over a session list — e.g. the session toolbar (Search/Group/Select/ + // AI Titles/Refresh + count) is meaningless in the Terminal and Overview views. + document.body.setAttribute('data-view', currentView); + // Detach (don't destroy) the live terminal when navigating away from the // Workspace view: its panes/ptys keep running in a hidden holder and are // re-attached intact on return. diff --git a/src/frontend/styles.css b/src/frontend/styles.css index f5c8df1..510f945 100644 --- a/src/frontend/styles.css +++ b/src/frontend/styles.css @@ -567,6 +567,12 @@ body.sidebar-hidden .toolbar { padding-left: 52px; } position: relative; } +/* The session toolbar (search / group / select / AI titles / count) only makes + sense over a session list. Hide it in the Terminal and Overview views, which + have their own chrome and no list to act on. */ +body[data-view="workspace"] .toolbar, +body[data-view="overview"] .toolbar { display: none; } + .search-box { flex: 1; min-width: 200px; @@ -2892,9 +2898,16 @@ body.sidebar-hidden .toolbar { padding-left: 52px; } border-radius: 8px; padding: 0 6px; min-width: 16px; text-align: center; } .ws-run-term { + position: relative; padding: 2px 8px 2px 22px; font-size: 11px; color: var(--text-secondary); cursor: pointer; border-radius: 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.ws-run-term::before { + content: ''; position: absolute; left: 11px; top: 50%; transform: translateY(-50%); + width: 5px; height: 5px; border-radius: 50%; background: var(--accent-green); +} +.ws-run-term.ws-run-idle { color: var(--text-muted); } +.ws-run-term.ws-run-idle::before { background: var(--text-muted); } .ws-run-term:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); } /* In-app prompt (codbashPrompt) — replaces window.prompt (no-op in Electron). */ diff --git a/src/frontend/workspace.js b/src/frontend/workspace.js index 274eec3..dc6195e 100644 --- a/src/frontend/workspace.js +++ b/src/frontend/workspace.js @@ -146,53 +146,104 @@ function _wsStartStatusLoop() { }, 1000); } -// Group live terminals by their project folder (skips the home dir — those -// aren't "projects"). Used by the sidebar running-terminals tree. +// Human label for an agent kind (the `kind` field from /api/active), e.g. +// 'claude' → 'Claude', 'kiro' → 'Kiro'. Falls back to a capitalized kind. +var _WS_TOOL_LABELS = { + claude: 'Claude', codex: 'Codex', qwen: 'Qwen', opencode: 'OpenCode', + kiro: 'Kiro', 'kiro-cli': 'Kiro', kilo: 'Kilo', cursor: 'Cursor', + copilot: 'Copilot', 'copilot-chat': 'Copilot', pi: 'Pi', gemini: 'Gemini', +}; +function _wsToolLabel(kind) { + if (!kind) return 'Agent'; + return _WS_TOOL_LABELS[kind] || (kind.charAt(0).toUpperCase() + kind.slice(1)); +} + +// Group *running agents* (from the live /api/active map, not Workspace panes) +// by their real project folder — so agents launched here, in another terminal, +// or that cd'd elsewhere are all detected correctly. Skips the home dir and +// entries with no cwd. Used by the sidebar running-agents tree. function _wsRunningByProject() { + var map = (typeof activeSessions === 'object' && activeSessions) || {}; var groups = {}, order = []; + Object.keys(map).forEach(function (k) { + var a = map[k]; + var cwd = (a && a.cwd) || ''; + if (!cwd || /^(\/Users\/[^/]+|\/home\/[^/]+|\/root)\/?$/.test(cwd)) return; + var key = cwd; // group by full path so two same-named folders don't merge + if (!groups[key]) { groups[key] = { name: _wsProjectBasename(cwd), cwd: cwd, items: [] }; order.push(key); } + groups[key].items.push(a); + }); + return order.map(function (k) { return groups[k]; }); +} + +// Find a live Workspace pane sitting in `cwd` (so clicking a running agent can +// jump straight to its terminal when we have one). Returns {tab,pane} or null. +function _wsPaneForCwd(cwd) { + var hit = null; _wsAllPanes().forEach(function (x) { + if (hit) return; var p = x.pane; if (!p.sock || p.exited || p.sock.readyState !== 1) return; - var cwd = p.cwd || ''; - if (!cwd || /^(\/Users\/[^/]+|\/home\/[^/]+|\/root)\/?$/.test(cwd)) return; - var key = _wsProjectBasename(cwd); - if (!groups[key]) { groups[key] = { name: key, items: [] }; order.push(key); } - groups[key].items.push(x); + if ((p.cwd || p.wantCwd) === cwd) hit = x; }); - return order.map(function (k) { return groups[k]; }); + return hit; +} + +// Click a running-agent row: jump to its Workspace pane if one exists here, +// otherwise open the session in a new pane (best-effort) or just show Workspace. +function jumpToRunningAgent(cwd, sessionId, kind) { + var hit = _wsPaneForCwd(cwd); + if (hit) { jumpToWorkspacePane(hit.tab.id, hit.pane.id); return; } + if (sessionId && typeof openSessionInWorkspace === 'function') { + openSessionInWorkspace(sessionId); return; + } + if (cwd && typeof openInWorkspace === 'function') { + openInWorkspace({ name: _wsProjectBasename(cwd), cwd: cwd }); + } } -// Render a compact tree at the bottom of the sidebar: each project folder that -// has running terminals, with its terminals underneath — click to jump. +// Render a compact tree at the bottom of the sidebar: each project folder with a +// running agent, the agents underneath labeled by agent name — click to jump. var _wsRunTreeSig = ''; function _wsRenderRunningTree() { var el = document.getElementById('wsRunningTree'); if (!el) return; var groups = _wsRunningByProject(); var sig = groups.map(function (g) { - return g.name + ':' + g.items.map(function (x) { return x.pane.id + '=' + _wsPaneLabel(x.pane); }).join(','); + return g.name + ':' + g.items.map(function (a) { + return (a.sessionId || a.pid) + '=' + a.kind + '/' + a.status; + }).join(','); }).join('|'); if (sig === _wsRunTreeSig) return; // no change → no rebuild _wsRunTreeSig = sig; if (!groups.length) { el.style.display = 'none'; el.innerHTML = ''; return; } - var html = '