Skip to content

Commit c162049

Browse files
feat(agent-cockpit): replace bottom-panel dashboard with inline per-card agents list (#1251)
Co-authored-by: Orca <help@stably.ai>
1 parent 89f41bf commit c162049

37 files changed

Lines changed: 548 additions & 2055 deletions

src/main/codex-accounts/runtime-home-service.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
6464
openLinksInApp: false,
6565
rightSidebarOpenByDefault: true,
6666
showTitlebarAgentActivity: true,
67-
showAgentDashboard: true,
6867
showTaskProviderIcons: true,
6968
diffDefaultView: 'inline',
7069
notifications: {

src/main/codex-accounts/service.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings
5858
openLinksInApp: false,
5959
rightSidebarOpenByDefault: true,
6060
showTitlebarAgentActivity: true,
61-
showAgentDashboard: true,
6261
showTaskProviderIcons: true,
6362
diffDefaultView: 'inline',
6463
notifications: {

src/main/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,15 +341,15 @@ app.whenReady().then(async () => {
341341
nativeTheme.themeSource = store.getSettings().theme ?? 'system'
342342
// Why: managed hook installation mutates user-global agent config.
343343
// Startup must fail open so a malformed local config never bricks Orca.
344-
// Claude/Codex/Gemini installs are gated behind the experimental
345-
// Agent Dashboard setting because the surface they feed (the in-progress
346-
// agent dashboard) isn't shippable yet. Cursor installs unconditionally
347-
// because cursor-agent emits no title-based working/idle signal at all
348-
// (its terminal title stays literally "Cursor Agent" across a turn), so
349-
// the hook channel is the only way to drive the sidebar spinner + unread
350-
// path for it — there is no "pre-dashboard" fallback to degrade to the
351-
// way Claude/Codex have. Toggling the setting takes effect on next launch
352-
// because the hook scripts are installed once per boot.
344+
// Claude/Codex/Gemini installs are gated behind the experimentalAgentDashboard
345+
// setting because the feature they feed (the inline agent-activity list) is
346+
// still in preview. Cursor installs unconditionally because cursor-agent
347+
// emits no title-based working/idle signal at all (its terminal title stays
348+
// literally "Cursor Agent" across a turn), so the hook channel is the only
349+
// way to drive the sidebar spinner + unread path for it — there is no
350+
// title-based fallback the way Claude/Codex have. Toggling the setting
351+
// takes effect on next launch because the hook scripts are installed once
352+
// per boot.
353353
const agentDashboardEnabled = store.getSettings().experimentalAgentDashboard === true
354354
if (agentDashboardEnabled) {
355355
for (const installManagedHooks of [

src/main/ipc/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type AppRuntimeFlags = {
99
/** Whether the experimental agent dashboard setting was enabled when this
1010
* session booted. When true, Claude/Codex/Gemini managed hook installation
1111
* was attempted at startup (individual install failures are logged but do
12-
* not flip this flag — the dashboard UI itself treats missing hooks as
12+
* not flip this flag — the inline agents list treats missing hooks as
1313
* no-ops). Toggling the setting only affects hook installation on the next
1414
* launch, so the renderer compares this against the current setting to
1515
* decide whether a "restart required" banner needs to be shown. */

src/main/rate-limits/gemini-usage-fetcher.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,7 @@ describe('fetchGeminiRateLimits', () => {
202202
return Promise.resolve(makeResponse(quotaResponse))
203203
}
204204
if (url.includes('token')) {
205-
return Promise.resolve(
206-
makeResponse({ access_token: 'retried-token', expires_in: 3600 })
207-
)
205+
return Promise.resolve(makeResponse({ access_token: 'retried-token', expires_in: 3600 }))
208206
}
209207
if (url.includes('loadCodeAssist')) {
210208
return Promise.resolve(makeResponse({ cloudaicompanionProject: 'proj-123' }))

src/renderer/src/App.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ function App(): React.JSX.Element {
139139
const rightSidebarOpen = useAppStore((s) => s.rightSidebarOpen)
140140
const isFullScreen = useAppStore((s) => s.isFullScreen)
141141
const settings = useAppStore((s) => s.settings)
142+
// Why: render-level gate for the experimental agent dashboard retention
143+
// sync. Reading the flag here (rather than only inside useDashboardData /
144+
// useRetainedAgentsSync) lets us skip mounting RetainedAgentsSyncGate
145+
// entirely for non-toggled users, which drops all feature-tied
146+
// subscriptions (agentStatusByPaneKey, agentStatusEpoch, etc.) instead of
147+
// keeping them alive behind an early-return inside the hook bodies.
148+
const agentDashboardEnabled = useAppStore((s) => s.settings?.experimentalAgentDashboard === true)
142149
const canGoBackWorktree = useAppStore(canGoBackWorktreeHistory)
143150
const canGoForwardWorktree = useAppStore(canGoForwardWorktreeHistory)
144151
const titlebarLeftControlsRef = useRef<HTMLDivElement | null>(null)
@@ -147,10 +154,10 @@ function App(): React.JSX.Element {
147154

148155
// Subscribe to IPC push events
149156
useIpcEvents()
150-
// Why: retention must run at App level (not inside AgentDashboard) because
151-
// the sidebar hovercard also reads retained entries. If retention only ran
152-
// when the dashboard is mounted, "done" agents would vanish from the hover
153-
// any time the user collapses the dashboard panel.
157+
// Why: retention must run at App level so the inline per-card agents list
158+
// always sees retained entries. If retention ran inside the sidebar-card
159+
// subtree, "done" agents would vanish any time the user collapsed a card's
160+
// inline agents section.
154161
//
155162
// The retention hooks are hosted inside <RetainedAgentsSyncGate /> (a leaf
156163
// component that renders null) rather than being called inline here.
@@ -159,10 +166,17 @@ function App(): React.JSX.Element {
159166
// event frequency), re-rendering the entire app tree on every agent status
160167
// update. Hosting the subscriptions in a leaf isolates that churn.
161168
//
162-
// The experimentalAgentDashboard gate still lives inside the hooks
163-
// themselves (useDashboardData early-returns [] from its memo;
164-
// useRetainedAgentsSync early-returns from its effect), so the gate
165-
// component is cheap when the setting is off.
169+
// The render-level gate on <RetainedAgentsSyncGate /> (see
170+
// agentDashboardEnabled above) keeps the experimental feature fully dark
171+
// for non-toggled users: without the gate mounted, none of its feature-tied
172+
// zustand selectors (agentStatusByPaneKey / agentStatusEpoch / etc.) are
173+
// ever subscribed, so PTY agent-status events cause zero work for them.
174+
//
175+
// The inner hook guards (useDashboardData early-returns [] from its memo;
176+
// useRetainedAgentsSync early-returns from its effect) remain as
177+
// defense-in-depth: they keep both hooks safe to call from any future
178+
// callsite, and they handle the in-session off→on toggle transition
179+
// cleanly without relying on a remount race when the setting flips.
166180
// Why: git conflict-operation state also drives the worktree cards. Polling
167181
// cannot live under RightSidebar because App unmounts that subtree when the
168182
// sidebar is closed, which leaves stale "Rebasing"/"Merging" badges behind
@@ -926,10 +940,16 @@ function App(): React.JSX.Element {
926940
}
927941
>
928942
<TooltipProvider delayDuration={400}>
929-
{/* Why: leaf-mounted retention sync. Hosts useDashboardData() +
930-
useRetainedAgentsSync() so their high-churn store subscriptions
931-
re-render a null component rather than the entire App tree. */}
932-
<RetainedAgentsSyncGate />
943+
{/* Why: leaf-mounted retention sync, gated at the render level by
944+
agentDashboardEnabled. Hosting useDashboardData() +
945+
useRetainedAgentsSync() inside a null-rendering leaf keeps their
946+
high-churn store subscriptions from re-rendering the App tree;
947+
the outer conditional drops those subscriptions entirely for
948+
users who have not toggled the experimental agent dashboard on,
949+
so PTY agent-status events do no feature-tied work for them.
950+
The hooks' internal early-returns remain as defense-in-depth
951+
(see the comment above useIpcEvents()). */}
952+
{agentDashboardEnabled ? <RetainedAgentsSyncGate /> : null}
933953
{/* Why: in workspace view (split groups always enabled), the full-width
934954
titlebar is removed so tab groups + terminal extend to the top of
935955
the window. Left titlebar controls move to a header above the sidebar.

0 commit comments

Comments
 (0)