## Summary Yolo (permission auto-accept) currently lives entirely in the UI. Each browser keeps its own toggle in `localStorage` and auto-replies to permission requests over a 4-hop path (`OpenCode → server SSE → UI → server proxy → OpenCode`). The server — which already sits on the event stream that carries every `permission.asked` — does none of the work. This PR makes the **server authoritative**: it owns the toggle state, resolves family-root inheritance, and performs the auto-reply in-process via loopback using the same `"once"` semantics the UI used to send. The UI becomes a pure view: it toggles via REST and mirrors state from a `yolo.stateChanged` SSE event. ## Why - **Correctness**: the server already consumes the instance SSE stream (`InstanceEventBridge`); auto-accepting there is the natural choke point instead of bouncing to the UI and back. - **Multi-client**: previously each browser had independent `localStorage` state and never synced. Toggles now broadcast to all connected clients in real time. - **Headless**: Yolo keeps auto-accepting even when no UI is connected (useful for long autonomous runs). - **Latency**: drops from 4 hops to a single in-process loopback call. ## What changed **Server (new, authoritative)** - `permissions/auto-accept-store.ts` — in-memory state keyed by family root. Faithful port of `resolvePermissionAutoAcceptFamilyRoot`: fork/`revert` sessions root at themselves; enabling any member enables the whole family. - `permissions/auto-accept-manager.ts` — subscribes to `instance.event`, builds the session tree from `session.created/updated/deleted` (`properties.info`), intercepts `permission.v2.asked` / `permission.asked`, dedupes in-flight replies, emits `yolo.stateChanged` / `yolo.autoAccepted`, clears per-instance state on `workspace.stopped/error`. - `permissions/opencode-replier.ts` — default replier calling OpenCode directly (`getInstancePort` + auth header), mirroring `background-processes/manager.ts`. - `server/routes/yolo.ts` — `GET/POST /workspaces/:id/yolo/sessions/:sid[/toggle]`, following existing route conventions. - `api-types.ts` / `events/bus.ts` — `YoloStateResponse` + the two new event types registered in `onEvent` so they flow over `/api/events`. **UI (pure view)** - `permission-auto-accept.ts` — removed `localStorage`, persistence, and drain logic; now a runtime (non-persisted) projection. `resolvePermissionAutoAcceptFamilyRoot` is **retained as a display aid** so the badge still lights up for child/sub-sessions of an enabled family (preserving the inheritance UX). - `instances.ts` — toggle calls REST (optimistic, reconciled on success, reverted on failure); subscribes to `yolo.stateChanged`; `ensureYoloStateSynced` backfills the active session's state on first connect (deduped per session, reset on SSE reconnect so it re-syncs after a server restart). - `session-events.ts` — removed the three `drainAutoAcceptPermissionsForInstance` hooks (the server drains now). - `api-client.ts` — `getYoloState` / `toggleYolo`. ## Behavior parity | Aspect | Before | After | |---|---|---| | Reply semantics | `"once"` | `"once"` (unchanged) | | Inheritance | whole family (root + non-fork descendants) | **same** | | Fork isolation | `revert` session is its own root | **same** | | Persistence | UI `localStorage` | none (intentional, see Notes) | | Multi-client sync | ❌ independent per browser | ✅ real-time via SSE | | Works with UI closed | ❌ | ✅ | | Auto-reply path | 4 hops | 1 in-process hop | ## Testing 32 unit tests added (`node:test`), all passing. Server + UI typecheck clean. - **Store (16)**: inheritance (parent/child/sibling), fork isolation, cyclic parent chains, late parent discovery, `revert` re-rooting, per-instance independence, tree maintenance. - **Manager (13)**: real `properties.info` event shapes, v2 vs legacy reply, in-flight dedup + retry-after-resolve, `yolo.stateChanged` emission, `session.deleted` keeps toggle, `workspace.stopped` clears state, `stop()` unsubscribes. - **UI (3)**: retained `resolvePermissionAutoAcceptFamilyRoot` display-projection tests. ## Notes for reviewers - **No persistence is intentional** for this milestone — server restart resets all Yolo state (matches the agreed scope). The UI mirror self-heals via SSE reconnect + `ensureYoloStateSynced`. Persistence can be layered on later (e.g. into `~/.config/codenomad/config.json`) without touching the manager. - **Family-root resolution lives in two places on purpose**: the server resolves it to decide whether to auto-reply; the UI resolves the same pure function to render the badge instantly (synchronous memo). Both are faithful ports; no network round-trip is added for display. - **`properties.info` nesting**: OpenCode wraps session records under `properties.info` for `session.*` events (permission events are flat). The manager handles both and the tests use the real nested shape to guard against regressions. - `getYoloState` / `yolo.autoAccepted` are wired but `yolo.autoAccepted` is not yet consumed by the UI — it's available on the wire for future observability (e.g. an audit log / toast). ## Risk / rollback The change is additive on the server and the UI gracefully degrades to the SSE mirror. If the server lacks the new routes (mixed-version), the UI's optimistic toggle still flips locally and `toggleYolo` failures are logged + reverted, so no hard breakage. --------- Co-authored-by: Pascal André <pascalandr@gmail.com> |
||
|---|---|---|
| .github | ||
| .opencode | ||
| dev-docs | ||
| docs | ||
| images | ||
| packages | ||
| scripts | ||
| temp | ||
| .gitignore | ||
| AGENTS.md | ||
| BUILD.md | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
CodeNomad
The AI Coding Cockpit for OpenCode
CodeNomad transforms OpenCode from a terminal tool into a premium desktop workspace — built for developers who live inside AI coding sessions for hours and need control, speed, and clarity.
OpenCode gives you the engine. CodeNomad gives you the cockpit.
Features
- 🚀 Multi-Instance Workspace
- 🌐 Remote Access
- 🧠 Session Management
- 🎙️ Voice Input & Speech
- 🌳 Git Worktrees
- 💬 Rich Message Experience
- 🧩 SideCars
- ⌨️ Command Palette
- 📁 File System Browser
- 🔐 Authentication & Security
- 🔔 Notifications
- 🎨 Theming
- 🌍 Internationalization
Getting Started
🖥️ Desktop App
Available as both Electron and Tauri builds — choose based on your preference.
Download the latest installer for your platform from Releases.
| Platform | Formats |
|---|---|
| macOS | DMG, ZIP (Universal: Intel + Apple Silicon) |
| Windows | NSIS Installer, ZIP (x64, ARM64) |
| Linux | AppImage, deb, tar.gz (x64, ARM64) |
💻 CodeNomad Server
Run as a local server and access via browser. Perfect for remote development.
npx @neuralnomads/codenomad --password <your-password> --launch
Authentication required: The server requires a password on first run. You can pass it via
--password, theCODENOMAD_SERVER_PASSWORDenvironment variable, or create anauth.jsonfile (see Server Documentation).
Self-signed certificate: On first launch with HTTPS enabled (the default), your browser will show a "Your connection is not private" warning. This is expected — the server generates a local self-signed certificate automatically. Click Advanced → Proceed to localhost to continue. For local-only use without the warning, run with
--https=false --http=true.
See Server Documentation for flags, TLS, auth, and remote access.
🧪 Dev Releases
Bleeding-edge builds from the dev branch:
npx @neuralnomads/codenomad-dev --password <your-password> --launch
SideCars
SideCars let you open local web tools inside CodeNomad as tabs.
Configuration
- Name: Display name used in CodeNomad
- Port: Local HTTP or HTTPS service running on
127.0.0.1:<port> - Base path: Mounted under
/sidecars/:id - Prefix mode:
- Preserve prefix forwards the full
/sidecars/:id/...path upstream - Strip prefix removes
/sidecars/:idbefore forwarding the request upstream
- Preserve prefix forwards the full
VSCode (OpenVSCode Server)
Run with Docker:
docker run -it --init -p 8000:3000 -v "${HOME}:${HOME}:cached" -e HOME=${HOME} gitpod/openvscode-server --server-base-path /sidecars/vscode
Add SideCar as:
- Name:
VSCode - Port:
http://127.0.0.1:8000 - Base path:
/sidecars/vscode - Prefix mode:
Preserve prefix
Terminal (ttyd)
Run with:
ttyd --writable zsh
Add SideCar as:
- Name:
Terminal - Port:
http://127.0.0.1:7681 - Base path:
/sidecars/terminal - Prefix mode:
Strip prefix
Requirements
- OpenCode CLI — must be installed and in your
PATH - Node.js 18+ — for server mode or building from source
Development
CodeNomad is a monorepo built with:
| Package | Description |
|---|---|
| packages/server | Core logic & CLI — workspaces, OpenCode proxy, API, auth, speech |
| packages/ui | SolidJS frontend — reactive, fast, beautiful |
| packages/electron-app | Desktop shell — process management, IPC, native dialogs |
| packages/tauri-app | Tauri desktop shell (experimental) |
Quick Start
git clone https://github.com/NeuralNomadsAI/CodeNomad.git
cd CodeNomad
npm install
npm run dev
Troubleshooting
macOS: "CodeNomad.app is damaged and can't be opened"
Gatekeeper flag due to missing notarization. Clear the quarantine attribute:
xattr -dr com.apple.quarantine /Applications/CodeNomad.app
On Intel Macs, also check System Settings → Privacy & Security on first launch.
Linux (Wayland + NVIDIA): Tauri App closes immediately
WebKitGTK DMA-BUF/GBM issue. Run with:
WEBKIT_DISABLE_DMABUF_RENDERER=1 codenomad
See full workaround in the original README.
Community
Built with ♥ by Neural Nomads · MIT License
