Agent-readable wiki

cmux Feature Scout Wiki

A fast discovery wiki for cmux, a native macOS terminal workbench with agent-aware notifications, browser sidecars, automation, remote workspaces, and provider-neutral Cloud VM plumbing. The table of contents favors source-backed features worth demoing, copying, or productizing beyond the README.

Pages

  1. Feature Scout BriefThe product surface, the features worth exploring first, and why those features deserve attention beyond the README.
  2. Workspaces, Splits & Vertical SidebarHow cmux turns terminal tabs into a spatial workspace system with Bonsplit panes, draggable workspaces, sidebar metadata, pinning, and split equalization.
  3. Agent Notification Rings & InboxThe unread-state system that converts terminal notifications, hook events, and CLI notifications into pane rings, sidebar badges, a review page, and jump-to-unread workflows.
  4. Command Palette, Nucleo Search & Editable ShortcutsThe power-user command layer: native fuzzy search, usage boosts, settings toggles, shortcut recording, JSON-backed shortcut files, and tests that preserve keyboard routing behavior.
  5. CLI & Socket Automation APIThe scriptable control plane for workspaces, panes, surfaces, browser actions, notifications, windows, and VM commands, including tagged socket resolution and v2 method dispatch.
  6. Agent Hook Library & Permission FeedThe vendor-agnostic hook adapter layer for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and other agents, plus the feed path for approval and permission events.
  7. Session Vault & Agent ResumeThe restorable-agent index that scans agent stores, groups sessions by folder or agent, uses SQL for Codex state when available, and supports drag-to-resume workflows.
  8. Project Commands, Surface Buttons & Trust GatesThe configuration mechanics behind project-specific commands, surface tab bar buttons, notification hooks, workspace commands, and confirmation gates for local automation.
  9. In-App Browser Pane AutomationThe WKWebView sidecar experience for authenticated browser panes, omnibar behavior, find-in-page scripts, devtools affordances, and socket-driven browser automation.
  10. Files, Markdown & Preview SidecarsThe right-sidebar file workflow that mixes file search, terminal path insertion, command-click routing, Markdown rendering, PDF/image previews, and review-feedback surfaces.
  11. Remote Machines: SSH, Loopback & Cloud VMThe remote-work architecture that combines local SSH workspaces, remote localhost browser routing, image upload planning, Cloud VM attach endpoints, and a provider registry that stays BYOC and BYOK friendly.
  12. What To Demo, Copy or Productize NextA closing scout map of the strongest reusable product ideas: composable terminal primitives, source-portable skill and hook adapters, browser-plus-terminal automation, provider-neutral remote compute, and trust-gated project actions.

Complete Markdown

# cmux Feature Scout Wiki

> A fast discovery wiki for cmux, a native macOS terminal workbench with agent-aware notifications, browser sidecars, automation, remote workspaces, and provider-neutral Cloud VM plumbing. The table of contents favors source-backed features worth demoing, copying, or productizing beyond the README.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a)
- [GitHub repository](https://github.com/manaflow-ai/cmux)

## Repository Metadata

- Repository: manaflow-ai/cmux

- Generated: 2026-05-23T17:54:24.269Z
- Updated: 2026-05-23T17:55:54.525Z
- Runtime: Codex CLI
- Format: Feature Scout
- Pages: 12

## Page Index

- 01. [Feature Scout Brief](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/01-feature-scout-brief.md) - The product surface, the features worth exploring first, and why those features deserve attention beyond the README.
- 02. [Workspaces, Splits & Vertical Sidebar](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/02-workspaces-splits-vertical-sidebar.md) - How cmux turns terminal tabs into a spatial workspace system with Bonsplit panes, draggable workspaces, sidebar metadata, pinning, and split equalization.
- 03. [Agent Notification Rings & Inbox](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/03-agent-notification-rings-inbox.md) - The unread-state system that converts terminal notifications, hook events, and CLI notifications into pane rings, sidebar badges, a review page, and jump-to-unread workflows.
- 04. [Command Palette, Nucleo Search & Editable Shortcuts](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/04-command-palette-nucleo-search-editable-shortcuts.md) - The power-user command layer: native fuzzy search, usage boosts, settings toggles, shortcut recording, JSON-backed shortcut files, and tests that preserve keyboard routing behavior.
- 05. [CLI & Socket Automation API](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/05-cli-socket-automation-api.md) - The scriptable control plane for workspaces, panes, surfaces, browser actions, notifications, windows, and VM commands, including tagged socket resolution and v2 method dispatch.
- 06. [Agent Hook Library & Permission Feed](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/06-agent-hook-library-permission-feed.md) - The vendor-agnostic hook adapter layer for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and other agents, plus the feed path for approval and permission events.
- 07. [Session Vault & Agent Resume](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/07-session-vault-agent-resume.md) - The restorable-agent index that scans agent stores, groups sessions by folder or agent, uses SQL for Codex state when available, and supports drag-to-resume workflows.
- 08. [Project Commands, Surface Buttons & Trust Gates](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/08-project-commands-surface-buttons-trust-gates.md) - The configuration mechanics behind project-specific commands, surface tab bar buttons, notification hooks, workspace commands, and confirmation gates for local automation.
- 09. [In-App Browser Pane Automation](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/09-in-app-browser-pane-automation.md) - The WKWebView sidecar experience for authenticated browser panes, omnibar behavior, find-in-page scripts, devtools affordances, and socket-driven browser automation.
- 10. [Files, Markdown & Preview Sidecars](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/10-files-markdown-preview-sidecars.md) - The right-sidebar file workflow that mixes file search, terminal path insertion, command-click routing, Markdown rendering, PDF/image previews, and review-feedback surfaces.
- 11. [Remote Machines: SSH, Loopback & Cloud VM](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/11-remote-machines-ssh-loopback-cloud-vm.md) - The remote-work architecture that combines local SSH workspaces, remote localhost browser routing, image upload planning, Cloud VM attach endpoints, and a provider registry that stays BYOC and BYOK friendly.
- 12. [What To Demo, Copy or Productize Next](https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/12-what-to-demo-copy-or-productize-next.md) - A closing scout map of the strongest reusable product ideas: composable terminal primitives, source-portable skill and hook adapters, browser-plus-terminal automation, provider-neutral remote compute, and trust-gated project actions.

## Source File Index

- `CLI/CLISocketPathResolver.swift`
- `CLI/cmux.swift`
- `CLI/CMUXCLI+AgentHookDefinitions.swift`
- `CLI/CMUXCLI+HermesAgentHooks.swift`
- `CLI/CMUXCLI+SSHCommandSupport.swift`
- `CLI/SocketOperationTelemetry.swift`
- `cmuxTests/BrowserImportMappingTests.swift`
- `cmuxTests/BrowserPanelTests.swift`
- `cmuxTests/CLIGenericHookPersistenceTests.swift`
- `cmuxTests/CmuxConfigContextMenuTests.swift`
- `cmuxTests/CmuxConfigTests.swift`
- `cmuxTests/CommandPaletteNucleoFFITests.swift`
- `cmuxTests/FeedCoordinatorTests.swift`
- `cmuxTests/FileExplorerStoreTests.swift`
- `cmuxTests/FilePreviewReviewFeedbackTests.swift`
- `cmuxTests/RestorableAgentSessionIndexTests.swift`
- `cmuxTests/SessionIndexViewTests.swift`
- `cmuxTests/ShortcutAndCommandPaletteTests.swift`
- `cmuxTests/TerminalControllerSocketSecurityTests.swift`
- `cmuxTests/TerminalNotificationQueueTests.swift`
- `cmuxUITests/AutomationSocketUITests.swift`
- `cmuxUITests/BonsplitTabDragUITests.swift`
- `cmuxUITests/JumpToUnreadUITests.swift`
- `Native/CommandPaletteNucleoFFI/src/lib.rs`
- `package.json`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift`
- `README.md`
- `Sources/BrowserWindowPortal.swift`
- `Sources/Cloud/VMClient.swift`
- `Sources/Cloud/VMClientSocketCommands.swift`
- `Sources/cmuxApp.swift`
- `Sources/CmuxConfig.swift`
- `Sources/CmuxConfigExecutor.swift`
- `Sources/CmuxSurfaceTabBarBuiltInAction.swift`
- `Sources/CommandClickFileOpenRouter.swift`
- `Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift`
- `Sources/CommandPalette/CommandPaletteSettingsToggle.swift`
- `Sources/ContentView.swift`
- `Sources/Feed/FeedCoordinator.swift`
- `Sources/Feed/FeedPermissionActionPolicy.swift`
- `Sources/FileExplorerStore.swift`
- `Sources/FileExplorerTerminalPathInsertion.swift`
- `Sources/Find/BrowserFindJavaScript.swift`
- `Sources/Find/BrowserSearchOverlay.swift`
- `Sources/KeyboardShortcutSettings.swift`
- `Sources/KeyboardShortcutSettingsFileStore.swift`
- `Sources/NotificationsPage.swift`
- `Sources/Panels/BrowserPanel.swift`
- `Sources/Panels/BrowserPanelView.swift`
- `Sources/Panels/FilePreviewPanel.swift`
- `Sources/Panels/FilePreviewWorkspaceOpenSupport.swift`
- `Sources/Panels/MarkdownPanel.swift`
- `Sources/RemoteLoopbackRuntimeBridge.swift`
- `Sources/RestorableAgentTypes.swift`
- `Sources/SessionAgentPresentation.swift`
- `Sources/SessionIndexModels.swift`
- `Sources/SessionIndexStore.swift`
- `Sources/SessionIndexStore+CodexSQL.swift`
- `Sources/SessionIndexView.swift`
- `Sources/Settings/ConfigSettingsView.swift`
- `Sources/Sidebar/SidebarDropPlanner.swift`
- `Sources/Sidebar/SidebarState.swift`
- `Sources/TabManager.swift`
- `Sources/TerminalController.swift`
- `Sources/TerminalControllerV2ParamParsingSupport.swift`
- `Sources/TerminalImageTransfer.swift`
- `Sources/TerminalNotificationCallerResolver.swift`
- `Sources/TerminalNotificationPolicy.swift`
- `Sources/TerminalNotificationQueue.swift`
- `Sources/TerminalNotificationStore.swift`
- `Sources/Workspace.swift`
- `Sources/Workspace+EqualizeSplitsSupport.swift`
- `Sources/WorkspaceActionDispatcher.swift`
- `Sources/WorkspaceContentView.swift`
- `Sources/WorkspaceRemoteConfiguration.swift`
- `tests_v2/test_cli_browser_console_errors_text.py`
- `tests_v2/test_tab_dragging.py`
- `web/package.json`
- `web/services/vms/drivers/index.ts`
- `web/services/vms/drivers/types.ts`
- `web/services/vms/providerGateway.ts`
- `web/services/vms/workflows.ts`

---

## 01. Feature Scout Brief

> The product surface, the features worth exploring first, and why those features deserve attention beyond the README.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/01-feature-scout-brief.md
- Generated: 2026-05-23T17:52:19.296Z

### Source Files

- `README.md`
- `package.json`
- `web/package.json`
- `Sources/cmuxApp.swift`
- `CLI/cmux.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [README.md](README.md)
- [package.json](package.json)
- [web/package.json](web/package.json)
- [Sources/cmuxApp.swift](Sources/cmuxApp.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [docs/cli-contract.md](docs/cli-contract.md)
- [docs/events.md](docs/events.md)
- [CLI/CMUXCLI+AgentHookDefinitions.swift](CLI/CMUXCLI+AgentHookDefinitions.swift)
- [CLI/CMUXCLI+Events.swift](CLI/CMUXCLI+Events.swift)
- [Sources/Panels/BrowserAutomation.swift](Sources/Panels/BrowserAutomation.swift)
- [Sources/Cloud/VMClientSocketCommands.swift](Sources/Cloud/VMClientSocketCommands.swift)
- [web/services/vms/drivers/types.ts](web/services/vms/drivers/types.ts)
- [web/services/vms/drivers/index.ts](web/services/vms/drivers/index.ts)
- [web/services/vms/providerGateway.ts](web/services/vms/providerGateway.ts)
- [web/services/vms/workflows.ts](web/services/vms/workflows.ts)
- [web/services/vms/config.ts](web/services/vms/config.ts)
- [web/app/api/vm/route.ts](web/app/api/vm/route.ts)
- [web/app/api/vm/[id]/route.ts](web/app/api/vm/[id]/route.ts)
- [web/data/cmux.schema.json](web/data/cmux.schema.json)
- [docs/cloud-vm-backend-rollout-todo.md](docs/cloud-vm-backend-rollout-todo.md)
- [Sources/CommandPalette/CommandPaletteOverlay.swift](Sources/CommandPalette/CommandPaletteOverlay.swift)
- [Sources/ContentView+ViewCommandPalette.swift](Sources/ContentView+ViewCommandPalette.swift)
</details>

# Feature Scout Brief

cmux is easiest to understand as a set of composable local primitives: a native macOS terminal, embedded browser, workspace/sidebar model, notification system, CLI, socket API, event stream, and an emerging Cloud VM control plane. The README explains the product promise, but the codebase shows which features are mature enough to demo, extend, or copy into adjacent tools.

Knowledge-profile note: no `STRATEGY.md` or `docs/solutions/**` files were present in this checkout during the focused source pass, so this page uses repository code and docs as source of truth. The Compound Engineering profile was treated as bundled page-shape guidance, not as proof that a local installed skill ran.

Sources: [README.md:119-139](), [package.json:1-9](), [web/package.json:6-30]()

## Product Surface Map

```text
Native app              Local automation                 Web/control plane
----------              ----------------                 -----------------
Swift/AppKit app  <---> Unix socket + CLI + events <---> Next.js VM API
Ghostty terminal        browser commands                 provider gateway
workspace/sidebar       notifications/hooks              Postgres-backed VM workflow
embedded browser        custom actions/config            E2B/Freestyle drivers
```

The scout-worthy surface is not one feature; it is the way local UI, terminal automation, browser automation, and optional remote compute share handles for windows, workspaces, panes, surfaces, notifications, and VMs. The app entrypoint wires native app state, Ghostty environment setup, settings, notification commands, debug windows, workspace commands, and command-palette entrypoints. The CLI contract preserves stable command names and handle formats for scripting. The web package adds a Next.js backend with Cloud VM scripts and dependencies.

Sources: [Sources/cmuxApp.swift:25-86](), [Sources/cmuxApp.swift:206-245](), [Sources/cmuxApp.swift:286-327](), [Sources/cmuxApp.swift:500-579](), [docs/cli-contract.md:26-56](), [web/package.json:6-30]()

## Explore First

| Feature | Why it deserves attention beyond the README | Best first demo | Caution |
| --- | --- | --- | --- |
| Scriptable browser pane | It exposes agent-browser-style navigation, snapshots, actions, state, cookies, network, console, and trace commands from the CLI. | Open a browser split, navigate, snapshot, click/fill, then request `--snapshot-after`. | It depends on an existing browser surface for most commands. |
| Notification and event fabric | Notifications are not just UI badges; they are socket commands plus a reconnectable JSONL/event stream. | Trigger `cmux notify`, inspect notification panel, then stream `cmux events --category notification`. | Event payloads redact sensitive fields; consumers must use snapshots for catch-up. |
| Agent hook bridge | Hook definitions cover multiple coding agents and feed events, with per-agent config locations and disable env vars. | Install hooks for one supported agent and watch workspace/feed events. | Agent integrations differ by hook format and upstream support. |
| CLI/socket topology control | The CLI covers windows, workspaces, panes, surfaces, terminal input, browser actions, notifications, status/log metadata, and raw RPC. | Script a workspace with a split, terminal send, browser open, and status pill. | Preserve focus semantics; not every command should activate the app. |
| Cloud VMs | The CLI, native socket methods, Next.js routes, Effect workflows, and provider drivers form a provider-neutral remote workspace path. | `cmux vm new --provider e2b`, attach, exec, destroy in a configured environment. | Rollout docs show Freestyle blockers and operational TODOs, so treat as early-access infrastructure. |
| Configurable product surface | `cmux.json` supports actions, commands, hooks, UI wiring, app preferences, and workspace layouts. | Add a project-local action/command and invoke it from Command Palette or UI wiring. | Project-local commands need trust and portability review. |

Sources: [docs/cli-contract.md:57-168](), [CLI/cmux.swift:13229-13295](), [docs/events.md:1-32](), [CLI/CMUXCLI+AgentHookDefinitions.swift:122-297](), [web/data/cmux.schema.json:1-41]()

## Browser Automation Is A Product Primitive

The embedded browser is more than a visual panel. The CLI help exposes a broad automation grammar: open, navigate, snapshot, eval, wait, click, type, select, screenshot, query getters, accessibility-style finders, profiles, import, cookies, storage, tabs, console/errors, scripts/styles, viewport, geolocation, offline mode, trace, network routing, screencast, and raw input. That makes cmux a practical local harness for agents that need to inspect and operate a dev server without leaving the terminal/workspace model.

A small but important detail: `open`, `open-split`, and `new` can run without an explicit surface, while most operations require a surface handle. That keeps the model deterministic for agents: create or identify a browser surface, then operate on that handle.

```swift
// CLI/cmux.swift
Usage: cmux browser [--surface <id|ref|index> | <surface>] <subcommand> [args]
Browser automation commands. Most subcommands require a surface handle.
```

Browser import and profile management are also implemented as automation paths, not just settings UI. `BrowserImportAutomation.importCookies` detects installed browsers, selects source profiles, supports destination profile resolution/creation, and can filter imported cookies by domain. This is worth exploring because authenticated browser panes are often the difference between a toy demo and a useful agent workflow.

Sources: [CLI/cmux.swift:9705-9768](), [CLI/cmux.swift:10232-10494](), [CLI/cmux.swift:13229-13295](), [Sources/Panels/BrowserAutomation.swift:367-412](), [Sources/Panels/BrowserAutomation.swift:415-523]()

## Notifications, Sidebar State, And Events

The README frames notifications as blue rings, sidebar badges, and a panel. The deeper feature is that notifications, sidebar metadata, progress, logs, browser events, workspace lifecycle, surface input, and agent hooks can all flow through a reconnectable event stream. The event stream writes newline-delimited JSON over the existing cmux socket, starts with an ack frame, supports category/name filters, and has replay/cursor semantics.

That matters for extensions and Grok-Wiki-style product intelligence: a companion view can bootstrap from snapshots, subscribe to workspace/notification/sidebar events, and update derived state without polling. The event docs explicitly warn that prompt and input fields are redacted, so integrations should preserve local privacy boundaries and forward sensitive context only with user opt-in.

Sources: [README.md:125-129](), [docs/events.md:1-32](), [docs/events.md:34-93](), [CLI/CMUXCLI+Events.swift:19-103](), [docs/events.md:216-255](), [docs/events.md:297-342]()

## Agent Hooks Are Vendor-Agnostic Glue

cmux is not locked to a single coding agent. The hook definition table includes Codex, Grok, OpenCode, Pi, Amp, Cursor, Gemini, Antigravity, Rovo Dev, Hermes Agent, Copilot, CodeBuddy, Factory, and Qoder. Each definition names its config directory/file, binary or env override where relevant, session store suffix, disable env var, hook marker, format, lifecycle events, and feed-hook events.

This is one of the strongest BYOC/BYOK-friendly areas of the product. The integration shape is “adapt an agent’s lifecycle events into cmux notifications/feed/session state,” not “call one hosted model provider.” A scout should look for which agents have enough hook surface to support session start, prompt submit, stop, notification, permission/tool events, and session end.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:108-135](), [CLI/CMUXCLI+AgentHookDefinitions.swift:136-217](), [CLI/CMUXCLI+AgentHookDefinitions.swift:218-297](), [CLI/CMUXCLI+AgentHookDefinitions.swift:304-320]()

## CLI And Socket API Are The Integration Boundary

The CLI contract is unusually important because it documents behavior that must survive an implementation rewrite to Swift ArgumentParser. It requires no-socket help/version behavior, stable global options, JSON output where supported, `--id-format`, socket/password routing, and UUID/ref/index handles for windows, workspaces, panes, surfaces, and tabs.

For product scouts, this means cmux can be copied or extended as an automation substrate. Workflows should prefer stable CLI/socket commands over UI scraping: create workspaces, split panes, move surfaces, send input, read screens, control browser panes, manage notifications, and stream events. The raw `rpc` command is an escape hatch for v2 socket methods, but polished workflows should use named commands when they exist.

Sources: [docs/cli-contract.md:1-24](), [docs/cli-contract.md:57-168](), [CLI/cmux.swift:2669-2765](), [CLI/cmux.swift:3387-3390]()

## Cloud VMs Are The Remote Workspace Bet

Cloud VM support spans native CLI commands, app socket methods, authenticated Next.js REST routes, Effect-based workflows, and provider drivers. The CLI supports listing, creating, attaching/shelling, destroying, SSH info, and remote exec. The socket layer exposes `vm.list`, `vm.create`, `vm.destroy`, `vm.exec`, `vm.ssh_info`, and `vm.attach_info`. The REST layer keeps provider credentials behind server-side auth and ownership checks.

The provider contract is explicitly portable: `ProviderId` is currently `"e2b" | "freestyle"`, and callers go through a `VMProvider` interface with create/destroy/status/exec/snapshot/restore/attach/SSH/revoke operations. The gateway calls `getProvider(provider)` instead of embedding provider-specific logic in workflows. Runtime kill switches exist globally and per provider.

```ts
// web/services/vms/drivers/types.ts
export type ProviderId = "e2b" | "freestyle";

export interface VMProvider {
  create(options: CreateOptions): Promise<VMHandle>;
  destroy(vmId: string): Promise<void>;
  exec(vmId: string, command: string, opts?: { timeoutMs?: number }): Promise<ExecResult>;
  openAttach(vmId: string, options?: AttachOptions): Promise<AttachEndpoint>;
  openSSH(vmId: string): Promise<SSHEndpoint>;
}
```

The scout conclusion: this is the most strategically interesting surface, but not the safest first-copy feature. The rollout TODO says VM logic already runs in the Vercel Next app with Postgres state, while also noting Freestyle snapshot blockers and that E2B is the current staging/production default. Demo it only in configured environments and keep the architecture provider-neutral.

Sources: [CLI/cmux.swift:3146-3384](), [Sources/Cloud/VMClientSocketCommands.swift:3-74](), [web/app/api/vm/route.ts:1-45](), [web/app/api/vm/route.ts:93-229](), [web/app/api/vm/route.ts:231-327](), [web/app/api/vm/[id]/route.ts:12-31](), [web/services/vms/drivers/types.ts:1-18](), [web/services/vms/drivers/types.ts:76-104](), [web/services/vms/drivers/index.ts:8-30](), [web/services/vms/providerGateway.ts:17-76](), [web/services/vms/config.ts:6-35](), [docs/cloud-vm-backend-rollout-todo.md:5-20](), [docs/cloud-vm-backend-rollout-todo.md:41-56]()

## Customization And Command Palette

The config schema is a strong feature-scout signal because it turns cmux from an app into a local workflow host. Global `cmux.json` supports app settings, shortcuts, actions, custom commands, notification hooks, and workspace layouts. Project-local `.cmux/cmux.json` supports actions, commands, hooks, and UI action wiring. The schema describes `actions` as a registry used by the surface tab bar, Command Palette, shortcuts, and plus-button menu.

The command palette implementation is also worth scouting because it treats command rows as rendered state with IDs, titles, match indices, trailing labels, selected index, and stable row actions. Built-in command contributions include examples such as “Flash Focused Panel” and “Task Manager,” showing the pattern for adding locally discoverable actions without making every workflow a top-level menu item.

Sources: [web/data/cmux.schema.json:1-41](), [web/data/cmux.schema.json:158-175](), [Sources/CommandPalette/CommandPaletteOverlay.swift:14-31](), [Sources/CommandPalette/CommandPaletteOverlay.swift:64-158](), [Sources/ContentView+ViewCommandPalette.swift:3-32]()

## Grok-Wiki Integration Angle

A provider-neutral Grok-Wiki integration should present these features as cited, portable product cards rather than as model-provider-specific magic. The source adapters should accept file-backed pages, repository-backed source ranges, and catalog/skill-pack metadata as separate evidence classes. A good UI flow would let a reader filter by surface: “Native app,” “CLI/socket,” “Browser automation,” “Agent hooks,” “Cloud VM,” and “Config/customization.” Each card should show what to demo, what code owns it, what is mature, and what is still rollout-sensitive.

For BYOC/BYOK support, keep the integration boundary at local commands, event streams, config files, and provider interfaces. Do not assume a hosted model, a proprietary connector, or a single VM provider. The code already points in that direction: agent hooks are file/config based, browser automation is local socket driven, and Cloud VM providers sit behind a driver contract.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:122-297](), [docs/events.md:248-255](), [web/services/vms/drivers/types.ts:76-104](), [web/data/cmux.schema.json:1-41]()

## Final Scout Take

Start with browser automation, notifications/events, and CLI/socket topology because they are local, visible, and well documented in code. Then explore agent hooks as the vendor-neutral bridge into real coding-agent workflows. Treat Cloud VMs as the bigger product direction: highly promising, already wired through CLI/app/web/provider layers, but still carrying rollout caveats that deserve careful operational validation before broad productization.

