AGENTS.md · git:20260922.01aa3e1 · 2026-09-22 · sha256 d25a09bfd11cc722
AGENTS.md git:20260922.01aa3e1A
Immutable. This exact content never changes and is served at /api/v1/blob/d25a09bfd11cc722.
# Working on Anode Anode is a Windows-only .NET 8 application. One executable implements the CLI, WinForms/RDP daemon, child-session host, setup and MCP stdio server. ## Build and verify Run from the repository root in PowerShell: ```powershell dotnet build Anode.sln --nologo & .\src\Anode\bin\Debug\net8.0-windows\win-x64\anode.exe selftest --quick | Out-Host powershell -ExecutionPolicy Bypass -File scripts\build.ps1 -QuickTest ``` The last command publishes and tests `dist\anode.exe`. Build output is ignored by Git. Use the freshly built executable, since an existing `dist` may be stale. An Anode or MCP client running from `dist\anode.exe` locks it; then build elsewhere with `scripts\build.ps1 -QuickTest -OutputDirectory artifacts\pkg-build` (add `-ArchiveDirectory artifacts\pkg-release -Package` to test the package, see [CONTRIBUTING.md](CONTRIBUTING.md#build-while-anode-is-running)). Use a separate `git worktree` for parallel work so builds do not share `src\Anode\obj` and `bin`. Pipe GUI-executable output to `Out-Host` so PowerShell waits and displays it; check `$LASTEXITCODE` for failures. There is no separate `dotnet test` project: regression checks live in `Cli/SelfTest.cs`, `Cli/TransportChecks.cs` and `Cli/McpChecks.cs`. `selftest --quick` uses private test pipes and a disposable MCP subprocess. It does not create a seat or inject input. Full `selftest` also captures the current desktop, launches a scheduler test process and exercises a machine-wide gamepad; choose it only when those actions are appropriate to the task. Every build shares the `anode-control` pipe. Never run `start`, `up`, `lease` or any other seat command from a development build while a daemon is running: it acts on the user's running seat. `selftest --quick` is private-pipe only and stays safe. Read a command's usage with `anode help <command>`, which never dispatches it. `doctor` reads prerequisites and exits nonzero when setup is missing. Live seat tests require Remote Desktop and child sessions. `setup` changes machine settings through UAC; do not treat a missing setup as a failed build. ## Design constraints - Preserve the seat host's refusal to run in an unverified or parent session. The parent daemon resolves child-session identity; the host verifies it before serving input. Keep input and capture inside the seat host. - Keep `Daemon/PointerGuard.cs` installed before the viewer connects. The Remote Desktop control moves the real pointer when a seat program calls `SetCursorPos` and the pointer is over the viewer's rectangle, even hidden or view-only; the guard lets that through only while the user has taken control in a visible, focused viewer. A live test is meaningless unless the real pointer is inside that rectangle; `scripts/test-pointer-isolation.ps1` handles this. - Keep CLI and MCP daemon startup on the shared `Core/Launch/DaemonLauncher.cs` path so Task Scheduler can detach the daemon from an agent's process lifetime. Only `seat_start` and `seat_lease` acquisition start a daemon over MCP, and only `start`, `up` and `lease acquire` on the CLI; status and diagnostics never do. - Keep MCP stdout exclusively newline-delimited JSON. Diagnostics go to the log or stderr. MCP tool discovery must work without machine setup or a live seat. - Keep protocol processing independent of long tool calls. Ordinary tools execute in order; Stop uses a separate daemon connection and cancels outstanding work. Validate arguments before any daemon startup or action. Test MCP scheduling with injected handlers and private pipes, without creating a real seat. - Pipe deadlines include queue time. Never replay a timed-out input command: its effects may already have happened. Reconnect before subsequent requests. - Keep Stop able to cancel startup and avoid waiting on long input operations. - Screenshot click coordinates use the original screen size, even when the returned image is smaller. Gamepads are visible across Windows sessions. See `docs/ARCHITECTURE.md`, `docs/PROTOCOL.md`, `docs/SECURITY.md` and `docs/CONNECTING-AGENTS.md` for the runtime model and client setup. ## Live testing without disturbing the user Use Anode's CLI/MCP tools for seat screenshots, mouse, keyboard and application launches, holding a desktop lease (`seat_lease` or `anode lease acquire`); each desktop command extends it, so renew only across long pauses, and release it after cleanup so a waiting agent gets its turn. The user explicitly wants their foreground desktop left available. Do not invoke foreground computer use, activate the parent viewer, or show it as a fallback when seat capture fails. Diagnose that failure through code and logs; stop input that requires a fresh screenshot. Show the viewer only when the user requests it or agrees to a specific interactive step. Virtual gamepads remain machine-wide and require separate consideration before input tests. Use `windows`/`inspect` or `seat_windows`/`seat_observe` for accessible controls. Control actions consume their snapshot; inspect again instead of replaying an uncertain action. `scripts/test-desktop.ps1` is an opt-in test using a disposable fixture in an already ready seat; it does not start the seat or show its viewer. `seat_exec`/`exec` runs noninteractive builds and tests with bounded command jobs. Use an absolute cwd, read output incrementally with `seat_job`/`job`, and cancel only your own jobs. Recover IDs with `jobs` after an interrupted request; never replay an uncertain start. `seat_wait` provides bounded UI predicates and a fresh snapshot on match. `scripts/test-development.ps1 -VerifyInput` tests an owned browser in the seat, including real mouse/keyboard delivery; it requires Node, Chrome and local Playwright. `capabilities` reports actual capture and known input blockers. A ready connection or successful SendInput call alone does not prove that an application received input.