Skip to content

feat(#59): menu bar status item for busy AI agents (gated behind spawnterm.agent_menubar)#64

Merged
carvalhoviniciusluiz merged 1 commit into
masterfrom
feat/59-import-670
Jul 22, 2026
Merged

feat(#59): menu bar status item for busy AI agents (gated behind spawnterm.agent_menubar)#64
carvalhoviniciusluiz merged 1 commit into
masterfrom
feat/59-import-670

Conversation

@carvalhoviniciusluiz

Copy link
Copy Markdown
Owner

Closes #59

Summary

Imports upstream gnachman#670 ("Menu bar status item for busy AI agents") into the fork, gated behind a new spawnTerm capability flag so it is OFF by default and stock behavior is unchanged.

When enabled, an NSStatusItem appears in the system menu bar. Idle, it shows the template StatusItem glyph. When one or more AI agents are working, it renders an orange icon with a count badge (19, or + for 10 or more). The busy set is the union of OSC 21337 tab indicators (terminal Claude Code sessions) and ChatBroker agent-typing chats, deduped by namespaced id, so a terminal session and a built-in AI chat each contribute one. Clicking opens the existing status bar menu.

Feature flag (master switch)

New flag spawnterm.agent_menubar (default OFF), wired in the three synced places plus docs:

  • spawnterm/flags/spawnterm-flag — added to KNOWN_FLAGS
  • spawnterm/flags/spawnterm_flag.py — added to KNOWN_FLAGS with description
  • sources/Settings/iTermSpawnTermCapabilities.mcapabilityIdentifiers + displayNameForCapability:"AI Agent Menu Bar"
  • spawnterm/docs/feature-flags.md — schema row + example

Parity test (spawnterm/flags/tests/test_flags.sh) still PASS=26, FAIL=0 (it counts assertions, not flags; shell/Python list and byte-for-byte writer parity both hold with the 13th flag).

Where activation is gated

Gating lives inside AIMenuBarStatusController.refreshOnMain() (the single funnel that every trigger — app launch, defaults change, session-status observer, ChatBroker subscription — flows through), so the flag governs the item no matter what wakes the controller:

  • flagOn = iTermSpawnTermCapabilities.isEnabledForCapability:@"agent_menubar" is the master switch.
  • The independent menu-bar item requires flagOn && iTermAdvancedSettingsModel.showMenuBarItem().
  • The pre-existing legacy path (excluded-from-Dock + statusBarIcon) is preserved regardless of the flag, and the busy-count badge is only drawn when flagOn. So with the flag OFF the result is byte-for-byte stock behavior.

Upstream's own showMenuBarItem Advanced setting is kept and AND-ed with our flag (flag is master).

Provenance / patch apply

Applied gh pr diff 670 --patch (3 commits) with git apply --reject:

  • pbxproj: applied cleanly, but reverted and re-added AIMenuBarStatusController.swift via tools/add_file_to_xcodeproj.rb ... iTerm2SharedARC (canonical, fork-local UUIDs) per repo convention.
  • iTermAdvancedSettingsModel.{h,m}: applied cleanly with offset — the anticipated conflict with our persistUserVarsToSidecar (Tier 4.C — user-var sidecar persistence (fork-direct) #51) did not materialize; both entries are present.
  • iTermApplication.m: only rejected hunk — our tree uses RLog where upstream had DLog. Resolved by hand: removed the inline NSStatusItem creation and replaced it with [[iTermAIMenuBarStatusController sharedInstance] refresh].
  • iTermPreferences.{h,m}: net-zero (commit 1 added ShowMenuBarItem, commit 2 removed it in favor of the Advanced setting) — file unchanged.
  • ChatListModel.swift / iTermApplicationDelegate.m: applied cleanly.

Adaptations to compile under the fork

  • AIMenuBarStatusController.swift: added the fork's third ChatBroker.Update.turnLifecycle case to the subscription switch (upstream had only two); wrapped the main-actor-isolated TypingStatusModel.instance.chatIDs(forParticipant:) read in MainActor.assumeIsolated; added the flag gate + showBadge: plumbing.
  • Added #import "iTermSpawnTermCapabilities.h" to sources/iTerm2SharedARC-Bridging-Header.h so the Swift gate can call the capabilities class.

Build status (honest)

Not verified by a clean compile in this environment. tools/build.sh Development aborts in the dependency phase, before the main iTerm2 target compiles, because the CNoise submodule vendor sources are not checked out (Companion/CompanionCore/Sources/CNoise/vendor/src/protocol/*.c "File not found", noise/protocol.h not found). This is a pre-existing environment limitation unrelated to this change — no error in the log references any file touched here.

Verified correct-by-inspection instead: every imported symbol exists in our tree — the @objc(sharedInstance) static let singleton pattern (matches ImageRegistry, FocusOrder, etc.), iTermApplicationDelegate.statusBarMenu() (protocol method the delegate class conforms to), iTermPreferences.boolForKey:, iTermAdvancedSettingsModel.showMenuBarItem()/.statusBarIcon(), SessionStatusController.instance.statuses values' hasIndicator/sessionID, and TypingStatusModel.chatIDs(forParticipant:).

Files changed

  • sources/AppKit/AIMenuBarStatusController.swift (new)
  • sources/AITerm/ChatListModel.swift
  • sources/AppKit/iTermApplication.m
  • sources/AppKit/iTermApplicationDelegate.m
  • sources/Settings/iTermAdvancedSettingsModel.{h,m}
  • sources/Settings/iTermSpawnTermCapabilities.m
  • sources/iTerm2SharedARC-Bridging-Header.h
  • iTerm2.xcodeproj/project.pbxproj
  • spawnterm/flags/spawnterm-flag, spawnterm/flags/spawnterm_flag.py, spawnterm/docs/feature-flags.md
  • docs/notes-3.7.txt

No submodule, ThirdParty, or version-stamp changes.

🤖 Generated with Claude Code

Import upstream gnachman#670 ("Menu bar status item for busy
AI agents") behind the new spawnterm.agent_menubar capability flag
(default OFF). When the flag is OFF, behavior is identical to stock
iTerm2: the controller only shows a status item for the legacy
"excluded from Dock + status bar icon" combination and never draws a
busy-count badge. When the flag is ON, the independent menu bar item
(gated additionally by the ShowMenuBarItem Advanced setting) and the
orange busy-agent count badge become available.

The flag is the master switch, wired in the three synced places
(spawnterm-flag, spawnterm_flag.py, iTermSpawnTermCapabilities) plus
the docs, and gated inside AIMenuBarStatusController.refreshOnMain.

Adapted from upstream gnachman#670.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@carvalhoviniciusluiz

Copy link
Copy Markdown
Owner Author

Review by tech-lead — code APPROVED by inspection; build UNVERIFIED (environment).

Import do upstream gnachman#670 adaptado (reject de RLog↔DLog em iTermApplication.m resolvido à mão; case ChatBroker.Update.turnLifecycle da fork adicionado; MainActor.assumeIsolated; import no bridging header p/ o gate Swift resolver; pbxproj via add_file_to_xcodeproj.rb). Higiene limpa; persistUserVarsToSidecar (#51) e showMenuBarItem coexistem.

  • Flag spawnterm.agent_menubar (default OFF) em shell + py + iTermSpawnTermCapabilities + docs; parity 26/26 (13º flag).
  • Gate: em AIMenuBarStatusController.refreshOnMain() (funil único) — isEnabled(forCapability:"agent_menubar") é o master switch (AND com o advanced setting showMenuBarItem); OFF = byte-for-byte stock. Proveniência: Adapted from upstream Menu bar status item for busy AI agents gnachman/iTerm2#670.
    Build não verificável aqui (submódulo CNoise). Sem ressalvas.

@carvalhoviniciusluiz
carvalhoviniciusluiz merged commit a7a1e9b into master Jul 22, 2026
@carvalhoviniciusluiz
carvalhoviniciusluiz deleted the feat/59-import-670 branch July 22, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Import upstream #670 — menu-bar busy-agent status (flag: agent_menubar)

1 participant