Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3667ae6
chore(spec-lint): split AGENTS.md's budget into prose plus a per-inde…
nedtwigg Aug 31, 2026
d5a844c
docs(dor-tool): re-cut the design — opt-in identity, port-triggered s…
nedtwigg Aug 31, 2026
8b47a63
feat(host): dormouse.yml parsing, dedupe keys, and the repo-trust record
nedtwigg Aug 31, 2026
6cfe6b7
feat(dor): `dor tool` and the tool Surface kind
nedtwigg Aug 31, 2026
d25ffc7
feat(tool): the tool Surface — host plumbing, params, and the two-cap…
nedtwigg Aug 31, 2026
3ed079b
feat(tool): `surface.tool` handler, the serving trigger, and the trus…
nedtwigg Aug 31, 2026
08b0ef9
feat(tool): OSC 367 — the announcement that disambiguates, never mints
nedtwigg Aug 31, 2026
fada288
feat(tool): ab-screencast rendering, so an agent can drive a tool's b…
nedtwigg Aug 31, 2026
dc780bb
docs(dor-tool): promote the atom above the fold; gate it on dormouse.…
nedtwigg Aug 31, 2026
a8c4c08
fix(tool): hide the inactive half with visibility, not display
nedtwigg Aug 31, 2026
8375116
simplify(tool): apply /simplify findings
nedtwigg Aug 31, 2026
50a625d
fix(tool): apply the correctness review — namespaced keys, safe rende…
nedtwigg Aug 31, 2026
a1d7fe4
chore(website): disclose the yaml dependency
nedtwigg Aug 31, 2026
1b05bdf
fix(tool): address the PR #493 review
nedtwigg Aug 31, 2026
ba789ca
fix(tool): make the size-cap tests load-bearing; stop offering pop-ou…
nedtwigg Aug 31, 2026
c460a83
fix(tool): gate pop-out at both registration sites; make the gate tes…
nedtwigg Aug 31, 2026
3d61939
fix(tool): call isToolParams in the controller; correct the header's …
nedtwigg Aug 31, 2026
cff909d
fix(tool): actually move the orphaned doc comment
nedtwigg Aug 31, 2026
0e3e7f1
feat(tool): autobind — declare the port strategy, and refuse to guess…
nedtwigg Aug 31, 2026
f572c11
feat(tool): key trust on the upstream remote, and ask in the pane
nedtwigg Aug 31, 2026
8bdb85c
fix(tool): let a late announcement override a committed conflict
nedtwigg Aug 31, 2026
22dbf2b
simplify(tool): apply /simplify findings
nedtwigg Aug 31, 2026
b5820bb
fix(tool): the pending-approval flow was broken end to end
nedtwigg Aug 31, 2026
a100a1b
fix(tools): stage approved shell before reveal
nedtwigg Aug 31, 2026
55269c6
fix(tools): bound untrusted tool file reads
nedtwigg Aug 31, 2026
433af76
fix(tools): respawn commands on session restore
nedtwigg Aug 31, 2026
71c2369
fix(tools): retire browser resources on exit
nedtwigg Aug 31, 2026
10d7e7b
fix(tools): route terminal-face clipboard keys
nedtwigg Aug 31, 2026
4eb2ea8
fix(tools): preserve untouched state until input
nedtwigg Aug 31, 2026
7abc558
fix(tools): validate integration before approval
nedtwigg Aug 31, 2026
14e11ac
fix(tools): serialize trust-file grants
nedtwigg Aug 31, 2026
e493eae
fix(tools): report revealed reuse as visible
nedtwigg Aug 31, 2026
437b650
fix(tools): start approved minimized sessions
nedtwigg Aug 31, 2026
c599f7a
fix(tools): guard untouched destructive actions
nedtwigg Aug 31, 2026
053c489
fix(tools): report revealed pending reuse
nedtwigg Aug 31, 2026
1ad6eb9
fix(tools): expire orphaned trust locks
nedtwigg Aug 31, 2026
149ea04
fix(tools): reject symlinks on every host
nedtwigg Aug 31, 2026
f4dff4d
fix(tools): resolve trust upstream host-side
nedtwigg Aug 31, 2026
a3f5995
fix(tools): preserve browser navigation
nedtwigg Aug 31, 2026
4592af2
fix(tools): reveal pending approval surfaces
nedtwigg Aug 31, 2026
a4123e9
docs(tools): repair trust approval flow
nedtwigg Aug 31, 2026
9254df4
fix(tools): confirm keyboard kills
nedtwigg Aug 31, 2026
5e18609
fix(tools): remove untrusted grant URL
nedtwigg Aug 31, 2026
fb6d7ee
fix(tools): make stale lock recovery race-free
nedtwigg Aug 31, 2026
d5fd9b9
refactor(tools): reuse tool params classifier
nedtwigg Aug 31, 2026
a6521f0
docs(tools): sync untouched kill shortcuts
nedtwigg Aug 31, 2026
a131c0e
fix(tools): migrate legacy trust lock
nedtwigg Aug 31, 2026
b4857b4
Merge main into phase-b; integrate Tools with Terminal Context and cu…
nedtwigg Sep 6, 2026
fe6de35
Fix Tool context focusing its browser instead of its terminal
nedtwigg Sep 6, 2026
f9bfede
Wait for deferred notepad closure in Wall tests
nedtwigg Sep 6, 2026
eaa7b18
Resolve Tool note pins after context mount and terminal refit
nedtwigg Sep 6, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ standalone/sidecar/burrow.cjs
# Kept beside it: a checkout that built before the Burrow rename still holds
# the old bundle, and `bundle.resources` would ship it.
standalone/sidecar/remote-host.cjs
standalone/sidecar/tool-host.cjs
standalone/sidecar/node_modules/
standalone/node_modules/

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ A spec is the accurate reference for the current code: it states the invariants
- **`docs/specs/theme.md`** — The two-layer CSS variable strategy, consumed-token resolver, terminal color contract, theme debugger.
- **`docs/specs/dor-cli.md`** — The `dor` CLI on every Dormouse terminal's `PATH`: bundling and env contract, `spawnAndCapture` rules, control-socket plumbing, the Surface handle model, the command set.
- **`docs/specs/dor-browser.md`** — The browser surface: `BrowserPanel` with swappable `renderMode`, browser chrome, the agent-browser stack, the iframe proxy and CSP boundaries.
- **`docs/specs/dor-tool.md`** — Dor Tools (design-stage): the `tool` Surface — a terminal and a browser on one Session spine — its capability-gated verbs and OSC 367 contract. Only capability gating is built.
- **`docs/specs/dor-tool.md`** — Dor Tools: the `tool` Surface — a terminal and a browser on one Session spine — its capability-gated verbs and OSC 367 contract. Designation, trust, serving, and persistence are built behind the Tools flag.
- **`docs/specs/vscode.md`** — VS Code host: webview hosting, webview ↔ Workspace mapping, persistence ordering, theme integration, CSP, the build/dogfood pipeline.
- **`docs/specs/standalone.md`** — Tauri host: the Rust ↔ Node-sidecar bridge, boot sequence, AppBar, persistence, shutdown ordering, the build/dev workflow.
- **`docs/specs/auto-update.md`** — Standalone auto-update: check → approved download → install-on-quit, the Baseboard notice, Windows sidecar teardown, per-platform quit behavior.
Expand Down
6 changes: 6 additions & 0 deletions docs/specs/dor-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,12 @@ Source of truth: `dor/src/commands/skill.ts`, `scripts/generate-dor-skill.mjs`,

