Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ npm run dev
| [Built-in browser and audit log](docs/browser.md) | [Bundled agent browser skill](agent/browser/SKILL.md) |
| [Install, releases, and local data](docs/installing-and-security.md) | [Security policy](SECURITY.md) |
| [Architecture](docs/ARCHITECTURE.md) | [UI contract](docs/UI_CONTRACT.md) |
| [Project context and learning](docs/context.md) | [Docker/Podman across computers](docs/container-overview.md) |
| [Runtime plugin authoring](docs/plugins.md) | [Typed plugin SDK](docs/plugin-api.d.ts) |
| [Changelog](CHANGELOG.md) | [MIT license](LICENSE) |

Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Electron main process
- `src/main/services/LimitsService.ts` reads Codex through the installed CLI's app-server protocol and Claude, Kimi, OpenCode Go, and Grok Build through their provider usage or billing endpoints. Qwen Code is multi-provider and exposes no provider-neutral read-only quota protocol, so its adapter reports `cli-not-found` or `unsupported-protocol` and never invents percentages. Provider credentials are read only inside the trusted main process, sent only to the matching provider over HTTPS, and never logged or exposed over IPC. The service owns timeout, structural normalization, caching, stale fallback, and subprocess cleanup; raw provider responses never cross IPC.
- `src/main/services/SettingsStore.ts` normalizes every update and persists through a serialized atomic write. Canvas regions and sticky notes have independent persistence gates: disabling one keeps its live objects for the current process but omits that collection from the disk snapshot and therefore from the next launch. The configurable canvas launcher and UI scale use the same boundary; transient window stacking does not.
- `src/main/services/PluginManager.ts` installs ready-to-run repositories without executing package scripts during install/update, rejects symlinks and oversized packages, persists the enabled registry, serves only contained package files, and enforces per-plugin permissions/storage quotas. Optional native agent-hook entries remain off by default; explicit per-hook trust is persisted in the plugin registry and compiled into a separate private atomic runtime registry. Update, module replacement, plugin disable, and uninstall revoke that trust before executable files change.
- `src/main/services/PluginSecretsService.ts` serializes per-plugin secret writes, encrypts the complete bounded payload through Electron `safeStorage`, rejects plaintext-only backends, and removes each encrypted file on uninstall.
- `src/main/services/PluginSecretsService.ts` serializes per-plugin secret writes, encrypts the complete bounded payload through Electron `safeStorage`, rejects plaintext-only backends, and removes each encrypted file on uninstall. `ProviderSecretsService.ts` applies the same architecture to provider API keys for BYOK-capable CLIs: values stay in the main process, and the renderer contract exposes only per-key `configured` flags plus set/clear actions. `ApiProfile` settings entries name model backends (protocol, HTTPS base URL, secret reference) for the same BYOK runtimes; they are not agent providers, and the settings normalizer drops invalid profiles instead of repairing them.
- `src/main/services/PluginMediaService.ts` persists per-plugin grants only after a native folder choice, hides absolute paths, skips symlinks, and serves contained audio with HTTP Range semantics. Playlist reads stay inside granted libraries; writes are bounded and atomic under the library's `Playlists/` directory.
- `src/main/services/HermesHudService.ts` is the only plugin-facing native application controller. It resolves the installed Hermes CLI through the immutable provider registry, sends only the fixed `--hud`/`--quit` control commands, and derives visible state from Hermes Desktop's validated live runtime record. It never accepts executable paths, arguments, PIDs, or arbitrary commands from plugin code.
- `src/main/services/BrowserService.ts` is the only owner of the built-in browser's `WebContentsView` tabs and shared persistent partition. Remote pages have no preload or Node access, keep context isolation and sandbox enabled, and cannot request hardware, location, notification, clipboard-read, certificate-bypass, or external-protocol capabilities. HTTP(S) popups are adopted as internal tabs; other schemes are rejected.
Expand All @@ -48,7 +48,7 @@ Electron main process
- `src/main/services/agent-runtime/` is a separate lifecycle boundary and is not controlled by the Browser access switch. When CanvasTTY status hooks are enabled, every agent PTY receives a distinct capability for a protected user-local socket/pipe. Provider command hooks and the OpenCode event plugin may report only the fixed status enum, bounded event name, and optional opaque turn/prompt ID; prompt text, responses, tool input, and arbitrary telemetry are rejected by the exact gateway schema. Electron helper commands carry `ELECTRON_RUN_AS_NODE=1` inside the exact hook command only; the provider PTY never inherits that process-mode flag, so a provider cannot accidentally launch a second CanvasTTY GUI instance. The user can revoke this capability from Agents settings, immediately returning live agent status to `unavailable`; re-enabling requires a new/restarted PTY. Explicitly trusted plugin hooks use a separate process runner which re-checks the private PluginManager registry on every invocation and strips CanvasTTY internal capabilities before passing the provider payload to third-party code. Provider-native review remains an independent gate; CanvasTTY does not bypass Codex hook trust globally.
- Lifecycle adapters use launch-only settings for Claude, Codex, Qwen, and OpenCode. Kimi, Hermes, and Grok, whose hook discovery is home-config based, receive ownership-checked temporary entries shared across live CanvasTTY sessions. Kimi and Hermes keep recovery journals and exact backups; Grok uses a dedicated owned hook file. Cleanup restores exact original bytes when no concurrent edit occurred and otherwise removes only CanvasTTY-owned entries.
- `TerminalManager` injects the MCP helper per launch without leaving permanent provider configuration. Claude Code, Codex, and Qwen Code receive CLI arguments; Qwen gets one inline `--mcp-config` entry that overrides only the CanvasTTY server name and leaves unrelated user servers available. OpenCode receives a merged launch-only `OPENCODE_CONFIG_CONTENT` entry plus one scoped browser-tool permission; Kimi uses its per-run MCP configuration when supported. Older Kimi versions receive a compare-and-swap temporary CanvasTTY entry and one exact permission rule with an atomic recovery journal. Hermes receives a temporary `mcp_servers.canvastty_browser` entry in `HERMES_HOME/config.yaml` (defaulting to `~/.hermes/config.yaml` on POSIX or `%LOCALAPPDATA%\hermes\config.yaml` on Windows); sensitive capability values stay as child-environment placeholders. Temporary Kimi and Hermes configuration remains until the final owning PTY session ends, then exact original bytes are restored when safe. A journal repairs an interrupted Hermes launch at the next CanvasTTY startup, while compare-and-swap checks preserve concurrent user edits. Unrelated MCP entries, credentials, and file/shell permissions are preserved. Qwen, OpenCode, and Hermes YOLO remain launch-only and do not change persistent permission settings.
- `src/main/services/providerCliRegistry.ts` is the single owner of provider CLI discovery. During main-process startup it creates a shared snapshot for Codex, Claude, Qwen Code, Kimi, OpenCode, Hermes, Grok Build, OMP, and Pi by checking smoke-only overrides, the inherited `PATH`, platform defaults, and known per-user/provider directories in that order. Available entries retain an absolute executable, launcher kind, and supplemented child `PATH`; POSIX entries must be executable files and Windows entries must be supported native or batch launchers. `TerminalManager`, `LimitsService`, agent-browser probes, and provider smoke tests consume that same snapshot and never repeat command lookup. Missing entries produce a failed session with copyable checked-path diagnostics before PTY or temporary browser configuration creation, while the limit adapter reports `cli-not-found`; its HOME row is hidden until explicitly selected after CLI detection. CanvasTTY never reads shell startup scripts. Agents settings can recheck candidate paths, atomically replace the registry snapshot, reconcile saved launcher and limit selections, and refresh CLI-bound adapters without restarting the app. Existing sessions keep running; a newly found CLI remains disabled until selected.
- `src/main/services/providerCliRegistry.ts` is the single owner of provider CLI discovery. During main-process startup it creates a shared snapshot for every provider in `PROVIDER_CLI_DEFINITIONS` — each definition declares the executable command names it may install as (which may differ from the provider ID, e.g. a provider shipping as `mcode`) and optional known home-relative or Windows LOCALAPPDATA-relative directories — by checking smoke-only overrides, the inherited `PATH`, platform defaults, and those known directories in that order. Available entries retain an absolute executable, launcher kind, and supplemented child `PATH`; POSIX entries must be executable files and Windows entries must be supported native or batch launchers. `TerminalManager`, `LimitsService`, agent-browser probes, and provider smoke tests consume that same snapshot and never repeat command lookup. Missing entries produce a failed session with copyable checked-path diagnostics before PTY or temporary browser configuration creation, while the limit adapter reports `cli-not-found`; its HOME row is hidden until explicitly selected after CLI detection. Launchers keep a provider without a local CLI visible when an account bound to a remote computer or a saved container profile provides another route. CanvasTTY never reads shell startup scripts. Agents settings can recheck candidate paths, atomically replace the registry snapshot, reconcile saved launcher and limit selections, and refresh CLI-bound adapters without restarting the app. Existing sessions keep running; a newly found CLI remains disabled until selected.

The primary `BrowserWindow` is created and shown with a lightweight local startup page before settings, plugins, media, and IPC services initialize. Successful initialization replaces that page with the trusted renderer; bootstrap failures replace it with a visible error page and retain a native-dialog fallback. The main process holds Electron's single-instance lock; a rejected second launch raises the running window through the `second-instance` handler so the app never appears to ignore a launch, while background plugin and browser requests never restore, show, or focus an existing window. Native browser contents are focused programmatically only while their owner `BrowserWindow` is already focused; explicit user pointer input remains the only cross-surface focus route.

Expand Down
Loading