Terminal and side panel
The right side panel's two built-in tabs - Chat and Terminal - how to choose which ship and which opens first, and what the Terminal gives the user's own CLI.
The right side panel hosts two built-in surfaces. Chat is the conversational assistant (Chat). Terminal launches the user's OWN coding CLI (Claude Code or Codex) in an embedded terminal, in the app's work folder and wired to the user's own MCP servers - so the user drives the real CLI right where the app works.
Terminal runs on the bundled daemon. The CLI runs on the user's machine, driven by the companion daemon - the app spawns the daemon's terminal command in a real pty and shows it. That daemon ships inside the packaged app, so nothing needs publishing and nothing needs pairing: the tab appears whenever config.desktop.agents.enabled is on, whether or not your product also offers the companion as a web feature. The daemon it runs on comes from generating the project with --desktop --ai - see the AI agents overview for why that half is not a runtime flag.
Choose which tabs ship
Set config.desktop.agents.sidePanel to pick which of the two built-in tabs ship and which opens first. Both ship by default, with Chat as the default tab.
// packages/config/src/desktop.mjs
agents: {
// ...
sidePanel: { chat: true, terminal: true, default: "chat" }
}| Field | Type | Default | Effect |
|---|---|---|---|
chat | boolean | true | Whether the Chat tab ships. Set false to hide it. |
terminal | boolean | true | Whether the Terminal tab ships. Set false to hide it. (It also needs a companion.) |
default | "chat" | "terminal" | "chat" | Which tab opens the first time the panel is shown. |
- Only one tab: set the other to
false. The panel then renders as a single surface (for example{ chat: false, terminal: true, default: "terminal" }for a terminal-only app). - No panel: set both
false. The panel and its trigger disappear entirely. - Default vs. the user's choice:
defaultonly picks the tab shown on first open. Once the user switches tabs, their choice is remembered across restarts and wins overdefault. Ifdefaultnames a disabled tab, the first available tab opens instead.
The whole side panel is gated on config.desktop.agents.enabled. With the AI feature off the panel, both tabs, and their trigger never render - the source still ships, so turning the flag on brings them back.
What the Terminal wires in
The app names only the CLI to run, the terminal's size, and (optionally) a folder the user granted. The rest of the session is composed by the user's companion, on their machine:
- Their own MCP servers: any server the user added with the daemon's
mcp add --localis wired into the session. - A working folder: the product's confined work folder by default, or one of the user's own folders once they have allowed it (below).
Nothing extra is asked of the user: the CLI resolves its own login exactly as it does in a plain terminal. A session is recorded in the daemon's local audit log before the CLI starts, so <daemon> log always shows why it ran.
Env-backed MCP servers are terminal-only. A server added with mcp add --local that carries a credential in its environment (--env KEY=VALUE) runs in a Terminal session - the session spawns the CLI and re-hydrates the value - but a local chat run skips it, because the on-device executor cannot inject an environment. <daemon> mcp list --local marks such a server [terminal-only], so the difference is visible where the user manages them.
Working folder, and allowing your own
By default a session runs in the product's confined work folder - the same one dispatched runs use. To work on a real project, the user picks a folder with Choose a folder..., which opens the OS's native dialog.
Picking a folder also allows it. The daemon refuses any working folder the user has not granted on that machine, so the app shows the exact path it is about to allow and says plainly that the grant is durable: every later session may use that folder, until the user removes it. Allowed folders are listed in the tab, each with a way to stop allowing it. Your backend can never add, name, or widen a grant - the session it composes cannot carry a path at all.
Approval prompts
A Terminal session is the user's own CLI on their own machine, so what keeps it in its folder is the CLI's native approval prompts - and the user's companion policy ceiling decides whether those stay on:
| Ceiling | What the user sees |
|---|---|
full | The CLI runs with its prompts bypassed (it acts freely, like the built-in chat), and nothing then keeps it in the folder it started in. |
auto-edit / read-only | The daemon withholds the bypass and the CLI keeps asking before it edits or runs. |
network: off | A terminal session is refused: the CLIs have no flag that blocks egress, so a policy promising the network is off must not open an online terminal. |
The tab shows the effective mode read-only. The ceiling is the user's to set (<daemon> policy set), never the app's - your product can only ever be clamped by it.
An older companion
The daemon updates on the user's schedule, so the app will meet companions installed before the terminal command existed. It probes for the capability (the version the daemon reports in its status) and, when it is missing, the tab shows the one command that fixes it - <daemon> update - instead of controls that would only print a usage banner into the terminal.
Chat
The side panel's persisted, on-device Chat tab - a multi-turn conversation with the user's chosen model, run by the companion daemon.
Scheduled agents
Prompts that run automatically on a cadence on the user's device - built-in ones you ship, plus ones the end-user creates - and, when the app is connected, their account's schedules beside them.