Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4bba2cf
fix(terminal): remove a closed card's run folder only after its launc…
BIackFIame Oct 12, 2026
b286d26
fix(main): run every shutdown step even when one fails
BIackFIame Oct 12, 2026
56192d2
feat(isolation): keep a plugin worktree's Git metadata read-only
BIackFIame Oct 12, 2026
93b5347
fix(agents): tie subagent answers to their turn and say when hooks ar…
BIackFIame Oct 12, 2026
cac98b6
fix(runtime): keep a turn whose start hook was lost, and map PostTool…
BIackFIame Oct 12, 2026
9572d22
fix: report an agent killed by a signal as failed with exit 128+n
BIackFIame Oct 12, 2026
dfb9a2a
fix(orchestration): keep the orchestrator's model choice for subagents
BIackFIame Oct 12, 2026
8a59c82
fix(agents): type OpenCode prompts only once its input field is up
BIackFIame Oct 12, 2026
1cb4ba9
fix(orchestration): return wait_for_agent before the MCP client gives up
BIackFIame Oct 12, 2026
0a9ed46
fix(safety): refuse read-only file tools on CanvasTTY-private data
BIackFIame Oct 12, 2026
b64348d
fix(safety): match ** and brace globs, refuse what cannot be checked
BIackFIame Oct 12, 2026
253a50e
fix(isolation): bind only CanvasTTY's own gateway sockets, read-only
BIackFIame Oct 12, 2026
ef4fc82
fix(isolation): open plugin data projects only where CanvasTTY placed…
BIackFIame Oct 12, 2026
dc96b36
fix(isolation): keep CanvasTTY's data folder read-only inside a proje…
BIackFIame Oct 12, 2026
8eb366e
fix(isolation): write-protect the git files that redirect config and …
BIackFIame Oct 12, 2026
a051042
fix(safety): keep CanvasTTY-private data private under broad projects
BIackFIame Oct 12, 2026
5b516fd
feat(orchestration): run subagents in plugin worktrees
BIackFIame Oct 12, 2026
1ed8ad0
fix(usage): read rollback-journal Hermes databases with locks, not as…
BIackFIame Oct 13, 2026
902225a
fix(redaction): mask overlapping secrets whole and keep labels for cr…
BIackFIame Oct 13, 2026
9d6fffb
fix(files): compare file ids as bigints and preserve the bytes that w…
BIackFIame Oct 12, 2026
f058216
feat(ui): show a card action's change review in a modal dialog
BIackFIame Oct 13, 2026
ec76e5a
feat(checkpoints): save rollback points of agent turns
BIackFIame Oct 13, 2026
ac912f9
feat(ui): add a Rollback points dialog to agent cards
BIackFIame Oct 13, 2026
e88f2a3
fix(orchestration): return a subagent's screen as plain text
BIackFIame Oct 13, 2026
9a4018f
fix(isolation): hide plugin data from isolated agents
BIackFIame Oct 12, 2026
3f62b7e
fix(isolation): give isolated Claude Code its own temp folder
BIackFIame Oct 14, 2026
b4b6919
fix(safety): keep plugin data private in base protection, except the …
BIackFIame Oct 14, 2026
690ec95
feat(plugins): let card actions answer with a change review
BIackFIame Oct 12, 2026
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
4 changes: 2 additions & 2 deletions agent/orchestrator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ description: Create and coordinate native agent sessions (Codex, Claude, Qwen, K
A session started from CanvasTTY's launcher with the **Orchestrator** role has the `canvastty_agents` MCP tools. Use them in this order:

1. `list_providers`: which agents CanvasTTY can launch here. Each entry's `id` is the exact `spawn_agent.provider` value; prefer entries that are `available` with `signIn: "ok"` (`unknown` only means CanvasTTY has not checked yet). When plugins offer launch options (for example an accounts plugin's `list_routes` tool), the answer names that tool.
2. `spawn_agent` once per part of the task: a known `provider` id, an absolute `cwd` and a self-contained prompt. An unknown provider is refused with a pointer back to `list_providers`. **If the person names a model, pass it as `model`** in the format `list_providers` gives for that provider (`model.format`; OpenCode takes `provider/model`, for example `zai-coding-plan/glm-5.3-flash`, and `model.known` lists what `opencode models` reported); without `model` the CLI uses its own default. `effort` sets the reasoning effort where the CLI has one (`efforts`). Without `profile` the subagent gets your own launch profile, or the next lower one its CLI has (never more than yours, never YOLO); `list_providers` shows each provider's `profiles`. `cwd` must be this project's folder or a folder inside it. A refusal says why (a folder outside the project, a profile above yours, a limit the person set): change the request, do not retry it.
3. `wait_for_agent` for each subagent (`timeoutSeconds` up to 600; call it again after `timeout`) instead of polling. `needs_approval` means the person must answer a prompt in that card: never answer it yourself.
2. `spawn_agent` once per part of the task: a known `provider` id, an absolute `cwd` and a self-contained prompt. An unknown provider is refused with a pointer back to `list_providers`. **If the person names a model, pass it as `model`** in the format `list_providers` gives for that provider (`model.format`; OpenCode takes `provider/model`, for example `zai-coding-plan/glm-5.3-flash`, and `model.known` lists what `opencode models` reported); without `model` a subagent of your own CLI keeps your plugin launch options (for example your model account), and any other CLI uses its own default. A plugin you do not name in `launchOptions` keeps your own values; passing `model` next to inherited options that already set the model is refused, and a subagent of another CLI is refused while your model comes from a plugin's options (spawn one of your own CLI instead). The answer's `servedBy` names the CLI, the model asked for, and whether each plugin's options were `inherited` or `explicit`. `effort` sets the reasoning effort where the CLI has one (`efforts`). Without `profile` the subagent gets your own launch profile, or the next lower one its CLI has (never more than yours, never YOLO); `list_providers` shows each provider's `profiles`. `cwd` must be this project's folder or a folder inside it. A refusal says why (a folder outside the project, a profile above yours, a limit the person set): change the request, do not retry it.
3. `wait_for_agent` for each subagent (`timeoutSeconds` up to 100; a call returns within 50 seconds unless you are Claude Code; `timeout` means it is still running, so read `output` as progress and call it again) instead of polling. `needs_approval` means the person must answer a prompt in that card: never answer it yourself.
4. `get_agent_result` to read what the subagent produced; check the actual files before reporting success.

