Desk runs every managed session under a Moor holder. The browser is a view over that durable process. Built-in agents add launch flags, resume behavior, permission handling, and attention signals on top of that Moor-managed lifetime.

Supported session kinds

Built-in agent values:
  • codex
  • claude
  • opencode
  • qwen
  • kimi
  • grok
  • bash
Any session can also use a custom command.
Custom commands are terminal sessions. They do not get built-in agent resume or permission logic unless the command implements compatible behavior itself.

Session identity

Every session has a durable sessionId. Desk preserves it across edits and uses it for the Moor control socket, terminal state, channels delivery, attention, and agent events. The agent’s resume id remains a separate provider-owned value. Desk sets these environment variables for managed launches:
DESK_SESSION_GENERATION is what lets Desk reject a report from a producer that outlived a restart: a hook stamps the generation it was launched in, and the daemon refuses anything carrying an older one. A hook process that inherits no generation posts nothing — silence is the correct answer when an event cannot be attributed, and it is why a session started before its hooks existed reads unknown rather than reporting stale state. A session bound to an agent profile also receives that provider’s credential-directory variable — CLAUDE_CONFIG_DIR or CODEX_HOME — pointing at the profile root.

Codex

Codex sessions launch as codex, with flags added for the features the session enables. Desk no longer configures Codex’s terminal notification settings: state comes from Codex’s own lifecycle hooks, so there is nothing to parse out of the TUI. When permission bypass is enabled, Desk adds Codex’s dangerous bypass flag. When resume is set, Desk launches Codex in resume mode. When LSP agent access is enabled, Desk passes a desk_lsp MCP server definition to Codex so the agent can call Desk’s language-server tools.

Claude

Claude sessions use Claude Code’s CLI and support:
  • resume ids
  • dangerous permission bypass
  • optional MCP config for Desk LSP tools
  • hook settings for state reporting and resume capture
Desk’s Claude hooks live in Desk’s own settings file and are handed to the CLI with --settings at launch:
Desk does not write ~/.claude/settings.json. That file is the operator’s — their model, their hooks, their preferences — and a tool that installs its reporting by editing it has taken something it was not given. The launch flag achieves the same thing for the session Desk starts and leaves the operator’s file exactly as they wrote it. desk hooks install writes Desk’s file.

OpenCode

OpenCode sessions launch through the configured OpenCode binary. Desk resolves it from:
  • DESK_OPENCODE_BIN
  • PATH
  • ~/.opencode/bin/opencode
Desk uses a Desk-owned OpenCode config directory, normally:
That directory contains Desk’s attention plugin. Desk does not rely on the user’s normal OpenCode config directory for its integration state. OpenCode permission behavior is controlled per session through OPENCODE_CONFIG_CONTENT:
  • bypass enabled: permission["*"] = "allow"
  • bypass disabled: permission["*"] = "ask"
Desk also sets:
This preserves Desk/xterm selection and copy behavior. OpenCode resume uses ses_... ids. Desk can discover recent OpenCode sessions from opencode session list --format json and persist a matching id.

Qwen

Qwen sessions launch as qwen (Qwen Code, a Gemini-CLI fork). Desk supports:
  • resume ids (--resume <id>, a UUID)
  • permission bypass (--yolo)
  • hook settings for state reporting and resume capture
Desk wires desk_lsp for Qwen in two parts. desk hooks install registers the server once in ~/.qwen/settings.json (Qwen has no per-session MCP flag), unconditionally — the install command has no view of LSP settings. The per-session part is gated: only when LSP agent access is enabled does a Desk launch carry the session’s DESK_LSP_ENV_FILE in the environment. Without that variable the registered tools stay visible to Qwen but every call returns a missing-environment error — including in Qwen sessions run outside Desk. Note that the variable lives in the Qwen process environment, so child processes Qwen spawns inherit it; the token it points to stays bound to the launching session’s workspace. Qwen’s hooks are Claude-compatible and live in:
desk hooks install merges Desk’s hooks into that file and also fills two terminal-context keys Qwen exposes only through settings — ui.mouseTracking: false and ui.useTerminalBuffer: false, so a click does not paste mouse-report bytes and the pane has one scrollback instead of two. It fills only keys the operator has not set: an explicit operator value is never overridden, and other settings are untouched. If the file is malformed JSON it is backed up and skipped rather than overwritten. Qwen reads a command hook’s timeout in milliseconds (unlike Claude/Codex, which use seconds), so Desk writes 10000 there. Permission bypass maps to Qwen’s --yolo (auto-approve all tools; needs Qwen Code ≥0.21.13 — older CLIs reject the flag, and Qwen’s own auto-updater keeps the effective version current). A successful --resume keeps the conversation id. What Qwen does not do is persist a resumable session before the first message is exchanged, and a fresh launch mints a new id — so a Qwen pane restarted before any input carries an id the CLI rejects. Desk keeps the pane alive (see Resume capture) rather than letting it exit. Qwen needs a provider credential. Point it at Alibaba ModelStudio, a third-party key, or any OpenAI-compatible endpoint through Qwen’s own Connect a Provider flow.

