## Summary PR #561 introduced `createInstanceClient` so server modules would stop hand-assembling the OpenCode loopback URL and use the generated SDK client. Three call sites adopted it; the background-process completion prompt was the remaining holdout — it still hand-built `http://127.0.0.1:{port}/session/{id}/prompt_async` with manual auth/content-type wiring. This PR routes it through the factory + `client.session.promptAsync`, and consolidates the loopback host constant. ## Why - **Consistency** — the last hand-built OpenCode-instance loopback URL (explicitly flagged in #561 as a future adoption target) now uses the same version-correct SDK path as the permission replier, yolo metadata, and the opencode updater. - **Correctness** — the old call sent no directory at all; `notify.directory` was stored but never used (dead data). The factory now scopes the prompt to the session's own directory, which for a worktree/subdirectory session is the correct project context (a normal session coincides with the workspace root, so no change there). ## What changed **Migration** — `background-processes/manager.ts`: `sendCompletionPrompt` → `createInstanceClient(...)` + `client.session.promptAsync({sessionID, parts:[{type:"text", text, synthetic:true}]}, {throwOnError:true})`. Synthetic `<system-message>` text + `synthetic:true` preserved. Side effect: the call now carries the factory's 10s loopback timeout (the old fetch had none; the failure is swallowed + warn-logged, so finalization is unaffected). **Factory** — `workspaces/instance-client.ts`: new optional `directory` override in `InstanceClientOptions` (defaults to workspace root); adopts shared `LOOPBACK_HOST`. Fetch wrapper unchanged from `dev`. **Consolidation** — new `workspaces/loopback.ts` exporting `LOOPBACK_HOST = "127.0.0.1"`, adopted by `instance-client.ts`, `instance-events.ts`, and the workspace manager's health + TCP probes (`manager.ts`). (Other `127.0.0.1` uses — remote-access proxy, sidecars — are different concerns, left alone.) **Tests** - `workspaces/instance-client.test.ts` (8 cases): null-when-no-port, loopback host/port targeting, auth header attach/omit, directory scoping present/absent, explicit directory override, timeout aborts stuck call. - `background-processes/manager.test.ts` (2 cases, first test for this manager): drives the real lifecycle (spawn + exit) against a mocked transport; asserts the `prompt_async` POST URL/body/authorization/`x-opencode-directory` (= session dir, distinct from workspace root), and that a failed prompt is swallowed + warn-logged without aborting finalization. ## Behavior parity | Aspect | Before | After | |---|---|---| | Route / body | `/session/{id}/prompt_async`, `{parts:[{type,text,synthetic}]}` | **same** (via SDK) | | Auth header | manual | via factory | | Directory scoping | none | session's `notify.directory` | | Loopback timeout | none | 10s (factory default) | | Error handling | throw on non-2xx → caught + warn-logged | `throwOnError` → caught + warn-logged (**same**) | ## Testing 10 new tests pass. The background-process integration test is stable 12/12 under `--test-concurrency=4` and is mutation-killed if the `directory` override or `throwOnError` is removed. Full server suite: 255 pass / 1 fail — the single failure is pre-existing (`git-clone.test.ts` Chinese-locale git message, confirmed failing identically on `dev`). Server typecheck clean. ## Risk / rollback Additive refactor; the factory is production-proven by #561's consumers. Rollback = revert the single commit. ## Notes - Per the file-length guideline: `packages/server/src/background-processes/manager.ts` is ~684 lines (above the 500 warn threshold, under the 800 limit); this PR shrank it by ~16 lines net. |
||
|---|---|---|
| .github | ||
| .opencode | ||
| dev-docs | ||
| docs | ||
| images | ||
| packages | ||
| scripts | ||
| temp | ||
| .gitignore | ||
| AGENTS.md | ||
| BUILD.md | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| THIRD_PARTY_NOTICES.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 | Tauri deb, Electron portable tar.gz (x64) |
The Tauri deb is currently built and installation-tested on Ubuntu 24.04. Compatibility with older Debian-based distributions is not yet guaranteed.
💻 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-tauri
See full workaround in the original README.
Community
Built with ♥ by Neural Nomads · MIT License
