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 ControlAgentEvent carrying a runId; the handler (ControlChannel.routeWorkActivity) pulls sessionKey from 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|result combined with name, plus optional meta/args.

IconState enum (Swift)

  • idle
  • workingMain(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):

KindSymbol
bashchevron.left.slash.chevron.right
readdoc
writepencil
editpencil.tip
attachpaperclip
other / jobgearshape.fill

Visual mapping

  • idle: standard critter, no badge shown.
  • workingMain: badge with symbol, full tint (.primary prominence), leg "working" animation.
  • workingOther: badge with symbol, muted tint (.secondary prominence), 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)" in MenuContentView), with the role label being Main or Other.
  • At rest: it reverts to the health summary.

Event ingestion

  • Origin: control-channel agent events, directed by ControlChannel.routeWorkActivity(from:).
  • Extracted fields:
    • stream: "job" paired with data.state to signal start/stop.
    • stream: "tool" alongside data.phase, data.name, and optionally data.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 UserDefaults key openclaw.iconOverride; translated into IconState.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.
807 words · updated Aug 3, 2026