Menu Bar Status and Gateway Controls for Mac
Learn how the Mac menu bar reflects agent work state, hides health during activity, and manages devices, usage, and Gateways. Useful for users monitoring multiple Gateways or configuring primary settings.
Read this when
- Tweaking mac menu UI or status logic
What is shown
- The menu bar icon, along with the first status row inside the menu, reflects the agent's current work state.
- While any work is in progress, health status stays hidden; it reappears only after every session goes idle.
- A top-level "Context" item reveals recent sessions through a submenu rather than listing them directly in the root menu.
- Only paired devices appear in the root menu's "Nodes" block (sourced from
node.list); client and presence entries are excluded. - When provider usage snapshots exist, a root-level "Usage" section sits below Context, with cost details following if available.
- With two or more Gateways present, the primary Gateway's name shows in the first status row, and a root "Gateways" section lists each Gateway with its health and primary indicator. Clicking a row opens or brings that Gateway's dashboard into focus; holding Option reveals "Set as Primary…" for saved Gateways that qualify.
- Quick Chat launches the floating main-session composer, and its current global shortcut is displayed next to the item.
For single-Gateway setups, the menu remains exactly as before. When two or more Gateways exist, the app's primary Gateways menu also maps Command-1 through Command-9 in catalog order. Its checkmark tracks the frontmost dashboard window, and choosing an item switches that window in place, or opens the selected Gateway if no dashboard window is present.
State model
- Origin:
WorkActivityStore(apps/macos/Sources/OpenClaw/WorkActivityStore.swift). - Incoming events are
ControlAgentEventcarrying arunId; the handler (ControlChannel.routeWorkActivity) pullssessionKeyfrom the event payload, falling back to"main"when it's missing. - Precedence: the main session (defaulting to
sessionKey == "main") takes priority. When main is active, its state appears immediately. If main is idle, the most recently active non-main session is shown instead. The store avoids mid-activity flips; it only transitions when the current session goes idle or main becomes active. - Activity types:
job: high-level command execution (state: started|streaming|done|error|...).tool:phase: start|resultcombined withname, plus optionalmeta/args.
IconState enum (Swift)
idleworkingMain(ActivityKind)workingOther(ActivityKind)overridden(ActivityKind)(debug override)
ActivityKind -> badge symbol
ActivityKind wraps a ToolKind (bash, read, write, edit, attach, other) or a bare job. Each one maps to an SF Symbol badge overlaid on the critter icon (IconState.badgeSymbolName):
| Kind | Symbol |
|---|---|
bash | chevron.left.slash.chevron.right |
read | doc |
write | pencil |
edit | pencil.tip |
attach | paperclip |
other / job | gearshape.fill |
Visual mapping
idle: standard critter, no badge shown.workingMain: badge with symbol, full tint (.primaryprominence), leg "working" animation.workingOther: badge with symbol, muted tint (.secondaryprominence), no scurry.overridden: applies the chosen symbol/tint regardless of actual activity.
Context submenu
- A single "Context" row with a session count/status appears in the root menu, opening a submenu (
MenuSessionsInjector). - The submenu's header displays the active session count from the past 24 hours.
- Every session row retains its token bar, age, preview, thinking/verbose toggle, reset, compact, and delete actions.
- Loading, disconnected, and session-load error messages show up within the Context submenu.
- Usage and cost sections remain at the root level below Context, staying visible without needing to open the submenu.
Status row text (menu)
- When two or more Gateways are present, the connection label tacks on the catalog display name of the primary Gateway, for example
OpenClaw Active — Mac Studio. - During active work:
<Session role> · <activity label>("\(roleLabel) · \(activity.label)"inMenuContentView), with the role label beingMainorOther. - At rest: it reverts to the health summary.
Event ingestion
- Origin: control-channel
agentevents, directed byControlChannel.routeWorkActivity(from:). - Extracted fields:
stream: "job"paired withdata.stateto signal start/stop.stream: "tool"alongsidedata.phase,data.name, and optionallydata.meta/data.args.
- Tool names are sourced from
ToolDisplayRegistry.resolve(name:args:meta:); if a name cannot be resolved, the raw tool name is used as a fallback.
Debug override
- Settings > Debug > "Icon override" picker:
System (auto)(the default)Working: main/Working: other(grouped by tool type: bash, read, write, edit, other)Idle
- Saved under the
UserDefaultskeyopenclaw.iconOverride; translated intoIconState.overridden.
Testing checklist
- Main session job triggered: the icon updates right away and the status line displays the main label.
- Non-main session job triggered while main is idle: the icon and status reflect the non-main session, holding steady until it completes.
- Main starts while another session is running: the icon switches to main without delay.
- Fast tool sequences: the badge avoids flicker thanks to a 2-second grace period before a finished tool clears,
WorkActivityStore.toolResultGrace. - The health row comes back once every session is idle.