Source of truth: `buildDorSurfacesInternal` in `lib/src/components/Wall.tsx`; `dispatchDorControlRequest` in `lib/src/lib/platform/dor-control-dispatch.ts`.

## Dor Tools

**Must route `dor tool` through the Tool launch contract**, including feature gating, approval, explicit-key reuse, and focus-neutral placement (`docs/specs/dor-tool.md` → CLI). Generated help owns syntax.

Source of truth: `toolCommand` in `dor/src/commands/tool.ts`; `ToolSurfaceResponse` in `dor/src/commands/types.ts`.

## Future

- **Surface a dead control channel in the UI.** A lost bind leaves one
Expand Down
436 changes: 192 additions & 244 deletions docs/specs/dor-tool.md

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions docs/specs/dor-tool.rationale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Dor Tools — Rationale

> Informative evidence for `docs/specs/dor-tool.md`, keyed by its headings.

## Declaring tools

YAML authors naturally collapse one-element lists to scalars. Overloading a scalar dedupe key as a command would make `prespawn_dedupe: storybook` execute instead of identify. Separate future fields avoid that ambiguity.

A misspelled substitution such as `$PROJECTROOT` retained as a literal silently makes distinct checkouts share a key. Rejecting unknown substitutions exposes the typo before reuse can target another checkout.