Sources: [README.md:131-139](), [docs/cli-contract.md:57-168](), [CLI/cmux.swift:13229-13295](), [docs/cloud-vm-backend-rollout-todo.md:72-107]()

---

## 02. Workspaces, Splits & Vertical Sidebar

> How cmux turns terminal tabs into a spatial workspace system with Bonsplit panes, draggable workspaces, sidebar metadata, pinning, and split equalization.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/02-workspaces-splits-vertical-sidebar.md
- Generated: 2026-05-23T17:52:40.603Z

### Source Files

- `Sources/ContentView.swift`
- `Sources/Workspace.swift`
- `Sources/WorkspaceContentView.swift`
- `Sources/TabManager.swift`
- `Sources/Sidebar/SidebarState.swift`
- `Sources/Sidebar/SidebarDropPlanner.swift`
- `Sources/Workspace+EqualizeSplitsSupport.swift`
- `cmuxUITests/BonsplitTabDragUITests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/ContentView.swift](Sources/ContentView.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [Sources/WorkspaceContentView.swift](Sources/WorkspaceContentView.swift)
- [Sources/TabManager.swift](Sources/TabManager.swift)
- [Sources/Sidebar/SidebarState.swift](Sources/Sidebar/SidebarState.swift)
- [Sources/Sidebar/SidebarDropPlanner.swift](Sources/Sidebar/SidebarDropPlanner.swift)
- [Sources/Workspace+EqualizeSplitsSupport.swift](Sources/Workspace+EqualizeSplitsSupport.swift)
- [Sources/TabManager+EqualizeSplits.swift](Sources/TabManager+EqualizeSplits.swift)
- [Sources/SplitEqualizer.swift](Sources/SplitEqualizer.swift)
- [Sources/cmuxApp+EqualizeSplitsMenu.swift](Sources/cmuxApp+EqualizeSplitsMenu.swift)
- [Sources/AppDelegate+EqualizeSplitsShortcut.swift](Sources/AppDelegate+EqualizeSplitsShortcut.swift)
- [Sources/GhosttyTerminalView.swift](Sources/GhosttyTerminalView.swift)
- [cmuxUITests/BonsplitTabDragUITests.swift](cmuxUITests/BonsplitTabDragUITests.swift)
- [cmuxTests/TabManagerUnitTests.swift](cmuxTests/TabManagerUnitTests.swift)
</details>

# Workspaces, Splits & Vertical Sidebar

cmux treats a terminal session as more than a flat tab. A workspace is the outer navigation unit, while Bonsplit manages panes inside that workspace and each pane can hold multiple terminal or browser surfaces. The vertical sidebar is the product surface that makes this model usable: it shows workspace identity, status, branch/directory metadata, unread signals, pin state, and drop targets.

This page follows the bundled Compound Engineering page-shape guidance provided for this run; implementation claims below are grounded in repository code and tests, not an installed local skill execution.

## Spatial Model

```text
Window / TabManager
  -> Workspace list in vertical sidebar
       -> Workspace
            -> BonsplitController split tree
                 -> Pane
                      -> Bonsplit tab
                           -> cmux Panel: TerminalPanel or BrowserPanel