Do not explore the filesystem, `PATH` or config folders (for example `~/.local/bin` or `~/.config/<cli>`) looking for agents or their configuration: `list_providers` is what CanvasTTY can launch, and delegation goes through `spawn_agent`.
Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Electron main process
- `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.
- `src/main/services/browser/` contains the browser kernel. `BrowserStore` atomically persists only tab order, active tab, and safe restore URLs. `BrowserPolicyService` centralizes URL, permission, download, and upload rules; validated uploads are copied through an already-open no-follow file descriptor into private staging before Chromium sees them. `BrowserAutomationService` attaches Electron's internal debugger to the existing live tab without a remote-debugging port. `BrowserCommandDispatcher` adds revisions, revision-bound refs, mutation request deduplication, per-tab FIFO mutation lanes, bounded concurrency, typed errors, and redacted fail-closed audit for agent mutations.
- `src/main/services/agent-browser/` exposes the kernel only through an authenticated user-local Unix socket (`0600`) or Windows named pipe. The Windows pipe is created by the bundled native host with a protected DACL containing only the exact current-user SID and rejects remote clients. Each agent PTY receives a one-use bootstrap capability through its child environment. A successful authentication rotates it to a session-scoped reconnect capability held only in helper memory; duplicate bootstrap authentication is accepted only while the same `connectionId` is already live, and every capability is revoked when the PTY ends. The bundled stdio MCP helper is the only protocol adapter; no TCP listener, cookie/storage endpoint, arbitrary evaluation tool, or raw CDP surface exists.
- `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. On macOS and Linux the helpers are the native `canvastty-helper` (`native/canvastty-helper`, Go, standard library only; subcommands `mcp-browser`, `mcp-orchestration`, `permission-gate`, `hook`), which speaks exactly the wire protocols of the `.mjs` helpers (`tests/native-helper-*-parity.test.mjs` run both) without an Electron-as-Node process per agent and per tool call; `src/main/services/agentHelpers.ts` falls back to the `.mjs` helpers when the binary is absent, on Windows, or with `CANVASTTY_HELPERS=node`. 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.
- `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. On macOS and Linux the helpers are the native `canvastty-helper` (`native/canvastty-helper`, Go, standard library only; subcommands `mcp-browser`, `mcp-orchestration`, `permission-gate`, `hook`), which speaks exactly the wire protocols of the `.mjs` helpers (`tests/native-helper-*-parity.test.mjs` run both) without an Electron-as-Node process per agent and per tool call; `src/main/services/agentHelpers.ts` falls back to the `.mjs` helpers when the binary is absent, on Windows, or with `CANVASTTY_HELPERS=node`. 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. A Codex or OpenCode subagent's final answer (Codex `Stop` hook, the OpenCode plugin at `session.idle`) travels on the same hooks, so it exists only while status hooks are on for that launch: whether a launch has them is decided when it is planned (a deferred plugin launch included), and a launch planned with them off has no lease and no lifecycle hooks at all. `wait_for_agent` and `get_agent_result` then return `answerUnavailable: { reason: "status_hooks_off", message }` and the orchestrator reads the output instead. An answer is kept only for its own turn (`src/main/services/agent-runtime/applyRuntimeSignal.ts`, the glue main runs for each signal): OpenCode names each turn, a submitted input other than an approval answer or an empty Enter in an idle card starts a new one, and a signal of the current turn that predates that input still ends the wait but brings no answer.
- 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 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.
Expand Down
Loading
Loading