## Identity and dedupe

`pnpm storybook`, `pnpm run storybook`, and `pnpm storybook --quiet` are different command strings for the same intended tool. `dor ensure` already supplies exact-command/CWD identity. An explicit Tool key allows authors to choose their own scope without making the declaration of a short command name implicitly enable dedupe.

A key list makes scope visible: `$PROJECT_ROOT` distinguishes worktrees without string-concatenation conventions. A runtime collision differs from a redundant spawn: both Surfaces may already hold edited documents, so merging or killing either can destroy work.

## Trust

A prompt rendered as terminal output is forgeable, and `dor send` can type bytes identical to a user's. The dedicated chrome action prevents terminal/control-socket input from granting approval through the normal command path. It does not establish a boundary against arbitrary programs running as the same OS user.

Upstream grants reduce repeated approval across clones and worktrees. They rely on the URL reported by Git, without authenticating the checkout's provenance. Folder grants provide narrower scope. A copied directory carrying `.git/config` can claim a previously trusted URL; cloning a chosen URL has a different provenance story.

Remembering a denial would disable tools across worktrees without a corresponding grant-management UI. Closing the pending pane is recoverable on another explicit invocation. Content-hashing approval would prompt after routine edits or pulls, making acceptance habitual.

## Serving

The standalone browser harness binds more than one HTTP port. Choosing the lowest port or the first observed listener cannot identify which service the user intended to see. A conflict in the browser area gives that refusal a visible explanation while keeping the terminal accessible.

Successive startup listeners can appear in different scan ticks. One unchanged tick catches changes within that window; it does not prove no later listener will appear. Remembering the last applied announced port keeps repeated announcements from undoing URL-bar navigation.

A hardcoded Storybook port can disagree with the port it obtains under contention, while Vite with strict-port behavior can fail entirely. Discovery therefore checks the Session process tree. An OSC can cross SSH, but the current host scan still requires a locally discoverable listener.

## Lifecycle

The September 2026 integration reuses Terminal Context for the Tool's primary terminal. The auxiliary helper's automatic refresh, Reset, and Promote semantics do not describe a serving command, whose Session also owns the browser and remote terminal identity. Sharing the presentation avoids introducing a second navigation mechanism or a second shell.

## Security

Hostile text printed by the designated command can contain an announcement. The current process-tree check limits port selection to that Session's discovered listeners; browser content still executes under the existing renderer boundaries. Earlier text describing arbitrary local-port selection did not match the scan implementation.

## Persistence and hosts

A derived URL or browser daemon binding belongs to one execution. Reusing it after cold restore can connect a Tool to another process that obtained the old port. The saved command and declaration metadata are sufficient to start again and discover the new endpoint.

Routing `dor tool` to a native editor on one host would change its result from a Surface handle to a host-specific side effect. Native file opening remains a separate operation.
7 changes: 5 additions & 2 deletions docs/specs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