```

`TabManager` still calls the outer list `tabs`, but the stored objects are `Workspace` instances. Each `Workspace` owns a `BonsplitController`, a panel map, and published workspace metadata such as title, custom description, pin state, color, current directory, panel titles, panel directories, and per-panel pin/unread state. `WorkspaceContentView` renders that controller through `BonsplitView`, mapping each Bonsplit tab back to a cmux panel before rendering `PanelContentView`.

Sources: [Sources/TabManager.swift:1104-1127](), [Sources/Workspace.swift:8976-9038](), [Sources/Workspace.swift:9121-9126](), [Sources/WorkspaceContentView.swift:148-190](), [Sources/WorkspaceContentView.swift:218-289]()

## Workspace Creation And Bonsplit Ownership

Creating a workspace is a `TabManager` responsibility. `addWorkspace` snapshots the current workspace list, derives working directory and config inheritance, computes an insertion index, creates a `Workspace`, wires ownership, inserts it into the live `tabs` array, and optionally selects it. The `Workspace` constructor then creates an initial terminal panel, creates a Bonsplit tab for it, removes Bonsplit’s default welcome tab, wires external drop/close/zoom callbacks, sets itself as the Bonsplit delegate, and forces initial pane/tab focus so the workspace starts interactive.

Sources: [Sources/TabManager.swift:2500-2585](), [Sources/Workspace.swift:9700-9810]()

## Vertical Sidebar As Workspace Index

The left sidebar is mounted by `ContentView` as `VerticalTabsSidebar` and framed at the current sidebar width. `SidebarState` keeps only visibility and persisted width, while `ContentView` sanitizes restored width, persists width changes, and schedules portal geometry synchronization when the sidebar changes. This keeps the sidebar layout separate from terminal/browser portal geometry.

Sources: [Sources/ContentView.swift:1977-1997](), [Sources/ContentView.swift:2148-2152](), [Sources/ContentView.swift:2626-2640](), [Sources/ContentView.swift:3107-3182](), [Sources/Sidebar/SidebarState.swift:4-17]()

The sidebar body builds a render context from `tabManager.tabs`, selected workspace ids, shortcut hints, remote workspace state, and terminal scrollbar visibility. It uses a non-lazy `VStack` for workspace rows because drag-state invalidations can recurse through lazy layout. Each row is an equatable `TabItemView` with precomputed inputs and plain object references for action handlers, reducing unnecessary row redraws while still allowing actions such as selection, drag, context menu, close, and move accessibility actions.

Sources: [Sources/ContentView.swift:9592-9846](), [Sources/ContentView.swift:10737-10922](), [Sources/ContentView.swift:13299-13415](), [Sources/ContentView.swift:14048-14100]()

## Sidebar Metadata

Sidebar rows are not just labels. The row snapshot can include title, custom description, pin state, custom color, remote workspace text/status/help, latest conversation or notification text, structured metadata entries/blocks, latest logs, progress, branch/directory lines, pull request rows, and listening ports. The row renders those details conditionally based on sidebar settings, so the same workspace model can support compact and information-dense sidebar modes.

Sources: [Sources/ContentView.swift:13321-13343](), [Sources/ContentView.swift:13628-13743](), [Sources/ContentView.swift:13743-13907]()

## Reordering Workspaces

Workspace reordering flows through the sidebar row drag/drop delegates and then into `TabManager.reorderWorkspace`. The sidebar delegate computes a legal target index from the current workspace ids, current drop indicator, and pinned workspace ids. `TabManager` applies the reorder, posts `.workspaceOrderDidChange`, and publishes a `CmuxEventBus` reorder event with full workspace ordering and pinned workspace ids.

Sources: [Sources/ContentView.swift:15682-15865](), [Sources/Sidebar/SidebarDropPlanner.swift:14-84](), [Sources/TabManager.swift:5400-5435]()

Pinned workspaces form a protected leading group. The drop planner clamps unpinned workspaces after the pinned count and pinned workspaces within the pinned region. `TabManager` applies the same rule when toggling pin state or clamping a direct reorder index, so sidebar drag/drop and programmatic reorder stay consistent.

Sources: [Sources/Sidebar/SidebarDropPlanner.swift:192-224](), [Sources/TabManager.swift:5561-5589]()

## Dragging Bonsplit Tabs Into Workspaces

The sidebar also accepts Bonsplit tab drags. Dropping a Bonsplit tab onto an existing workspace calls `AppDelegate.moveBonsplitTab` through the sidebar drop delegate. Dropping over the workspace-list geometry can also choose between an existing workspace and a new workspace insertion point; the overlay builds `WorkspaceDropTarget` values from row frames, then calls either `moveBonsplitTab` or `moveBonsplitTabToNewWorkspace`.

Sources: [Sources/ContentView.swift:10758-10821](), [Sources/ContentView.swift:15682-15723](), [Sources/Sidebar/SidebarDropPlanner.swift:86-168]()

## Pane And Surface Pinning

cmux has two pin layers. Workspace pinning is on `Workspace.isPinned` and controls the outer sidebar order. Surface pinning is per panel inside a workspace via `pinnedPanelIds`; changing it updates the Bonsplit tab and normalizes the pane’s tab order so pinned surfaces stay before unpinned surfaces. Bonsplit tab context actions expose this as `togglePin`.

Sources: [Sources/Workspace.swift:8995-9000](), [Sources/Workspace.swift:10325-10348](), [Sources/Workspace.swift:10470-10484](), [Sources/Workspace.swift:16910-16928]()

## Split Creation And Pane Events

Programmatic terminal and browser splits pre-create the cmux panel, pre-generate a Bonsplit tab, install the tab-to-panel mapping, then call `bonsplitController.splitPane`. This avoids transient empty panels and lets cmux publish split-created events with kind and origin. Terminal splits inherit working directory and terminal config from the source panel where possible; browser splits carry browser profile and remote workspace proxy context.

Sources: [Sources/Workspace.swift:12312-12500](), [Sources/Workspace.swift:12615-12712]()

When Bonsplit itself reports a split, the delegate distinguishes programmatic splits from UI splits. Programmatic splits only normalize pins and reconcile geometry. UI-originated splits auto-create a terminal in the new pane, and drag-to-split repairs placeholder-only source panes by replacing placeholder tabs with real terminal surfaces or dropping extra placeholders.

Sources: [Sources/Workspace.swift:16560-16608](), [Sources/Workspace.swift:16610-16690](), [Sources/Workspace.swift:16693-16755]()

## Split Equalization

Equalization is a shared action, not a one-off UI patch. The command palette, menu command, app shortcut, and Ghostty action all call `TabManager.equalizeSplits`. That method runs `SplitEqualizer` over the Bonsplit tree snapshot, then tells the workspace that split geometry changed so normal geometry reconciliation flows run.

```swift
// Sources/SplitEqualizer.swift
let totalLeafCount = firstLeafCount + secondLeafCount
let position = CGFloat(firstLeafCount) / CGFloat(totalLeafCount)
controller.setDividerPosition(position, forSplit: splitId, fromExternal: true)
```

`SplitEqualizer` recursively counts leaves under each split and sets each divider proportionally, so a three-leaf tree becomes balanced by subtree leaf count rather than blindly setting every divider to 50%.

Sources: [Sources/TabManager+EqualizeSplits.swift:3-22](), [Sources/SplitEqualizer.swift:5-75](), [Sources/Workspace+EqualizeSplitsSupport.swift:1-4](), [Sources/ContentView.swift:7288-7307](), [Sources/ContentView.swift:7894-7906](), [Sources/cmuxApp+EqualizeSplitsMenu.swift:3-16](), [Sources/AppDelegate+EqualizeSplitsShortcut.swift:1-22](), [Sources/GhosttyTerminalView.swift:4437-4444]()

## Verification Surface

The UI tests cover the high-risk parts of this model: Bonsplit tab drag reorder, sidebar row top inset across minimal and standard presentation modes, standard titlebar control placement, and minimal-mode hover reveal behavior for sidebar controls. Unit coverage also asserts that equalization succeeds and produces a proportional split tree.

Sources: [cmuxUITests/BonsplitTabDragUITests.swift:18-68](), [cmuxUITests/BonsplitTabDragUITests.swift:307-470](), [cmuxTests/TabManagerUnitTests.swift:2268-2285]()

## Product And Architecture Notes

This design stays BYOC/BYOK friendly because the workspace, split, sidebar, pinning, and equalization model is local app state plus repository code. The sidebar metadata can display remote workspace and provider-sourced details, but the core interaction model does not require a specific model provider, hosted connector, or proprietary catalog. A Grok-Wiki-style integration should therefore document the feature from portable source artifacts: files, repository history, tests, and optional skill-pack text as guidance, while treating the code paths above as source of truth.

The key reusable idea is the split between spatial state and presentation state: `Workspace` owns Bonsplit and panel identity, `TabManager` owns workspace ordering and selection, `VerticalTabsSidebar` owns the navigable index, and `SplitEqualizer` owns layout balancing. That separation is what lets cmux support draggable workspaces, Bonsplit pane tabs, sidebar metadata, pin boundaries, and equalization without making any one UI surface the only implementation path. Sources: [Sources/Workspace.swift:8976-9038](), [Sources/TabManager.swift:5400-5435](), [Sources/ContentView.swift:10758-10922](), [Sources/SplitEqualizer.swift:14-75]()

---

## 03. Agent Notification Rings & Inbox

> The unread-state system that converts terminal notifications, hook events, and CLI notifications into pane rings, sidebar badges, a review page, and jump-to-unread workflows.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/03-agent-notification-rings-inbox.md
- Generated: 2026-05-23T17:52:46.225Z

### Source Files

- `Sources/TerminalNotificationStore.swift`
- `Sources/TerminalNotificationQueue.swift`
- `Sources/TerminalNotificationPolicy.swift`
- `Sources/NotificationsPage.swift`
- `Sources/TerminalNotificationCallerResolver.swift`
- `cmuxTests/TerminalNotificationQueueTests.swift`
- `cmuxUITests/JumpToUnreadUITests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/TerminalNotificationStore.swift](Sources/TerminalNotificationStore.swift)
- [Sources/TerminalNotificationQueue.swift](Sources/TerminalNotificationQueue.swift)
- [Sources/TerminalNotificationPolicy.swift](Sources/TerminalNotificationPolicy.swift)
- [Sources/NotificationsPage.swift](Sources/NotificationsPage.swift)
- [Sources/TerminalNotificationCallerResolver.swift](Sources/TerminalNotificationCallerResolver.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [Sources/GhosttyTerminalView.swift](Sources/GhosttyTerminalView.swift)
- [Sources/App/MenuBarExtraController.swift](Sources/App/MenuBarExtraController.swift)
- [Sources/AppDelegate.swift](Sources/AppDelegate.swift)
- [Sources/WorkspaceContentView.swift](Sources/WorkspaceContentView.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [Sources/CmuxConfig.swift](Sources/CmuxConfig.swift)
- [Sources/Feed/FeedCoordinator.swift](Sources/Feed/FeedCoordinator.swift)
- [cmuxTests/TerminalNotificationQueueTests.swift](cmuxTests/TerminalNotificationQueueTests.swift)
- [cmuxTests/TerminalNotificationCallerTests.swift](cmuxTests/TerminalNotificationCallerTests.swift)
- [cmuxTests/JumpToUnreadUITests.swift](cmuxUITests/JumpToUnreadUITests.swift)
- [cmuxTests/NotificationAndMenuBarTests.swift](cmuxTests/NotificationAndMenuBarTests.swift)
</details>

# Agent Notification Rings & Inbox

cmux turns terminal and agent activity into a shared unread-state system: terminal desktop-notification actions, socket/CLI notification calls, feed permission events, and policy hooks all converge on `TerminalNotificationStore`. From there, the app derives pane rings, Bonsplit tab badges, the Notifications page, menu bar counts, dock badges, and jump-to-unread behavior.

This page is written as a feature scout: it identifies the reusable design, the concrete code paths that make it work, and the failure modes worth preserving if this system is copied or extended. The selected Compound Engineering profile was used only as page-shape guidance; no repository `STRATEGY.md` or `docs/solutions/**` source was present in this checkout, so implementation claims below cite cmux source files only.

Sources: [Sources/TerminalNotificationStore.swift:767-895](), [Sources/GhosttyTerminalView.swift:4268-4291](), [Sources/TerminalController.swift:3493-3513]()

## Product Workflow

A notification can arrive from several places:

- A terminal app action emits a desktop notification through Ghostty integration.
- A socket or CLI client calls `notification.create`, `notification.create_for_surface`, `notification.create_for_target`, or `notification.create_for_caller`.
- Feed/coordinator code builds a policy envelope for hook-controlled external alerts.
- A notification hook rewrites the payload or disables selected effects before the store records or delivers it.

The store then produces four user-facing outcomes:

| Outcome | What the user sees | Backing state |
| --- | --- | --- |
| Inbox row | A row in the Notifications page with unread dot, title, body, time, and workspace title | `notifications: [TerminalNotification]` |
| Pane or tab ring | A visible unread indicator on the pane/tab that owns the surface | `hasVisibleNotificationIndicator` plus workspace panel unread state |
| Menu/status badge | Menu bar icon count, inline recent notification list, menu actions | `NotificationMenuSnapshot` |
| Jump target | `Jump to Latest Unread` opens the newest actionable unread item or workspace unread indicator | `jumpToLatestUnread` and workspace unread fallbacks |

Sources: [Sources/NotificationsPage.swift:16-45](), [Sources/WorkspaceContentView.swift:218-239](), [Sources/App/MenuBarExtraController.swift:166-190](), [Sources/AppDelegate.swift:11026-11047]()

```text
Terminal/Ghostty action       Socket/CLI notification       Feed/hook event
        |                              |                          |
        v                              v                          v
TerminalNotificationStore.addNotification / TerminalMutationBus queue
        |
        v
Policy hooks can rewrite payload and effect flags
        |
        v
Recorded notification + derived indexes + side effects
        |
        +--> pane rings / Bonsplit badges
        +--> Notifications page rows
        +--> menu bar and dock counts
        +--> jump-to-unread navigation
```

## Core Model: Notifications Plus Derived Unread Indexes

`TerminalNotification` is the record type. It carries workspace ID, optional surface and panel IDs, title/subtitle/body, creation time, read state, whether pane flash is allowed, and an optional click action such as reveal-in-Finder. Matching is panel-aware: a notification can match either its `surfaceId` or its resolved `panelId`, which matters when panes and surfaces are rebound.

The store keeps notifications newest-first and rebuilds indexes whenever the array changes. Those indexes track total unread count, unread count by workspace, unread surface keys, latest unread notification by workspace, and latest notification by workspace. Workspace-level manual, panel-derived, and restored unread indicators are counted separately so a workspace can be visibly unread even if it has no notification row.

Sources: [Sources/TerminalNotificationStore.swift:694-739](), [Sources/TerminalNotificationStore.swift:749-786](), [Sources/TerminalNotificationStore.swift:873-895](), [Sources/TerminalNotificationStore.swift:2126-2148]()

```swift
// Sources/TerminalNotificationStore.swift
private static func buildIndexes(for notifications: [TerminalNotification]) -> NotificationIndexes {
    var indexes = NotificationIndexes()
    for notification in notifications {
        if indexes.latestByTabId[notification.tabId] == nil {
            indexes.latestByTabId[notification.tabId] = notification
        }
        guard !notification.isRead else { continue }
        indexes.unreadCount += 1
        indexes.unreadCountByTabId[notification.tabId, default: 0] += 1
        indexes.unreadByTabSurface.insert(
            TabSurfaceKey(tabId: notification.tabId, surfaceId: notification.surfaceId)
        )
    }
    return indexes
}
```

## Ingress Paths

### Terminal Notifications

Ghostty desktop-notification actions are converted into store notifications. The code resolves the selected workspace and focused surface, respects workspace suppression for raw terminal notifications, uses the tab title as a fallback title, and records through `TerminalNotificationStore.shared.addNotification`.

Sources: [Sources/GhosttyTerminalView.swift:4268-4291](), [Sources/GhosttyTerminalView.swift:4544-4564]()

### CLI and Socket Notifications

The V2 socket router exposes notification operations for create, caller-targeted create, surface-targeted create, target-targeted create, list, clear, dismiss, mark-read, open, and jump-to-unread. Direct create methods resolve a workspace and surface, then call `deliverNotificationSynchronously`, which discards conflicting queued notifications for the same target before adding to the store. The caller-specific path resolves stale environment data by considering preferred workspace/surface IDs, TTY identity, registered window contexts, and the selected workspace.

Sources: [Sources/TerminalController.swift:3493-3513](), [Sources/TerminalController.swift:10222-10250](), [Sources/TerminalController.swift:10254-10323](), [Sources/TerminalNotificationQueue.swift:357-378](), [Sources/TerminalNotificationCallerResolver.swift:11-89]()

### Feed and Hook Events

Feed notifications use the same policy envelope shape, but default to effects that do not record inbox rows, mark unread, reorder workspaces, play sound, run commands, or flash panes. This lets feed events request external notification behavior while staying out of the pane-ring workflow unless policy changes it.

Sources: [Sources/Feed/FeedCoordinator.swift:696-748]()

## Queueing and Coalescing

`TerminalMutationBus` is the async boundary for socket-originated notification mutations. It stores pending mutations behind an `NSLock`, assigns sequences, batches up to 16 mutations per main-actor drain, and can coalesce repeated notification deliveries by generation plus `(tabId, surfaceId)`. Clear mutations drop matching pending notification deliveries before they drain, which prevents stale unread rings from appearing after a clear.

The boundary-generation methods are important: `markNotificationClearBoundary` advances the generation so a clear can discard older queued notifications without deleting fresh ones that arrived after the boundary.

Sources: [Sources/TerminalNotificationQueue.swift:35-90](), [Sources/TerminalNotificationQueue.swift:122-166](), [Sources/TerminalNotificationQueue.swift:168-233](), [Sources/TerminalNotificationQueue.swift:273-354]()

Tests lock in the main queue invariants: clearing drops a queued notification before drain, a queued clear only removes older notifications, regular queueing coalesces repeated same-surface events, and explicit async notify commands still deliver both side effects while only the latest record remains.

Sources: [cmuxTests/TerminalNotificationQueueTests.swift:96-142](), [cmuxTests/TerminalNotificationQueueTests.swift:144-197](), [cmuxTests/TerminalNotificationQueueTests.swift:199-255](), [cmuxTests/TerminalNotificationQueueTests.swift:257-360]()

## Policy Hooks and Provider Neutrality

Notification policy is intentionally provider-neutral. Hooks are resolved from cmux configuration files and executed as local shell commands; they receive JSON on stdin plus environment variables such as `CMUX_NOTIFICATION_TITLE`, `CMUX_NOTIFICATION_BODY`, `CMUX_NOTIFICATION_WORKSPACE_ID`, and `CMUX_NOTIFICATION_POLICY_JSON`. A hook can return a partial JSON patch that changes notification fields, effect flags, or `stop`.

This is BYOC/BYOK friendly because the architecture does not require any hosted model provider or proprietary connector. A hook can be a local script, repo script, catalog-provided command, or user-managed binary. A Grok-Wiki integration should document and surface these hooks as portable file/repository/catalog sources, not as a dependency on one model service.

Sources: [Sources/TerminalNotificationPolicy.swift:5-29](), [Sources/TerminalNotificationPolicy.swift:231-279](), [Sources/TerminalNotificationPolicy.swift:553-562](), [Sources/TerminalNotificationPolicy.swift:763-818](), [Sources/TerminalNotificationPolicy.swift:913-928](), [Sources/CmuxConfig.swift:2282-2342]()

| Effect flag | Meaning in the store |
| --- | --- |
| `record` | Whether to create or replace an inbox row |
| `markUnread` | Whether the new record starts unread |
| `reorderWorkspace` | Whether notification activity can move the workspace to top |
| `desktop` | Whether to schedule a macOS user notification |
| `sound` | Whether to play notification sound or attach sound to desktop notification |
| `command` | Whether to run the custom notification command |
| `paneFlash` | Whether unread state can request pane flash/ring attention |

Tests show hooks can transform notification content, disable desktop delivery, return partial effect patches, and preserve omitted flags.

Sources: [cmuxTests/NotificationAndMenuBarTests.swift:16-50](), [cmuxTests/NotificationAndMenuBarTests.swift:52-90](), [cmuxTests/NotificationAndMenuBarTests.swift:92-119]()

## Store Application and Side Effects

`addNotification` handles cooldowns, builds the policy context, authorizes hooks, evaluates policy, and applies either the hook-modified envelope or default effects on failure. Applying a notification creates a `TerminalNotification` whose `isRead` is the inverse of `effects.markUnread`, then either records it or executes side effects only.

Recording replaces any existing notification for the same workspace/surface pair, clears the workspace manual unread marker, inserts the new record at the front, commits cooldown state, removes old macOS notification requests off-main, and then dispatches side effects. If the app is already focused on the target workspace/surface, external delivery is suppressed and local feedback is used instead.

Sources: [Sources/TerminalNotificationStore.swift:1156-1256](), [Sources/TerminalNotificationStore.swift:1297-1339](), [Sources/TerminalNotificationStore.swift:1368-1472](), [Sources/TerminalNotificationStore.swift:1474-1507]()

The macOS notification path writes `tabId`, `surfaceId`, and `notificationId` into `userInfo`, attaches custom click-action metadata when present, and falls back to local sound/command feedback when desktop delivery is unavailable. Notification removal is deliberately dispatched off-main because `UNUserNotificationCenter` removal can block on XPC.

Sources: [Sources/TerminalNotificationStore.swift:12-36](), [Sources/TerminalNotificationStore.swift:1853-1925](), [Sources/TerminalNotificationStore.swift:1939-1955]()

## Pane Rings, Tab Badges, and Workspace Indicators

Pane rings are not stored as a separate notification object. They are derived from store state and workspace panel state. `hasVisibleNotificationIndicator` returns true when there is an unread notification for the workspace/surface or when a focused read indicator remains for that tab/surface. Workspace rendering combines that with manual and restored panel unread IDs, then passes the result into `Workspace.shouldShowUnreadIndicator`.

This design matters because ring visibility survives more than one source of attention: actual unread notifications, user-marked workspace unread, restored session unread state, and panel-level manual unread state.

Sources: [Sources/TerminalNotificationStore.swift:1122-1137](), [Sources/WorkspaceContentView.swift:218-239](), [Sources/WorkspaceContentView.swift:344-379](), [Sources/WorkspaceContentView.swift:439-483](), [Sources/Workspace.swift:10263-10308]()

## Inbox and Menu Surfaces

The Notifications page is a review inbox. It shows an empty state when there are no notifications or workspace unread indicators, a workspace-unread-only hint when badges exist without notification rows, and otherwise a scrollable list of rows. Opening a row calls `AppDelegate.openTerminalNotification`; clearing a row removes it from the store. The page also has `Clear All` and `Jump to Latest Unread`, with the jump button disabled when there is no unread state.

Sources: [Sources/NotificationsPage.swift:11-67](), [Sources/NotificationsPage.swift:69-88](), [Sources/NotificationsPage.swift:115-156](), [Sources/NotificationsPage.swift:185-253]()

The menu bar surface consumes `NotificationMenuSnapshot`, not raw store internals. The snapshot counts unread rows plus workspace unread indicators, exposes whether any notification exists, and includes up to six recent inline notifications by default. The menu uses that snapshot to enable or disable jump, mark-all-read, and clear-all actions, update the icon, and rebuild inline notification items.

Sources: [Sources/App/MenuBarExtraController.swift:75-82](), [Sources/App/MenuBarExtraController.swift:166-190](), [Sources/App/MenuBarExtraController.swift:209-242](), [Sources/App/MenuBarExtraController.swift:316-361]()

## Jump-to-Unread Workflow

`jumpToLatestUnread` scans notifications newest-first and opens the first unread notification that is actionable. It skips excluded IDs and click-action notifications, because click actions may reveal a file instead of focusing a pane. If no notification row can be opened, it falls back to workspace unread indicators and opens the preferred unread panel.

Opening a notification switches sidebar selection back to tabs, brings the owning window forward, focuses the tab/surface, and marks the notification read after successful focus. If the owning window context is not registered yet, the app falls back to the active tab manager/window path.

Sources: [Sources/AppDelegate.swift:11026-11055](), [Sources/AppDelegate.swift:11057-11098](), [Sources/AppDelegate.swift:14730-14743](), [Sources/AppDelegate.swift:14774-14814](), [Sources/AppDelegate.swift:14816-14860](), [Sources/AppDelegate.swift:14917-14933]()

The UI test covers the user-level promise: pressing `Command-Shift-U` focuses the expected workspace and surface across tabs, using test-only data written by the app to verify the focused model state.

Sources: [cmuxUITests/JumpToUnreadUITests.swift:14-50](), [Sources/AppDelegate.swift:11026-11035](), [Sources/AppDelegate.swift:14936-14952]()

## Read, Clear, and Dismiss Semantics

Read and clear operations are scoped. `markRead(id:)` marks one row read. `markRead(forTabId:)` marks all unread rows in a workspace, clears workspace manual unread, clears panel unread indicators, and removes delivered notifications. The `(tabId, surfaceId)` overload only touches matching notifications, and a `nil` surface also clears workspace-level restored/panel-derived unread state. `markAllRead` clears all notification and workspace unread indicators.

Clear operations remove rows rather than marking them read. `clearAll` discards pending queued notifications by default, empties all unread indicator sets, clears focused read indicators, publishes a notification-cleared event, and removes delivered/pending macOS notifications. Scoped clear methods remove only matching workspace or surface records and publish scoped clear events.

Sources: [Sources/TerminalNotificationStore.swift:1558-1610](), [Sources/TerminalNotificationStore.swift:1612-1687](), [Sources/TerminalNotificationStore.swift:1689-1779](), [Sources/TerminalNotificationStore.swift:1817-1844](), [Sources/TerminalController.swift:10391-10460](), [Sources/TerminalController.swift:10549-10551]()

## What To Preserve When Extending

- Keep one store as the source of truth. UI surfaces should consume `TerminalNotificationStore` or `NotificationMenuSnapshot`, not invent their own unread counts.
- Use `TerminalMutationBus` for socket-driven async mutations so clears and notification arrivals remain ordered.
- Treat policy hooks as local, portable automation. They may call a model provider if a user chooses, but cmux should not require one.
- Preserve target validation before queued delivery. The queue skips notifications whose workspace/surface no longer exists.
- Keep `UNUserNotificationCenter` removal off-main; the code comments identify synchronous XPC as a UI-freeze risk.
- Add behavioral tests for queue boundaries and jump focus. Existing tests assert the cases that are easiest to regress.

Sources: [Sources/TerminalNotificationQueue.swift:381-419](), [Sources/TerminalNotificationStore.swift:12-36](), [cmuxTests/TerminalNotificationQueueTests.swift:431-478](), [cmuxTests/TerminalNotificationCallerTests.swift:22-88](), [cmuxTests/TerminalNotificationCallerTests.swift:90-160]()

In short, the notification system is valuable because it treats unread state as a product workflow, not just a desktop alert. One policy-aware store drives panes, tabs, inbox rows, menu badges, dock counts, and jump navigation while keeping hook execution and provider choice portable. Sources: [Sources/TerminalNotificationStore.swift:767-895](), [Sources/AppDelegate.swift:11026-11047](), [Sources/App/MenuBarExtraController.swift:330-361]().

---

## 04. Command Palette, Nucleo Search & Editable Shortcuts

> The power-user command layer: native fuzzy search, usage boosts, settings toggles, shortcut recording, JSON-backed shortcut files, and tests that preserve keyboard routing behavior.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/04-command-palette-nucleo-search-editable-shortcuts.md
- Generated: 2026-05-23T17:52:25.276Z

### Source Files

- `Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift`
- `Sources/CommandPalette/CommandPaletteSettingsToggle.swift`
- `Native/CommandPaletteNucleoFFI/src/lib.rs`
- `Sources/KeyboardShortcutSettings.swift`
- `Sources/KeyboardShortcutSettingsFileStore.swift`
- `cmuxTests/CommandPaletteNucleoFFITests.swift`
- `cmuxTests/ShortcutAndCommandPaletteTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift](Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift)
- [Sources/CommandPalette/CommandPaletteSettingsToggle.swift](Sources/CommandPalette/CommandPaletteSettingsToggle.swift)
- [Sources/CommandPalette/CommandPaletteNucleoSearch.swift](Sources/CommandPalette/CommandPaletteNucleoSearch.swift)
- [Native/CommandPaletteNucleoFFI/src/lib.rs](Native/CommandPaletteNucleoFFI/src/lib.rs)
- [Sources/KeyboardShortcutSettings.swift](Sources/KeyboardShortcutSettings.swift)
- [Sources/KeyboardShortcutSettingsLookup.swift](Sources/KeyboardShortcutSettingsLookup.swift)
- [Sources/KeyboardShortcutSettingsFileStore.swift](Sources/KeyboardShortcutSettingsFileStore.swift)
- [Sources/KeyboardShortcutSettingsControls.swift](Sources/KeyboardShortcutSettingsControls.swift)
- [Sources/KeyboardShortcutRecorder.swift](Sources/KeyboardShortcutRecorder.swift)
- [cmuxTests/CommandPaletteNucleoFFITests.swift](cmuxTests/CommandPaletteNucleoFFITests.swift)
- [cmuxTests/ShortcutAndCommandPaletteTests.swift](cmuxTests/ShortcutAndCommandPaletteTests.swift)
- [web/data/cmux.schema.json](web/data/cmux.schema.json)
- [docs/configuration.md](docs/configuration.md)
</details>

# Command Palette, Nucleo Search & Editable Shortcuts

This page covers cmux’s power-user command layer: native fuzzy search, settings toggles exposed as commands, usage-based result boosts, shortcut recording, JSON-backed shortcut files, and tests that keep keyboard routing behavior stable.

The implementation is intentionally local and provider-neutral. The search index is a native Rust dylib loaded by the macOS app, shortcut state is stored in UserDefaults or `~/.config/cmux/cmux.json`, and command definitions are app/runtime artifacts rather than model-provider contracts. The supplied Compound Engineering profile shaped this page, but repository code and tests are the source of truth; no `STRATEGY.md` or `docs/solutions/**` source was present in this checkout.

Sources: [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:35-46](), [Sources/KeyboardShortcutSettingsFileStore.swift:33-41](), [docs/configuration.md:1-3]()

## Mental Model

```text
User input
  -> Command Palette UI
     -> Swift orchestrator
        -> Nucleo native index when available
        -> Swift search fallback for compatibility and typo handling
     -> Command handlers
        -> app actions
        -> settings toggles
  -> Shortcut layer
     -> recorder UI
     -> UserDefaults shortcut overrides
     -> cmux.json managed overrides
     -> menu/global-hotkey routing
```

The command palette is not just a search box. It is a shared command surface that can execute actions, switch contexts, and toggle settings. The shortcut system feeds the same actions from menus, UI controls, file configuration, and global hotkey registration.

Sources: [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:90-134](), [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:768-787](), [Sources/KeyboardShortcutSettingsLookup.swift:3-43]()

## Search Orchestration

`CommandPaletteSearchOrchestrator.resolvedSearchMatches` is the main decision point. It prepares the query, composes usage-history boosts with any additional boost function, prefers the Nucleo index when present, and falls back to the Swift search engine when native search is unavailable.

A subtle feature worth preserving: the orchestrator still invokes a Swift “single-edit” fallback when Nucleo’s top matches might miss a query token that permits a one-character edit. It probes the first 12 Nucleo matches, filters Swift matches that actually used the single-edit word-prefix path, then merges the two result sets by score, rank, localized title, and command id.

Sources: [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:47-134](), [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:143-186](), [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:189-243]()

### Usage Boosts

Usage history is deliberately strongest when the query is empty. The boost combines recency and use count: recent entries can get up to 320 points, repeated use can add up to 180 points, and typed queries receive one third of the total. This makes the blank palette feel personalized without overpowering explicit search intent.

Sources: [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:373-387]()

### Preview And Scope Behavior

The orchestrator distinguishes command search from switcher previews. Command scope can search the full corpus through the native index. Switcher preview scope builds a deduplicated candidate subset and runs Swift search over that smaller list. The UI can synchronously seed results when no visible results exist and either a native index is ready or the corpus is small enough.

Sources: [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:259-313](), [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:338-353]()

## Nucleo Native Search

The Rust FFI is a compact native search engine around `nucleo`. It exposes a C ABI with version `2`, builds an immutable `CmuxNucleoIndex` from a UTF-8 blob plus candidate spans, and stores candidate title, searchable text, line-split search fields, rank, ASCII prefilter masks, and title initials.

Sources: [Native/CommandPaletteNucleoFFI/src/lib.rs:10-37](), [Native/CommandPaletteNucleoFFI/src/lib.rs:89-147](), [Native/CommandPaletteNucleoFFI/src/lib.rs:100-103]()

The search function normalizes whitespace, supports optional per-candidate boosts, returns early on invalid ABI inputs, and limits output by both requested result count and output buffer capacity. Empty queries are ranked by boost and candidate rank; non-empty queries use ASCII masks to skip impossible candidates before scoring.

Sources: [Native/CommandPaletteNucleoFFI/src/lib.rs:204-241](), [Native/CommandPaletteNucleoFFI/src/lib.rs:243-304]()

The scoring model has product taste baked in:

| Behavior | Implementation signal |
| --- | --- |
| Title matches beat generic keyword matches | title score gets a `+2_000` bias |
| Exact short aliases can beat initialisms | exact keyword score for short queries starts at `30_000` |
| Multi-token queries can match across fields | each token is scored independently, with an exact full-line query override |
| Initialisms are first-class | title initials are extracted and scored for 2-8 alphanumeric query chars |
| ASCII candidates are cheap to reject | query and candidate ASCII bitmasks skip impossible matches |

Sources: [Native/CommandPaletteNucleoFFI/src/lib.rs:357-392](), [Native/CommandPaletteNucleoFFI/src/lib.rs:427-483](), [Native/CommandPaletteNucleoFFI/src/lib.rs:485-562](), [Native/CommandPaletteNucleoFFI/src/lib.rs:571-601]()

## Swift Wrapper And Fallback Boundary

`CommandPaletteNucleoSearchLibrary` loads the native dylib with `dlopen`, validates required symbols, checks the FFI version, and supports multiple library locations: `CMUX_NUCLEO_FFI_LIB`, app bundle private frameworks, and local Rust target directories. That keeps local development, CI, and packaged app runs on one ABI contract.

Sources: [Sources/CommandPalette/CommandPaletteNucleoSearch.swift:36-103](), [Sources/CommandPalette/CommandPaletteNucleoSearch.swift:110-157]()

The Swift wrapper serializes corpus entries into the blob/span shape the Rust side expects, sends optional boost arrays only when at least one boost is non-zero, clamps result limits to corpus size, and reconstructs title match indices in Swift for UI highlighting.

Sources: [Sources/CommandPalette/CommandPaletteNucleoSearch.swift:159-199](), [Sources/CommandPalette/CommandPaletteNucleoSearch.swift:278-340]()

```swift
// Sources/CommandPalette/CommandPaletteNucleoSearch.swift
let boost = Int32(clamping: historyBoost(entry.payload, queryIsEmpty))
hasNonZeroBoost = hasNonZeroBoost || boost != 0
values.append(boost)
```

Sources: [Sources/CommandPalette/CommandPaletteNucleoSearch.swift:287-307]()

## Settings Toggles As Commands

Settings toggles are modeled as `CommandPaletteSettingToggleDescriptor` values. Each descriptor owns a command id, settings key, localized title and section title, search keywords, availability, state lookup, and setter. The generated command title flips between “Enable …” and “Disable …”; the subtitle includes the settings section and current on/off state.

Sources: [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:3-40](), [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:62-83]()

`ContentView.commandPaletteSettingsToggleCommandContributions()` turns descriptors into command palette contributions with extra keywords like `settings`, `toggle`, and the settings key. `registerSettingsToggleCommandHandlers` wires those command ids back to descriptor toggles. This is a good extension pattern: adding a setting to the palette is mostly data, not a new bespoke handler.

Sources: [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:86-143](), [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:751-787]()

## Editable Shortcuts

`KeyboardShortcutSettings.Action` is the action catalog for public shortcut bindings. It includes app/window actions, command palette navigation, workspace navigation, pane splits, browser commands, find commands, and panel-specific actions. Each action has a localized label, a default `StoredShortcut`, display formatting, conflict behavior, and special handling for numbered shortcuts.

Sources: [Sources/KeyboardShortcutSettings.swift:60-146](), [Sources/KeyboardShortcutSettings.swift:148-228](), [Sources/KeyboardShortcutSettings.swift:243-397](), [Sources/KeyboardShortcutSettings.swift:403-420]()

Shortcut persistence has two layers:

| Layer | Role |
| --- | --- |
| UserDefaults | Interactive Settings UI writes encoded `StoredShortcut` values per action defaults key. |
| `cmux.json` | Managed file overrides can supersede app settings and disable editing for managed rows. |
| Built-in defaults | Used when neither file nor UserDefaults supplies a binding. |

Sources: [Sources/KeyboardShortcutSettingsLookup.swift:3-23](), [Sources/KeyboardShortcutSettings.swift:765-783](), [Sources/KeyboardShortcutSettingsControls.swift:144-176]()

### Conflict And Validation Rules

The shortcut recorder does not accept everything the user types. It can reject bare keys, conflicts with other actions, macOS-reserved shortcuts, invalid numbered shortcuts, and system-wide hotkeys without a primary modifier. Numbered action families such as selecting workspace or surface by number normalize to `1` for persistence/display while matching the 1-9 family.

Sources: [Sources/KeyboardShortcutSettings.swift:47-58](), [Sources/KeyboardShortcutSettings.swift:438-516](), [Sources/KeyboardShortcutSettings.swift:519-611]()

Conflict detection is context-aware. Chords conflict only when their first strokes conflict and their second strokes conflict; numbered digit actions compare by digit family rather than exact digit. This lets “select workspace 1…9” behave like one shortcut family instead of nine unrelated shortcuts.

Sources: [Sources/KeyboardShortcutSettings.swift:613-732]()

The settings control presents localized validation messages and offers “Swap” only when both the current and conflicting shortcuts can be replaced and neither action is managed by `cmux.json`.

Sources: [Sources/KeyboardShortcutSettingsControls.swift:3-43](), [Sources/KeyboardShortcutSettingsControls.swift:64-141](), [Sources/KeyboardShortcutSettingsControls.swift:225-243]()

## JSON-Backed Shortcut Files

Global app preferences live at `~/.config/cmux/cmux.json`. The store also knows legacy/fallback paths, bootstraps the primary file if missing, parses JSONC, and watches the primary plus fallback files for reloads. When file-managed shortcuts change, it posts the same shortcut settings notification used by the rest of the app.

Sources: [docs/configuration.md:1-3](), [Sources/KeyboardShortcutSettingsFileStore.swift:33-41](), [Sources/KeyboardShortcutSettingsFileStore.swift:76-115](), [Sources/KeyboardShortcutSettingsFileStore.swift:134-165](), [Sources/KeyboardShortcutSettingsFileStore.swift:184-219]()

The schema declares `shortcuts.bindings` as shortcut overrides keyed by cmux action id. Bindings accept a string for a single shortcut, an array for a chord, or null/empty/`none`/`clear`/`unbound`/`disabled` to unbind. The same schema also exposes `app.commandPaletteSearchesAllSurfaces`, which controls switcher breadth.

Sources: [web/data/cmux.schema.json:316-320](), [web/data/cmux.schema.json:887-980]()

The parser accepts both nested `shortcuts.bindings` and direct shortcut keys inside `shortcuts`, resolves action ids through `KeyboardShortcutSettings.Action(rawValue:)`, parses string or string-array bindings, and normalizes settings-file shortcuts without consulting the global recorder conflict path during store initialization.

Sources: [Sources/KeyboardShortcutSettingsFileStore.swift:316-352](), [Sources/KeyboardShortcutSettingsFileStore.swift:853-898]()

Shortcut config strings support modifier aliases (`cmd`, `command`, `⌘`, etc.), named keys, arrow keys, media keys, function keys, and up to two strokes for chords. The stored shortcut type also has explicit chord fields, so JSON-backed chords are first-class rather than encoded as opaque text.

Sources: [Sources/KeyboardShortcutSettings.swift:1910-2008](), [Sources/KeyboardShortcutSettings.swift:2140-2281](), [Sources/KeyboardShortcutSettings.swift:2284-2298]()

## Recorder And Global Hotkey Coordination

Shortcut recording is global state, not just button-local state. `KeyboardShortcutRecorderActivity` tracks the active recorder count, emits change notifications, and can stop all recording. The recorder dispatch path consumes key events only when a recorder is active and the event is a key or system-defined event.

Sources: [Sources/KeyboardShortcutSettings.swift:2301-2347](), [Sources/KeyboardShortcutRecorder.swift:407-450]()

System-wide hotkeys are extra conservative. The controller registers only `.showHideAllWindows` and `.globalSearch`, unregisters hotkeys while shortcut recording is active, validates Carbon hotkey registration, and refreshes registration when UserDefaults, shortcut settings, recorder activity, or keyboard input source changes.

Sources: [Sources/KeyboardShortcutSettings.swift:969-1050](), [Sources/KeyboardShortcutSettings.swift:1052-1105]()

## Tests That Preserve Behavior

The Nucleo tests lock down ranking and search semantics: open-folder queries, multi-token field matching, title initialism preference, exact alias preference, title-over-keyword preference, diacritic matching, empty-query ordering, deep workspace matches, bundled dylib presence, and history boosts before limiting.

Sources: [cmuxTests/CommandPaletteNucleoFFITests.swift:9-80](), [cmuxTests/CommandPaletteNucleoFFITests.swift:82-171](), [cmuxTests/CommandPaletteNucleoFFITests.swift:173-209](), [cmuxTests/CommandPaletteNucleoFFITests.swift:238-252]()

The shortcut and command palette tests protect keyboard routing details that users feel immediately: arrow and Ctrl-N/Ctrl-P navigation, ignoring unsupported modifiers, leaving clipboard/undo/editing shortcuts available while the palette is visible, consuming Escape, blocking terminal/browser focus stealers, restoring browser or terminal focus after command palette flows, and preserving scroll positioning.

Sources: [cmuxTests/ShortcutAndCommandPaletteTests.swift:392-558](), [cmuxTests/ShortcutAndCommandPaletteTests.swift:561-656](), [cmuxTests/ShortcutAndCommandPaletteTests.swift:659-796](), [cmuxTests/ShortcutAndCommandPaletteTests.swift:827-859]()

Shortcut configuration tests also verify that right-sidebar mode shortcuts use default private Control-digit bindings, honor customized app bindings, honor settings-file bindings, and leave untouched mode shortcuts on their defaults when only one binding is overridden.

Sources: [cmuxTests/ShortcutAndCommandPaletteTests.swift:1074-1213]()

## What To Copy Or Productize

The strongest reusable pattern is the layered command contract:

| Pattern | Why it works |
| --- | --- |
| Data-driven setting toggles | New toggles become searchable commands through descriptors. |
| Native search with Swift fallback | Fast common path without giving up compatibility and typo behavior. |
| Usage boosts outside the search engine | Ranking personalization remains portable and testable. |
| File-managed shortcuts | BYOC-friendly: teams can manage shortcuts through plain JSON rather than hosted state. |
| Recorder activity as global routing state | Menu/global shortcuts can stand down while the user is recording a new binding. |

For Grok-Wiki or another documentation/product surface, this should be described as a portable local command layer: file-backed configuration, repository-defined action ids, and native/client-side fuzzy search. Nothing in the architecture requires a specific model provider, hosted connector, or proprietary runtime catalog.

Sources: [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:768-787](), [Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift:47-134](), [Sources/KeyboardShortcutSettingsFileStore.swift:853-898](), [Sources/KeyboardShortcutSettings.swift:2301-2347]()

In summary, cmux’s command palette is a high-leverage command router: Nucleo makes search fast, the Swift orchestrator keeps behavior resilient, setting toggles make preferences discoverable, and the shortcut stack lets users and teams own bindings through either UI recording or `cmux.json`. The tests are not decorative; they encode the interaction contract for search ranking, focus, text editing, and shortcut routing. Sources: [cmuxTests/CommandPaletteNucleoFFITests.swift:254-280](), [cmuxTests/ShortcutAndCommandPaletteTests.swift:392-656]()

---

## 05. CLI & Socket Automation API

> The scriptable control plane for workspaces, panes, surfaces, browser actions, notifications, windows, and VM commands, including tagged socket resolution and v2 method dispatch.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/05-cli-socket-automation-api.md
- Generated: 2026-05-23T17:52:02.725Z

### Source Files

- `CLI/cmux.swift`
- `CLI/CLISocketPathResolver.swift`
- `CLI/SocketOperationTelemetry.swift`
- `Sources/TerminalController.swift`
- `Sources/TerminalControllerV2ParamParsingSupport.swift`
- `cmuxTests/TerminalControllerSocketSecurityTests.swift`
- `cmuxUITests/AutomationSocketUITests.swift`
- `tests_v2/test_tab_dragging.py`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [CLI/cmux.swift](CLI/cmux.swift)
- [CLI/CLISocketPathResolver.swift](CLI/CLISocketPathResolver.swift)
- [CLI/SocketOperationTelemetry.swift](CLI/SocketOperationTelemetry.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [Sources/TerminalControllerV2ParamParsingSupport.swift](Sources/TerminalControllerV2ParamParsingSupport.swift)
- [Sources/SocketControlSettings.swift](Sources/SocketControlSettings.swift)
- [cmuxTests/TerminalControllerSocketSecurityTests.swift](cmuxTests/TerminalControllerSocketSecurityTests.swift)
- [cmuxUITests/AutomationSocketUITests.swift](cmuxUITests/AutomationSocketUITests.swift)
- [tests_v2/test_tab_dragging.py](tests_v2/test_tab_dragging.py)
</details>

# CLI & Socket Automation API

cmux exposes a local, scriptable control plane through the `cmux` CLI and the app’s Unix-domain control socket. This API is the bridge between shell automation and app state: it can create and select workspaces, split panes, move surfaces, drive browser panels, send terminal input, post notifications, inspect windows, and route Cloud VM commands.

This page treats the API as an integration surface, not as a UI feature. The important design choice is that automation is local and method-based: callers use handles, JSON params, and socket paths rather than any model-provider-specific contract. That keeps the architecture BYOC/BYOK friendly: agents, scripts, skill packs, and repository tools can call the same local API regardless of which model or hosted service produced the command.

Sources: [CLI/cmux.swift:29452-29644](), [Sources/TerminalController.swift:2903-2928]()

## Mental Model

The CLI is a typed convenience layer over a newline-delimited socket protocol. Most modern commands call v2 JSON methods through `SocketClient.sendV2`; older and compatibility commands still dispatch through v1 text commands. The app-side owner is `TerminalController`, which accepts local socket clients, applies access policy, parses each line, and dispatches either v1 command names or v2 method names.

```text
Shell / agent / test
        |
        |  cmux command or raw rpc
        v
CLI/cmux.swift
        |
        |  AF_UNIX line protocol
        v
Sources/TerminalController.swift
        |
        +--> v1 text commands: ping, send, new_split, notify, browser_back, ...
        |
        +--> v2 JSON methods: workspace.*, pane.*, surface.*, browser.*, notification.*, vm.*
```

The API is useful to productize because it gives external tools a stable “remote control” for a live desktop workspace without tying those tools to SwiftUI internals or to a particular AI runtime.

Sources: [CLI/cmux.swift:2168-2205](), [Sources/TerminalController.swift:2681-2754](), [Sources/TerminalController.swift:3278-3324]()

## Socket Resolution And Tagged Debug Apps

Socket selection is intentionally layered:

| Priority | Source | Notes |
| --- | --- | --- |
| 1 | `--socket <path>` | Explicit override from CLI args. |
| 2 | `CMUX_SOCKET_PATH` / legacy `CMUX_SOCKET` | The CLI refuses to choose if both are set to different paths. |
| 3 | Bundle-aware default | Stable app defaults to Application Support; debug/nightly/staging variants use marker logic. |
| 4 | Auto-discovery | Implicit default resolution can prefer live tagged debug sockets. |

`CLISocketPathResolver.resolve` only auto-discovers when the socket source is implicit default. It prefers candidates that accept connections, then existing socket files, then the first candidate. For debug variants, it can scan for recent `cmux-debug-*.sock` files and uses `CMUX_TAG` to decide when tagged discovery is appropriate.

This is why tagged builds can be automated safely: a debug app can have a separate socket and password scope, while the CLI still falls back to stable paths for ordinary users.

Sources: [CLI/cmux.swift:28-40](), [CLI/cmux.swift:2669-2826](), [CLI/CLISocketPathResolver.swift:133-190](), [CLI/CLISocketPathResolver.swift:216-229](), [CLI/CLISocketPathResolver.swift:249-329]()

## Access Modes And Authentication

Socket control is guarded by `SocketControlMode`:

| Mode | File mode | Behavior |
| --- | ---: | --- |
| `off` | `0600` | Socket control disabled by setting. |
| `cmuxOnly` | `0600` | Only processes descended from cmux terminals may connect. |
| `automation` | `0600` | External local automation from the same macOS user. |
| `password` | `0600` | Requires socket authentication. |
| `allowAll` | `0666` | Any local process/user can connect; marked unsafe in code comments. |

On the server, `TerminalController` applies `accessMode.socketFilePermissions` with `chmod`. In `cmuxOnly`, it verifies peer process ancestry when possible, with a same-UID fallback for peer PID races. In password mode, v1 clients authenticate with `auth <password>` and v2 clients authenticate with `auth.login`.

On the CLI side, password resolution is ordered as explicit `--password`, `CMUX_SOCKET_PASSWORD`, a local password file, then keychain lookup. The keychain service can be scoped by `CMUX_TAG` or by the socket filename, which matters for tagged debug apps.

Sources: [Sources/SocketControlSettings.swift:8-61](), [Sources/TerminalController.swift:2090-2121](), [Sources/TerminalController.swift:2164-2235](), [Sources/TerminalController.swift:2681-2715](), [CLI/cmux.swift:1328-1390](), [CLI/cmux.swift:4825-4848](), [cmuxTests/TerminalControllerSocketSecurityTests.swift:28-75]()

## v2 JSON Method Dispatch

The v2 protocol is newline-delimited JSON. A request is a JSON object with `method`, optional `params`, and optional `id`; the response is an object with either `ok: true` plus `result`, or `ok: false` plus `error`.

Example raw call:

```bash
cmux rpc surface.report_tty '{"workspace_id":"...","surface_id":"...","tty_name":"ttys001"}'
```

Example request shape:

```json
{"id":"request-id","method":"surface.health","params":{"workspace_id":"workspace:1"}}
```

`SocketClient.sendV2` encodes the object, sends it as one line, parses the JSON response, returns `result` on success, and formats v2 errors into CLI errors. `cmux rpc <method> [json-params]` is the escape hatch for methods not wrapped by a first-class CLI command.

The app keeps some v2 methods on a socket worker instead of the main actor. That worker group includes auth, feed, browser profile/import work, system top/memory, remote PTY operations, and all `vm.*` methods. Other methods dispatch on the main actor because they mutate UI/app model state.

Sources: [CLI/cmux.swift:2168-2224](), [CLI/cmux.swift:3387-3396](), [CLI/cmux.swift:11635-11641](), [Sources/TerminalController.swift:2237-2278](), [Sources/TerminalController.swift:2280-2395](), [Sources/TerminalController.swift:3280-3319]()

## Main Method Families

The v2 dispatcher is organized by product domain:

| Family | Representative methods | What it controls |
| --- | --- | --- |
| `system.*` | `system.ping`, `system.capabilities`, `system.identify`, `system.tree` | Health, feature discovery, caller/window/workspace identity. |
| `window.*` | `window.list`, `window.current`, `window.focus`, `window.create`, `window.close` | Top-level cmux windows. |
| `workspace.*` | `workspace.list`, `workspace.create`, `workspace.select`, `workspace.reorder`, `workspace.remote.*` | Workspace lifecycle, ordering, remote workspace state. |
| `surface.*` / `tab.*` | `surface.create`, `surface.split`, `surface.move`, `surface.send_text`, `tab.action` | Terminal/browser surfaces and tab-like actions. |
| `pane.*` | `pane.list`, `pane.focus`, `pane.create`, `pane.resize`, `pane.join` | Split-pane layout and focus. |
| `notification.*` | `notification.create`, `notification.list`, `notification.dismiss`, `notification.open` | In-app notifications targeted to workspaces or surfaces. |
| `browser.*` | `browser.navigate`, `browser.snapshot`, `browser.click`, `browser.fill`, `browser.screenshot` | Browser panel automation. |
| `vm.*` | `vm.list`, plus create/destroy/attach helpers through CLI | Cloud VM lifecycle through socket-worker dispatch. |

The CLI help exposes the same domains with friendlier command names: `list-workspaces`, `new-split`, `new-pane`, `new-surface`, `send`, `notify`, `browser ...`, `vm ...`, and `rpc ...`.

Sources: [Sources/TerminalController.swift:3318-3519](), [Sources/TerminalController.swift:3521-3605](), [CLI/cmux.swift:29495-29634]()

## Handle Inputs And Output IDs

The CLI accepts UUIDs, short refs such as `window:1`, `workspace:2`, `pane:3`, and `surface:4`, or indexes where commands support those targets. It defaults output to refs and can print UUIDs or both with `--id-format`.

This is a developer-experience detail worth copying: it lets humans use compact references in scripts while still allowing machines to store stable UUIDs.

Sources: [CLI/cmux.swift:2672-2717](), [CLI/cmux.swift:29460-29467](), [CLI/cmux.swift:4882-4895](), [Sources/TerminalControllerV2ParamParsingSupport.swift:87-103]()

## Browser Automation Surface

Browser commands are not a separate server. They are socket methods in the same dispatch tree and CLI wrapper. The CLI parses `browser` subcommands, resolves a target surface where required, and calls methods such as `browser.navigate`, `browser.snapshot`, `browser.eval`, `browser.wait`, `browser.click`, `browser.fill`, `browser.screenshot`, `browser.tab.*`, and browser profile/import commands.

This makes browser automation composable with workspaces and panes: scripts can create a browser split, navigate it, inspect page state, and keep all state tied to cmux workspace handles.

Sources: [CLI/cmux.swift:9705-9768](), [CLI/cmux.swift:29600-29634](), [Sources/TerminalController.swift:2351-2377](), [Sources/TerminalController.swift:3521-3605]()

## Notifications, Telemetry, And Agent Status

The socket API is also a lightweight status bus. CLI commands can create notifications, list/mark/dismiss them, set status/progress/log entries, report TTY and shell state, and kick port metadata updates. Tests verify targeted v2 notifications attach to the intended workspace/surface instead of the currently focused panel.

Socket operation telemetry extracts an operation name from either a v2 JSON `method` or the first token of a v1 command, and tracks phase, timeout, duration, bytes read, and whether a newline response was seen. That makes hangs diagnosable without hard-coding command-specific telemetry.

Sources: [CLI/cmux.swift:29555-29570](), [Sources/TerminalController.swift:2989-3089](), [Sources/TerminalController.swift:3493-3513](), [CLI/SocketOperationTelemetry.swift:3-50](), [cmuxTests/TerminalControllerSocketSecurityTests.swift:800-830](), [cmuxTests/TerminalControllerSocketSecurityTests.swift:900-932]()

## Listener Reliability

The app’s listener is designed to survive common local automation failure modes. `TerminalController` binds an `AF_UNIX` socket, applies permissions, listens, records the last socket path, and publishes listener-start state. A path monitor detects when the socket file disappears and restarts the listener for the same path and access mode.

The UI tests cover three important operational cases: the socket appears when enabled, a deleted socket path is recreated and answers `ping`, and the socket stays absent when the setting is `off`.

Sources: [Sources/TerminalController.swift:1608-1778](), [Sources/TerminalController.swift:1898-1943](), [cmuxUITests/AutomationSocketUITests.swift:20-70](), [cmuxUITests/AutomationSocketUITests.swift:184-240]()

## Test Coverage And Dogfood Patterns

The test suite treats the socket as a real automation boundary. Unit tests open Unix sockets directly, write v2 JSON lines, and decode response envelopes. UI tests launch a tagged debug app with `CMUX_SOCKET_PATH` and `CMUX_TAG`. Python E2E tests use the socket client to create workspaces, split panes, focus surfaces, send terminal input, and verify terminal responsiveness with marker files.

That coverage explains the API’s shape: it is not just for public CLI commands, it is the same surface used to dogfood layout, focus, browser, notification, and remote-terminal workflows.

Sources: [cmuxTests/TerminalControllerSocketSecurityTests.swift:1089-1175](), [cmuxUITests/AutomationSocketUITests.swift:73-119](), [tests_v2/test_tab_dragging.py:49-93](), [tests_v2/test_tab_dragging.py:508-590]()

## Provider-Neutral Integration Guidance

For Grok-Wiki or any external automation UI, the portable integration point is the local CLI/socket contract:

| Integration need | Preferred API shape | Provider-neutral reason |
| --- | --- | --- |
| Discover current context | `cmux identify` or `system.identify` | Uses local handles, not model-session internals. |
| Run scripted workspace actions | First-class CLI commands where available | Stable UX labels over raw implementation names. |
| Access newer or niche methods | `cmux rpc <method> <json>` | Keeps method dispatch open without coupling to one client. |
| Target tagged debug apps | `CMUX_TAG`, `CMUX_SOCKET_PATH`, or `--socket` | Portable across local app builds and CI. |
| Feed agent/UI events | v2 feed/notification/status methods | Skill packs can be files, repositories, or catalogs; the socket does not depend on their provider. |

A good UI flow should show product actions first: “Create workspace,” “Split pane,” “Send text,” “Open browser,” “Create notification,” and “List VMs.” Raw `rpc` should be available as an advanced escape hatch, not the primary user-facing affordance.

Sources: [CLI/cmux.swift:29475-29644](), [CLI/cmux.swift:3378-3396](), [Sources/TerminalController.swift:3318-3519]()

## Summary

The CLI and socket API form cmux’s scriptable control plane: local Unix socket transport, layered socket discovery for stable and tagged apps, access modes for local automation, v1 compatibility, v2 JSON method dispatch, and broad product coverage across windows, workspaces, panes, surfaces, browser panels, notifications, and VM commands. The result is a practical automation boundary that can be driven by humans, tests, agents, or external tools without binding the architecture to a specific AI provider or hosted connector.

Sources: [CLI/cmux.swift:29452-29644](), [Sources/TerminalController.swift:3278-3519]()

---

## 06. Agent Hook Library & Permission Feed

> The vendor-agnostic hook adapter layer for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and other agents, plus the feed path for approval and permission events.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/06-agent-hook-library-permission-feed.md
- Generated: 2026-05-23T17:52:06.969Z

### Source Files

- `CLI/CMUXCLI+AgentHookDefinitions.swift`
- `CLI/CMUXCLI+HermesAgentHooks.swift`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift`
- `Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift`
- `Sources/Feed/FeedCoordinator.swift`
- `Sources/Feed/FeedPermissionActionPolicy.swift`
- `cmuxTests/CLIGenericHookPersistenceTests.swift`
- `cmuxTests/FeedCoordinatorTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [CLI/CMUXCLI+AgentHookDefinitions.swift](CLI/CMUXCLI+AgentHookDefinitions.swift)
- [CLI/CMUXCLI+HermesAgentHooks.swift](CLI/CMUXCLI+HermesAgentHooks.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift](Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift)
- [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift](Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift)
- [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift](Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift)
- [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift](Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift)
- [Sources/Feed/FeedCoordinator.swift](Sources/Feed/FeedCoordinator.swift)
- [Sources/Feed/FeedPermissionActionPolicy.swift](Sources/Feed/FeedPermissionActionPolicy.swift)
- [Sources/Feed/FeedPanelView.swift](Sources/Feed/FeedPanelView.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [cmuxTests/CLIGenericHookPersistenceTests.swift](cmuxTests/CLIGenericHookPersistenceTests.swift)
- [cmuxTests/FeedCoordinatorTests.swift](cmuxTests/FeedCoordinatorTests.swift)
- [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift](Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift)
- [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift](Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift)
</details>

# Agent Hook Library & Permission Feed

cmux has a vendor-agnostic hook adapter layer that installs small agent-native hook commands into each supported CLI’s own config format. Those hooks report session lifecycle, prompt, stop, notification, and selected tool events back to cmux without assuming a specific model provider or hosted control plane.

The permission feed is the second half of the design: hook payloads are normalized into `WorkstreamEvent`, sent over the cmux socket as `feed.push`, displayed as Feed items, and sometimes held open until the user approves, denies, answers a question, or times out. This keeps BYOC/BYOK compatibility because agents keep their own binaries, config homes, and keys; cmux only bridges local hook events and decisions.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:6-30](), [CLI/cmux.swift:28181-28217](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-10]()

## Adapter Model

The central abstraction is `AgentHookDef`. It records each agent’s CLI name, display name, status key, config directory/file, optional config-home environment override, session-store suffix, disable environment variable, binary name, hook config format, lifecycle events, aliases, and feed-hook events. This makes agent support mostly data-driven: adding an agent usually means adding a definition plus a serializer only if its config format is not already covered.

The supported format enum is deliberately small: flat JSON, nested JSON with timeouts, Antigravity JSON, Rovo Dev YAML, and Hermes Agent YAML. The table includes Codex, Grok, OpenCode, Pi, Amp, Cursor, Gemini, Antigravity, Rovo Dev, Hermes Agent, Copilot, CodeBuddy, Factory, and Qoder.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:6-47](), [CLI/CMUXCLI+AgentHookDefinitions.swift:122-245](), [CLI/CMUXCLI+AgentHookDefinitions.swift:246-297]()

| Agent | Config path basis | Format | Feed hook events |
|---|---:|---|---|
| Codex | `.codex/hooks.json`, or `CODEX_HOME` | nested JSON | `PreToolUse`, `PermissionRequest` |
| Grok | `.grok/hooks/cmux-session.json`, or `GROK_HOME/hooks` | nested JSON | `PreToolUse` |
| Cursor | `.cursor/hooks.json` | flat JSON | `beforeShellExecution` |
| Gemini | `.gemini/settings.json` | nested JSON | `PreToolUse` |
| Antigravity | `.gemini/config/hooks.json` | named JSON group | `PreToolUse`, `PostToolUse` |
| Rovo Dev | `.rovodev/config.yml` | YAML | none in `feedHookEvents`; `on_tool_permission` maps to generic prompt-submit telemetry |
| Hermes Agent | `.hermes/config.yaml`, or `HERMES_HOME` | YAML plus allowlist | `pre_tool_call`, `post_tool_call`, `pre_approval_request`, `post_approval_response` |
| Copilot / CodeBuddy / Factory / Qoder | agent-specific config homes | nested JSON | `PreToolUse` |

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:123-228](), [CLI/CMUXCLI+AgentHookDefinitions.swift:230-295]()

## Runtime Shape

The adapter layer separates installation from runtime handling. Installation writes each agent’s native hook config. Runtime hooks execute `cmux hooks <agent> <subcommand>` for lifecycle/status work or `cmux hooks feed --source <agent> --event <event>` for feed bridge work.

```mermaid
flowchart LR
  subgraph AgentConfig["Agent-owned config"]
    Hooks["hooks.json / settings.json / config.yml / config.yaml"]
  end

  subgraph CLI["cmux CLI hook commands"]
    Lifecycle["cmux hooks <agent> <event>"]
    FeedHook["cmux hooks feed --source <agent> --event <event>"]
  end

  subgraph Socket["cmux socket API"]
    FeedPush["feed.push"]
    Reply["feed.permission.reply / feed.question.reply / feed.exit_plan.reply"]
  end

  subgraph App["cmux app Feed"]
    Coordinator["FeedCoordinator"]
    Store["WorkstreamStore"]
    Panel["FeedPanelView actions"]
  end

  Hooks --> Lifecycle
  Hooks --> FeedHook
  FeedHook --> FeedPush
  FeedPush --> Coordinator --> Store
  Panel --> Reply --> Coordinator
```

Sources: [CLI/cmux.swift:23140-23207](), [CLI/cmux.swift:28197-28326](), [Sources/TerminalController.swift:10722-10747](), [Sources/Feed/FeedPanelView.swift:928-953]()

## Installing Hooks Without Owning the Agent

For JSON-style agents, `installAgentHooks` reads the agent config, builds cmux hook entries, removes prior cmux-owned entries, preserves non-cmux hooks, then writes the new config after an optional preview prompt. Flat configs get `version = 1`; nested configs preserve groups and only remove commands recognized as cmux-owned. This lets users keep their own agent hooks alongside cmux.

Rovo Dev and Hermes Agent have dedicated YAML paths. Rovo Dev writes under `eventHooks.events` and marks inserted blocks with `# cmux hooks rovodev begin/end`. Hermes Agent inserts under `hooks`, can add missing event sections, restores inline-empty YAML forms during uninstall, and writes a separate `shell-hooks-allowlist.json` approval entry for the cmux shell hooks.

Sources: [CLI/cmux.swift:24115-24280](), [CLI/cmux.swift:24280-24327](), [CLI/cmux.swift:23857-23937](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/RovoDevHookConfig.swift:17-45](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift:22-89](), [Packages/CMUXAgentLaunch/Sources/CMUXAgentLaunch/HermesAgentHookConfig.swift:267-314]()

```swift
// CLI/CMUXCLI+AgentHookDefinitions.swift
static func feedHookCommandString(for def: AgentHookDef, agentEvent: String) -> String {
    agentHookShellCommand("cmux hooks feed --source \(def.name) --event \(agentEvent)", for: def)
}
```

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:304-310]()

## Feed Bridge Classification

`cmux hooks feed` reads hook JSON from stdin, derives the raw event from `hook_event_name`, `event`, or `--event`, extracts tool name/input/session/cwd/workspace, and builds a normalized `WorkstreamEvent` dictionary. Non-actionable telemetry uses `wait_timeout_seconds = 0`; actionable permission, plan, and question events wait up to 120 seconds for the user.

The classification is provider-aware but not provider-locked. Claude’s native `PermissionRequest` carries decisions. Hermes maps side-effecting `pre_tool_call` events to `PermissionRequest`; `post_tool_call` stays telemetry. Other agents use `PreToolUse` or `beforeShellExecution`, and only side-effecting tools become permission requests. Read-only tools intentionally stay telemetry to avoid flooding the actionable feed.

Sources: [CLI/cmux.swift:28216-28245](), [CLI/cmux.swift:28246-28312](), [CLI/cmux.swift:28357-28461](), [CLI/cmux.swift:28463-28488]()

## Workstream Event Contract

`WorkstreamEvent` is the wire contract between hook adapters and the app. Its field names mirror existing hook payload names: `session_id`, `hook_event_name`, `workspace_id`, `cwd`, `tool_name`, `tool_input`, `_source`, `_ppid`, and `_opencode_request_id`. Unknown extra fields are retained as JSON, and arbitrary `tool_input` shapes are normalized into stable JSON strings.

`WorkstreamStore` then converts event kinds into UI payloads: `PermissionRequest` becomes a permission card, `AskUserQuestion` becomes a question card, `ExitPlanMode` becomes a plan card, and lifecycle/tool/prompt events become telemetry or contextual items.

Sources: [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-23](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:52-82](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:84-139](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamStore.swift:263-320]()

## Blocking Approval Path

The app-side socket handler validates `feed.push`, decodes the nested event, publishes receipt/completion events, and delegates to `FeedCoordinator.ingestBlocking`. The coordinator registers a waiter by request id before inserting the item, hops to the main actor to update `WorkstreamStore`, arms a PID watcher when `_ppid` is present, posts a native notification only if the request is still awaiting a decision, and blocks the socket worker until resolution or timeout.

Replies enter through `feed.permission.reply`, `feed.question.reply`, or `feed.exit_plan.reply`. All three call `FeedCoordinator.deliverReply`, which stores the decision, signals the semaphore, marks the matching Feed item resolved, and cancels any notification. If the timeout wins, the item is marked expired and the hook receives `timed_out`.

Sources: [Sources/TerminalController.swift:10680-10747](), [Sources/TerminalController.swift:10784-10852](), [Sources/Feed/FeedCoordinator.swift:81-151](), [Sources/Feed/FeedCoordinator.swift:154-180](), [Sources/Feed/FeedCoordinator.swift:222-226]()

## Decision Encoding Back to Agents

Once `feed.push` returns a resolved decision, the CLI renders the result in the agent’s expected stdout shape. Claude and Codex use `hookSpecificOutput` for `PermissionRequest`. Generic PreToolUse-style agents receive allow/deny decisions and optional context. Hermes Agent receives `{ "action": "block" }` for denies and `{}` for allows. Antigravity receives its own `decision`/`reason` shape. Exit-plan and question replies are also translated into provider-appropriate hook output instead of simulating keystrokes.

Sources: [CLI/cmux.swift:28332-28355](), [CLI/cmux.swift:28492-28637](), [CLI/cmux.swift:28638-28720](), [CLI/cmux.swift:28722-28783]()

## Permission Mode Policy

The Feed UI does not expose every permission mode for every source. `FeedPermissionActionPolicy` disables persistent permission modes for Codex and Hermes Agent, and disables bypass permissions for Codex, Claude, and Hermes Agent. The tests assert that Claude still supports persistent modes but keeps bypass user-owned, while OpenCode supports both persistent and bypass modes.

Sources: [Sources/Feed/FeedPermissionActionPolicy.swift:3-10](), [cmuxTests/FeedCoordinatorTests.swift:10-23]()

## Persistence, Focus, and Recovery

Hook session stores let Feed items map back to a cmux workspace and surface. `FeedJumpResolver` parses workstream ids like `<agent>-<sessionId>`, reads `~/.cmuxterm/<agent>-hook-sessions.json`, and can dispatch focus or send-text intents without coupling Feed directly to terminal controllers.

Tests cover sanitized launch command persistence across multiple agent families. The scenarios strip resume IDs, old prompts, and secret API-key-like environment variables while preserving useful config-home variables such as `GEMINI_CLI_HOME`, `GROK_HOME`, `COPILOT_HOME`, `CODEBUDDY_CONFIG_DIR`, and `QODER_CONFIG_DIR`. This is the BYOK-friendly part of persistence: the restored route remembers how to resume an agent surface without storing provider secrets.

Sources: [Sources/Feed/FeedCoordinator.swift:278-326](), [Sources/Feed/FeedCoordinator.swift:329-400](), [cmuxTests/CLIGenericHookPersistenceTests.swift:16-73](), [cmuxTests/CLIGenericHookPersistenceTests.swift:102-160](), [cmuxTests/CLIGenericHookPersistenceTests.swift:161-249]()

## Verification Signals

The test suite checks several important invariants. Hermes YAML tests verify installation into empty and existing configs, preservation of user hook commands, clean uninstall, inline-empty YAML restoration, and allowlist changes that only remove cmux-owned commands. Rovo Dev tests verify insertion under the direct `eventHooks.events` child and protect unrelated YAML when markers are dangling. Feed coordinator tests verify timeout expiry and that already-resolved blocking requests do not post stale native notifications. Generic hook tests verify Antigravity feed timeout seconds, stable fallback session ids, and `_ppid` propagation.

Sources: [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift:7-45](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/HermesAgentHookConfigTests.swift:113-143](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift:6-29](), [Packages/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/RovoDevHookConfigTests.swift:31-53](), [cmuxTests/FeedCoordinatorTests.swift:25-66](), [cmuxTests/FeedCoordinatorTests.swift:68-136](), [cmuxTests/CLIGenericHookPersistenceTests.swift:585-697]()

## Product Notes for Grok-Wiki Integration

A Grok-Wiki integration should present this as a local adapter architecture, not as a model-provider feature. The portable unit is the hook definition plus serializer plus feed classifier: those can come from checked-in files, a repository catalog, or a skill-pack source, as long as generated docs cite repository code as source of truth. The UI flow should describe “agent hook setup,” “feed event normalization,” and “approval reply rendering” before naming provider-specific commands, so the same mental model works for Codex, Grok, OpenCode, Gemini, Cursor, Rovo Dev, Hermes Agent, and future BYOC agents.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:75-101](), [CLI/cmux.swift:28191-28196](), [Packages/CMUXWorkstream/Sources/CMUXWorkstream/WorkstreamEvent.swift:3-10]()

## Summary

The hook library is a small adapter table plus targeted serializers for divergent agent config formats. The permission feed is a blocking socket workflow around `WorkstreamEvent`, `FeedCoordinator`, `WorkstreamStore`, and reply-specific decision rendering. Together they let cmux support multiple local agents while preserving user-owned configs, provider keys, and native hook semantics.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:299-310](), [Sources/Feed/FeedCoordinator.swift:10-14](), [Sources/Feed/FeedCoordinator.swift:762-794]()

---

## 07. Session Vault & Agent Resume

> The restorable-agent index that scans agent stores, groups sessions by folder or agent, uses SQL for Codex state when available, and supports drag-to-resume workflows.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/07-session-vault-agent-resume.md
- Generated: 2026-05-23T17:52:09.055Z

### Source Files

- `Sources/RestorableAgentTypes.swift`
- `Sources/SessionIndexStore.swift`
- `Sources/SessionIndexStore+CodexSQL.swift`
- `Sources/SessionIndexView.swift`
- `Sources/SessionIndexModels.swift`
- `Sources/SessionAgentPresentation.swift`
- `cmuxTests/RestorableAgentSessionIndexTests.swift`
- `cmuxTests/SessionIndexViewTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/RestorableAgentTypes.swift](Sources/RestorableAgentTypes.swift)
- [Sources/RestorableAgentSession.swift](Sources/RestorableAgentSession.swift)
- [Sources/SessionIndexStore.swift](Sources/SessionIndexStore.swift)
- [Sources/SessionIndexStore+CodexSQL.swift](Sources/SessionIndexStore+CodexSQL.swift)
- [Sources/SessionIndexView.swift](Sources/SessionIndexView.swift)
- [Sources/SessionIndexModels.swift](Sources/SessionIndexModels.swift)
- [Sources/SessionAgentPresentation.swift](Sources/SessionAgentPresentation.swift)
- [Sources/SessionIndexRegisteredAgents.swift](Sources/SessionIndexRegisteredAgents.swift)
- [Sources/VaultAgentRegistry.swift](Sources/VaultAgentRegistry.swift)
- [Sources/RightSidebarToolPanel.swift](Sources/RightSidebarToolPanel.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [docs/vault.md](docs/vault.md)
- [docs/agent-hooks.md](docs/agent-hooks.md)
- [cmuxTests/RestorableAgentSessionIndexTests.swift](cmuxTests/RestorableAgentSessionIndexTests.swift)
- [cmuxTests/SessionIndexViewTests.swift](cmuxTests/SessionIndexViewTests.swift)
- [cmuxTests/PiVaultAgentPersistenceTests.swift](cmuxTests/PiVaultAgentPersistenceTests.swift)
</details>

# Session Vault & Agent Resume

Session Vault is cmux's restorable-agent index: a sidebar workflow that scans local agent history stores, normalizes the results into `SessionEntry` rows, groups them by folder or agent, and turns a selected row back into the agent's native resume command. It matters because it treats agent state as portable local data, not as a dependency on one model provider or hosted control plane.

The central product idea is simple: users can find an old Claude, Codex, Grok, OpenCode, Rovo Dev, Hermes, or registered Vault agent session, preview enough context to recognize it, then resume it by menu action, new tab, or drag-to-pane. The implementation keeps the repository code as source of truth; the requested Compound Engineering wiki recipe was used only as page-shape guidance. No `STRATEGY.md` or `docs/solutions/**` source was found in the focused scan.

Sources: [Sources/SessionIndexStore.swift:187-259](), [Sources/SessionIndexModels.swift:255-266](), [Sources/SessionIndexView.swift:49-96](), [docs/vault.md:1-5]()

## Product Workflow

The Vault appears in the right sidebar's sessions mode. `RightSidebarToolPanelView` mounts `SessionIndexView` and wires row resume actions to `SessionEntryResumeCoordinator.resume`, which either opens a terminal surface in the focused pane when the cwd matches or creates a new workspace with the resume command typed and submitted.

Sources: [Sources/RightSidebarToolPanel.swift:265-275](), [Sources/SessionIndexView.swift:8-46]()

```text
Right sidebar: Sessions
  -> group By folder / By agent
  -> optionally scope to current folder
  -> inspect rows, open preview, or Show more
  -> Resume in New Tab or drag row into a pane/split
  -> terminal runs the agent-native resume command
```

The control bar exposes grouping, current-folder scoping, and reload. The list is sectioned, capped at five visible rows per section, and uses a "Show more" popover for deeper search and pagination. Rows support double-click preview, drag, context actions, file reveal/copy, resume command copy, cwd opening, and PR opening when available.

Sources: [Sources/SessionIndexView.swift:98-138](), [Sources/SessionIndexView.swift:362-414](), [Sources/SessionIndexView.swift:542-599](), [Sources/SessionIndexView.swift:650-707]()

## Data Model

`SessionEntry` is the normalized row model. It records the agent, native session id, display title, cwd, git branch, optional pull request, modified time, backing file URL, and agent-specific resume metadata. `SessionAgent` separates built-in agents from registered Vault agents, while `SessionAgentPresentation` maps agents to localized display names and brand icons.

Sources: [Sources/SessionIndexModels.swift:37-75](), [Sources/SessionIndexModels.swift:197-206](), [Sources/SessionIndexModels.swift:255-266](), [Sources/SessionAgentPresentation.swift:3-29]()

| Concept | Implementation | Why it matters |
| --- | --- | --- |
| Built-in session agent | `SessionAgent.builtInCases` | Stable UI ordering for first-party integrations. |
| Registered agent | `SessionAgent.registered(RegisteredSessionAgent)` | BYOC/BYOK-friendly extension point for local or project config. |
| Resume metadata | `AgentSpecifics` | Preserves model, sandbox, approval, config, and agent-specific flags. |
| Section key | `SectionKey.agent` / `SectionKey.directory` | Lets the same entries render by agent or by working folder. |

Registered agents are intentionally portable. `CmuxVaultAgentRegistration` validates an id, display name, detect rule, session id source, resume command, cwd policy, and optional session directory. Config can come from global or project-local `cmux.json`, and built-in Pi, Antigravity, and Grok registrations are loaded through the same registry path.

Sources: [Sources/VaultAgentRegistry.swift:12-88](), [Sources/VaultAgentRegistry.swift:116-152](), [Sources/VaultAgentRegistry.swift:349-365](), [docs/vault.md:30-60]()

## Scan and Search Architecture

`SessionIndexStore` owns the index state: entries, loading, grouping, current-directory scoping, persisted section order, and cached section projections. Reload runs a detached scan, merges agent entries, sorts by modification time, then backfills agent and directory order outside SwiftUI body computations to avoid feedback loops.

Sources: [Sources/SessionIndexStore.swift:187-259](), [Sources/SessionIndexStore.swift:507-523](), [Sources/SessionIndexStore.swift:315-334](), [Sources/SessionIndexStore.swift:336-370]()

```mermaid
flowchart LR
  subgraph UI["UI"]
    SessionIndexView["SessionIndexView"]
    Popover["SectionPopoverView / Show more"]
  end

  subgraph Store["SessionIndexStore"]
    Sections["sectionsForCurrentGrouping"]
    Search["searchSessions"]
    LoadAgents["loadAgents task group"]
  end

  subgraph Sources["Agent stores"]
    Claude["Claude JSONL projects"]
    CodexSQL["Codex state_5.sqlite"]
    CodexDisk["Codex JSONL fallback"]
    OpenCode["OpenCode SQLite snapshot"]
    Registered["Vault registered JSONL/history"]
  end

  SessionIndexView --> Sections
  Popover --> Search
  Search --> LoadAgents
  LoadAgents --> Claude
  LoadAgents --> CodexSQL
  CodexSQL --> CodexDisk
  LoadAgents --> OpenCode
  LoadAgents --> Registered
```

Search is deliberately multi-source. Agent loads run in a task group, then merge into a global modified-time order for directory scopes. `ripgrep` is used when available for fixed-string transcript prefiltering, with a Foundation fallback and cancellation path that terminates active `rg` work when the user changes the query.

Sources: [Sources/SessionIndexStore.swift:1111-1178](), [Sources/SessionIndexStore.swift:1180-1208](), [Sources/SessionIndexStore.swift:1276-1369]()

## Codex SQL First, Disk Fallback

Codex is the most explicit example of "use structured state when available." `loadCodexEntriesViaSQL` snapshots `~/.codex/state_5.sqlite` into a temporary read-only copy, queries the `threads` table for unarchived records, and maps thread rows into `SessionEntry` values with title, cwd, model, branch, approval, sandbox, reasoning effort, rollout path, and update time.

Sources: [Sources/SessionIndexStore+CodexSQL.swift:25-55](), [Sources/SessionIndexStore+CodexSQL.swift:57-83](), [Sources/SessionIndexStore+CodexSQL.swift:90-132](), [Sources/SessionIndexStore+CodexSQL.swift:158-190]()

When SQL is absent or unsupported, Codex falls back to scanning `~/.codex/sessions` JSONL files. The fallback uses `rg` when possible, peeks `session_meta` for cwd rejection before streaming larger files, and caps candidate inspection with `searchMaxFiles`.

Sources: [Sources/SessionIndexStore.swift:1536-1554](), [Sources/SessionIndexStore.swift:1565-1641]()

`SessionIndexViewTests` includes a behavioral test that creates a Codex state database, writes a rollout transcript, and verifies SQL search still matches content found in the rollout file path. That protects the mixed SQL-plus-transcript search path from becoming metadata-only.

Sources: [cmuxTests/SessionIndexViewTests.swift:154-187](), [cmuxTests/SessionIndexViewTests.swift:313-360]()

## Resume Command Construction

Resume is owned by `SessionEntry`, not by the view. The command builder injects agent-specific flags: Claude uses `claude --resume`, Codex uses `codex resume`, Grok uses `grok -r`, OpenCode uses `opencode --session`, Rovo Dev uses `acli rovodev run --restore`, Hermes has its own helper, and registered agents delegate to `AgentResumeCommandBuilder` with the Vault registration.

Sources: [Sources/SessionIndexModels.swift:300-343](), [Sources/SessionIndexModels.swift:344-399]()

```swift
// Sources/SessionIndexModels.swift
var resumeCommandWithCwd: String? {
    guard let command = resumeCommandWithoutWorkingDirectory else { return nil }
    guard let cwd = resumeWorkingDirectory else {
        return command
    }
    return "cd \(Self.shellQuote(cwd)) && \(command)"
}
```

`resumeWorkingDirectory` respects registered-agent cwd policy: a registration with `.ignore` will not force a `cd`, while the default preserves cwd. Shell quoting is centralized, and tests cover Claude config preservation and Grok flag preservation.

Sources: [Sources/SessionIndexModels.swift:268-275](), [Sources/SessionIndexModels.swift:307-313](), [Sources/SessionIndexModels.swift:438-445](), [cmuxTests/SessionIndexViewTests.swift:59-87](), [cmuxTests/SessionIndexViewTests.swift:121-138]()

## Drag-to-Resume

Drag-to-resume is implemented as a bridge into Bonsplit's existing external tab drop path. A row drag registers the `SessionEntry` in a process-wide `SessionDragRegistry`, encodes a synthetic tab-transfer payload with a UUID, and writes only the custom `com.splittabbar.tabtransfer` type so terminal text drop targets do not intercept the gesture.

Sources: [Sources/SessionIndexStore.swift:94-120](), [Sources/SessionIndexView.swift:2527-2604]()

On drop, `Workspace.handleExternalTabDrop` first checks `SessionDragRegistry`. If the UUID belongs to a session row, the workspace spawns a new terminal in the target insert or split destination and submits the resume command. This makes "resume here" a spatial workflow: the user chooses both the session and the pane layout by dragging.

Sources: [Sources/Workspace.swift:15662-15670](), [Sources/Workspace.swift:15366-15390]()

## Restorable Agent Index

Separate from the visible Vault list, `RestorableAgentSessionIndex` loads hook-session stores from `~/.cmuxterm/<agent>-hook-sessions.json` and maps saved records back to workspace/panel keys. It includes built-in restorable kinds plus custom registered agent ids, so project or user registrations participate without changing the enum for every new provider.

Sources: [Sources/RestorableAgentTypes.swift:3-19](), [Sources/RestorableAgentTypes.swift:111-142](), [Sources/RestorableAgentSession.swift:679-808]()

Claude has a stricter restorable check: the hook record must have an explicit transcript path or a discoverable non-empty transcript file. Tests cover records that should restore, records missing transcripts that must not restore, startup-only records with transcripts, and a panel fallback that picks the latest hook record for a moved panel.

Sources: [Sources/RestorableAgentSession.swift:815-891](), [cmuxTests/RestorableAgentSessionIndexTests.swift:11-136](), [cmuxTests/RestorableAgentSessionIndexTests.swift:138-200]()

## Performance and UI Boundaries

The Session Vault code is shaped around SwiftUI invalidation control. `SessionIndexView` builds closure bundles above the lazy-list boundary so row and gap views do not observe `SessionIndexStore` directly. Sections, rows, and gaps are `Equatable` where possible, and drag state lives in a separate `SessionDragCoordinator` to avoid forcing the whole index to re-render on drag transitions.

Sources: [Sources/SessionIndexView.swift:165-251](), [Sources/SessionIndexView.swift:297-330](), [Sources/SessionIndexView.swift:333-360](), [Sources/SessionIndexView.swift:469-539]()

The store also has directory snapshot caching for the "Show more" empty-query path. It loads a large merged snapshot once, sorts it, stores it behind an LRU cache, and discards stale results if a reload races with a snapshot build.

Sources: [Sources/SessionIndexStore.swift:525-586](), [Sources/SessionIndexStore.swift:588-614]()

## Provider-Neutral Extension Pattern

Session Vault is BYOC/BYOK-friendly because the index treats agents as local stores plus resumable commands. Built-ins are conveniences, not a closed architecture. Registered agents can be discovered from `cmux.json`, specify where session ids come from, provide a resume command template, opt into cwd preservation or ignore, and expose their own session directory. The UI falls back to a neutral system icon when no registered brand asset exists.

Sources: [Sources/VaultAgentRegistry.swift:46-88](), [Sources/VaultAgentRegistry.swift:200-309](), [Sources/VaultAgentRegistry.swift:334-365](), [Sources/SessionAgentPresentation.swift:25-37](), [docs/vault.md:53-60]()

For a Grok-Wiki or similar integration, the portable design is to read file, repository, or catalog skill sources as configuration inputs and emit Vault registrations or scan adapters that still resolve to local session ids and local resume commands. Do not bind the feature to a specific hosted model vendor; bind it to stable local artifacts: session directories, SQLite state, JSONL histories, and command templates.

Sources: [Sources/SessionIndexRegisteredAgents.swift:369-464](), [cmuxTests/PiVaultAgentPersistenceTests.swift:80-150]()

## What To Demo or Productize

| Feature | Demo hook | Code path |
| --- | --- | --- |
| SQL-backed Codex search | Search for text only present in a rollout transcript while Codex SQL exists. | `loadCodexEntriesViaSQL` plus rollout content matching. |
| Folder-first recovery | Toggle "This folder only" and group by folder. | `scopeToCurrentDirectory`, `filteredEntriesForCurrentScope`, `SectionKey.directory`. |
| Drag-to-resume | Drag an old row into a split destination. | `sessionDragItemProvider`, `SessionDragRegistry`, `handleSessionDrop`. |
| Registered-agent Vault | Add a `cmux.json` Vault agent with `sessionDirectory` and `resumeCommand`. | `CmuxVaultAgentRegistry.load`, `loadRegisteredAgentEntries`. |
| Safe relaunch resume | Relaunch with hook stores present and stale Claude records missing transcripts. | `RestorableAgentSessionIndex.load`, Claude transcript eligibility tests. |

Sources: [Sources/SessionIndexStore.swift:382-390](), [Sources/SessionIndexStore+CodexSQL.swift:110-132](), [Sources/SessionIndexView.swift:2584-2604](), [Sources/Workspace.swift:15366-15390](), [Sources/RestorableAgentSession.swift:815-832]()

## Summary

Session Vault is a compact but high-leverage pattern: scan agent-owned local state, normalize it into provider-neutral session rows, keep UI grouping and pagination responsive, and resume through each agent's native command rather than replacing the agent runtime. The code already supports built-ins, SQL-backed Codex state, disk fallbacks, registered custom agents, and drag-to-resume workflows, making it a strong candidate for reuse anywhere cmux wants restorable local agent work without assuming a particular model provider or cloud service.

Sources: [Sources/SessionIndexStore.swift:633-649](), [Sources/SessionIndexStore.swift:1243-1274](), [Sources/SessionIndexModels.swift:315-399]()

---

## 08. Project Commands, Surface Buttons & Trust Gates

> The configuration mechanics behind project-specific commands, surface tab bar buttons, notification hooks, workspace commands, and confirmation gates for local automation.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/08-project-commands-surface-buttons-trust-gates.md
- Generated: 2026-05-23T17:52:18.715Z

### Source Files

- `Sources/CmuxConfig.swift`
- `Sources/CmuxConfigExecutor.swift`
- `Sources/CmuxSurfaceTabBarBuiltInAction.swift`
- `Sources/WorkspaceActionDispatcher.swift`
- `Sources/Settings/ConfigSettingsView.swift`
- `cmuxTests/CmuxConfigTests.swift`
- `cmuxTests/CmuxConfigContextMenuTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/CmuxConfig.swift](Sources/CmuxConfig.swift)
- [Sources/CmuxConfigExecutor.swift](Sources/CmuxConfigExecutor.swift)
- [Sources/CmuxSurfaceTabBarBuiltInAction.swift](Sources/CmuxSurfaceTabBarBuiltInAction.swift)
- [Sources/WorkspaceActionDispatcher.swift](Sources/WorkspaceActionDispatcher.swift)
- [Sources/Settings/ConfigSettingsView.swift](Sources/Settings/ConfigSettingsView.swift)
- [Sources/CmuxActionTrust.swift](Sources/CmuxActionTrust.swift)
- [Sources/TerminalNotificationPolicy.swift](Sources/TerminalNotificationPolicy.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [Sources/ContentView.swift](Sources/ContentView.swift)
- [Sources/AppDelegate.swift](Sources/AppDelegate.swift)
- [cmuxTests/CmuxConfigTests.swift](cmuxTests/CmuxConfigTests.swift)
- [cmuxTests/CmuxConfigContextMenuTests.swift](cmuxTests/CmuxConfigContextMenuTests.swift)
</details>

# Project Commands, Surface Buttons & Trust Gates

cmux lets a global or project-local `cmux.json` turn repository knowledge into runnable UI: command-palette actions, surface tab bar buttons, notification policy hooks, and workspace creation flows. The important design point is that these are local automation hooks, not a provider-specific integration layer. The config model can start known agents such as Codex or Claude Code, but it can also run arbitrary shell commands and workspace definitions, which keeps the architecture BYOC/BYOK-friendly.

This page used repository code as source of truth. No `STRATEGY.md` or `docs/solutions/**` files were present in the prepared checkout; the Compound Engineering guidance was available only as bundled prompt context for page shape, not as an installed local repo skill.

Sources: [Sources/CmuxConfig.swift:10-18](), [Sources/CmuxConfig.swift:783-908](), [Sources/CmuxConfig.swift:1030-1095]()

## Mental Model

```text
cmux.json
  actions + commands + ui + notifications
        |
        v
CmuxConfigStore
  merges global/local config, resolves action ids, records source paths
        |
        +--> Command palette / shortcuts
        +--> New workspace button and context menu
        +--> Bonsplit surface tab bar buttons
        +--> Terminal notification policy hooks
        |
        v
CmuxConfigExecutor + CmuxActionTrust
  run once, trust and run, or cancel project-local automation
```

`CmuxConfigStore` is the coordinator. It publishes resolved commands, actions, surface buttons, notification hooks, configuration issues, and source-path metadata. Those source paths matter because the trust gate distinguishes global config from project-local config and fingerprints the exact automation being approved.

Sources: [Sources/CmuxConfig.swift:1831-1853](), [Sources/CmuxConfig.swift:2089-2269](), [Sources/CmuxConfigExecutor.swift:188-241]()

## Configuration Shape

The root config supports these project-automation fields:

| Field | Purpose |
| --- | --- |
| `actions` | Named reusable actions for palette entries, shortcuts, buttons, and new-workspace menus. |
| `commands` | Named commands. Each entry is either a shell command or a workspace definition, never both. |
| `newWorkspaceCommand` | Legacy/direct way to bind the new-workspace action to a workspace command name. |
| `ui.newWorkspace.action` | Action-based replacement for `newWorkspaceCommand`. |
| `ui.newWorkspace.contextMenu` | Ordered new-workspace menu entries, including separators. |
| `ui.surfaceTabBar.buttons` | Current surface tab bar button list. |
| `surfaceTabBarButtons` | Legacy root-level button list. |
| `notifications.hooks` | Local command hooks that can rewrite or suppress notification envelopes. |
| `notifications.hooksMode` | `append` or `replace` behavior for inherited notification hooks. |

The decoder trims and validates sensitive fields. `newWorkspaceCommand` cannot be blank, surface tab bar button ids cannot duplicate, notification hooks require nonblank `id` and `command`, and hook timeouts must be positive. `ui.surfaceTabBar.buttons` takes precedence over the legacy root `surfaceTabBarButtons` field when both are present.

Sources: [Sources/CmuxConfig.swift:10-85](), [Sources/CmuxConfig.swift:127-167](), [Sources/CmuxConfig.swift:188-217]()

## Global, Local, And Hierarchical Resolution

The default global config path is `~/.config/cmux/cmux.json`. A local config is discovered from the selected workspace directory by walking upward and checking `.cmux/cmux.json` and `cmux.json`; if nothing exists, cmux still has a default local target at `.cmux/cmux.json`. For notification hooks, cmux can collect the full config hierarchy from root to leaf rather than only the nearest file.

Local config takes precedence for new-workspace action, context menu, `newWorkspaceCommand`, surface tab buttons, and commands. Global config fills in missing values. Command names are de-duplicated by first-seen name, so a local command with the same name shadows the global one.

Sources: [Sources/CmuxConfig.swift:1859-1862](), [Sources/CmuxConfig.swift:1975-2003](), [Sources/CmuxConfig.swift:2040-2087](), [Sources/CmuxConfig.swift:2125-2186]()

## Actions, Commands, And Provider Neutrality

`actions` are reusable UI actions. An action can be:

| Action type | Runtime meaning |
| --- | --- |
| `builtin` | One of cmux's built-in UI actions. |
| `command` | Shell input sent to a terminal or started in a new tab depending on target. |
| `agent` | Convenience wrapper for known agent commands. |
| `workspaceCommand` | Reference to a named workspace command. |
| no runnable action | Ignored unless overriding metadata on an existing built-in. |

The built-in agent enum currently maps `codex` to `codex` and `claude`/`claudeCode`/`claude-code` to `claude`. That is a convenience, not the architectural boundary: plain `command` actions can run any CLI, including BYOC/BYOK wrappers, locally managed model gateways, or provider-neutral scripts.

`commands` are separate named entries. A command must define either `command` or `workspace`, and the decoder rejects entries that define both or neither. Shell commands can appear in the command palette and run in the current terminal. Workspace commands create configured workspaces with optional cwd, color, restart policy, and layout tree.

Sources: [Sources/CmuxConfig.swift:308-360](), [Sources/CmuxConfig.swift:783-908](), [Sources/CmuxConfig.swift:1030-1095](), [Sources/CmuxConfig.swift:1563-1638]()

## Surface Tab Bar Buttons

Surface tab bar buttons can be legacy strings or objects. Object buttons accept `id`, `title`, `icon`, `tooltip`, exactly one runnable form (`action`, `builtin`, `command`, `agent`, or `type: "workspaceCommand"`), optional `confirm`, and optional terminal `target`. Default buttons are new terminal, new browser, split right, and split down.

Built-in ids are canonicalized. For example, `newTerminal` becomes `cmux.newTerminal`, and several Cloud VM aliases collapse to `cmux.cloudvm`. This lets users write shorter config while the resolver still avoids duplicate alias definitions.

Button actions resolve through the action registry first, then through built-ins. Resolved buttons inherit title, tooltip, icon, confirm, terminal target, and source paths from the referenced action unless the button overrides them. Workspace-command buttons are hidden if their referenced command is missing or is not actually a workspace command.

Sources: [Sources/CmuxSurfaceTabBarBuiltInAction.swift:4-68](), [Sources/CmuxConfig.swift:1125-1210](), [Sources/CmuxConfig.swift:1234-1379](), [Sources/CmuxConfig.swift:2438-2560](), [cmuxTests/CmuxConfigTests.swift:1281-1326]()

### Icons Are Part Of The Trust Surface

Button icons can be SF Symbols, emoji, or image paths. Project-local image paths are restricted: project config cannot use absolute paths, tilde expansion, HTTP(S), or files outside the project root. Images are capped at 1 MB, SVGs are parsed, and unsafe SVG constructs such as scripts, external references, `javascript:`, `data:`, `file:`, and remote URLs are rejected.

Untrusted project-local icons can render as a lock placeholder before trust is granted. This is why the surface button trust descriptor includes an icon fingerprint: changing the local icon can invalidate prior trust.

Sources: [Sources/CmuxConfig.swift:362-445](), [Sources/CmuxConfig.swift:512-579](), [Sources/CmuxConfig.swift:589-730](), [Sources/Workspace.swift:9898-9913](), [cmuxTests/CmuxConfigTests.swift:494-564]()

## Workspace Commands And New Workspace UI

Workspace commands are the high-leverage feature for project onboarding. A repository can define a named workspace, cwd, color, restart behavior, and layout. Layouts are trees of panes and splits, and surfaces can be terminal or browser entries. Tests cover plain workspace commands, restart behaviors, pane layouts, split layouts, and nested split/browser layouts.

When a workspace command runs, `CmuxConfigExecutor` resolves cwd relative to the base cwd, creates a workspace, sets custom title/color, optionally closes an old workspace according to restart behavior, and applies the custom layout. Restart behavior includes `new`, `ignore`, `recreate`, and `confirm`.

Sources: [Sources/CmuxConfig.swift:1641-1765](), [Sources/CmuxConfigExecutor.swift:454-515](), [cmuxTests/CmuxConfigTests.swift:1347-1465]()

### New Workspace Action And Context Menu

The new-workspace button can use `ui.newWorkspace.action` or the older `newWorkspaceCommand`. Action-based config is more flexible because the same action model can target built-ins, shell commands, or workspace commands. If an action points to a workspace command, cmux validates that the named command exists and has a `workspace` body.

The context menu defaults to New Workspace and Cloud VM. A configured empty context menu hides defaults. Context menu entries preserve order, allow separators, resolve built-ins and action overrides, filter invalid workspace command actions, and surface configuration issues for missing or non-workspace command references.

Sources: [Sources/CmuxConfig.swift:1831-1835](), [Sources/CmuxConfig.swift:2596-2674](), [Sources/CmuxConfig.swift:2676-2747](), [Sources/AppDelegate.swift:6692-6725](), [cmuxTests/CmuxConfigContextMenuTests.swift:39-99](), [cmuxTests/CmuxConfigContextMenuTests.swift:312-370]()

## Command Palette And Shortcuts

Resolved actions can enter the command palette when `palette` is enabled. Built-ins are not returned as custom palette actions, but command definitions are automatically converted into actions when no explicit action id exists for the command. Action shortcuts are supported as a single string such as `cmd+shift+c` or a one/two-stroke chord array.

Built-in palette commands also consult the config action registry before falling back to hardcoded behavior. For example, New Terminal, New Browser, Split Right, and Split Down first try `executeConfiguredAction` for their canonical built-in ids, then fall back to the app default.

Sources: [Sources/CmuxConfig.swift:2375-2436](), [Sources/CmuxConfig.swift:2571-2594](), [Sources/ContentView.swift:7308-7335](), [Sources/ContentView.swift:7468-7490](), [Sources/ContentView.swift:7858-7892](), [cmuxTests/CmuxConfigTests.swift:586-622]()

## Trust Gates For Project Automation

The trust gate is deliberately source-aware. Global config actions are authorized automatically. Project-local config actions prompt unless the exact trust descriptor is already trusted and the action does not explicitly request `confirm`. The dialog offers Run Once, Trust and Run, or Cancel. Trust and Run stores a fingerprint in Application Support under `cmux/trusted-actions.json`.

The fingerprint includes schema version, action id, kind, command or workspace command payload, target, config path, project root, and project-local icon fingerprint. That makes trust content-addressed to the automation definition rather than a blanket repository approval.

Sources: [Sources/CmuxConfigExecutor.swift:188-241](), [Sources/CmuxConfigExecutor.swift:275-326](), [Sources/CmuxConfigExecutor.swift:328-437](), [Sources/CmuxActionTrust.swift:4-25](), [Sources/CmuxActionTrust.swift:27-76]()

### Execution Paths

Terminal commands are sanitized for display, approved, then sent with a trailing newline. Actions default to opening a new tab in the current pane, while command definitions default to the current terminal path. Workspace commands go through the same authorization boundary, then call the workspace creation path.

Surface tab bar executable buttons have three branches: non-Bonsplit built-ins such as New Workspace and Cloud VM, workspace commands, and terminal commands. Workspace buttons focus the pane first, derive a base cwd from the pane or workspace, then execute through `CmuxConfigExecutor`. Terminal buttons use the same shell-input authorization path and then send input to the current terminal or create a new tab depending on target.

Sources: [Sources/CmuxConfigExecutor.swift:7-65](), [Sources/CmuxConfigExecutor.swift:68-161](), [Sources/Workspace.swift:16766-16845](), [Sources/AppDelegate.swift:13847-13863]()

## Notification Hooks

Notification hooks are command-line policy filters. Global hooks run without project trust descriptors; local hooks get `notificationHook` trust descriptors and must be approved unless already trusted. Hooks are disabled when `enabled` is false, use a default timeout of 20 seconds, and run with cwd set to the project root for their config file.

Hook inheritance starts with global hooks, then applies local configs in hierarchy order. A local config with `hooksMode: "replace"` clears previously inherited hooks before appending its own. Tests verify append order, disabled hooks, cwd selection, explicit local configs outside the discovered hierarchy, and replacement behavior.

Sources: [Sources/CmuxConfig.swift:146-167](), [Sources/CmuxConfig.swift:2005-2019](), [Sources/CmuxConfig.swift:2271-2343](), [cmuxTests/CmuxConfigTests.swift:951-1131]()

At runtime, the notification policy engine passes an envelope through hooks in order. Each hook receives sorted JSON on stdin and environment variables such as `CMUX_NOTIFICATION_TITLE`, `CMUX_NOTIFICATION_SUBTITLE`, `CMUX_NOTIFICATION_BODY`, `CMUX_NOTIFICATION_WORKSPACE_ID`, `CMUX_NOTIFICATION_SURFACE_ID`, and `CMUX_NOTIFICATION_POLICY_JSON`. Hooks run via `/bin/sh -c`, with stdout capped and stderr separately buffered.

Sources: [Sources/TerminalNotificationPolicy.swift:231-312](), [Sources/TerminalNotificationPolicy.swift:314-371](), [Sources/TerminalNotificationPolicy.swift:419-561]()

## Settings And Feedback Loops

The Config Settings window gives users a direct editor for the cmux config and a read-only preview for the effective synced config. Save writes the cmux config and reloads configuration; Reload refreshes from disk and asks `GhosttyApp` to reload configuration. The view also exposes paths, open-in-editor, and reveal-in-Finder affordances.

Schema and resolution issues are not silent. `CmuxConfigStore` records `schemaError`, `newWorkspaceActionNotFound`, `newWorkspaceCommandNotFound`, and `newWorkspaceCommandRequiresWorkspace`; the command palette can surface those issues so users can jump to the source config.

Sources: [Sources/Settings/ConfigSettingsView.swift:43-150](), [Sources/Settings/ConfigSettingsView.swift:186-263](), [Sources/CmuxConfig.swift:1778-1828](), [Sources/CmuxConfig.swift:2860-2935](), [Sources/ContentView.swift:7308-7318](), [Sources/ContentView.swift:7922-7929]()

## Feature Scout Notes

The reusable idea worth copying is the combination of project-local automation and content-addressed approval. cmux does not hardcode a model provider into the trust system; it fingerprints the actual local command, workspace definition, target, config path, project root, and icon content. That pattern supports BYOC/BYOK because teams can plug in their own CLI, local broker, model gateway, or hosted agent wrapper while keeping one UI and one confirmation model.

A Grok-Wiki integration should keep the same boundary: treat skill packs and generated wiki recipes as portable file, repository, or catalog sources; resolve them into local commands or actions; and route execution through the same trust-descriptor mechanism rather than special-casing a proprietary connector.

Sources: [Sources/CmuxConfig.swift:2375-2436](), [Sources/CmuxConfigExecutor.swift:328-437](), [Sources/CmuxActionTrust.swift:4-25]()

---

## 09. In-App Browser Pane Automation

> The WKWebView sidecar experience for authenticated browser panes, omnibar behavior, find-in-page scripts, devtools affordances, and socket-driven browser automation.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/09-in-app-browser-pane-automation.md
- Generated: 2026-05-23T17:52:29.903Z

### Source Files

- `Sources/Panels/BrowserPanel.swift`
- `Sources/Panels/BrowserPanelView.swift`
- `Sources/BrowserWindowPortal.swift`
- `Sources/Find/BrowserFindJavaScript.swift`
- `Sources/Find/BrowserSearchOverlay.swift`
- `cmuxTests/BrowserPanelTests.swift`
- `cmuxTests/BrowserImportMappingTests.swift`
- `tests_v2/test_cli_browser_console_errors_text.py`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/Panels/BrowserPanel.swift](Sources/Panels/BrowserPanel.swift)
- [Sources/Panels/BrowserPanelView.swift](Sources/Panels/BrowserPanelView.swift)
- [Sources/BrowserWindowPortal.swift](Sources/BrowserWindowPortal.swift)
- [Sources/Find/BrowserFindJavaScript.swift](Sources/Find/BrowserFindJavaScript.swift)
- [Sources/Find/BrowserSearchOverlay.swift](Sources/Find/BrowserSearchOverlay.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [Sources/Panels/BrowserAutomation.swift](Sources/Panels/BrowserAutomation.swift)
- [cmuxTests/BrowserPanelTests.swift](cmuxTests/BrowserPanelTests.swift)
- [cmuxTests/BrowserImportMappingTests.swift](cmuxTests/BrowserImportMappingTests.swift)
- [tests_v2/test_cli_browser_console_errors_text.py](tests_v2/test_cli_browser_console_errors_text.py)
- [docs/agent-browser-port-spec.md](docs/agent-browser-port-spec.md)
- [skills/cmux-browser/SKILL.md](skills/cmux-browser/SKILL.md)
</details>

# In-App Browser Pane Automation

cmux’s in-app browser is a `WKWebView` surface embedded into the same pane/tab system as terminal surfaces. The interesting part is not just page rendering: the browser pane preserves authenticated state, exposes an omnibar with history/search suggestions, hosts find-in-page UI above WebKit, keeps DevTools usable through split/layout churn, and exposes a socket/CLI automation API for agents.

This page follows the provided Compound Engineering wiki-shape guidance as a portable writing profile. I did not find `STRATEGY.md` or `docs/solutions/**` in this checkout, so the repository code, tests, and local docs are the source of truth.

## Mental Model

```text
SwiftUI BrowserPanelView
  - toolbar, omnibar, profile/theme/devtools buttons
  - lightweight portal anchor

BrowserPanel
  - WKWebView owner, profile/data store, navigation, find, devtools intent
  - console/error/dialog bootstrap scripts

WindowBrowserPortal
  - per-window AppKit sidecar host
  - reparents WKWebView above SwiftUI
  - hosts find bar and omnibar suggestions where WebKit will not cover them

TerminalController + CLI/cmux.swift
  - socket methods and CLI commands
  - browser.open_split, navigate, wait, snapshot, eval, click, cookies, storage, tabs, logs
```

Sources: [Sources/Panels/BrowserPanelView.swift:406-460](), [Sources/Panels/BrowserPanel.swift:2306-2328](), [Sources/BrowserWindowPortal.swift:3091-3150](), [Sources/TerminalController.swift:3888-3970]()

## Authenticated Browser Pane State

Browser panes are profile-backed. `BrowserProfileStore` owns named profiles, tracks last-used profile ID, creates per-profile `WKWebsiteDataStore` instances, and stores per-profile history files outside the default profile. The built-in default profile uses `WKWebsiteDataStore.default()`, while custom profiles use `WKWebsiteDataStore(forIdentifier:)`; that is the key persistence boundary for cookies, local storage, and repeated sign-in flows.

`BrowserPanel` also uses a shared `WKProcessPool`, configures persistent website data, enables JavaScript, and injects cmux bootstrap scripts at document start. Navigation delegates record visits into the bound profile history store only when the callback belongs to the current web view instance, which matters when profiles switch or WebKit processes are replaced.

Sources: [Sources/Panels/BrowserPanel.swift:371-520](), [Sources/Panels/BrowserPanel.swift:3353-3438](), [Sources/Panels/BrowserPanel.swift:3440-3502](), [cmuxTests/BrowserPanelTests.swift:270-313]()

A notable authentication affordance is explicit default handling for `URLAuthenticationChallenge`. The comment calls out TLS client certificate and MDM/SSO flows: implementing this delegate avoids WebKit’s default rejection path and lets system URL loading, keychain identities, root CAs, and SSO extensions participate.

Sources: [Sources/Panels/BrowserPanel.swift:7602-7618]()

## Omnibar And Navigation Behavior

The omnibar is both an address bar and a search box. Search engines are modeled as `BrowserSearchEngine` cases with query URL builders for Google, DuckDuckGo, Bing, Kagi, and Startpage. `navigateSmart(_:)` first asks `resolveNavigableURL(from:)`; if the input is not URL-like, it falls back to the configured search engine. The URL resolver has explicit localhost handling because `URL(string: "localhost:3777")` would otherwise parse `localhost` as a scheme.

Sources: [Sources/Panels/BrowserPanel.swift:91-157](), [Sources/Panels/BrowserPanel.swift:5029-5044](), [Sources/Panels/BrowserPanel.swift:5339-5374]()

The view layer treats the omnibar as stateful UI, not as a thin text field. It tracks buffer text, selection, IME marked text, inline completion, local and remote suggestion loading, and whether suggestions should be rendered in SwiftUI or the AppKit portal. Suggestions combine recent history, history matches, open-tab matches, stale remote suggestions, and URL resolution; remote search suggestions are skipped for empty, single-character, URL-like, or disabled-suggestion cases.

Sources: [Sources/Panels/BrowserPanelView.swift:417-491](), [Sources/Panels/BrowserPanelView.swift:553-599](), [Sources/Panels/BrowserPanelView.swift:1377-1412](), [Sources/Panels/BrowserPanelView.swift:2273-2407]()

The toolbar around the omnibar gives browser-pane workflows first-class controls: back, forward, reload/stop, screenshot-to-clipboard, React Grab injection, DevTools, profile selection, theme selection, and import hints. Profile and theme controls are popovers backed by `BrowserProfileStore` and `BrowserThemeMode`.

Sources: [Sources/Panels/BrowserPanelView.swift:1056-1193](), [Sources/Panels/BrowserPanelView.swift:1195-1370]()

## AppKit Portal Sidecar

`BrowserPanelView` intentionally keeps find UI out of normal SwiftUI when a live WebKit view is mounted: the comment states that rendering find UI in SwiftUI can put it behind the portal-hosted `WKWebView`. `WindowBrowserPortal` is the sidecar that solves this. It binds a `WKWebView` to a SwiftUI anchor, reparents it into a per-window AppKit host, preserves visibility and z-priority, and exposes registry methods for hiding, discarding, refreshing, updating overlays, and locating the web view under a window point.

Sources: [Sources/Panels/BrowserPanelView.swift:709-735](), [Sources/BrowserWindowPortal.swift:3091-3188](), [Sources/BrowserWindowPortal.swift:4031-4195]()

The portal is also where overlay UI survives WebKit layering. `WindowBrowserSlotView` can host the search overlay and omnibar suggestions as AppKit-hosted SwiftUI views. Its omnibar suggestions hosting view only hit-tests inside the popup frame, so the overlay can sit above WebKit without stealing unrelated pointer events.

Sources: [Sources/BrowserWindowPortal.swift:1271-1331](), [Sources/BrowserWindowPortal.swift:1469-1490](), [Sources/BrowserWindowPortal.swift:1623-1692](), [Sources/BrowserWindowPortal.swift:1707-1725]()

DevTools are part of this sidecar problem. Attached Web Inspector can mutate the moved `WKWebView`’s frame, so the slot view pins plain web views to bounds but preserves WebKit-managed split frames when companion WebKit subviews exist.

Sources: [Sources/BrowserWindowPortal.swift:1811-1872]()

## Find In Page

Find-in-page has three cooperating pieces:

| Piece | Responsibility |
|---|---|
| `BrowserSearchState` | Observable `needle`, selected match, and total count |
| `BrowserSearchOverlay` | Draggable find UI with Return, Shift+Return, Escape, and buttons |
| `BrowserFindJavaScript` | DOM mutation scripts that mark matches and restore text nodes |

`BrowserPanel.startFind()` creates or reuses search state, suppresses address-bar focus, posts focus notifications more than once to survive portal mount races, and replays the search after navigation. The actual search scripts run through `webView.evaluateJavaScript`.

Sources: [Sources/Panels/BrowserPanel.swift:2306-2316](), [Sources/Panels/BrowserPanel.swift:2800-2835](), [Sources/Panels/BrowserPanel.swift:6235-6352]()

The JavaScript uses `TreeWalker` over visible text nodes, skips script/style/template/iframe/SVG content, wraps matches in `<mark class="__cmux-find">`, scrolls the current match into view, and returns JSON with `{total,current}`. Next/previous scripts cycle through `window.__cmuxFindMatches`; clear restores text nodes and removes the injected style element.

Sources: [Sources/Find/BrowserFindJavaScript.swift:3-104](), [Sources/Find/BrowserFindJavaScript.swift:106-172](), [Sources/Find/BrowserFindJavaScript.swift:176-206]()

The overlay is compact and operational: the field shows match counts, Return moves next, Shift+Return moves previous, Escape closes, and the user can drag the overlay to the nearest corner.

Sources: [Sources/Find/BrowserSearchOverlay.swift:21-95](), [Sources/Find/BrowserSearchOverlay.swift:116-181](), [Sources/Find/BrowserSearchOverlay.swift:267-293]()

## DevTools Affordances

DevTools are enabled at the WebKit configuration level with `developerExtrasEnabled`, and `WKWebView.isInspectable` is set on macOS 13.3+. The toolbar button calls `panel.toggleDeveloperTools()`, beeping if the operation is not handled.

Sources: [Sources/Panels/BrowserPanel.swift:3353-3374](), [Sources/Panels/BrowserPanel.swift:3388-3390](), [Sources/Panels/BrowserPanelView.swift:1178-1193](), [Sources/Panels/BrowserPanelView.swift:2061-2068]()

The implementation tracks user intent separately from current WebKit visibility. `preferredDeveloperToolsVisible`, presentation mode, detached-window grace periods, retry work items, and visibility-loss checks let cmux restore DevTools after split/layout attachment churn while still respecting manual close gestures. There is also a `showDeveloperToolsConsole()` helper that tries known private WebKit inspector selectors for opening the console tab.

Sources: [Sources/Panels/BrowserPanel.swift:2881-2915](), [Sources/Panels/BrowserPanel.swift:5891-5941](), [Sources/Panels/BrowserPanel.swift:5959-6139](), [Sources/Panels/BrowserPanel.swift:6141-6176]()

## Socket-Driven Browser Automation

The v2 socket surface is broad enough for agent loops. `TerminalController` routes browser commands including opening, navigation, URL reads, snapshots, evaluation, waits, input actions, screenshots, get/is queries, locator families, frames, dialogs, downloads, cookies, storage, tabs, console/errors, state, scripts, styles, and several unsupported-emulation families.

Sources: [Sources/TerminalController.swift:3560-3668](), [Sources/TerminalController.swift:3888-3970]()

Core commands resolve a workspace and browser surface, then execute against the `BrowserPanel.webView`. `browser.open_split` creates or reuses a right-side browser pane; `browser.navigate` calls `navigateSmart`; `browser.eval` runs arbitrary JavaScript with normalized return payloads; `browser.wait` supports selector, URL substring, text substring, load state, and JavaScript function conditions.

Sources: [Sources/TerminalController.swift:11587-11728](), [Sources/TerminalController.swift:11995-12013](), [Sources/TerminalController.swift:12296-12389]()

Interactive automation gets stable element refs from snapshots and finder methods. The finder path computes a CSS path for matched elements, allocates an `@e` element reference, and returns both selector and ref. Role and text finders are implemented in JavaScript against page DOM and ARIA-ish metadata rather than a browser-provider service.

Sources: [Sources/TerminalController.swift:13081-13165](), [Sources/TerminalController.swift:13167-13255]()

Stateful automation can inspect and mutate WebKit cookies, local storage, session storage, and browser tabs. Console and error commands read arrays installed by `BrowserPanel.telemetryHookBootstrapScriptSource`.

Sources: [Sources/Panels/BrowserPanel.swift:2335-2389](), [Sources/TerminalController.swift:14124-14360](), [Sources/TerminalController.swift:14362-14616]()

## CLI Shape And Agent Workflow

The CLI maps `cmux browser ...` onto the socket API and keeps legacy browser aliases routed to the v2 surface. The help text documents an agent-friendly loop: open, navigate, wait, snapshot, act, and optionally request `--snapshot-after`.

Sources: [CLI/cmux.swift:4458-4491](), [CLI/cmux.swift:9705-9762](), [CLI/cmux.swift:13229-13296](), [CLI/cmux.swift:29598-29634]()

The local `cmux-browser` skill file gives the same practical workflow: target a surface, verify URL, wait, snapshot with `--interactive`, act with refs, then re-snapshot after DOM or navigation changes. This is portable guidance: it is a file-backed repository skill, not a dependency on a hosted model provider.

Sources: [skills/cmux-browser/SKILL.md:10-29](), [skills/cmux-browser/SKILL.md:31-56](), [skills/cmux-browser/SKILL.md:81-90]()

## Tests And Gaps Worth Watching

Tests cover both in-process model behavior and socket/CLI behavior. `BrowserPanelTests` includes profile-isolation regression coverage for stale navigation callbacks, address-bar focus request semantics, and browser file-picker bridge behavior. `BrowserImportMappingTests` verifies import plan defaults, separate-profile mapping, duplicate-name handling, hint presentation, and profile creation/reuse behavior. The CLI regression test proves text-mode `browser console` and `browser errors` print captured entries rather than collapsing to `OK`.

Sources: [cmuxTests/BrowserPanelTests.swift:91-210](), [cmuxTests/BrowserPanelTests.swift:270-360](), [cmuxTests/BrowserImportMappingTests.swift:9-93](), [cmuxTests/BrowserImportMappingTests.swift:147-190](), [tests_v2/test_cli_browser_console_errors_text.py:75-145]()

The agent-browser port spec says the target is an LLM-friendly browser API with stable handles, public v2 terminology around `surface`, and meaningful parity with `agent-browser` where `WKWebView` supports it. The repository skill documents current WKWebView limits: viewport emulation, offline emulation, trace/screencast recording, network route interception, and low-level raw input injection are called out as unsupported.

Sources: [docs/agent-browser-port-spec.md:8-15](), [docs/agent-browser-port-spec.md:24-35](), [docs/agent-browser-port-spec.md:189-220](), [skills/cmux-browser/SKILL.md:114-123]()

## Productization Notes

The feature is BYOC/BYOK friendly because the automation boundary is local: WebKit, AppKit, socket methods, CLI commands, and repository skill files. Agents can call `cmux browser` or socket methods regardless of model provider. A Grok-Wiki integration should present this as portable browser-pane knowledge sourced from files, repositories, or cataloged skills, not as a proprietary connector flow.

The strongest reusable ideas are the portal-hosted WebKit sidecar, profile-backed authenticated surfaces, and snapshot/ref-driven CLI loop. Those three patterns let cmux act like an app-native browser for humans while still exposing deterministic automation hooks for agents. Sources: [Sources/BrowserWindowPortal.swift:4031-4140](), [Sources/Panels/BrowserPanel.swift:510-520](), [CLI/cmux.swift:29598-29634]()

---

## 10. Files, Markdown & Preview Sidecars

> The right-sidebar file workflow that mixes file search, terminal path insertion, command-click routing, Markdown rendering, PDF/image previews, and review-feedback surfaces.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/10-files-markdown-preview-sidecars.md
- Generated: 2026-05-23T17:52:57.270Z

### Source Files

- `Sources/FileExplorerStore.swift`
- `Sources/FileExplorerTerminalPathInsertion.swift`
- `Sources/CommandClickFileOpenRouter.swift`
- `Sources/Panels/MarkdownPanel.swift`
- `Sources/Panels/FilePreviewPanel.swift`
- `Sources/Panels/FilePreviewWorkspaceOpenSupport.swift`
- `cmuxTests/FileExplorerStoreTests.swift`
- `cmuxTests/FilePreviewReviewFeedbackTests.swift`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [Sources/FileExplorerStore.swift](Sources/FileExplorerStore.swift)
- [Sources/FileExplorerState.swift](Sources/FileExplorerState.swift)
- [Sources/RightSidebarPanelView.swift](Sources/RightSidebarPanelView.swift)
- [Sources/FileExplorerView.swift](Sources/FileExplorerView.swift)
- [Sources/FileExplorerSearchController.swift](Sources/FileExplorerSearchController.swift)
- [Sources/FileExplorerTerminalPathInsertion.swift](Sources/FileExplorerTerminalPathInsertion.swift)
- [Sources/CommandClickFileOpenRouter.swift](Sources/CommandClickFileOpenRouter.swift)
- [Sources/cmuxApp.swift](Sources/cmuxApp.swift)
- [Sources/CommandPalette/CommandPaletteSettingsToggle.swift](Sources/CommandPalette/CommandPaletteSettingsToggle.swift)
- [Sources/SettingsNavigation.swift](Sources/SettingsNavigation.swift)
- [Sources/Panels/MarkdownPanel.swift](Sources/Panels/MarkdownPanel.swift)
- [Sources/Panels/MarkdownPanelView.swift](Sources/Panels/MarkdownPanelView.swift)
- [Sources/Panels/MarkdownWebSupport.swift](Sources/Panels/MarkdownWebSupport.swift)
- [Sources/Panels/MarkdownPanelFileLinkResolver.swift](Sources/Panels/MarkdownPanelFileLinkResolver.swift)
- [Sources/Panels/MarkdownWebRenderer.swift](Sources/Panels/MarkdownWebRenderer.swift)
- [Sources/Panels/FilePreviewPanel.swift](Sources/Panels/FilePreviewPanel.swift)
- [Sources/Panels/FilePreviewModeSupport.swift](Sources/Panels/FilePreviewModeSupport.swift)
- [Sources/Panels/FilePreviewWorkspaceOpenSupport.swift](Sources/Panels/FilePreviewWorkspaceOpenSupport.swift)
- [Sources/Panels/PanelOwnedNativeViewSession.swift](Sources/Panels/PanelOwnedNativeViewSession.swift)
- [Sources/FileOpenSocketSupport.swift](Sources/FileOpenSocketSupport.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [cmuxTests/FileExplorerStoreTests.swift](cmuxTests/FileExplorerStoreTests.swift)
- [cmuxTests/FilePreviewReviewFeedbackTests.swift](cmuxTests/FilePreviewReviewFeedbackTests.swift)
</details>

# Files, Markdown & Preview Sidecars

cmux treats files as a first-class pane workflow, not just a Finder-style convenience. The right sidebar can browse a workspace root, run content search, insert selected paths into the active terminal, open local files into cmux surfaces, and hand supported content to Markdown or preview sidecars.

This page focuses on the product workflow and the implementation boundaries that make it useful for agents and humans: local/SSH file roots, ripgrep-backed search, terminal-safe path insertion, command-click routing, Markdown rendering/editing, PDF/image/media/Quick Look previews, and review-feedback-adjacent socket surfaces.

Generation scope: no `docs/solutions/**` directory or `STRATEGY.md` file was present in this checkout, so repository code and tests are the source of truth. The requested Compound Engineering page-shape and QA guidance was treated as bundled recipe metadata, not as an installed local skill execution.

## Workflow Map

```text
Right Sidebar
  files mode  -> NSOutlineView tree -> double-click local file -> open preview/markdown sidecar
  find mode   -> ripgrep results    -> Enter/context menu/drag -> open preview or insert path

Terminal / agent entrypoints
  context menu -> Insert Path / Insert Relative Path -> focused TerminalPanel.sendText(...)
  cmd-click    -> CommandClickFileOpenRouter         -> MarkdownPanel or FilePreviewPanel
  socket API   -> file.open                          -> explicit pane or focused pane

Sidecar surfaces
  MarkdownPanel     -> WKWebView preview + TextEdit mode + Markdown link routing
  FilePreviewPanel  -> text/PDF/image/media/QuickLook + external-open menu
```

Sources: [Sources/RightSidebarPanelView.swift:149-158](), [Sources/RightSidebarPanelView.swift:365-381](), [Sources/FileExplorerView.swift:605-621](), [Sources/FilePreviewWorkspaceOpenSupport.swift:4-58](), [Sources/FileOpenSocketSupport.swift:73-176]()

## Right Sidebar Files And Find Modes

`FileExplorerState` owns sidebar visibility, width, divider position, hidden-file display, and the active `RightSidebarMode`. The root sidebar view hosts a mode bar, then renders `FileExplorerPanelView` for both `.files` and `.find`; the same opening callback is passed through both modes. This keeps file tree and search result opening on one path instead of divergent UI actions.

`FileExplorerStore` is the file tree model. It has a provider abstraction for local and SSH roots, tracks expanded paths, selected paths, loading paths, root status, git status, and a content revision. Local roots use `FileManager.contentsOfDirectory`; SSH roots use a transport that shells out to `/usr/bin/ssh`, resolves `$HOME`, and lists directories remotely. That shape is important for BYOC/BYOK and vendor neutrality: file browsing is provider/transport based, not tied to any hosted model provider or proprietary connector.

Sources: [Sources/FileExplorerState.swift:6-47](), [Sources/RightSidebarPanelView.swift:188-226](), [Sources/RightSidebarPanelView.swift:365-381](), [Sources/FileExplorerStore.swift:258-309](), [Sources/FileExplorerStore.swift:264-289](), [Sources/FileExplorerStore.swift:664-696]()

### Search Is A File Workflow, Not A Separate Product

Find mode is backed by `FileSearchController`, which parses ripgrep JSON into `FileSearchResult` values containing absolute path, relative path, line/column, and preview text. It resolves `rg` from a configured path, common package-manager paths, `PATH`, and Nix-style paths, then streams results through a pipeline that emits partial snapshots and stops after the configured maximum.

The UI result table can open a selected result in cmux, open externally, reveal in Finder, copy paths, insert paths into the terminal, or drag the result as a file preview payload. Tests cover ignored generated directories, all matching files in a folder, high-volume result limiting at 500, refresh on content revision changes, and typing debounce.

Sources: [Sources/FileExplorerSearchController.swift:4-67](), [Sources/FileExplorerSearchController.swift:69-168](), [Sources/FileExplorerSearchController.swift:245-371](), [Sources/FileExplorerView.swift:1484-1519](), [Sources/FileExplorerView.swift:1522-1674](), [cmuxTests/FileExplorerStoreTests.swift:520-631](), [cmuxTests/FileExplorerStoreTests.swift:633-725]()

## Terminal Path Insertion

The file sidebar context menu adds two terminal-oriented actions: `Insert Path` and `Insert Relative Path`. Both tree rows and search results can use them. The implementation delegates quoting/escaping to `TerminalImageTransferPlanner.insertedText`, then finds the focused terminal panel for the current window, window context, or fallback tab manager and sends the text directly.

Relative insertion normalizes filesystem paths, handles `/private/tmp`, `/private/var`, and `/private/etc` display rewrites, returns `.` when the selected path equals the root, and otherwise drops the root prefix. Search results already carry a relative path, so their relative insertion path sends the result’s `relativePath` directly.

Sources: [Sources/FileExplorerTerminalPathInsertion.swift:3-87](), [Sources/FileExplorerTerminalPathInsertion.swift:89-150](), [Sources/FileExplorerTerminalPathInsertion.swift:152-184](), [Sources/FileExplorerView.swift:623-699](), [Sources/FileExplorerView.swift:1653-1674]()

## Command-Click And Socket Routing

Command-click file routing is intentionally narrow. `CommandClickFileOpenRouter.shouldRouteInCmux` checks Markdown routing and general supported-file routing. `openInCmux` tries Markdown first, then falls back to file preview only when the supported-file setting accepts the path. The settings are user-visible through the command palette and settings navigation as “Open Supported Files in cmux” and “Open Markdown in cmux Viewer.”

Routing settings reject unreadable or non-regular files. Markdown routing is disabled by default, extension-gated, and uses the same readable regular-file check as supported file previews. Supported-file routing is enabled by default and posts a notification when changed.

The socket API offers the agent-facing version of this workflow through `file.open`: paths are expanded and standardized, must be absolute readable files, and then open as Markdown or file preview surfaces in an explicit pane/surface destination or the focused pane. The response reports surface IDs, pane IDs, panel type, path, and either preview mode or Markdown display mode.

Sources: [Sources/CommandClickFileOpenRouter.swift:3-24](), [Sources/cmuxApp.swift:4909-4971](), [Sources/CommandPalette/CommandPaletteSettingsToggle.swift:170-216](), [Sources/SettingsNavigation.swift:300-305](), [Sources/FileOpenSocketSupport.swift:4-70](), [Sources/FileOpenSocketSupport.swift:73-176]()

## Markdown Sidecars

Markdown files use a dedicated `MarkdownPanel`, not the generic file preview panel. New panels default to preview mode, keep a panel-owned `MarkdownRendererSession`, load file content immediately, and install file/directory watchers so external saves, atomic rewrites, deletes, and recreates update the panel. Text mode supports editing, dirty tracking, saving through `FilePreviewTextSaver`, and global search capture.

Rendering is WebKit-based. `MarkdownPanelView` keeps the rendered preview and text editor stacked, toggling hit testing and opacity by `displayMode`. The comments in the view explain the product reason: browser-native selection, GitHub markdown CSS behavior, and copyable rendered HTML. The renderer session is panel-owned so SwiftUI wrapper churn during split/tab layout changes does not recreate the web view and flash content.

Markdown links are also routed inside cmux. `MarkdownPanelFileLinkResolver` accepts path-like Markdown filenames, strips fragments/query strings, rejects non-file URLs, resolves relative paths against the Markdown file directory and current working directory, and only returns existing files. `MarkdownWebRenderer` uses that resolver to answer web requests and open Markdown links as new Markdown surfaces in the same pane.

Sources: [Sources/Panels/MarkdownPanel.swift:12-82](), [Sources/Panels/MarkdownPanel.swift:110-187](), [Sources/Panels/MarkdownPanel.swift:191-246](), [Sources/Panels/MarkdownPanel.swift:269-429](), [Sources/Panels/MarkdownPanelView.swift:5-19](), [Sources/Panels/MarkdownPanelView.swift:81-160](), [Sources/Panels/MarkdownWebSupport.swift:69-98](), [Sources/Panels/MarkdownPanelFileLinkResolver.swift:3-58](), [Sources/Panels/MarkdownWebRenderer.swift:503-541]()

## File Preview Sidecars

`FilePreviewPanel` is the general-purpose preview surface. It resolves a `FilePreviewMode` from filename, extension, UTType, and light content sniffing. Known text filenames/extensions become text, PDF/image/media UTTypes get native preview modes, binary plists prefer Quick Look, and extensionless content initially opens in Quick Look until async sniffing can promote it to text. Text loading has a 16 MiB guard and supports UTF-8, UTF-16, and ISO Latin-1.

```swift
// Sources/Panels/FilePreviewPanel.swift
enum FilePreviewMode: Equatable {
    case text
    case pdf
    case image
    case media
    case quickLook
}
```

The view layer switches on that mode: text editor, PDF view, image view, media view, or Quick Look. Non-PDF previews get a file path header with save/revert controls when editable and an external-open menu. Native AppKit views are held in `FilePreviewNativeViewSessions` via `PanelOwnedNativeViewSession`, which reuses mounted views, retires views on close, and avoids letting stale SwiftUI teardown reset the active preview item.

| Mode | Main surface | Notes |
|---|---|---|
| `text` | `FilePreviewTextEditor` | Editable, dirty-tracked, save/revert enabled. |
| `pdf` | `FilePreviewPDFView` | Dedicated PDF chrome and focus intent. |
| `image` | `FilePreviewImageView` | Native image canvas with zoom/rotate support. |
| `media` | `FilePreviewMediaView` | AVKit-backed playback surface. |
| `quickLook` | `QuickLookPreviewView` | Fallback for supported binary/unknown files. |

Sources: [Sources/Panels/FilePreviewPanel.swift:629-826](), [Sources/Panels/FilePreviewPanel.swift:828-897](), [Sources/Panels/FilePreviewPanel.swift:899-944](), [Sources/Panels/FilePreviewPanel.swift:1056-1186](), [Sources/Panels/FilePreviewPanel.swift:1188-1320](), [Sources/Panels/FilePreviewModeSupport.swift:3-17](), [Sources/Panels/PanelOwnedNativeViewSession.swift:3-68](), [cmuxTests/FilePreviewReviewFeedbackTests.swift:60-68](), [cmuxTests/FilePreviewReviewFeedbackTests.swift:70-184]()

## Open-Surface Selection Rules

The workspace helper `openFileSurfaces` is the central product boundary for mixed file opens. It checks `MarkdownPanelFileLinkResolver.isMarkdownPathLike(filePath)` first; Markdown files become Markdown surfaces, while all other paths become file preview surfaces. The same helper supports opening multiple files, target index placement, focus inheritance from the current pane, and optional reuse of existing surfaces.

That shared helper is used by socket `file.open` and by pane drop/split paths. A regression test verifies that explicit pane destinations create a new preview in the requested pane instead of reusing an existing preview elsewhere, which is the right behavior for review workflows and agent-driven UI composition.

Sources: [Sources/Panels/FilePreviewWorkspaceOpenSupport.swift:4-58](), [Sources/Panels/FilePreviewWorkspaceOpenSupport.swift:60-99](), [Sources/Workspace.swift:15456-15477](), [Sources/FileOpenSocketSupport.swift:107-141](), [cmuxTests/FilePreviewReviewFeedbackTests.swift:200-247]()

## Review-Feedback Surfaces

There is no separate “review feedback preview panel” in the inspected implementation. Instead, review feedback connects through two adjacent surfaces:

1. Sidebar metadata commands: `report_pr` and `report_review` both route to pull-request reporting, and help text describes `report_review` as an alias for provider-specific review items.
2. File preview regression coverage: `FilePreviewReviewFeedbackTests` protects the file preview behaviors that matter when review feedback asks an agent or human to inspect artifacts: chorded save shortcuts, extensionless text sniffing, Quick Look lifecycle safety, oversized text rejection, pending focus restoration, and explicit pane targeting.

Settings navigation also exposes review-related sidebar features such as showing pull requests, making PR links clickable, and opening sidebar PR links in the cmux browser. The useful product pattern is to keep review metadata in the sidebar while file artifacts open through the same Markdown/file preview sidecars as every other path.

Sources: [Sources/TerminalController.swift:3067-3074](), [Sources/TerminalController.swift:15895-15905](), [Sources/SettingsNavigation.swift:343-347](), [cmuxTests/FilePreviewReviewFeedbackTests.swift:25-58](), [cmuxTests/FilePreviewReviewFeedbackTests.swift:186-247]()

## Portable Integration Notes

For a Grok-Wiki or similar integration, the safest architecture is to treat files, repositories, and skill/catalog context as portable sources that produce paths and metadata, then let cmux’s existing routing decide the surface. Do not bind wiki previews to a specific model provider. Use repository-relative citations and generated Markdown files as ordinary files: Markdown paths route to `MarkdownPanel`, supported binary/text artifacts route to `FilePreviewPanel`, and terminal commands can insert absolute or relative paths through the existing context menu helpers.

A provider-neutral integration should preserve these boundaries:

| Boundary | Reuse this | Avoid |
|---|---|---|
| File source | `FileExplorerProvider` / `SSHFileExplorerTransport` style abstraction | Hard-coding local-only or vendor-hosted paths |
| Opening files | `openFileSurfaces` / `file.open` | Separate Markdown-vs-preview duplicate routing |
| Markdown links | `MarkdownPanelFileLinkResolver` | Treating arbitrary URLs as local files |
| Agent review metadata | `report_pr` / `report_review` sidebar metadata | Embedding review state inside preview panels |
| Terminal workflows | `FileExplorerTerminalPathInsertion` | Ad hoc shell quoting |

Sources: [Sources/FileExplorerStore.swift:258-289](), [Sources/FileExplorerStore.swift:311-577](), [Sources/Panels/FilePreviewWorkspaceOpenSupport.swift:4-58](), [Sources/Panels/MarkdownPanelFileLinkResolver.swift:6-45](), [Sources/FileExplorerTerminalPathInsertion.swift:57-87](), [Sources/TerminalController.swift:15897-15899]()

## Summary

The right-sidebar file workflow is a compact, reusable product pattern: search and tree navigation produce file paths; path insertion keeps terminal work fast; command-click and socket APIs route files into sidecars; Markdown gets a richer rendered/editor surface; non-Markdown files get mode-specific previews; and review metadata stays in the sidebar while artifacts open through the same file surfaces. The implementation is mostly provider-neutral already because roots and remote access are abstracted, and surface selection is centralized rather than spread across UI entrypoints. Sources: [Sources/FileExplorerStore.swift:664-717](), [Sources/FileExplorerTerminalPathInsertion.swift:57-87](), [Sources/CommandClickFileOpenRouter.swift:3-24](), [Sources/Panels/FilePreviewWorkspaceOpenSupport.swift:4-58](), [Sources/FileOpenSocketSupport.swift:73-176]()

---

## 11. Remote Machines: SSH, Loopback & Cloud VM

> The remote-work architecture that combines local SSH workspaces, remote localhost browser routing, image upload planning, Cloud VM attach endpoints, and a provider registry that stays BYOC and BYOK friendly.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/11-remote-machines-ssh-loopback-cloud-vm.md
- Generated: 2026-05-23T17:54:24.265Z

### Source Files

- `CLI/CMUXCLI+SSHCommandSupport.swift`
- `Sources/WorkspaceRemoteConfiguration.swift`
- `Sources/RemoteLoopbackRuntimeBridge.swift`
- `Sources/TerminalImageTransfer.swift`
- `Sources/Cloud/VMClient.swift`
- `Sources/Cloud/VMClientSocketCommands.swift`
- `web/services/vms/drivers/index.ts`
- `web/services/vms/workflows.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [CLI/CMUXCLI+SSHCommandSupport.swift](CLI/CMUXCLI+SSHCommandSupport.swift)
- [CLI/cmux.swift](CLI/cmux.swift)
- [Sources/WorkspaceRemoteConfiguration.swift](Sources/WorkspaceRemoteConfiguration.swift)
- [Sources/RemoteLoopbackRuntimeBridge.swift](Sources/RemoteLoopbackRuntimeBridge.swift)
- [Sources/RemoteLoopbackProxyAlias.swift](Sources/RemoteLoopbackProxyAlias.swift)
- [Sources/Panels/BrowserPanel.swift](Sources/Panels/BrowserPanel.swift)
- [Sources/TerminalImageTransfer.swift](Sources/TerminalImageTransfer.swift)
- [Sources/TerminalSSHSessionDetector.swift](Sources/TerminalSSHSessionDetector.swift)
- [Sources/Workspace.swift](Sources/Workspace.swift)
- [Sources/Cloud/VMClient.swift](Sources/Cloud/VMClient.swift)
- [Sources/Cloud/VMClientSocketCommands.swift](Sources/Cloud/VMClientSocketCommands.swift)
- [web/app/api/vm/route.ts](web/app/api/vm/route.ts)
- [web/app/api/vm/[id]/attach-endpoint/route.ts](web/app/api/vm/[id]/attach-endpoint/route.ts)
- [web/app/api/vm/[id]/ssh-endpoint/route.ts](web/app/api/vm/[id]/ssh-endpoint/route.ts)
- [web/services/vms/drivers/index.ts](web/services/vms/drivers/index.ts)
- [web/services/vms/drivers/types.ts](web/services/vms/drivers/types.ts)
- [web/services/vms/providerGateway.ts](web/services/vms/providerGateway.ts)
- [web/services/vms/workflows.ts](web/services/vms/workflows.ts)
</details>

# Remote Machines: SSH, Loopback & Cloud VM

cmux treats remote work as one product surface with several transports underneath: user-managed SSH workspaces, detected SSH terminals, browser panels that can reach remote `localhost`, and Cloud VM sessions that mint short-lived attach endpoints. The strongest architectural choice is that the Mac app keeps using common local primitives such as SSH, `scp`, WebSocket, and JSON-RPC while the web backend owns Cloud VM provider calls, billing, leases, and image resolution.

Evidence note: this run found no `STRATEGY.md` or `docs/solutions/**` files in the checkout. The selected Compound Engineering profile was used only as page-shape guidance; repository code remains the source of truth.

## System Shape

```mermaid
flowchart LR
  subgraph MacClient["Mac app and CLI"]
    CLI["cmux CLI"]
    Workspace["WorkspaceRemoteConfiguration"]
    Browser["BrowserPanel + RemoteLoopbackRuntimeBridge"]
    Drop["TerminalImageTransferPlanner"]
    VMClient["VMClient + socket vm.* commands"]
  end

  subgraph WebControlPlane["Web VM control plane"]
    Routes["/api/vm routes"]
    Workflows["VM workflows"]
    Gateway["VmProviderGateway"]
    Registry["provider registry"]
  end

  subgraph Providers["Cloud VM providers"]
    E2B["E2BProvider"]
    Freestyle["FreestyleProvider"]
  end

  subgraph RemoteMachine["Remote machine"]
    SSHD["sshd / SSH gateway"]
    Cmuxd["cmuxd-remote"]
    Loopback["remote localhost apps"]
  end

  CLI --> VMClient
  VMClient --> Routes
  Routes --> Workflows
  Workflows --> Gateway
  Gateway --> Registry
  Registry --> E2B
  Registry --> Freestyle
  CLI --> Workspace
  Workspace --> SSHD
  Workspace --> Cmuxd
  Browser --> Loopback
  Drop --> SSHD
```

Sources: [Sources/WorkspaceRemoteConfiguration.swift:275-294](), [Sources/Cloud/VMClient.swift:265-269](), [web/services/vms/workflows.ts:50-60](), [web/services/vms/providerGateway.ts:55-75](), [web/services/vms/drivers/index.ts:8-29]()

## Local SSH Workspaces

A remote workspace snapshot stores just enough durable connection state to rebuild an SSH workspace: transport, destination, port, identity file, SSH options, and flags for PTY preservation or Cloud VM daemon bootstrap skipping. Before persistence, cmux strips transient OpenSSH control socket options such as `ControlMaster`, `ControlPath`, and `ControlPersist`, and expands `~` in identity paths so restored workspaces do not inherit stale control sockets. Sources: [Sources/WorkspaceRemoteConfiguration.swift:4-31](), [Sources/WorkspaceRemoteConfiguration.swift:56-64](), [Sources/WorkspaceRemoteConfiguration.swift:460-475]()

`WorkspaceRemoteConfiguration` is the richer runtime form. It includes proxy and relay ports, relay credentials, local socket path, terminal startup command, foreground auth token, optional WebSocket daemon endpoint, and `skipDaemonBootstrap`. The transport key deliberately includes the transport, bootstrap mode (`bootstrap` vs `vm-baked`), destination, port, identity, durable SSH options, local proxy port, WebSocket daemon identity, and required PTY capability marker. This makes cache/reuse decisions sensitive to the parts that change remote behavior, not just the host string. Sources: [Sources/WorkspaceRemoteConfiguration.swift:275-294](), [Sources/WorkspaceRemoteConfiguration.swift:335-361]()

For normal SSH restores, cmux builds a reconnect command. For preserved PTY sessions, it uses `ssh-pty-attach`, but only when daemon bootstrap is not skipped and the SSH options support reusable foreground auth. That path adds OpenSSH control defaults when missing and generates a foreground auth token. Sources: [Sources/WorkspaceRemoteConfiguration.swift:373-414](), [Sources/WorkspaceRemoteConfiguration.swift:179-217]()

The attach script is intentionally defensive: it resolves the bundled CLI, requires `CMUX_SOCKET_PATH` and `CMUX_WORKSPACE_ID`, derives a session id, and retries bridge exits `254` or `255` with environment-controlled limits. Foreground auth performs a short SSH command, then notifies the app through `workspace.remote.foreground_auth_ready`. Sources: [Sources/WorkspaceRemoteConfiguration.swift:97-118](), [Sources/WorkspaceRemoteConfiguration.swift:121-150]()

Small CLI helpers keep SSH command embedding predictable by wrapping shell snippets as `/bin/sh -c ...`, escaping `%` for OpenSSH option values, and joining already self-delimiting shell snippets. Sources: [CLI/CMUXCLI+SSHCommandSupport.swift:3-31]()

## Remote Loopback Browser Routing

cmux uses a stable alias host, `cmux-loopback.localtest.me`, to represent remote loopback addresses inside browser panels. The alias mapper recognizes `localhost`, `127.0.0.1`, `::1`, `0.0.0.0`, and subdomains of `localhost`; it can translate both from alias to loopback and from loopback to alias. Sources: [Sources/RemoteLoopbackProxyAlias.swift:3-24](), [Sources/RemoteLoopbackProxyAlias.swift:26-53]()

Browser panels inject `RemoteLoopbackRuntimeBridge.runtimeBridgeScriptSource` at document start for all frames. The script only activates on the alias host, installs once, and rewrites cleartext `http:` and `ws:` URLs from loopback hosts to the alias. It explicitly avoids rewriting `https:` and `wss:` because TLS certificate and SNI expectations depend on the original hostname. Sources: [Sources/Panels/BrowserPanel.swift:3412-3418](), [Sources/RemoteLoopbackRuntimeBridge.swift:23-35](), [Sources/RemoteLoopbackRuntimeBridge.swift:49-71]()

The runtime bridge patches the browser APIs that developer servers commonly use after page load: `fetch`, `XMLHttpRequest.open`, `WebSocket`, and `EventSource`. Separately, BrowserPanel rewrites typed/navigated `http://localhost...` URLs to the alias and rewrites displayed alias URLs back to loopback-looking URLs for the user. Sources: [Sources/RemoteLoopbackRuntimeBridge.swift:74-126](), [Sources/Panels/BrowserPanel.swift:5003-5027]()

## Image And File Transfer Planning

File drop and paste are planned before execution. `TerminalImageTransferPlanner` can insert text, insert delayed text segments, upload files, or reject a payload. It resolves the target as local, workspace remote, or a detected SSH session based on the owning workspace and terminal TTY. Sources: [Sources/TerminalImageTransfer.swift:15-25](), [Sources/TerminalImageTransfer.swift:159-183](), [Sources/TerminalImageTransfer.swift:484-496]()

The planner is conservative. Local files become shell-escaped paths unless the action is a multi-image drop, where paths are sent as delayed segments. Remote targets upload only regular file URLs; unsupported file URL payloads fall back to inserting local paths instead of pretending an upload happened. Sources: [Sources/TerminalImageTransfer.swift:213-237](), [Sources/TerminalImageTransfer.swift:335-360]()

Execution is dependency-injected: workspace remote upload and detected SSH upload are passed as closures, then successful remote paths are shell-escaped and inserted into the terminal. This separation is worth copying because it makes planning testable without requiring a live SSH server. Sources: [Sources/TerminalImageTransfer.swift:262-309](), [Sources/TerminalImageTransfer.swift:426-440](), [Sources/GhosttyTerminalView.swift:10627-10655]()

Workspace remote uploads use `scp` on a background queue, force `ControlMaster=no`, add `StrictHostKeyChecking=accept-new` if the user did not specify host-key policy, and pass through port, identity file, and SSH options. On failure, already-uploaded paths are cleaned up. Detected SSH sessions use the same basic `scp` approach but preserve detected flags such as IPv4/IPv6, agent forwarding, compression, config file, jump host, control path, and identity file. Sources: [Sources/Workspace.swift:5765-5815](), [Sources/Workspace.swift:7296-7342](), [Sources/TerminalSSHSessionDetector.swift:83-123](), [Sources/TerminalSSHSessionDetector.swift:125-170]()

## Cloud VM Client And Socket Commands

The Swift `VMClient` is the Mac-side HTTP client for `/api/vm/*`. It lists, creates, destroys, opens SSH endpoints, opens attach endpoints, and executes commands. It gets Stack Auth tokens from `AuthManager`, derives the base URL from `AuthEnvironment.vmAPIBaseURL`, and sends the selected team id as `X-Cmux-Team-Id` when present. Sources: [Sources/Cloud/VMClient.swift:265-333](), [Sources/Cloud/VMClient.swift:342-407](), [Sources/Cloud/VMClient.swift:412-452]()

Attach endpoints are polymorphic. An endpoint may be SSH with host, port, username, credential, and optional fingerprint; or WebSocket with URL, headers, token, session id, expiry, and optionally a daemon WebSocket endpoint. The client validates response shape and maps both password and authorized-key credentials. Sources: [Sources/Cloud/VMClient.swift:226-263](), [Sources/Cloud/VMClient.swift:360-390](), [Sources/Cloud/VMClient.swift:513-544]()

The app exposes the same Cloud VM operations to the CLI over socket methods: `vm.list`, `vm.create`, `vm.destroy`, `vm.exec`, `vm.ssh_info`, and `vm.attach_info`. `vm.create` requires an idempotency key at the socket boundary, while `vm.attach_info` accepts `require_daemon`/`requireDaemon` and returns either SSH or WebSocket attach payloads. Sources: [Sources/Cloud/VMClientSocketCommands.swift:9-70](), [Sources/Cloud/VMClientSocketCommands.swift:86-121]()

The CLI uses those socket calls rather than embedding provider APIs. `cmux vm ssh` asks for `vm.attach_info` with `require_daemon: true`; if the endpoint is WebSocket and has a daemon endpoint, it opens a WebSocket workspace, otherwise it falls back to SSH options that mark the workspace as `skipDaemonBootstrap`. For the SSH gateway case, the CLI disables known-host persistence and control socket reuse, uses `PreferredAuthentications=none,password`, and embeds the one-time token in the destination. Sources: [CLI/cmux.swift:7708-7770](), [CLI/cmux.swift:7838-7869]()

## Web API, Workflows, And Provider Registry

The web API is an authenticated control plane. VM creation validates JSON shape, provider override, image override, and team id before resolving the default provider/image and before calling the paid workflow. It forwards an `Idempotency-Key` or `x-cmux-idempotency-key`, capped at 128 characters. Sources: [web/app/api/vm/route.ts:93-180](), [web/app/api/vm/route.ts:181-229](), [web/app/api/vm/route.ts:231-274]()

Attach and SSH endpoint routes are small adapters around workflows. The attach route parses `requireDaemon`/`require_daemon`, calls `openAttachEndpoint`, and returns the provider endpoint. The SSH route documents the boundary: the Mac client receives short-lived SSH details and then dials the VM directly through the existing SSH transport, so Next.js is not in the data plane. Sources: [web/app/api/vm/[id]/attach-endpoint/route.ts:12-40](), [web/app/api/vm/[id]/ssh-endpoint/route.ts:12-23](), [web/app/api/vm/[id]/ssh-endpoint/route.ts:24-46]()

The provider layer is BYOC-friendly in shape, with one caveat: the current `ProviderId` union is `"e2b" | "freestyle"`, so adding a provider is a code change, not runtime plugin discovery. The interface still keeps the rest of the system provider-neutral: create, destroy, status, pause/resume, exec, snapshot/restore, attach, SSH, and credential revocation all sit behind `VMProvider`. Sources: [web/services/vms/drivers/types.ts:1-18](), [web/services/vms/drivers/types.ts:76-104]()

The registry currently installs `E2BProvider` and `FreestyleProvider`, with `CMUX_VM_DEFAULT_PROVIDER` selecting either when configured and Freestyle as the fallback default. `VmProviderGateway` wraps provider calls in Effect errors so workflows depend on the gateway interface rather than concrete SDKs. Sources: [web/services/vms/drivers/index.ts:1-29](), [web/services/vms/providerGateway.ts:17-37](), [web/services/vms/providerGateway.ts:44-75]()

Workflows compose repository, provider gateway, and billing gateway layers. Creation begins with an idempotent repository reservation, reserves create credit, calls the provider, finalizes the running VM, refunds/marks failed on errors, and records usage events. Attach and SSH endpoint workflows revoke existing identities before minting a new endpoint, store endpoint leases, and record attach/SSH usage. Sources: [web/services/vms/workflows.ts:50-81](), [web/services/vms/workflows.ts:82-170](), [web/services/vms/workflows.ts:306-369](), [web/services/vms/workflows.ts:384-428]()

## Cloud VM Bootstrap Modes

`skipDaemonBootstrap` is the key bridge between user-managed SSH remotes and Cloud VM remotes. For normal SSH, cmux probes, uploads, and starts `cmuxd-remote`. For Cloud VM images where `cmuxd-remote` is pre-baked and systemd/socket activated, cmux skips probe/upload/stdio hello and synthesizes a baked daemon hello. Sources: [Sources/WorkspaceRemoteConfiguration.swift:289-294](), [Sources/Workspace.swift:5887-5898]()

When bootstrap is skipped, cmux also skips reverse-relay metadata cleanup and only starts the proxy when a daemon WebSocket endpoint exists. The SSH-only Cloud VM fallback intentionally keeps the shell connected with proxy disabled because provider gateways may not support the ssh-exec or socket-forwarding paths needed by the normal relay. Sources: [Sources/Workspace.swift:5899-5935](), [Sources/Workspace.swift:6901-6915]()

## Product Hooks Worth Exploring

| Hook | Why it matters | Implementation anchor |
| --- | --- | --- |
| Remote `localhost` browser preview | Lets a browser panel use familiar loopback URLs while the runtime redirects page traffic through a remote-safe alias. | [Sources/RemoteLoopbackRuntimeBridge.swift:49-126](), [Sources/Panels/BrowserPanel.swift:5003-5027]() |
| Upload planning before execution | Separates UX decisions from SSH mechanics; easy to test and reuse across paste, drop, workspace remote, and detected SSH. | [Sources/TerminalImageTransfer.swift:198-237](), [Sources/TerminalImageTransfer.swift:262-309]() |
| Cloud VM attach polymorphism | Allows SSH and WebSocket providers to coexist while the CLI consumes one `vm.attach_info` method. | [Sources/Cloud/VMClient.swift:260-263](), [Sources/Cloud/VMClientSocketCommands.swift:86-111]() |
| Provider registry plus gateway | Keeps provider SDKs behind a small contract, which supports BYOC/BYOK-oriented deployment choices without pushing provider details into the Mac client. | [web/services/vms/drivers/types.ts:76-104](), [web/services/vms/providerGateway.ts:55-75]() |
| Idempotent create workflow | Prevents duplicate paid creates and records billing/usage events around provider operations. | [Sources/Cloud/VMClient.swift:303-333](), [web/services/vms/workflows.ts:70-170]() |

## Design Constraints And Failure Modes

- TLS loopback rewriting is intentionally out of scope. `https:` and `wss:` are not rewritten because aliasing would change certificate/SNI expectations. Sources: [Sources/RemoteLoopbackRuntimeBridge.swift:60-64]()
- Cloud VM provider neutrality exists at the workflow/driver boundary, but provider ids are currently compile-time literals. A new provider should implement `VMProvider`, register in `buildRegistry`, and pass through the existing route/workflow validation. Sources: [web/services/vms/drivers/types.ts:4-18](), [web/services/vms/drivers/index.ts:10-29]()
- Cloud VM SSH attach credentials are short-lived endpoint details, not a permanent client-side provider credential store. The workflow stores leases and revokes prior identities before minting replacements. Sources: [web/services/vms/workflows.ts:384-428](), [web/services/vms/workflows.ts:728-754]()
- Remote file upload uses `scp`, so unsupported file payloads fall back to text insertion and upload errors are surfaced rather than hidden. Sources: [Sources/TerminalImageTransfer.swift:231-236](), [Sources/Workspace.swift:7330-7340]()

## Summary

The remote-machine architecture is strongest where it keeps boundaries boring: SSH remains SSH, browser loopback routing is an alias-and-rewrite layer, file transfer is planned before execution, and Cloud VM providers live behind authenticated web workflows plus a driver registry. That makes the system portable across user-owned machines and hosted VM providers while preserving room for BYOC/BYOK deployment choices through provider selection, environment configuration, and server-side credential ownership. Sources: [web/services/vms/drivers/types.ts:76-104](), [web/services/vms/workflows.ts:306-369](), [Sources/WorkspaceRemoteConfiguration.swift:335-356]()

---

## 12. What To Demo, Copy or Productize Next

> A closing scout map of the strongest reusable product ideas: composable terminal primitives, source-portable skill and hook adapters, browser-plus-terminal automation, provider-neutral remote compute, and trust-gated project actions.

- Page Markdown: https://grok-wiki.com/public/wiki/manaflow-ai-cmux-5a511656cb1a/pages/12-what-to-demo-copy-or-productize-next.md
- Generated: 2026-05-23T17:54:05.939Z

### Source Files

- `README.md`
- `CLI/cmux.swift`
- `CLI/CMUXCLI+AgentHookDefinitions.swift`
- `Sources/CmuxConfig.swift`
- `Sources/Panels/BrowserPanelView.swift`
- `Sources/TerminalController.swift`
- `web/services/vms/drivers/types.ts`
- `web/services/vms/providerGateway.ts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [README.md](README.md)
- [CLI/cmux.swift](CLI/cmux.swift)
- [CLI/CMUXCLI+AgentHookDefinitions.swift](CLI/CMUXCLI+AgentHookDefinitions.swift)
- [Sources/CmuxConfig.swift](Sources/CmuxConfig.swift)
- [Sources/CmuxActionTrust.swift](Sources/CmuxActionTrust.swift)
- [Sources/CmuxConfigExecutor.swift](Sources/CmuxConfigExecutor.swift)
- [Sources/Panels/BrowserPanelView.swift](Sources/Panels/BrowserPanelView.swift)
- [Sources/TerminalController.swift](Sources/TerminalController.swift)
- [web/services/vms/drivers/types.ts](web/services/vms/drivers/types.ts)
- [web/services/vms/drivers/index.ts](web/services/vms/drivers/index.ts)
- [web/services/vms/providerGateway.ts](web/services/vms/providerGateway.ts)
- [docs/feed.md](docs/feed.md)
- [docs/agent-browser-port-spec.md](docs/agent-browser-port-spec.md)
- [docs/internal/skills-customization-ideas.md](docs/internal/skills-customization-ideas.md)
</details>

# What To Demo, Copy or Productize Next

This page is a closing scout map: it identifies the cmux ideas most worth demoing, copying into other products, or hardening into standalone product surfaces. The strongest pattern is not a single “AI IDE” workflow. It is a set of composable primitives: native terminal surfaces, browser automation, agent hook adapters, project-defined actions, trust prompts, and provider-neutral remote compute.

The knowledge profile for this run was applied as wiki-shaping guidance only. No `STRATEGY.md` or `docs/solutions/**` files were present in this checkout, so repository code and checked-in docs remain the source of truth.

Sources: [README.md:119-139](), [docs/internal/skills-customization-ideas.md:1-14]()

## Scout Summary

| Candidate | Demo next | Copy elsewhere | Productize when |
| --- | --- | --- | --- |
| Composable terminal primitives | Multi-agent workspace with splits, notifications, CLI sends, and browser sidecar | Any developer tool that wants agent orchestration without owning the agent | Workflows can be saved, shared, and trusted per project |
| Source-portable hooks and skills | `cmux hooks setup` across Codex, Grok, Cursor, Gemini, OpenCode, and others | Adapter registry for any tool that emits lifecycle or permission events | Hook install, uninstall, validation, and restore become reliable user flows |
| Browser-plus-terminal automation | Agent inspects a dev server, clicks UI, reads console errors, then fixes code in terminal | Browser automation panel for repo-local QA and docs previews | Browser commands expose stable snapshots and deterministic post-action state |
| Provider-neutral remote compute | Create/attach/exec/destroy VM flows with provider selection | BYOC remote sandbox abstraction | Driver contract supports billing, credentials, revoke, snapshot, and browser proxy uniformly |
| Trust-gated project actions | `.cmux` or `cmux.json` action runs after explicit trust | Any repo-defined command palette or dock surface | Trust fingerprints, rollback, previews, and portable bundles are first-class |

Sources: [README.md:87-90](), [CLI/cmux.swift:4035-4459](), [web/services/vms/drivers/types.ts:1-104](), [Sources/CmuxConfigExecutor.swift:188-240]()

## 1. Demo Composable Terminal Primitives

The README’s strongest product claim is that cmux is “a primitive, not a solution”: terminal, browser, notifications, workspaces, splits, tabs, and CLI control are intentionally left composable rather than forced into one agent workflow. That maps directly to the code: the CLI can send text and keys to a selected workspace/surface, create notifications, manage sidebar status/progress/logs, run hooks, and dispatch browser commands through the same socket-facing command surface.

A good demo should avoid “one magic agent” framing. Show three small primitives combining:

1. Start two agent terminals in separate surfaces.
2. Use `cmux notify`, status, progress, or logs to mark which one needs review.
3. Open a browser split and let an agent inspect or act on a local dev server.
4. Jump between unread notifications and focused surfaces.

Sources: [README.md:125-129](), [CLI/cmux.swift:4041-4078](), [CLI/cmux.swift:4124-4193](), [CLI/cmux.swift:4295-4377]()

```text
Developer workflow
  ├─ terminal surfaces: agent CLIs, shell commands, tmux-compatible verbs
  ├─ browser surfaces: app preview, DOM snapshots, console/errors
  ├─ sidebar state: notifications, status, progress, logs
  └─ socket/CLI: shared automation layer for scripts and agents
```

The reusable product idea is a “developer workbench primitive layer”: other tools can copy the split between UI surfaces and socket commands without copying cmux’s macOS UI.

## 2. Copy the Source-Portable Hook Adapter Pattern

`AgentHookDef` is the clearest adapter shape in the repo. It models each agent by name, display name, config path, disable env var, hook marker, binary name, config format, native events, feed-hook events, aliases, and optional post-install behavior. The definitions cover multiple hook formats instead of assuming one vendor protocol.

The hook shell command is also source-portable: it prefers `CMUX_BUNDLED_CLI_PATH`, falls back to `command -v cmux`, respects `CMUX_SOCKET_PATH`, checks `CMUX_SURFACE_ID`, and emits `{}` when it cannot safely route the event. That is a productizable pattern for portable hook packs: a repo, file, or catalog source can install an adapter without binding the architecture to a model provider.

Sources: [CLI/CMUXCLI+AgentHookDefinitions.swift:6-47](), [CLI/CMUXCLI+AgentHookDefinitions.swift:122-245](), [CLI/CMUXCLI+AgentHookDefinitions.swift:299-320](), [CLI/CMUXCLI+AgentHookDefinitions.swift:479-520]()

### What to Productize

| Product surface | Why it is strong | Provider-neutral rule |
| --- | --- | --- |
| Hook adapter catalog | Agent integrations are data-shaped and already support multiple config formats | Catalog entries describe files, commands, events, and reply shapes, not model vendors |
| Hook health doctor | Installed hooks can drift; adapters need validation and repair | Validate local files and binaries only; do not require hosted accounts |
| Feed bridge SDK | Feed already normalizes permission, plan, and question events | Source may be file, repo, or catalog; event transport remains cmux socket JSON |

The Feed docs confirm the higher-level workflow: agents pipe events to `cmux hooks feed --source <agent>`, cmux forwards them as `feed.push`, and replies return through `feed.permission.reply`, `feed.question.reply`, or `feed.exit_plan.reply`. The important caveat is also documented: Feed is advisory and times out, so it should not be positioned as a hard workflow lock.

Sources: [docs/feed.md:13-50](), [docs/feed.md:63-99](), [docs/feed.md:101-134]()

## 3. Demo Browser-Plus-Terminal Automation as a Real QA Loop

The browser surface is more than “open a URL.” The v2 socket dispatch includes navigation, snapshots, JS evaluation, waits, clicks, typing, filling, screenshots, selectors, frames, dialogs, cookies, storage, tabs, console logs, errors, highlighting, state save/load, viewport/geolocation/offline controls, network routing, and screencast/input commands.

That makes the best demo a local QA loop:

1. Agent runs the app in a terminal.
2. Agent opens a browser split.
3. Agent snapshots the page, clicks or fills a form, reads console/errors, and takes a screenshot.
4. Agent returns to the terminal to patch the bug.

Sources: [README.md:51-53](), [README.md:127-129](), [Sources/TerminalController.swift:3521-3689](), [docs/agent-browser-port-spec.md:8-22]()

```swift
// Sources/TerminalController.swift
case "browser.snapshot":
    return v2Result(id: id, self.v2BrowserSnapshot(params: params))
case "browser.eval":
    return v2Result(id: id, self.v2BrowserEval(params: params))
case "browser.click":
    return v2Result(id: id, self.v2BrowserClick(params: params))
case "browser.fill":
    return v2Result(id: id, self.v2BrowserFill(params: params))
```

The copyable pattern is “browser as an adjacent programmable surface,” not a separate browser-testing service. For a Grok-Wiki integration, keep this provider-neutral by generating browser workflows from repository files or reusable catalog recipes, then executing through the local socket API. The recipe source can be a checked-in file, a repository skill, or a catalog package; the browser runtime should not assume a specific LLM provider.

## 4. Productize Provider-Neutral Remote Compute

The VM driver contract is already intentionally narrow and provider-neutral. `ProviderId` currently includes `e2b` and `freestyle`, but callers hold a `VMProvider` interface with create/destroy/status/pause/resume/exec/snapshot/restore/openAttach/openSSH/revoke methods. Attach endpoints are typed as either SSH or WebSocket PTY, and SSH credentials include revocation handles for providers that mint revocable identities.

The gateway wraps provider calls in Effect and converts provider failures into typed `VmProviderOperationError`s. Provider selection is registry-based, with `CMUX_VM_DEFAULT_PROVIDER` choosing the default when set.

Sources: [web/services/vms/drivers/types.ts:1-36](), [web/services/vms/drivers/types.ts:54-104](), [web/services/vms/drivers/index.ts:8-30](), [web/services/vms/providerGateway.ts:17-76]()

```mermaid
flowchart LR
  subgraph CLI_and_App["CLI / app socket"]
    VMCLI["cmux vm / cloud commands"]
  end

  subgraph WebServices["web/services/vms"]
    Gateway["VmProviderGateway"]
    Registry["drivers/index.ts registry"]
    Contract["VMProvider interface"]
  end

  subgraph Providers["Provider drivers"]
    E2B["E2BProvider"]
    Freestyle["FreestyleProvider"]
  end

  VMCLI --> Gateway
  Gateway --> Registry
  Registry --> Contract
  Contract --> E2B
  Contract --> Freestyle
```

This is the most obvious BYOC/BYOK-compatible product line: let users bring a provider, image, key, or remote workspace policy while cmux keeps a stable attach and automation contract. Productize the driver interface before adding provider-specific UI flourishes.

## 5. Turn Trust-Gated Project Actions Into Shareable Workflow Bundles

Project-local actions are powerful because they can define terminal commands, agent launches, workspace commands, icons, shortcuts, command-palette metadata, confirmation behavior, and surface tab bar buttons. The parser validates action types and prevents ambiguous button definitions, while the executor checks project-local commands against a trust descriptor before running.

The trust model is fingerprint-based. A descriptor includes schema version, action ID, kind, command or workspace command, config path, project root, and icon fingerprint; trusted fingerprints are stored in Application Support. Project actions from the global config run directly, while project-local actions prompt unless already trusted or explicitly confirmed.

Sources: [Sources/CmuxConfig.swift:10-21](), [Sources/CmuxConfig.swift:783-908](), [Sources/CmuxConfig.swift:1030-1095](), [Sources/CmuxConfig.swift:1234-1336](), [Sources/CmuxActionTrust.swift:4-24](), [Sources/CmuxActionTrust.swift:27-76](), [Sources/CmuxConfigExecutor.swift:188-240]()

### What to Demo

| Workflow | Code-backed capability | Product risk to solve |
| --- | --- | --- |
| Repo command palette | `actions` include title, subtitle, keywords, shortcut, icon, tooltip, confirm, and target | Users need preview/diff before trusting generated config |
| Project tab buttons | `surfaceTabBarButtons` can reference built-ins, commands, agents, or workspace commands | Buttons must stay readable and safe when inherited from a repo |
| Team workflow bundle | Internal planning notes already list `.cmux` bundles, Dock controls, hooks, and browser previews as candidate examples | Need import/export/reset and one-command rollback |

Sources: [docs/internal/skills-customization-ideas.md:15-28](), [docs/internal/skills-customization-ideas.md:45-74](), [Sources/CmuxConfigExecutor.swift:275-325]()

A Grok-Wiki integration should treat generated workflow bundles as portable source artifacts. The wiki can propose `.cmux/` examples or catalog skills, but execution must flow through cmux validation and trust prompts. That keeps the design independent of any model provider and makes BYOK/BYOC possible: users can generate bundles with one tool, review them as files, and run them locally through trusted cmux actions.

## 6. Keep the Page Shape Honest

The strongest reusable ideas are source-grounded, but not equally mature:

| Idea | Confidence | Why |
| --- | --- | --- |
| Terminal/browser/socket primitives | High | README, CLI routing, and `TerminalController` all expose broad scriptable surfaces |
| Agent hook adapters | High | `AgentHookDef` centralizes real integrations across many agents |
| Feed as decision surface | Medium-high | Docs describe working event/reply path and timeout semantics; Codex plan questions have a documented limitation |
| Remote compute abstraction | Medium | Driver contract is clean, but product readiness depends on provider rollout and image behavior |
| Shareable project workflow bundles | Medium | Config/trust primitives exist; examples and lifecycle tooling are still product candidates |

Sources: [docs/feed.md:124-134](), [web/services/vms/drivers/types.ts:76-104](), [docs/internal/skills-customization-ideas.md:45-74]()

## Closing Summary

The next product bets should preserve cmux’s core advantage: local, scriptable, provider-neutral primitives that users can compose around their own agents, repos, keys, and compute. Demo the primitives together, copy the adapter boundaries, and productize only where trust, portability, and rollback are strong enough for project-local automation. The code already supports that direction through the socket/CLI surface, multi-agent hook definitions, browser automation verbs, VM provider interface, and fingerprinted project-action trust path. Sources: [README.md:133-139](), [CLI/CMUXCLI+AgentHookDefinitions.swift:6-47](), [Sources/TerminalController.swift:3521-3689](), [web/services/vms/providerGateway.ts:44-76](), [Sources/CmuxConfigExecutor.swift:313-373]()

---