Kimi

Kimi sessions launch as kimi (Kimi Code). Desk supports:
  • resume ids (--session <id>; Kimi mints session_... values, which Desk validates as opaque tokens)
  • permission bypass (--yolo)
  • hook settings for state reporting and resume capture
Kimi’s hooks are TOML [[hooks]] blocks appended to:
desk hooks install rewrites only Desk’s own blocks and leaves operator-authored hooks in place; a config whose hooks key is not an array of tables is skipped rather than corrupted. Validate with kimi doctor. Kimi’s coding endpoint needs an active Kimi (Moonshot) membership for OAuth, or a provider key added through kimi provider.

Grok

Grok sessions launch as grok (superagent-ai’s grok-cli, published to npm as grok-dev; it runs under Bun). Desk supports:
  • resume ids (--session <id>; Grok mints 12-hex values, which Desk validates as opaque tokens)
  • hook settings for state reporting and resume capture
Grok’s hooks are Claude-compatible and live in:
Grok has no per-tool approval system, so there is no permission-bypass flag and the bypass checkbox is hidden for it. Grok also fires SessionStart lazily on the first prompt (not at launch), so a freshly launched Grok pane reads unknown until the first message, and its tool hooks fire only for the bash tool. There is no periodic heartbeat to fill that gap: a long non-bash tool run (e.g. an MCP tool from Grok’s own settings) outlives the working lease and the session reads unknown until the next hook fires. Grok cannot join desk_lsp yet: it reads MCP servers only from its global settings and spawns them with a sanitized environment, so the per-session token cannot reach the server.
desk hooks install only writes a new agent’s hook config when that CLI’s config directory already exists, so a machine that never installed Qwen, Kimi, or Grok is left untouched.

Bash

Bash sessions run:
Bash does not have agent-specific permission bypass, resume capture, or LSP MCP wiring.

Permission bypass

The Add Session modal shows a bypass-permissions option for Codex, Claude, OpenCode, Qwen, and Kimi. The manifest field is:
For Codex and Claude, Desk maps that field to the agent CLI’s dangerous bypass mode; for Qwen and Kimi it maps to --yolo. For OpenCode, Desk maps it to a per-session OpenCode permission config. Unchecking the box makes OpenCode ask for tool permissions. Grok has no bypass flag (it has no per-tool approvals at all): the checkbox is hidden for it, and bypassPermissions: true on a Grok session is a manifest error rather than a silent no-op. (A bash session still ignores the field silently, as before.)

Resume capture

Desk can start a session without resume and later capture the conversation id:
  • Codex: reads Codex session records and startup shell snapshots
  • Claude, Qwen, Kimi, Grok: receive hook events carrying the provider session id
  • OpenCode: queries OpenCode’s session list
Captured ids are validated before writing the manifest. A resume id is a hint, not a guarantee. If the CLI rejects it — most often a Qwen session restarted before its first message, or any conversation the provider no longer has — Desk keeps the pane alive with a diagnostic and a shell instead of letting it exit. Start fresh by running the agent without a resume id, or clear the binding with desk reset-provider-session <sessionId> --force.

State reporting

Every supported CLI reports its own lifecycle to Desk through hooks that desk hooks install writes. Each hook posts a small, bounded observation — which event fired, and the few fields Desk needs from it — to /api/agent-event. Desk decides what that observation means server-side, so one definition of “working” serves every provider instead of one per adapter. The facts a provider can assert: Anything a provider does not assert stays unknown. Desk never fills the gap from terminal output: the OSC 9 and BEL sniffing earlier versions used is gone, along with the screen poller that backed it, because a signal that means “something happened” cannot be told apart from one that means “a turn ended”. Attention is a projection of that state for the operator. It does not gate channel delivery — see the delivery engine.
Hook configuration is read when a session launches. A session started before desk hooks install ran reports nothing until it is restarted, and shows unknown in the meantime.

Agent LSP access

When LSP is enabled and agent LSP access is enabled, Desk wires the desk_lsp MCP server into supported managed agents (Codex, Claude, and Qwen). The server exposes language-server tools such as hover, definitions, references, diagnostics, symbols, completions, rename preparation, rename edits, formatting, and code actions. The MCP surface is token-bound to the session workspace. Agents receive the token through a locked-down runtime env file or agent-specific MCP config. Tools return data and edits; they do not directly apply changes to files.

Channels membership

Channels give every managed agent session — Claude, Codex, OpenCode, Qwen, Kimi, and Grok — its own first-class member type. Custom-command and bash sessions enter through the generic bash-typed member path. The member type is a roster/notice label only — no delivery or supervisor behavior gates on it — so a bash-typed member still participates fully.