AGENTS.md · git:20260921.ed03377 · 2026-09-21 · sha256 60441bf349062f74
AGENTS.md git:20260921.ed03377A
Immutable. This exact content never changes and is served at /api/v1/blob/60441bf349062f74.
# 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`); renew it during long work and release it after cleanup. 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.