CodeNomad/dev-docs/technical-implementation.md
Pascal André b2fa1ba7de
fix(v2): stabilize prompt delivery and reconnect state
Make Enter steer immediately by default while keeping newline modifiers and a rare Ctrl/Cmd+Shift+Enter queue path. Render only authoritative queued inbox prompts in the queue panel, preserve their order during edits and reordering, and keep queued admissions out of the conversation timeline and bottom-pin calculations.

Prevent completed explicit scroll pins from replaying, preserve active turn agent/model state while queued work is admitted, update the current session command contract, and reconcile loaded inboxes plus Shell resources after browser or native reconnects.

Follow the @opencode-ai/client beta channel in manifests and documentation while retaining the concrete lockfile resolution. Refresh the Desktop comparison and migration capability notes against the reviewed upstream V2 baseline.

Validated with UI/server typechecks, 76 targeted Node tests, server and UI production builds, and a complete Tauri Windows release/NSIS build.
2026-08-25 22:36:53 +02:00

6.8 KiB

Technical Implementation

OpenCode Dependency

Server and UI follow the latest experimental @opencode-ai/client@beta contract. Import the generated Promise client from @opencode-ai/client. The runtime CLI independently follows its latest beta and startup validates service health without requiring the client package's exact version string. Review current documentation, installed declarations, and proxy/API parity whenever the beta contract changes.

Do not add @opencode-ai/sdk, old { data, error } SDK wrappers, createOpencodeClient(), or a packages/opencode-plugin package. Verify method signatures in node_modules/@opencode-ai/client/dist/promise/.

Server Integration

OpenCodeSharedService is the sole service adapter. Host and WSL paths both execute the selected CLI's official service status, service start, and service get password commands, validate the authenticated loopback endpoint, pin its identity while active, and never stop the externally owned global daemon on backend shutdown.

OpenCode owns the daemon's standard state, database, and registration; CodeNomad has no private port, database, registration, or daemon PID. Allowed configured environment variables and the current NODE_EXTRA_CA_CERTS are passed only to service start for a missing daemon. Existing daemons are unchanged, and OPENCODE_DB/XDG_STATE_HOME are ignored. WSL requires Windows localhost forwarding and runs this lifecycle inside Linux without Windows PID operations.

Workspace creation passes a native location:

await client.location.get({ location: { directory } })

WorkspaceManager records the returned directory/workspace ID. Explicit Stop Workspace evicts that location/resources and removes the logical workspace without stopping the global daemon. Ordinary tab/window close only detaches local UI state and does not call the delete/eviction path.

Native Windows And Restore State

Each channel/config profile has one native singleton process and one backend. A second launch focuses the MRU window unless --new-window requests another UUID window; stable, dev, and non-default config profiles use isolated native/browser/client-state scopes.

OpenCode sessions/messages are shared service data. Each window separately persists tab membership, drafts, view state, and native bounds in the client-state V3 envelope. Snapshot V2 is a SHA-256 content-addressed partition graph. Electron and Tauri prepare immutable partitions, fence migration/root replacement on current ownership and renderer authority, atomically publish the envelope, then conservatively sweep partitions no window references.

UI Integration

packages/ui/src/lib/sdk-manager.ts constructs clients with OpenCode.make() at /workspaces/:id/instance/. Use getRootClient(instanceId) from packages/ui/src/stores/opencode-client.ts; pass native directory/location inputs when required.

Session actions use native APIs directly:

await client.session.prompt({ sessionID, text, files })
await client.session.shell({ sessionID, command })
await client.session.instructions.entry.put({ sessionID, key, value })

Shell mode and conversation instructions are upstream features and remain separate from native background Shells and interactive V2 PTYs. None requires a CodeNomad plugin.

Native background Shells are location-scoped and listed in the Status panel. packages/ui/src/stores/shell-store.ts refreshes the list on native Shell events and reconnect, exposes native metadata, and supports removal. The proxy verifies Shell cwd ownership before ID-scoped operations and forwards native output cursors unchanged. Interactive pty.* terminals remain separate.

Routing And Security

  • CodeNomad operations: packages/ui/src/lib/api-client.ts -> /api/*.
  • OpenCode operations: generated client -> /workspaces/:id/instance/api/*.
  • Browser events: GET /api/events; heartbeat response: POST /api/client-connections/pong.
  • The proxy exposes only an explicit method/path allowlist, checks client-provided directories and prompt files, defaults safe requests to the workspace location, and verifies session ownership before forwarding. New OpenCode routes are unavailable until reviewed and allowlisted.

Never trust a browser-supplied worktree path. Resolve workspace/worktree ownership server-side.

Native SideCar/browser preview iframes are sandboxed without allow-same-origin; DOM comment inspection is therefore web-only.

CodeNomad-Owned Mutations

Git status/diff and mutations remain CodeNomad APIs. Stage, unstage and commit execute validated Git commands in packages/server/src/workspaces/git-mutations.ts; the UI calls /api/workspaces/:id/worktrees/:slug/git-*.

Yolo also remains CodeNomad-owned. AutoAcceptManager persists policy state, observes native permission events, replies with client.permission.reply, and publishes yolo.stateChanged/yolo.autoAccepted over /api/events.

Events

InstanceEventBridge consumes the one shared client.event.subscribe() iterable. It maps location-scoped events to workspace IDs and publishes instance.event through the CodeNomad EventBus. This stream is volatile: reconnection does not replay a guaranteed history, so UI stores refetch sessions and pending requests and other consumers must re-read authoritative file/config state.

Use current protocol names. Session events include session.created, session.renamed, session.moved, session.status, session.idle, session.execution.*, session.compaction.*, session.text.*, session.reasoning.*, and session.tool.*; background-process refresh events include shell.created, shell.exited, and shell.deleted; file and config invalidations are filesystem.changed and config.updated.

Current Structure

packages/server/src/
  server/routes/            CodeNomad /api routes
  workspaces/manager.ts     workspace/location ownership
  workspaces/opencode-service.ts
  workspaces/instance-events.ts
  workspaces/git-status.ts
  workspaces/git-mutations.ts
  permissions/              Yolo and permission policy

packages/ui/src/
  lib/api-client.ts         CodeNomad API and /api/events
  lib/sdk-manager.ts        native OpenCode Promise clients
  stores/opencode-client.ts root client authority
  stores/session-api.ts     session queries/lifecycle
  stores/session-actions.ts prompt, Shell, instructions
  stores/shell-store.ts     location-scoped native background Shell state/actions

Deleted packages/opencode-plugin, server plugin/background-process, and per-workspace runtime files are not architectural extension points and must not be restored.

Validation

  • Run root typecheck or the relevant server/UI workspace typecheck.
  • Run focused tests for service lifecycle, instance proxy, event bridge, Git mutations, or Yolo when changing those boundaries.
  • Update server API types and UI consumers together.