Commit graph

21 commits

Author SHA1 Message Date
Pascal André
761aa66486
fix(opencode): keep a working connection when a periodic recheck is inconclusive (#1090)
The shared OpenCode connection is rechecked by the first acquisition more
than 30 seconds after its last check, by rerunning CLI discovery (`service
status`, `service get password` and an authenticated status probe, bounded by
the caller's deadline). When that discovery failed or reported no service,
the healthy connection was invalidated. A slow daemon, or slow CLI spawns on
Windows, could therefore tear down a working connection. Every event stream
on it then broke and the relay reconnected in the middle of streaming: the
reconnection the transcript now survives (#1088), triggered without any real
failure.

Discovery failure and absence are now inconclusive. The working connection
is kept and the next recheck waits for the following interval, so a slow
service is not re-probed on every request. A discovered daemon that differs
in URL, PID, version or credentials still replaces the connection
immediately. A dead connection is still retired by failed location
validation (`withClient`), proxy transport or authentication failures and
upstream event-stream EOF/errors, after which the next acquisition discovers
or starts the service as before. An inconclusive recheck never starts
another service. A frozen daemon that accepts TCP but never answers is now
detected through those paths, bounded by Undici's inactivity timeouts,
rather than by the periodic recheck.

Validation: two new `opencode-service` tests fail on the previous code. One
keeps the connection through a timed-out and an empty recheck without
rediscovering before the next interval or starting a service. The other
keeps a live event stream delivering events while another request's recheck
fails. The shared-service, connection generation, setup reconnect, event
relay/routing/ownership, subscriber lifetime, pending discovery, plugin
lifecycle and proxy suites pass (82/82), the wider workspace manager,
CLI/WSL lifecycle, native registration and opencode-update suites pass
(227/227), and the server typecheck is clean.
2026-10-22 00:47:04 +02:00
Pascal André
7a1fc90310
docs(agents): keep AGENTS.md to universal rules and agent docs to the current state (#1082)
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.
2026-10-21 16:59:43 +02:00
Pascal André
73c0621d0d
fix(sessions): wire finish, retry and usage signals to OpenCode V2 events (#1033)
## What changes for the user
Four things that silently stopped working with OpenCode V2 work again:

- **"A session finished" notifications** arrive again. A successful or
failed run notifies. A run you stopped yourself does not.
- **Provider retries** show the retrying status and the countdown toast
again.
- **Usage statistics** refresh after each run.
- **The token usage line** under assistant replies (preference,
transcript filter row and hotkey) shows each settled reply's tokens and
cost again.

Per-session transcript reducers are also released again once a run has
settled.

## Why
OpenCode V2 declares `session.idle` and `session.status` in its schema,
but core never publishes them. Runs end with
`session.execution.{succeeded,failed,interrupted}`, and retries arrive
as `session.retry.scheduled`. These features were still keyed only to
the V1 events.

The token usage line had a second problem: it was drawn from V1
`step-finish` parts, which V2 never produces. V2 stores tokens and cost
on each assistant message instead.

A new isolated native fixture,
`scripts/test-session-lifecycle-native.mjs`, confirms this on CLI 2.1.0.
It uses its own storage, port and fake provider, and never the shared
service. In its complete, retry, fail and interrupt scenarios, it sees
no `session.idle` or `session.status` event. Upstream 2.0.27 sources
show the same: these events are declared but never published.

## How
- **Finish notification:** sent from the V2 terminal path after
`session.active` confirms the run ended.
  - Succeeded and failed runs notify.
- User and shutdown interrupts stay silent; idle-timeout interrupts
notify.
  - Subsessions and unknown sessions stay silent.
  - A stray `session.idle` cannot cause a duplicate.
- **Retry:** `session.retry.scheduled` sets the retry state and the
transient toast. The next attempt or the end of the run clears them.
- **Run-end detection:** a shared `lib/native-execution-events.ts`
drives the usage refresh and reducer retirement.
- **Reducer retirement:** it also waits for queued prompts, so prompts
left in the inbox after an interrupt stay listed.
- **Usage line:** moves to `components/message-usage.tsx` with the
existing classes.
- **Removed:**
  - the dead V1 `StepCard`, its step-part types and CSS;
  - the V1 `session.idle`/`session.status` handlers.
- **AGENTS.md:** one bullet so future features wire to V2 lifecycle
events.

Left for the follow-up cleanup PR: the remaining dead `session.idle`
fallbacks in pending discovery, Missions triggers and test fixtures.

## Validation
New tests that fail on unmodified dev and pass here:

| Suite | dev | this PR |
|---|---|---|
| lifecycle unit | 3/4 fail | 4/4 |
| retirement | 8/9 fail | 9/9 |
| browser `usage-metrics` | 3/3 fail | 3/3 |
| browser `service-usage` | 2/3 fail | 3/3 |

- UI typecheck is clean.
- **Passing unit suites:**
- opencode-data-idle 9, runtime-contract 4, session-native-events 8,
message-part-grouping 6
- opencode-transcript-replay 17, opencode-compaction 11,
session-pruning-projection 2, session-pruning-pagination 6
- session-request-authority 77, opencode-data 30, message-timeline-v2 2,
transcript-visibility 8
  - missions 1, instances-pending-compaction 7
- **Passing browser suite:** `system-messages` 10/10.
- The native lifecycle capture passes.

Oversized files touched:

| File | Lines |
|---|---|
| `message-block.tsx` | ~1,760 (down ~240) |
| `message-section.tsx` | ~1,630 |
| `opencode-data.ts` | ~905 |
| `session-events.ts` | ~670 |
| `message-timeline.tsx` | ~645 |
| `message-base.css` | ~675 |
| `session-request-authority.test.ts` | ~2,400 |
| `opencode-data.test.ts` | ~1,215 |
2026-10-20 15:51:48 +02:00
Pascal André
008e53aa27
docs(sidecars): keep rejected-isolation history out of the architecture notes (#1003)
Documentation only. Removes the 'isolation was evaluated and rejected'
sentence from \dev-docs/architecture.md\: the architecture notes
describe how SideCars work (trusted same-origin extensions), not the
alternatives tried. \AGENTS.md\ and the architecture-guide reference
kept a pointer to that sentence; they now state the reason directly, so
agents are still told not to reintroduce isolation.
2026-10-18 03:47:16 +02:00
Pascal André
c12d473ac1
docs(sidecars): condense the rejected-isolation rationale to one line (#1002)
Documentation only. Replaces the bullet list added in #1001 with one
sentence in the existing SideCar paragraph of
\dev-docs/architecture.md\: isolating SideCars (opaque sandbox, separate
origin or per-view port) was evaluated and rejected because it breaks
service cookies/storage or single-port, proxied and tunnelled access.
The detailed measurements remain in #1001.
2026-10-18 03:40:03 +02:00
Pascal André
1ce629b9a6
docs(sidecars): document SideCars as trusted same-origin extensions (#1001)
## Summary
Documentation only. SideCars are now documented as **trusted same-origin
extensions** of CodeNomad, replacing the "temporary exception /
isolation follow-up" wording introduced with #994.

## Why
The isolation follow-up was prototyped, measured and reviewed, then
rejected:
- **Opaque sandbox:** the frame loses cookies and storage, so the
service's own login and state break.
- **Separate origin:** keeping service cookies requires a same-site
origin (another port or subdomain). Chromium still sends the
application's `Lax` cookie across ports of the same host, and CORS alone
did not block authenticated writes, so every API would need an origin
barrier.
- **Per-view ports:** these are unreachable through a single exposed
port, reverse proxy or tunnel, and reset service storage on every
opening.
- **IP and single port:** with a literal IP and one exposed port, no
frame topology gives both isolation and working `Lax` cookies.

## Changes
- **`dev-docs/architecture.md`:** records the decision and these
constraints.
- **`AGENTS.md` and the architecture-guide `desktop-conventions.md`:**
refer to the decision instead of a pending prototype.
- **`CONTRIBUTING.md`:** no longer claims SideCar frames are sandboxed.
- **`README.md`:** tells users to add only services whose content they
trust.

The #995 cookie filtering stays as is; no behavior change.
2026-10-18 03:38:56 +02:00
Pascal André
be66133469
fix(sidecars): isolate application cookies from upstream services (#995)
Authenticate SideCar HTTP/WS requests before removing the configured CodeNomad session cookie, desktop session-cookie namespace and Remote Control device credential from upstream requests. Filter reserved response cookie names as well while retaining service cookies. Use the bounded Node HTTP parser for WebSocket handshakes so successful and rejected upgrades receive the same cookie treatment; preserve buffered bytes, binary forwarding and non-101 response framing. Document that this is credential filtering, not same-origin DOM/API isolation. Validation: 143 HTTP/proxy tests, 11 focused cookie/transport/preview tests, server typecheck, and isolated Chromium/Electron prototype exercises. General CI was still running at the explicitly authorized admin merge.
2026-10-18 00:28:05 +02:00
heunghingwan
d19d1c39fa
fix(sidecars): keep SideCar frames same-origin so proxied requests authenticate (#994)
## Summary

Since 0.20.0, opening a SideCar panel (e.g. ttyd) shows the page but
every in-page request fails: the terminal immediately falls back to
"Press ⏎ to Reconnect". All HTTP subresource fetches
(`/sidecars/<id>/token`, …) and the WebSocket upgrade
(`/sidecars/<id>/ws`) are rejected with 401/403. Connecting to the same
service directly (no iframe sandbox) works fine.

## Root cause

The browser-frame sandbox added in 0.20.0 (`allow-scripts allow-forms
allow-modals allow-popups allow-downloads`, no `allow-same-origin`) also
applies to SideCar frames. That makes the frame an **opaque origin**, so
the browser sends its in-page requests with `Origin: null`, cross-site,
and **without the `SameSite=Lax` session cookie**:

1. `sandbox` lacks `allow-same-origin` → iframe document is an opaque
origin
2. In-page fetch/WS requests become cross-site (`Origin: null`, no
cookies)
3. `codenomad_session` is `SameSite=Lax` → never attached
4. Server enforces session auth on `/sidecars/*` → **401**; the
opaque-origin gate additionally answers **403** (`http-server.ts`,
"Opaque origins may access preview capabilities only")

Only the frame's *subsequent* requests fail — the initial navigation is
issued by the top-level page (same-origin, with cookies), which is why
the panel visibly loads before disconnecting. Browser previews already
solve this with their token-scoped capability path; SideCars have no
such path and depend on the session cookie, so their frames must stay
same-origin.

## Change

Opt-in relaxation, scoped to user-configured app-proxied content only:

- `getBrowserFramePolicy(environment, { trustedSameOrigin })` appends
`allow-same-origin` to the sandbox only when explicitly requested
(`browser-frame-security.ts`)
- `BrowserFrame` gains a documented `trustedSameOrigin` prop; arbitrary
web previews must never set it
- `SideCarView` passes `trustedSameOrigin` — restoring pre-0.20 behavior
for content the user explicitly configured (a local service on
`127.0.0.1` reached through the app's own authenticated proxy)

Security posture is otherwise unchanged: the server-side gates stay
exactly as strict (the new server tests pin this), and browser preview
frames keep the full sandbox.

`allow-scripts` + `allow-same-origin` together do let SideCar content
script its same-origin parent — the same trust level SideCars had before
0.20.0 for services the user deliberately added. A follow-up can remove
even that by issuing short-lived tokens in the iframe URL (see below).

## Tests

- **Unit** (`browser-frame-security.test.ts`): trusted SideCar policy
includes `allow-same-origin`; existing tests still assert the default
policy excludes it
- **Browser** (`tests/browser/sidecar-sandbox.test.ts`, new): asserts in
a real Chromium DOM that the `SideCarView` iframe carries
`allow-same-origin` while a plain preview `BrowserFrame` does not
- **Server** (`server/__tests__/sidecar-proxy.test.ts`, new): WebSocket
upgrade **without cookie → 401**, **with valid session cookie → 101**
(also verifies `strip` prefixMode drops `/sidecars/:id` before
proxying); HTTP with `Origin: null` → 403, same-origin + cookie → 200 —
locking the exact contract that forces same-origin frames, so the server
gates cannot be quietly loosened instead

## Validation

- CI's runnable-UI unit list: 56/56 pass
- Targeted browser suite (`sidecar-sandbox` + full
`browser-frame-native` Electron suite) under xvfb: 20/20 pass
- Full server suite: 2286/2329 pass; all 27 failures are pre-existing
Git/gpg-signing environment failures (reproduced on an unmodified tree
via stash), none touch sidecars/auth/http-server
- `tsc --noEmit` clean for `@codenomad/ui` and the server package
- Manual: `ttyd -i 127.0.0.1 -p 7682 bash` as a port SideCar (insecure,
strip) — terminal stays connected; before the fix the token fetch was
403 and the WS handshake 401

## Follow-ups (not in this PR)

- Short-lived token in the SideCar iframe URL accepted by `/sidecars/*`
HTTP + WS auth, so even trusted sidecars could drop `allow-same-origin`
(mirrors the preview capability mechanism)
- Optional `SameSite=None; Secure` configuration for authenticated
sandboxed iframes behind HTTPS reverse proxies

---------

Co-authored-by: Pascal André <pascalandr@gmail.com>
2026-10-17 21:01:38 +02:00
Pascal André
db6685cb55
fix(workspaces): closing a project tab closes the project in CodeNomad (#970)
Some checks failed
Publish help / build (push) Has been cancelled
Publish help / deploy (push) Has been cancelled
## Problem

Closing a project tab only hid it in that window. The backend workspace
record kept routing the folder's events, notifications, auto-accept and
pending-request recovery, invisibly, until the palette-only **Stop
Workspace** command was run on the active tab. That command still showed
the V1 message "Stop OpenCode instance? This will stop the server."
although V2 has one shared OpenCode service, and it called
`debug.location.evict`, which (per OpenCode source) rejects pending
permissions, cancels pending Forms, fails plugin RPC and kills terminals
of that directory, even for sessions started elsewhere. OpenCode loads
locations on demand and unloads idle ones itself.

## Change

- **Closing a project tab** (×, Ctrl/Cmd+W) removes that tab's backend
workspace record. CodeNomad stops following it; sessions keep running in
OpenCode and reappear when the project is reopened.
- **Another tab of the same folder** has its own record and is
unaffected.
- **The same record shown in another window**: that window's tab closes
through the existing `workspace.stopped` broadcast, with an info
notification ("closed from another window"); the initiating window shows
nothing.
- **CodeNomad never evicts OpenCode locations**: eviction, its
serialization helpers and `locationOwned` bookkeeping are removed. A
failed launch now always removes its logical record (the retention only
existed to retry a failed eviction).
- **Stop Workspace removed** (command, confirm dialog, i18n keys in all
locales).
- Unchanged: opening an open project still offers a new tab (recent row)
or switching (switch button); temporary projects keep Keep / Discard /
Cancel; window close, app quit, reload, restore and backend shutdown
delete nothing; disconnected-instance dismissal stays local.
- Delete failure: error toast, record kept and reachable from the home
page, no automatic retry.

## Docs

Help in all 10 locales (`projects`, `index`, `desktop`, `server`,
`opencode`, `missions`), `AGENTS.md`, `CONTRIBUTING.md`,
`dev-docs/architecture.md`, `packages/server/README.md` and the
architecture-guide skill now describe the new contract.

## Validation

- UI store tests (`--conditions=browser`): app-tabs,
temporary-instances, workspace-list-reconciliation-fence,
instances-restore-ownership — 30/30.
- Server: manager + shared service 54/54; event ownership and workspace
identity suites pass.
- New real-component browser regression (click ×, Ctrl+W, Cmd+W): 3/3;
project restoration (Electron/Tauri) and new-instance launcher browser
suites pass.
- `npm run test:help` 9/9 (250 pages); UI, server and Electron
typechecks.
2026-10-15 15:14:29 +02:00
Pascal André
9dd4b3f059
feat(remote-control): add Remote Control over OpenTunnel alongside direct access (#668)
Adds Remote Control alongside direct/LAN access. A phone or another computer pairs with CodeNomad and continues its sessions from anywhere, without an inbound port. The transport is OpenTunnel, the blind TLS relay used by OpenCode: TLS ends inside the CodeNomad backend, and nothing has to be deployed.

- packages/remote-tunnel wraps @opentunnel/client. The server build bundles it with its own Effect 4.0.x, so the published server has no runtime dependency on a private workspace package. CodeNomad claims its own route on the device's shared identity and never deletes it.
- A remote-only loopback ingress marks its sockets as remote, so local privileges come from isLocalRequest(), never the peer address. The gate runs before CORS, auth and routing:
  - exact tunnel Host, and the public Origin on mutations;
  - stripped forwarding headers;
  - origin-form targets only, judged as routed (decoded, cut at ';');
  - host-only paths denied;
  - a paired device required, except for the public UI bundle.
- Host-owned pairing: a single-use five-minute code, hashed device credentials with 30-day idle expiry, and a __Host- cookie. Revocation or eviction closes live streams. A revoked device is asked to pair again, keeping drafts.
- The Cloudflare Worker/Durable Object relay, the custom E2EE protocol and the browser transport shims are removed. Electron and Tauri keep the backend alive after the last window only while Remote Control is enabled.

Validation:
- the real createHttpServer is exercised through the ingress, plus raw-socket canonicalization tests and device registry tests;
- recovery unit and browser tests;
- an opt-in end-to-end run through the hosted relay;
- a packaged smoke import of the bundled runtime;
- five gatekeeper rounds to zero findings, and green CI on all desktop targets.
2026-10-09 12:43:25 +02:00
Pascal André
027f50947c
refactor: retire obsolete docs, dead APIs and speculative loaders (#880)
## Scope
Implement the two repository-wide Ponytail audit lists, deduplicating
overlapping recommendations. Based on current `dev@aa2776ef`, on
`refactor/ponytail-cleanup`; no direct dev changes. Initial delta:
**3,548 net lines removed**, two direct dependencies removed (and their
unused transitive lockfile entries).

## Changes and preserved behavior
- Delete superseded MVP/UI/tool-rendering/summary docs; compact the
index and canonical architecture references and remove
per-review/temp-path bookkeeping from runtime-transition history.
Preserve security/ownership rules, technical minimum rationale,
compatibility provenance and reproduction commands.
- Remove confirmed-unused UI surfaces, Electron JSON storage IPC,
obsolete release fetch/shell runners and dead
bridge/storage/command/keyboard utilities. Keep existing native
environment discovery and shared-service lifecycle untouched.
- Replace the empty first-party plugin loading layer with direct
built-in right-panel construction. External sandboxed extensions,
consent, assets authority, activation, ordering and visibility remain
intact.
- Retire the unused incremental-part message mutation engine and
exclusively owned pending-part buffers/budgets/types. The active native
reducer still publishes bounded authoritative snapshots. Preserve
optimistic send identity, pending sends, permissions, usage,
pruning/revert, memory accounting and scroll restoration. Rewrite old
tests using snapshots rather than removing relevant behavioral
assertions.
- Share speech streaming/base64 helpers, retaining playback lifecycle
and cancellation in each caller; deterministic transport regression
covers ordering, start-once, completion and failures.
- Replace three Material icons with existing Lucide icons; remove
`@suid/icons-material` and unused `ts-node` without upgrading other
dependencies.
- Drop the redundant Favorites implementation-shape test; behavioral
store/browser regressions stay. Repair two inherited tests to current
palette and lazy copy contracts, preserving image/privacy assertions.

## Validation
Server/UI/Electron typechecks, UI production build, targeted
core-panel/Favorites/audio/image/palette checks pass. Full local suites
and focused browser/native Electron regressions are in progress. The
broad server run found one existing deadline-sensitive pending-broker
test; its entire 20-test file passes in isolation. Independent
gatekeeper review is required on this exact committed head, findings
will be corrected and re-reviewed to zero before the explicitly
authorized admin merge. No user credentials/shared daemon/database were
used for testing.

## Size notes
Touched existing large files:
`packages/ui/src/stores/message-v2/instance-store.ts` ~1,450 lines,
`packages/ui/src/stores/message-v2/instance-store.test.ts` ~516 lines,
`packages/ui/src/stores/conversation-speech.ts` ~432 lines. No size-only
refactor; source/test coverage reduction is tied to proven-unused paths.
Native application is not replaced/restarted by these isolated checks.
2026-10-08 01:03:40 +02:00
Pascal André
5d1c10e86f
docs: prune obsolete specifications and development readouts
Remove superseded MVP/UI/tool-rendering specifications, duplicate implementation and index summaries, completed attachment planning, incident readouts and redundant CI/pruning/native-backport reports at the user's explicit request. Trim the worktree validation diary while retaining placement, ownership and native-instruction contracts.

Preserve public extension contracts, attribution/licenses, private RPC safety, runtime capability policy and vendor patch provenance/removal criteria. Redirect native-backport instructions to the existing vendor README and remove dangling deployment links and a stale architecture dependency snapshot.

Validation: Markdown-only diff, whitespace checks and repository-wide checks for removed-file references. No runtime source, tests, build dependencies, installed application or shared OpenCode service is changed.
2026-10-08 00:42:53 +02:00
Pascal André
c954841f09
revert(missions): undo PR #673 without dropping later dev changes
Revert merge c118478805 relative to its first parent after the user confirmed the merge was requested in the wrong conversation. Remove the published Missions plugin, control routes, UI, playbooks and packaging references; do not rewrite dev history or touch durable-refactor work in the separate missions-v2 checkout.

Preserve subsequent PRs #792, #829 and #830. Remove only #829's now-invalid Mission reader import, dismissal hook and dedicated reader scenario, retaining the native questions/permissions dock, file-preview navigation and its other tests. Keep the 2.0.22 package pin and parent-session creation fence.

Validation: revert applied without conflicts; all 139 original merge paths reversed; later-only paths retained except the three minimal interruption-dock integration adaptations. Server and UI TypeScript checks and 18 automation/plugin lifecycle/desktop resource tests pass using existing local dependencies. Staged diff checks are clean. Full hosted CI, browser suites and native 2.0.22 qualification were not rerun for this immediate user-authorized admin revert.
2026-10-02 16:18:06 +02:00
Pascal André
4d7408631d
feat(missions): bundle project coordination with the desktop runtime
Provision codenomad.missions through connected-daemon discovery and independent backend presence instead of requiring a source plugin in every project. Bundle the integration for Electron and Tauri, retain project-scoped mission journals across shutdown, and use the same secondary surface as Status.

Complete the existing native actor execution work: persist explicit agent/model/variant choices, validate catalog selections and avoid switching busy actors. Admit assignment and report writes through the authenticated desktop bridge with durable-contract verification, session ownership, connection and worktree fences, and per-send profile environment synchronization.

Advance the older source branch client/plugin pin to 2.0.11 and retain its bounded runtime metadata adaptation. The isolated 2.0.16 mission fixture passes native catalog, selection, busy queues, conflict, environment, reports, reconnect and idempotence checks. Server, UI and plugin typechecks, 31 mission tests, 10 lifecycle/transport tests and six resource-layout tests pass.
2026-09-27 00:57:56 +02:00
Pascal André
f0a6e2e0dd
feat(browser): add autonomous native previews (#666)
## Summary

- Add autonomous native browser previews for Electron and Windows Tauri,
using the authenticated backend/native bridge already shared with
desktop inspection.
- Ship one bundled **`codenomad.automation`** V2 plugin for browser,
inspect, act and screenshot. All tools are available by backend
presence; ordinary desktop startup prepares the instrumentation. Remove
the Developer Mode toggle, marker-based activation and activation
restart requirement.
- Provision through native discovery using the **connected daemon's
authenticated `config.get` roots**, including WSL and reconnects. Share
installation/lease mechanics with pruning, preserve user-authored
entries and older backends' leases, and unwind stale or interrupted
preparation.
- Preserve session/location ownership, inspected-host pinning, browser
registration/navigation generations, local-window targeting and
authenticated transport. The OpenCode daemon remains externally owned.

## Gatekeeper review — September 18, 2026

Merged current `dev` at `96ef38c6` and repeated independent UI, native
and server reviews after each finding until all three scopes reported
**zero remaining actionable findings**.

Corrections include ordinary-project discovery without Developer Mode;
connected-daemon namespace selection and stale lease cleanup; Tauri
child-webview capability isolation, native-window counting,
primary-renderer reload cleanup, fragment navigation, snapshot lock
ordering and deadline-fenced protocol dispatch; and Electron guest
readiness, redirects and explicit navigation intent. Shared-frame
regressions also cover insecure HTTP and late/rejected native IPC.

### Architecture decision

Current OpenCode V2 documents MCP `_meta.sessionID`; the historical
missing-session-metadata argument no longer applies. A single native V2
plugin remains appropriate for the requested unified automation surface:
it reuses the existing authenticated transport and native controllers,
supports backend presence, and needs no second protocol server or
mode-based permission framework. Plugin loading, tool availability and
execution targeting are separate concerns.

## Validation

- Full server suite: **570 passed, 2 skipped**.
- Electron native suite: **214 passed, 4 skipped**.
- Tauri native suite: **180 passed**.
- Real Solid/browser preview suite: **14 passed**, including real
isolated Electron guests and insecure HTTP.
- Actual isolated OpenCode automation fixtures: **beta-19271, 2.0.3,
2.0.5 and 2.0.7**. Covers mismatched backend/daemon environments, late
discovery, all four tools without activation, execution fences,
independent leases, shutdown and reopening.
- Full isolated native pruning fixture on **2.0.7**, including
concurrency, durable claims, payload isolation, backend presence,
restart and worktree/location integration.
- UI, server and Electron typechecks; Electron production build; Windows
Tauri release build (`--no-bundle`); focused Rust formatting and `git
diff --check`.
- Electron and Tauri packaged-resource smoke checks, plus five desktop
packaging regressions including loading the dependency-free automation
bundle from both layouts.
- CI now runs automation discovery across the runtime matrix, includes
2.0.7, and uses Xvfb for real Electron renderer regressions on Linux.

## Validation boundaries

The fixtures never use or stop the shared daemon or a user's database.
Actual WSL runtime execution and a live packaged WebView2 interaction
smoke were not performed in this pass; WSL resolution and Tauri
controller/capability behavior have focused regressions. The currently
running desktop artifact was not replaced. Agent interruption remains
deadline-based rather than end-to-end native cancellation.
Cross-platform CI must validate the pushed head.
2026-09-18 17:46:14 +02:00
Pascal André
aa51cbb9ff
fix(opencode): support earlier and modern V2 runtime contracts (#695)
Closes #694

## Status

This PR contains the stable 2.0.4 client migration, authenticated V2
discovery fallback and shared cross-runtime compatibility
implementation. The validation results below are local execution
evidence; the newly added CI matrix must also run on the updated PR
head.

## Published changes

- Pin UI/server to official `@opencode/client@2.0.4` and the bundled
pruning plugin to `@opencode/plugin@2.0.4`, with the corresponding
lockfile migration.
- Probe authenticated `/api/status` first. Fall back to earlier V2
`/api/health` **only on HTTP 404**, retaining the endpoint, credentials,
64 KiB response bound and absolute deadline. Authentication, transport,
server and malformed-status failures do not trigger fallback.
- Migrate the canonical UI/server contract to stable Forms, permission
decisions, command names, interrupts, renaming, forks, message lookup
and inbox delivery.
- Update the guarded proxy for stable methods/paths, native
active-session envelopes, native `cursor.next`, global Form directory
headers and current resource/plugin events.
- Follow the renamed upstream platform binary `bin/opencode(.exe)` while
retaining public CLI aliases.

Discovery success alone does not establish compatibility with an earlier
runtime's conversation APIs.

## Shared cross-runtime compatibility

### Shared cross-runtime integration

- Introduce one connection-scoped adapter under
`packages/server/src/opencode/compatibility/`, used by both the guarded
UI proxy and direct server callers, including Yolo.
- Select the contract using authenticated daemon identity rather than
the CLI executable's version. Recognize unknown publications through
authenticated, bounded OpenAPI negotiation instead of an exact
runtime-version gate.
- Bind authorization, client, transport and profile to one connection
generation. Fence actual mutation dispatch after asynchronous body
preparation; prevent late old streams from publishing or invalidating
their replacement.
- Translate earlier V2 routes, methods and payloads before sending.
Never retry a mutation speculatively after 400, 404, authentication or
transport failures.
- Normalize legacy HTTP inbox timestamps, including compaction
admission, before the pinned Solid reducer. Preserve the distinct native
SSE inbox schema and durable event metadata.
- Restore legacy catalog/content invalidation and permission-event
convergence. Preserve meaningful method/path/status/profile diagnostics
and native 401 error envelopes.

### Location authority and pruning

- Preserve legacy workspace identity internally through request
serialization, catalog/cache keys, Forms, imports, cursors, events and
exact move rollback. Modern public APIs continue to reject obsolete
workspace selectors.
- Authorize complete locations before translating directories or
synthesizing upstream Form/location headers. Keep native cursor tokens
unchanged.
- Preserve session/path ownership, worktree mutation fences and the
restricted proxy allowlist. Generic RPC, plugin management and
destructive revert commit remain blocked.
- Carry verified legacy identity through pruning membership checks,
preview and the existing transactional storage/claim fence; do not relax
deletion checks.
- Treat native idle records as control records, retaining
identity/outcome without fabricated assistant text.
- Add a common multi-runtime CI matrix and the audit/evidence register
in `dev-docs/OPENCODE_V2_COMPATIBILITY.md`, including preserved
historical architecture findings.

## Local validation

All native tests use isolated synthetic daemons, configuration and
databases, never the shared daemon or user storage.

| Check | Result |
| --- | --- |
| Same native suite on beta-19271, 2.0.3, 2.0.4 and 2.0.5 with pinned
client/plugin 2.0.4 | Passed |
| Full native pruning suite with non-null legacy workspace identity on
beta-19271 and 2.0.3 | Passed |
| Rendered UI on modern 2.0.5 and beta-19271 with non-null identity |
Passed |
| Native 2.0.5 suite inside Ubuntu/WSL, using a temporary Linux
filesystem root | Passed |
| Server tests (after CI follow-up) | 500 passed, 2 skipped |
| UI store tests | 441 passed |
| Electron native tests | 194 passed, 4 skipped |
| UI/server/Electron typechecks | Passed |
| UI/server and Electron production builds | Passed |
| Windows Tauri release executable (`--no-bundle`) | Passed |
| Electron/Tauri packaged-resource smoke checks | Passed |
| Desktop packaging invariants | 4 passed |
| `git diff --check` | Passed |

The native matrix exercises the actual shared connection, generated
client, UI fetch adapter and guarded proxy: voice-off/on instructions,
prompts, commands, session Shell, background Shell output
cursors/removal, UI/Yolo permission acceptance and rejection, busy inbox
delivery/cancellation, interrupt/wait, staged revert clearing,
session/global/worktree Forms, native pagination, export/import,
same-directory legacy identities, scoped events, foreign authority
refusal and exact rollback.

Pruning coverage includes actual storage identity and execution claims,
busy refusal, idempotency, competing admission, next-model payload, fork
isolation, pre-compaction history, restart, backend presence
disposal/reopening and crash expiry. Rendered tests cover
individual/group/response/session cleanup, confirmation counts,
persistence and reload.

## Autonomous gatekeeper review

Review/fix/retest loops concluded with no remaining actionable findings
in the reviewed transport/lifecycle/proxy,
identity/UI/events/pruning/rollback, scripts/CI and rendered-fixture
scopes.

Corrections included dispatch-generation races, compaction admission
normalization, independent negotiation cancellation, first-request
profile resolution, preservation of streamed 401 envelopes and direct
Shell/PTY identity propagation. A final rendered failure was traced to
the fixture awaiting only the first RPC of a cross-message deletion; the
fixture and regression now await every affected message. Production
deletion guards were not relaxed.

This scoped approval does not close the separately preserved historical
architecture findings.

## Validation boundaries

### CI follow-up

The first run on `a0c1c943`
([35142559235](https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/35142559235))
failed in five native jobs. The local bundle tests had missed the
standalone npm archive's missing sibling import, and Windows legacy jobs
exposed a short-path versus canonical-path project comparison. The
dependent artifact-comment job reported that same build failure.

The follow-up makes the plugin's identity primitives self-contained,
adds an isolated npm-file-list regression for all exported entrypoints,
and corrects project identity selection with alias and nested-repository
regressions. The packed-plugin failure was reproduced before correction;
the corrected installed package passes the native suite on Windows 2.0.4
and Linux/WSL 2.0.4/2.0.5. The corrected 2.0.4 shipped bundle also
passes rendered/native pruning. Fresh full beta-19271 and 2.0.3
`--legacy-pruning` suites pass under a reproducing Windows short-path
TEMP root after the final exact-directory fix. Independent final reviews
approve packaging/documentation and worktree identity with zero
actionable findings. Remote checks on the new head remain the CI
authority; these are local follow-up results.

- Full interaction with the installed desktop application through
Developer Mode was not verified because Developer Mode is inactive. The
release executable and packaged-resource checks above are build/smoke
evidence, not a fresh-install interactive certification.
- Remote run
[35146390688](https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/35146390688)
at `486e856d` passes all runtime-contract jobs, Windows/Linux native
pruning, service compatibility, general tests and both Tauri checks.
macOS passes packed-plugin loading/pruning, then times out on late
bundle discovery. The fixture now canonicalizes its root before daemon
startup to align `/var` configuration paths with `/private/var` FSEvents
paths. Late installation remains mandatory and asserts the plugin was
initially absent; failures now upload isolated diagnostic
logs/inventory/captures. Actual macOS confirmation remains pending on
the new head.
- Interactive native TUI validation was not performed.
- The passing WSL result is Linux-native execution inside WSL, not a
complete Windows-host/WSL desktop traversal. With the fixture's
configuration directory under `/mnt/c`, conversation/proxy checks passed
but late plugin discovery timed out; the otherwise equivalent
Linux-filesystem run passed. Mounted-configuration late discovery
remains a documented environment limitation.
- Other audited publications are declaration-reviewed, not individually
native-certified. The four-runtime matrix does not imply universal V2
compatibility.
- Earlier PR artifacts do not contain the completed cross-runtime
implementation; use artifacts built from the updated head. Local passing
results do not imply that the updated remote CI has completed.
2026-09-16 23:48:08 +02:00
Pascal André
52f0e62935
feat(opencode): migrate CodeNomad to native V2 (#647)
# OpenCode V2 Migration and Desktop Restructuring

## Summary

This branch replaces CodeNomad's OpenCode V1 SDK, custom plugin, and
per-workspace runtime architecture with the native OpenCode V2 client
and one shared OpenCode service. It intentionally ships no V1 runtime
fallback.

The scope includes native location ownership, Forms and Shell resources,
project-wide cursor pagination, bounded resident message windows,
streaming reconciliation, duplicate-folder instances, multi-window
Electron/Tauri behavior, content-addressed restore, worktree mutation
fencing, and reproducible desktop packaging.

The reviewed implementation head is `dea20996`. UI and server resolve
`@opencode-ai/client@beta` to `beta-18999`; the runtime CLI is managed
independently. The official comparison baseline remains `upstream/beta`
at `eb1ac54d73`.

Full details: [`MIGRATION_V2.md`](./MIGRATION_V2.md),
[`DESKTOP_V2_COMPARISON.md`](./DESKTOP_V2_COMPARISON.md), and
[`dev-docs/DEV_V2_CODE_REDUCTION.md`](./dev-docs/DEV_V2_CODE_REDUCTION.md).

## Stabilization Fixes

- Preserve manual scroll escape across pagination, remote updates,
capped-window shifts, tab changes, and desktop restore; only a local
prompt submission forces the bottom.
- Keep the timeline resident window capped at 200 messages. Ordered
window slides use Virtua shift, while reorder, pagination, and
non-aligned replacement remount with a fresh measurement epoch.
- Finalize the temporary `200 -> 201 -> 200` transition in a
generation-guarded microtask before paint and compensate followed
content growth from a `ResizeObserver` attached to the mounted Virtua
root.
- Keep all pending inbox prompts after delivered transcript messages in
native inbox admission order.
- Add a persisted `steer`/`queue` follow-up preference; the alternate
shortcut uses the inverse while idle sends remain `steer`.
- Edit a pending prompt only after authoritative inbox cancellation
succeeds, then restore its text, structured payload, files, and agent
references to the composer.
- Expose native `session.background` through the ownership-checked
proxy, a prompt action, and `Ctrl/Cmd+B`.
- Remove the duplicate model-selector "show all" action and make the
favorites toggle label reflect its current action.
- Restore permission/Form synchronization plus skill, reference,
MCP-resource, and websearch-provider reads in the reviewed proxy
allowlist.
- Validate and preserve native location workspace identities for
root/worktree requests, session moves/imports, cursors, and global
Forms.

## Capability Decisions

| Capability | Native V2 availability | CodeNomad `DEV-v2` decision |
Revisit or complete when |
| --- | --- | --- | --- |
| Assistant content mutation | `session.messageUpdate` authoritatively
replaces the text, reasoning, and tool content of a completed assistant
message. There is still no generic deletion of a complete delivered
message or mutation of user messages. | Restores individual
tool/reasoning deletion through the native update route. Arbitrary
whole-message deletion remains absent rather than locally simulated. |
Add broader deletion only if V2 exposes an authoritative operation for
the required message type. |
| Bulk tool and reasoning cleanup | No native bulk operation, but
`session.messageUpdate` can safely compose cleanup one assistant message
at a time. | Restores selected-group and full-session technical-part
cleanup through authoritative per-message updates. | A native bulk
operation is only needed if per-message updates become a measured
performance problem. |
| Delete-to-boundary / undo | `session.revert.stage` and
`session.revert.clear` use V2 staged-revert semantics instead of
arbitrary deletion. | Command-palette Undo uses `revert.stage`; Redo
clears the staged revert through `revert.clear`. | Revisit only if V2
adds broader authoritative transcript deletion. |
| Compaction | Native checkpoint compaction summarizes the older head
and retains a server-selected recent tail controlled by
`compaction.keep.tokens`. | Uses `session.compact`; there is no
message-level selective compaction. CodeNomad displays the partial
summary through the same reactive streaming path as text and reasoning,
then replaces it with the terminal summary. | Add scoped controls only
if V2 defines scoped compaction semantics. |
| Transcript pagination authority | Message pages expose only opaque
previous/next cursors. They provide no transcript revision, total,
absolute range, position, or random seek target. | Uses bounded cursor
traversal and a 200-message resident window, with defensive
reconciliation after destructive events. It cannot prove that pages
loaded across compaction or revert belong to one snapshot. | V2 adds
snapshot revision authority
([#43766](https://github.com/anomalyco/opencode/issues/43766)) and
navigable position metadata
([#44660](https://github.com/anomalyco/opencode/issues/44660)). |
| Full-session search | `session.list(search)` filters session titles
only. There is no server transcript-search endpoint returning message
identity, excerpt, navigable position, and transcript revision. |
Retained through exhaustive message-cursor traversal while keeping only
the 200-message resident window and collected matches. Large searches
still fetch every page. | V2 exposes transcript search
([#45801](https://github.com/anomalyco/opencode/issues/45801)) with a
target that composes with transcript revision and seek metadata. |
| Queued prompt management | Native inbox list, cancel, steer, and queue
operations cover authoritative follow-up delivery. The TUI's edit action
cancels a queued prompt before restoring it to the composer; it is not
an atomic inbox update. | Implements persisted primary `steer`/`queue`
delivery, inverse alternate shortcut, delivery switching, cancellation,
cancel-first composer restoration with structured payload retention, and
native-order timeline projection. | Revisit atomic editing or reordering
only if V2 adds an authoritative operation and a concrete product need
is established. |
| Background execution | Native `session.background` moves blocking
tools out of the foreground; Shell resources separately support listing,
bounded output, and removal. | Exposes `session.background` through the
proxy and UI with `Ctrl/Cmd+B`, and replaces the custom process manager
with native Shells. Unsupported controls such as rename remain removed.
| Add controls only when the native session or Shell APIs support them.
|
| Service lifecycle | V2 uses one shared externally owned service rather
than one runtime per workspace. | CodeNomad discovers or starts the
service. Stop Workspace evicts the native location after its final
logical owner is removed; application shutdown never stops the daemon. |
No parity work planned unless V2 changes service ownership semantics. |

## Validation

Migration gate (`878550ca`):

- UI and server TypeScript typechecks.
- 86 focused UI tests covering follow, pagination, queue ordering,
bottom pinning, shortcuts, delivery resolution, and model visibility.
- 64 focused server tests covering proxy ownership/location enforcement
and shared-service workspace identity.
- Production UI/server build.
- Tauri `cargo check` and all 121 Rust tests.
- `git diff --check`.

Timeline stabilization (`dea20996`):

- UI TypeScript typecheck and all 68 focused timeline, pagination,
request-authority, and restore tests.
- Tauri release build against the `beta-18999` lock.
- Native Developer Mode validation: no remount or empty frame during
capped shifts, same-cycle growth compensation, preserved manual escape,
and inactive-tab anchor restore within 0.3125 px.

## Remaining Gaps

- Active-session-location authority for MCP/plugin status.
- Optional interactive PTY, session export, and reference/MCP-resource
attachment UI.
- Desktop release signing/notarization hardening.
2026-09-04 11:36:11 +01:00
Shantur Rathore
f9ec757c64 refactor message stream layout 2025-12-02 19:23:05 +00:00
Shantur Rathore
91ace25333 Batch hydrate normalized messages for session load 2025-11-26 10:57:39 +00:00
Shantur Rathore
146eae5220 Add CLI instance proxy and route UI traffic through it 2025-11-19 02:03:15 +00:00
Shantur Rathore
6e7003c57c Puff-up README 2025-11-14 23:26:13 +00:00
Renamed from docs/architecture.md (Browse further)