mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-22 22:31:45 +00:00
AGENTS.md loaded about 33,000 tokens into every agent session, subagent and reviewer, mostly per-feature contracts, and the agent documentation around it had accumulated duplicates, dated logs and history of removed designs. This PR makes AGENTS.md a short set of rules that apply everywhere plus an index of area documents, and leaves AGENTS.md, dev-docs and the repository skills describing only the current state. Three commits, reviewable separately: 1. **Move (lossless).** Every per-area rule moved verbatim from AGENTS.md into its area document (existing ones, or six new ones: UI_CONVENTIONS, UI_SHELL, COMPOSER, TRANSCRIPT, SETTINGS, PROJECTS). A script split the old file into 208 units, required one destination for each and checked all 199 content units verbatim at their destination. AGENTS.md went from 134,000 to 16,000 characters. 2. **Trim.** A per-area ponytail review removed rules stated two or three times (keeping the more precise copy), dated validation logs, per-version qualification sections below the current minimum, abandoned designs and self-references; seven documents whose surviving rules live elsewhere were deleted. It also fixed statements the code contradicts (locale registration, the Mission play/recovery condition, plugin package updates, cleanup progress UI, timeline index persistence, automation restart/targets, stale pins and minimums). 3. **Current state only.** The new AGENTS.md rule: no history (removed features and their names, former behavior, provenance, dated logs) and no denials; correct a contradicted statement at its source. DEVELOPER_MODE.md is merged into BROWSER_AUTOMATION.md and every Developer Mode mention is gone (including the README and the browser help page in ten locales). Names that only made sense against older designs are renamed with every reference updated, MISSIONS_ENVIRONMENT_ADMISSION is folded into SESSION_ENVIRONMENT, and INDEX lists every document. The server npm README now states the real OpenCode minimum (2.1.2). AGENTS.md is now about 14,000 characters (roughly 3,500 tokens); AGENTS.md, dev-docs and the vendor README went from about 11,200 lines to under 4,700. Validation: every relative Markdown link and every cited dev-docs path resolves; no reference to a renamed or deleted document remains; `npm run test:help` passes for all ten locales. Review: three independent GPT-6.1 Sol (high) reviews by area found binding rules lost in the trim and current-state passes; 25 were restored as present-tense facts or prohibitions and four contradicting statements corrected, each checked against code. A Fable 5.1 gatekeeper review of the whole PR passed; its follow-up routes session recovery from the index, points to dev-docs/INDEX.md and documents the family identity read.
6.8 KiB
6.8 KiB
Remote Control
Remote Control reaches CodeNomad from another device without an inbound port.
It is separate from direct --host access; neither falls back to the other.
browser ─TLS─▶ OpenTunnel relay (forwards ciphertext) ─▶ OpenTunnel SDK in the backend (TLS ends here)
─TCP─▶ remote-only loopback ingress ─▶ remote gate ─▶ the normal CodeNomad application
Transport
packages/remote-tunnelwraps@opentunnel/clientbehind CodeNomad-owned types. The server build bundles it with its resolved Effect copy intodist/remote-control/tunnel-runtime.js; never give the published server a runtime dependency on the private workspace package.- CodeNomad claims one random route (
codenomad-<hex>, persisted) on the device's shareddefaultidentity. OpenCode'sopencode pair --remoteand the OpenTunnel CLI use the same identity with their own routes. Stopping closes CodeNomad's bridge only and never callstunnel.remove(). - The relay can observe hostnames, connection timing and sizes. It cannot read
traffic. Impersonating the host requires a misissued certificate for the
tunnel hostname, which would appear in Certificate Transparency logs.
opentunnel.xyzis on the Public Suffix List, so other users' tunnels are cross-site; OpenCode's route on the same device is same-site, so mutations checkOriginrather than rely onSameSite. - First use provisions the identity and its certificate (about 30 seconds); the start request waits up to five minutes.
- Start records the choice in the registry file and only Stop clears it. A
launch after Start reconnects in the background (
manager.resume()) and issues no pairing code; shutdown, a fatal tunnel stop and a failed resume keep the choice, so the next launch retries. Only one backend per config directory resumes (remote-control.owner, live-PID lease replaced after a crash): OpenTunnel gives a route to one bridge and a contender retries until it takes over, so a second backend would otherwise silently serve the paired devices. An explicit Start always proceeds. Settings polls the status only while the tunnel is (re)connecting, keeps paired devices listed while it is off and offers Stop after a failed resume. - Keep OpenTunnel's per-route PROXY protocol off: the ingress expects plain HTTP.
Admission
server/src/remote-control/:
ingress.tsmarks every accepted socket as remote before any request is parsed and routes requests into the same Fastify application, with the local listener's timeouts (no request timeout, 72 s keep-alive). Upgrades are refused: the remote surface exposes no WebSocket endpoint.request-origin.tsis the only authority for local privileges:isLocalRequest()requires a loopback peer and a non-ingress socket.gate.tsruns before CORS, authentication and routing. It requires the exact tunnelHostand the publicOriginon mutations, and strips forwarding headers. It accepts origin-form targets only (no absolute-form, leading//or fragment, which the router would reinterpret) and judges the path the router routes: percent-decoded and cut at;. It denies host-only paths: login and token bootstrap, Remote Control management, remote windows/proxy, SideCars, previews and the automation bridge. Static UI bundle files stay public, as on local listeners, because browsers fetch the PWA manifest without cookies. Every other path requires a paired device. Refusals close the connection, because an unread body would otherwise break the client's next keep-alive request.- A remote
/api/auth/statuswithout a valid device reportspairingRequired. The recovery dialog then explains how to pair again instead of offering password login, keeping the page and its drafts. Pairing from another tab restores it on the next check. devices.tsissues one five-minute, single-use pairing code. The link carries it in the fragment, never in the query or a log. It stores SHA-256 hashes of device credentials and expires devices after 30 days without use.__Host-codenomad_deviceisSecure; HttpOnly; SameSite=Lax. Revocation, and replacement of the least recently seen device at the 32-device limit, also destroy that device's open connections. The registry file is written only once Remote Control is used.- Remote devices authenticate even with
--dangerously-skip-auth, and the local session cookie is never accepted on the ingress. - A desktop CodeNomad pairs as a device: pasting a pairing link into Add server
saves only the tunnel origin and opens the remote window once on the link
(
lib/remote-pairing-link.ts). The profile keeps TLS verification on, since a Tauri TLS-proxy window uses an incognito store without the device cookie. The code is never stored with the server; re-pairing a saved server reuses its window storage, which keeps the device cookie across launches. Covered bytests/browser/remote-server-pairing.test.ts.
Validation
remote-surface.test.tsdrives the realcreateHttpServerthrough the ingress. It covers unpaired access, pairing,Origin/Hostchecks, host-only paths including encoded ones, loopback privileges, spoofed headers, upgrades, revocation of a live stream, skipped local auth and tunnel failure.manager.test.tscovers resuming after a restart, Stop, never-started installations, failed resumes and backends sharing a configuration;owner-lease.test.tsthe lease;tests/browser/remote-control-settings.test.tsthe settings panel after a failed, skipped or slow resume and across a reconnect.scripts/remote-control-live.test.ts(opt-in,CODENOMAD_REMOTE_CONTROL_LIVE=1) repeats the flow through the hosted relay, using a throwaway profile and data directory, and deletes that tunnel afterwards. It creates a public, certificate-logged hostname.- The packaged smoke test imports the bundled runtime with the packaged Node.
Agent conventions
- Remote Access settings put Remote Control first. Direct/LAN access (listening mode, server password, reachable addresses) sits in one collapsed disclosure that opens by itself while the server listens beyond localhost. Its texts refer to direct connections only, because Remote Control pairs devices and needs neither a password nor an open port. It uses the shared
.settings-disclosure(styles/components/settings-disclosure.css). - The tunnel runtime (
build:remote-tunnel) keeps its own backend-side Effect runtime, separate from OpenCode's native plugin graph. Ingress sockets are remote by membership: grant local privileges only throughisLocalRequest()/isLoopbackRequest(), never peer addresses or headers. The resume lease lives inowner-lease.ts; never auto-start an installation that was not started. Run the Validation tests above when changing the transport.