## The core idea

A **Surface** is the durable occupant of a Pane — the content in a slot. Two kinds:
A **Surface** is the durable occupant of a Pane — the content in a slot. Three kinds:

- a **terminal Surface**, which Dormouse calls a **Session**: a PTY-backed shell with scrollback and semantic terminal state. The six-axis model below describes this kind.
- a **tool Surface**: a Session with terminal and browser capabilities (`docs/specs/dor-tool.md`).
- a **browser Surface**: a web view (`docs/specs/dor-browser.md`), taking only a subset of the axes ([Panes and Surfaces](#panes-and-surfaces)).

**Unless a passage says "Surface" or "browser Surface," it describes a Session.** A Session's state lives on six distinct axes; an operation can change several together. Their separate preconditions define the **[Liskov contract](#liskov-contract)**.
Expand All @@ -22,18 +23,20 @@ A Pane holds exactly one Surface today, but the model reserves several (a future
| Kind | Sub-kinds | Backed by |
|---|---|---|
| `terminal` | — | a PTY + xterm.js instance — a **Session** |
| `tool` | — | a PTY and an optional browser on the same Session |
| `browser` | `iframe`, `ab-screencast`, `ab-popout` | an iframe proxy grant, or an agent-browser daemon session (`docs/specs/dor-browser.md`) |

**For a browser Surface `renderMode` is canonical**; the CLI `render_mode` is derived from it and never stored.

| Surface | Persisted `surfaceType` (`docs/specs/transport.md`) | `renderMode` (`docs/specs/dor-browser.md`) | CLI `kind` | CLI `render_mode` |
|---|---|---|---|---|
| tool Session | `'tool'` | `iframe` or `ab-screencast` when serving | `tool` | renderer or `null` |
| terminal Session | `'terminal'` (default, omitted) | — | `terminal` | `null` |
| browser · iframe | `'browser'` | `iframe` | `browser` | `iframe` |
| browser · screencast | `'browser'` | `ab-screencast` | `browser` | `ab-screencast` |
| browser · popped out | `'browser'` | `ab-popout` | `browser` | `ab-popout` |

**Kinds are capability sets, not exclusive categories** — the two above carry one capability each, the staged `tool` (`docs/specs/dor-tool.md`) both. **Operations gate on the capability they need, never on the kind enum** ([Liskov contract](#liskov-contract)): `read` / `send` / `await` / port scans need the terminal, nav / render-mode / agent-browser verbs the browser. **`dor list --json` rows always emit `has_terminal` and `has_browser`** (rationale). **Must declare each kind's capabilities in the `hasTerminal` / `hasBrowser` table.** Persistence keeps its own `PersistedSurfaceType` discriminant (`docs/specs/transport.md`).
**Kinds are capability sets, not exclusive categories** — terminal and browser carry one capability each, `tool` both. **Operations gate on the capability they need, never on the kind enum** ([Liskov contract](#liskov-contract)): `read` / `send` / `await` / port scans need the terminal, nav / render-mode / agent-browser verbs the browser. **`dor list --json` rows always emit `has_terminal` and `has_browser`** (rationale). **Must declare each kind's capabilities in the `hasTerminal` / `hasBrowser` table.** Persistence keeps its own `PersistedSurfaceType` discriminant (`docs/specs/transport.md`).

Source of truth: `hasTerminal` / `hasBrowser` in `dor/src/commands/types.ts`; `surfaceKindFromParams` in `lib/src/components/wall/browser-surface.ts`.

Expand Down
8 changes: 5 additions & 3 deletions docs/specs/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ Panes are separated by a 7px gap (`PANE_GUTTER_PX`), odd so the 1px selection ri

A 30px header doubling as a drag handle: **a `pointerdown` past a 5px threshold begins a Lath pane drag**; below the threshold the header's own click behavior stands. It uses `cursor-grab` / `active:cursor-grabbing`, `select-none`, the shared terminal top radius from `lib/src/components/design.tsx`, and the `--color-header-active-*` / `--color-header-inactive-*` token pairs (VSCode file-tree list colors).

**Must use browser chrome for a serving Tool, with a Terminal Context disclosure for its serving terminal.** Tool composition belongs to `docs/specs/dor-tool.md` → Lifecycle.

Elements left to right: derived label; alert bell; TODO pill (compact+); flexible gap; mouse-reporting override icon (compact+, only while the inside program requests mouse reporting); notepad icon (`docs/specs/notepad.md` → "Notepad UI"); split left/right, split top/bottom, zoom/unzoom (full only); minimize; kill (hover turns error-red).

The label is the `DerivedHeader` from `deriveHeader(...)`; `docs/specs/terminal-state.md` owns the priority chain and disambiguator. Layout renders it: primary truncates with ellipsis, secondary muted beside it, a failed last command appends an error-colored glyph. Click renames/pins; right-click — or `>` in command mode — opens the header context menu.

#### Header context menu

**Must open the terminal context from terminal header, alert, body, and command-mode `>` entry points.** Browser-only Surfaces and Doors have no context. Application mouse ownership follows `docs/specs/mouse-and-clipboard.md` → Terminal context input.
**Must open the terminal context from terminal header, alert, body, and command-mode `>` entry points.** Browser-only Surfaces and Doors have no context. Tool context displays its primary terminal; `docs/specs/terminal-context.md` → Tool context owns that composition. Application mouse ownership follows `docs/specs/mouse-and-clipboard.md` → Terminal context input.

**Must float the context inside its source Pane with a one-rem inset on every side**, overlapping the header, with a theme-derived edge and raised shadow. Render it in the Lath leaf's overlay slot, outside the body's clipping box, so it follows the leaf's layout without remounting the helper. Keep one context per Wall. Outside pointer press and explicit close dismiss it. No separate context heading or clipboard toolbar is shown.

Expand Down Expand Up @@ -198,7 +200,7 @@ The source cwd is read from `getTerminalPaneState(sourceId).cwd`. **Never inheri

**Every kill routes through the notepad close coordinator**, confirmed and untouched-fast-path alike, which archives the Surface's notes before teardown and can refuse the close (`docs/specs/notepad.md` → "Closure"; that spec also names who may still tear a Surface down immediately).

**Untouched sessions skip this confirmation.** A newly spawned shell starts `untouched: true`; the first user-originated PTY input flips it to false. Counted: printable keys, Enter, control keys, keyboard CSI such as arrows/history, paste, file-drop path insertion. Not counted: replay-shaped terminal reports and stripped mouse-report-only input — **the gate checks `inputIsReplayTerminalReport`**, the broader synthetic-report check gating input recording and alert attention, not this flag. Killing an untouched pane runs the normal kill animation/dispose path immediately; killing an untouched door first reattaches it only far enough to reuse that removal path, then kills it with no overlay.
**Untouched plain terminal sessions skip this confirmation; Tools still require it.** A newly spawned shell starts `untouched: true`; the first user-originated PTY input flips it to false. Counted: printable keys, Enter, control keys, keyboard CSI such as arrows/history, paste, file-drop path insertion. Not counted: replay-shaped terminal reports and stripped mouse-report-only input — **the gate checks `inputIsReplayTerminalReport`**, the broader synthetic-report check gating input recording and alert attention, not this flag. Killing an untouched pane runs the normal kill animation/dispose path immediately; killing an untouched door first reattaches it only far enough to reuse that removal path, then kills it with no overlay.

Source of truth: `requestKill` (every kill gesture: Door reattach, untouched fast path, or staging the overlay) and `acceptKill` in `lib/src/components/Wall.tsx`, `lib/src/components/KillConfirm.tsx`.

Expand Down Expand Up @@ -310,7 +312,7 @@ Source of truth: `lib/src/components/wall/IllegalRenameWarning.tsx`, `lib/src/co
| **Swap** | `Cmd/Ctrl+Arrow` trades two leaf identities via a Lath `swap`; registry entries follow the ids ([Spatial navigation](#spatial-navigation)). |

- **Untouched**: new `getOrCreateTerminal` sessions start untouched; `isUntouched(id)` exposes the flag, user-originated PTY input clears it, and resume/restore seed the persisted one. **Missing legacy snapshot data defaults to touched (`false`)**, keeping close confirmation conservative.
- **Shell selection replacement**: the standalone Settings dialog's Shell row and the VS Code shell picker send `dormouse:new-terminal` with `replaceUntouched` when the selected shell type changes. **A shell is identified by executable path plus ordered arguments**, so WSL distributions and Windows Developer shells sharing an executable stay distinct. **`Wall` always mints a new session id and a fresh `surface:N` ref.** An untouched selected pane or door has the new terminal take over its leaf via a Lath `replace` op (an atomic identity swap; doors reattach through the normal restore path first), the old session disposed and its ref retired; a touched selection, or none, spawns a new pane beside it. Announced spawns show a transient pane-anchored notice (`Switched to zsh`, `Opened bash`). **A replacement migrates the Surface's notepad to the new id rather than archiving it** (`docs/specs/notepad.md` → "Closure").
- **Shell selection replacement**: the standalone Settings dialog's Shell row and the VS Code shell picker send `dormouse:new-terminal` with `replaceUntouched` when the selected shell type changes. **A shell is identified by executable path plus ordered arguments**, so WSL distributions and Windows Developer shells sharing an executable stay distinct. **`Wall` always mints a new session id and a fresh `surface:N` ref.** An untouched selected plain terminal pane or door has the new terminal take over its leaf via a Lath `replace` op (an atomic identity swap; doors reattach through the normal restore path first), the old session disposed and its ref retired; a touched selection, or none, spawns a new pane beside it. Announced spawns show a transient pane-anchored notice (`Switched to zsh`, `Opened bash`). **A replacement migrates the Surface's notepad to the new id rather than archiving it** (`docs/specs/notepad.md` → "Closure").
- **Replay-time terminal reports must be dropped; user input must not be** — during **resume** replay the registry drops the replies xterm.js emits to queries embedded in buffered output, before they reach the retained PTY (`docs/specs/terminal-escapes.md` → "Report filtering on the input side").

Source of truth: `lib/src/lib/terminal-store.ts` (registry maps and pending shell opts, imported directly, including by `lib/src/remote/burrow/`), `lib/src/lib/terminal-lifecycle.ts` (the ops), `lib/src/lib/terminal-registry.ts` (the facade).
Expand Down
2 changes: 2 additions & 0 deletions docs/specs/mouse-and-clipboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

Owns terminal selection, copy, paste, mouse override, and their chrome across platforms. Header placement: `docs/specs/layout.md`; sequence registry: `docs/specs/terminal-escapes.md`.

For tools, these rules apply while the terminal is forward; the browser or conflict view owns the keys otherwise.

## Background: The Two Mouse Regimes

Mouse events belong to one of two consumers:
Expand Down
2 changes: 2 additions & 0 deletions docs/specs/notepad.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Source of truth: `registerTerminalSource`, `resolveTerminalSource` and `revealRe

## Notepad UI

**Must retain one notepad per Tool Surface.** Context changes its presentation only (`docs/specs/terminal-context.md` → Tool context); following a source pin reveals that same terminal.

**The header notepad icon sits after the mouse-override icon and before the split controls** (`docs/specs/layout.md` → "Pane header"), filled while the Surface has notes and regular otherwise. **At the minimal tier an empty notepad yields its space to the title; one with notes stays**, so notes are never invisible.

- **The attached notepad is a panel in the top-right of the Surface body, three quarters of it wide and tall.** It closes on its close control, Escape, or an outside click.
Expand Down
Loading