+ Control what appears in the application titlebar.
+
+
+
+
+
+
+
+ Show the number of active agents in the titlebar.
+
+
+
+
+
) : null
].filter(Boolean)
diff --git a/src/renderer/src/lib/agent-status.ts b/src/renderer/src/lib/agent-status.ts
index 2ff9d2c7ac3..8982de36f25 100644
--- a/src/renderer/src/lib/agent-status.ts
+++ b/src/renderer/src/lib/agent-status.ts
@@ -27,29 +27,61 @@ export function countWorkingAgents({
for (const tabs of Object.values(tabsByWorktree)) {
for (const tab of tabs) {
- const paneTitles = runtimePaneTitlesByTabId[tab.id]
- // Why: split-pane tabs can host multiple concurrent agents, but the
- // legacy tab title only reflects the last pane title update that won the
- // tab label. Prefer pane-level titles whenever TerminalPane is mounted,
- // and fall back to the tab title only for tabs we have not mounted yet
- // (for example restored-but-unvisited worktrees).
- if (paneTitles && Object.keys(paneTitles).length > 0) {
- for (const title of Object.values(paneTitles)) {
- if (detectAgentStatusFromTitle(title) === 'working') {
- count += 1
- }
- }
- continue
- }
- // Why: restored session tabs can keep the last agent title even before a
- // PTY reconnects (or after the PTY is gone). Count only live PTY-backed
- // tab fallbacks so the titlebar matches the sidebar's notion of
- // "actively running" instead of surfacing stale pre-shutdown state.
- if (tab.ptyId && detectAgentStatusFromTitle(tab.title) === 'working') {
- count += 1
- }
+ count += countWorkingAgentsForTab(tab, runtimePaneTitlesByTabId)
}
}
return count
}
+
+/**
+ * Returns a map of worktreeId → number of active agents for that worktree.
+ * Only includes worktrees with at least one working agent.
+ */
+export function countWorkingAgentsPerWorktree({
+ tabsByWorktree,
+ runtimePaneTitlesByTabId
+}: CountWorkingAgentsArgs): Record {
+ const result: Record = {}
+
+ for (const [worktreeId, tabs] of Object.entries(tabsByWorktree)) {
+ let count = 0
+ for (const tab of tabs) {
+ count += countWorkingAgentsForTab(tab, runtimePaneTitlesByTabId)
+ }
+ if (count > 0) {
+ result[worktreeId] = count
+ }
+ }
+
+ return result
+}
+
+function countWorkingAgentsForTab(
+ tab: TerminalTab,
+ runtimePaneTitlesByTabId: Record>
+): number {
+ let count = 0
+ const paneTitles = runtimePaneTitlesByTabId[tab.id]
+ // Why: split-pane tabs can host multiple concurrent agents, but the
+ // legacy tab title only reflects the last pane title update that won the
+ // tab label. Prefer pane-level titles whenever TerminalPane is mounted,
+ // and fall back to the tab title only for tabs we have not mounted yet
+ // (for example restored-but-unvisited worktrees).
+ if (paneTitles && Object.keys(paneTitles).length > 0) {
+ for (const title of Object.values(paneTitles)) {
+ if (detectAgentStatusFromTitle(title) === 'working') {
+ count += 1
+ }
+ }
+ return count
+ }
+ // Why: restored session tabs can keep the last agent title even before a
+ // PTY reconnects (or after the PTY is gone). Count only live PTY-backed
+ // tab fallbacks so the titlebar matches the sidebar's notion of
+ // "actively running" instead of surfacing stale pre-shutdown state.
+ if (tab.ptyId && detectAgentStatusFromTitle(tab.title) === 'working') {
+ count += 1
+ }
+ return count
+}
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 49db23cf776..98c0813c5eb 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -106,6 +106,7 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
terminalScrollbackBytes: 10_000_000,
openLinksInApp: true,
rightSidebarOpenByDefault: true,
+ showTitlebarAgentActivity: true,
notifications: getDefaultNotificationSettings(),
diffDefaultView: 'inline',
promptCacheTimerEnabled: false,
diff --git a/src/shared/types.ts b/src/shared/types.ts
index 2be2a745e71..ca7c8726440 100644
--- a/src/shared/types.ts
+++ b/src/shared/types.ts
@@ -403,6 +403,8 @@ export type GlobalSettings = {
* until the user explicitly wants worktree-scoped in-app browsing. */
openLinksInApp: boolean
rightSidebarOpenByDefault: boolean
+ /** Whether to show the live agent activity count badge in the titlebar. */
+ showTitlebarAgentActivity: boolean
diffDefaultView: 'inline' | 'side-by-side'
notifications: NotificationSettings
/** When true, a countdown timer is shown after a Claude agent becomes idle,