feat: headless agent pills in sessions view#49
Conversation
There was a problem hiding this comment.
Pull request overview
Adds end-to-end “headless agent session” tracking and UI so background (non-terminal) agent runs can be listed, monitored, and controlled from the renderer.
Changes:
- Introduces renderer store state/actions for headless sessions (add/update/dismiss/prune + last-output tracking).
- Adds UI for displaying headless sessions above the grid (including an expandable pill with live log streaming), plus settings for visibility and retention.
- Adds IPC + server method support for listing headless sessions and polls/syncs them in the renderer.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/renderer/stores/types.ts | Extends TerminalsSlice with headless session tracking types. |
| src/renderer/stores/terminals-slice.ts | Implements headless session state/actions in Zustand. |
| src/renderer/lib/workflow-execution.ts | Immediately adds newly created headless sessions to the UI store. |
| src/renderer/global.css | Adds borderPulse keyframes for headless pill styling. |
| src/renderer/components/settings/GeneralSettings.tsx | Adds settings toggles for headless visibility + retention duration. |
| src/renderer/components/HeadlessPill.tsx | New UI component to render headless session status/actions/logs. |
| src/renderer/components/GridView.tsx | Renders a “Headless Agents” section above the session grid with filtering. |
| src/renderer/App.tsx | Subscribes to headless data/exit, polls listHeadlessSessions, and prunes exited sessions. |
| src/preload/index.ts | Exposes listHeadlessSessions to the renderer. |
| src/main/ipc-handlers.ts | Registers IPC handler for headless:list. |
| packages/shared/src/types.ts | Adds config defaults + IPC constant for headless list. |
| packages/shared/src/protocol.ts | Adds JSON-RPC method typing for headless:list. |
| packages/server/src/register-methods.ts | Registers server method headless:list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // Reconcile: merge with existing, don't add back dismissed | ||
| const dismissed = state.headlessDismissed | ||
| const existing = new Map(state.headlessSessions.map((s) => [s.id, s])) | ||
| for (const s of sessions) { | ||
| if (dismissed.has(s.id)) continue | ||
| const prev = existing.get(s.id) | ||
| if (prev) { | ||
| // Update status/exitCode/endedAt from server, keep local overrides | ||
| existing.set(s.id, { | ||
| ...prev, | ||
| status: s.status, | ||
| exitCode: s.exitCode, | ||
| endedAt: s.endedAt | ||
| }) | ||
| } else { | ||
| existing.set(s.id, s) | ||
| } | ||
| } | ||
| return { headlessSessions: Array.from(existing.values()) } |
There was a problem hiding this comment.
Fixed: setHeadlessSessions now rebuilds from the server list, dropping sessions the server no longer reports. Locally-added running sessions (not yet known to server) are preserved. Output entries for removed sessions are also cleaned up.
| return { | ||
| headlessSessions: state.headlessSessions.filter((s) => s.id !== id), | ||
| headlessDismissed: dismissed | ||
| } | ||
| }), | ||
|
|
||
| pruneExitedHeadless: (retentionMs) => | ||
| set((state) => { | ||
| const now = Date.now() | ||
| return { | ||
| headlessSessions: state.headlessSessions.filter( | ||
| (s) => s.status === 'running' || !s.endedAt || now - s.endedAt < retentionMs | ||
| ) |
There was a problem hiding this comment.
Fixed: headlessLastOutput entries are now deleted in dismissHeadlessSession, pruneExitedHeadless, and setHeadlessSessions (when sessions disappear from server). headlessDismissed is also cleaned up during pruning.
| ? 'border-red-500/25' | ||
| : 'border-white/[0.06]' | ||
|
|
||
| const opacityClass = !isRunning ? 'opacity-65' : '' |
There was a problem hiding this comment.
Fixed: changed opacity-65 to opacity-[0.65] (Tailwind arbitrary value syntax).
| hover:border-white/[0.12] | ||
| ${borderClass} ${opacityClass} | ||
| ${expanded ? 'flex-col !items-stretch w-[320px]' : ''}`} | ||
| style={isRunning ? { animation: 'borderPulse 2.5s ease-in-out infinite' } : undefined} |
There was a problem hiding this comment.
Fixed: removed the duplicate inline style={{ animation }} — now only using the Tailwind animate-[borderPulse_...] class.
Headless agents spawned by workflows are now visible in the Sessions view as compact inline pills above the interactive session grid. - Add headless:list IPC to query active headless sessions from server - Track headless sessions in Zustand store with real-time updates - HeadlessPill component: status dot, agent icon, name, branch, live duration timer, exit code badge, expandable log output - Close button hides pill (process continues), Kill button stops process - Auto-prune exited sessions after configurable retention (default 5 min) - Settings: toggle visibility + retention duration in General settings - Project/workspace/status filters apply to headless pills
- Fix phantom sessions: setHeadlessSessions now removes sessions gone from server instead of only merging (keeps locally-added running ones) - Fix memory leak: dismissHeadlessSession and pruneExitedHeadless now clean up headlessLastOutput and headlessDismissed maps - Fix opacity-65 (not a default Tailwind class) to opacity-[0.65] - Remove duplicate inline style animation (keep Tailwind class only)
e94a314 to
230ed24
Compare
Summary
Test plan