Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
17dd011
docs(opencode): prepare the post-beta runtime support transition
pascalandr Sep 16, 2026
245601e
Merge branch 'dev' into prep/opencode-v2-post-beta
pascalandr Sep 19, 2026
e986e66
docs(opencode): define stable runtime setup and update plan
pascalandr Sep 20, 2026
01023f7
Merge branch 'dev' into prep/opencode-v2-post-beta
pascalandr Sep 20, 2026
3e14312
Merge remote-tracking branch 'origin/dev' into prep/opencode-v2-post-…
pascalandr Sep 20, 2026
7869e60
feat(opencode): enforce stable runtime support and add managed setup
pascalandr Sep 20, 2026
76e8568
Merge remote-tracking branch 'origin/prep/opencode-v2-post-beta' into…
pascalandr Sep 20, 2026
419fe6a
fix(opencode): close managed setup activation and publication races
pascalandr Sep 20, 2026
8e32d46
test(opencode): bound native migration acceptance and cleanup
pascalandr Sep 20, 2026
4e56480
fix(opencode): canonicalize WSL plugin provisioning paths
pascalandr Sep 20, 2026
550e0a4
feat(opencode): add explicit configuration reload recovery
pascalandr Sep 20, 2026
91c6523
test(opencode): certify bounded WSL setup and reload effects
pascalandr Sep 20, 2026
75200b7
fix(opencode): report setup failures without denying successful insta…
pascalandr Sep 20, 2026
d8f9b38
fix(opencode): preserve old daemons until explicit setup restart
pascalandr Sep 20, 2026
4a77d16
docs(opencode): record final native acceptance and gatekeeper closure
pascalandr Sep 20, 2026
e76e448
fix(opencode): ground compatibility retirement in native contracts
pascalandr Sep 20, 2026
781c3a4
fix(setup): keep recovery above pending folder loading
pascalandr Sep 20, 2026
baaa375
docs(opencode): record final corrected desktop acceptance
pascalandr Sep 20, 2026
8517fb2
merge(dev): qualify history integration before PR 696 merge
pascalandr Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ jobs:
needs: authorize
if: ${{ needs.authorize.outputs.allowed == 'true' && !github.event.pull_request.draft }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
runtime: [minimum, latest]
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
Expand All @@ -150,11 +154,16 @@ jobs:
- run: npm ci --ignore-scripts
- run: npm run build:pruning --workspace @neuralnomads/codenomad
- run: npm run build:automation --workspace @neuralnomads/codenomad
- name: Qualify the pinned client against the latest stable runtime
- name: Qualify the pinned client against the technical minimum and latest stable
shell: pwsh
run: |
$sandbox = Join-Path $env:RUNNER_TEMP 'opencode-contract-runtime'
npm install --prefix $sandbox '@opencode/cli@latest' --ignore-scripts --no-audit --no-fund
$version = '${{ matrix.runtime }}'
if ($version -eq 'minimum') {
$version = node --import tsx --input-type=module -e "import { MINIMUM_OPENCODE_VERSION } from './packages/server/src/opencode/runtime-support.ts'; console.log(MINIMUM_OPENCODE_VERSION)"
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
npm install --prefix $sandbox "@opencode/cli@$version" --ignore-scripts --no-audit --no-fund
if ($LASTEXITCODE) { exit $LASTEXITCODE }
$cli = Join-Path $sandbox 'node_modules/@opencode/cli-windows-x64/bin/opencode.exe'
& $cli --version
Expand Down Expand Up @@ -400,6 +409,43 @@ jobs:
"packages/server/src/opencode-update/*.test.ts"
packages/ui/src/lib/launch-errors.test.ts

- name: Test first installation without system Node
timeout-minutes: 10
run: node scripts/test-opencode-install-native.mjs

- name: Test migration failure guards
run: node --test scripts/native-fixture-guards.test.mjs "scripts/fixtures/wsl-*.test.mjs"

- name: Certify native history migration at the minimum and latest stable
timeout-minutes: 20
shell: pwsh
run: |
$platform = node -p "process.platform === 'win32' ? 'windows' : process.platform"
$arch = node -p "process.arch"
$extension = node -p "process.platform === 'win32' ? '.exe' : ''"
$seeds = @(
@{ scope = '@opencode'; version = '2.0.3'; command = 'opencode' },
@{ scope = '@opencode-ai'; version = '0.0.0-beta-19271'; command = 'opencode2' }
)
$oldClis = foreach ($entry in $seeds) {
$seed = Join-Path $env:RUNNER_TEMP "opencode-migration-seed-$($entry.version)"
npm install --prefix $seed "$($entry.scope)/cli@$($entry.version)" --ignore-scripts --no-audit --no-fund | Out-Host
if ($LASTEXITCODE) { exit $LASTEXITCODE }
Join-Path $seed "node_modules/$($entry.scope)/cli-$platform-$arch/bin/$($entry.command)$extension"
}
$minimum = node --import tsx --input-type=module -e "import { MINIMUM_OPENCODE_VERSION } from './packages/server/src/opencode/runtime-support.ts'; console.log(MINIMUM_OPENCODE_VERSION)"
if ($LASTEXITCODE) { exit $LASTEXITCODE }
foreach ($version in @($minimum, 'latest')) {
$target = Join-Path $env:RUNNER_TEMP "opencode-migration-$version"
npm install --prefix $target "@opencode/cli@$version" --ignore-scripts --no-audit --no-fund
if ($LASTEXITCODE) { exit $LASTEXITCODE }
$cli = Join-Path $target "node_modules/@opencode/cli-$platform-$arch/bin/opencode$extension"
foreach ($oldCli in $oldClis) {
node scripts/test-opencode-history-migration.mjs $oldCli $cli
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
}

# Exercise the window persistence regressions on the architecture reported in
# #676, independently of the Linux server gate. Packaging alone cannot test them.
tests-tauri-macos:
Expand Down
4 changes: 3 additions & 1 deletion .opencode/skills/codenomad-architecture-guide/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ description: |

## Native OpenCode V2 Baseline

- Server and UI pin the official `@opencode/client@2.0.11` together; the pruning plugin pins `@opencode/plugin@2.0.11`. Review official V2 docs, installed declarations, generated wire paths and native integration tests when upgrading. Qualify against the latest published stable runtime. Each CodeNomad release sets its minimum supported OpenCode version to the latest stable available at that CodeNomad release; the minimum stays fixed for that release. Record results in PR/CI logs and update `dev-docs/OPENCODE_V2_COMPATIBILITY.md` in place rather than adding per-version reports. The runtime CLI is managed independently and startup has no exact version gate.
- Runtime requirements live in `opencode/runtime-support.ts`; each blocking requirement needs demonstrated API/behavior evidence. Distinguish technically incompatible, recommended/tested and unverified versions. Setup uses bundled Node/npm and a versioned user prefix; shared-daemon restart is explicit and uses the existing native-parent launch bridge. Retire obsolete wire translations while retaining current identity/authority checks. See `dev-docs/OPENCODE_V2_POST_BETA.md` for precise retirement boundaries and validation evidence.

- Server and UI pin the official `@opencode/client@2.0.11` together; the pruning plugin pins `@opencode/plugin@2.0.11`. Review official V2 docs, installed declarations, generated wire paths and native integration tests when upgrading. Qualify against the latest published stable runtime, but never infer the minimum from that version or from the dependency pin. Record technical minimum requirements and tested scenarios in PR/CI logs and update `dev-docs/OPENCODE_V2_COMPATIBILITY.md` in place rather than adding per-version reports. The runtime CLI is managed independently.
- Do not use `@opencode-ai/sdk`, `@opencode-ai/sdk/v2/client`, or `createOpencodeClient()`; follow installed `@opencode/client` declarations.
- There is no legacy `packages/opencode-plugin/`. Do not restore the V1 compatibility runtime or add general plugin extension points. The narrow integrations are the bundled `codenomad.automation` plugin and bundled session-pruning RPC; see `dev-docs/DEVELOPER_MODE.md`, `dev-docs/BROWSER_AUTOMATION.md` and `dev-docs/SESSION_PRUNING_RPC.md`. All automation tools follow backend presence without a Developer Mode gate, sharing the authenticated native transport and execution-time session/window fences.
- The server uses the selected host or WSL CLI's official `service status`, `service start`, and `service get password` lifecycle to connect to one externally owned global OpenCode daemon. It owns no private port/database/registration/PID and never stops the daemon on backend shutdown. WSL requires Windows localhost forwarding and uses no cross-namespace PID operations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

CodeNomad server and UI pin `@opencode/client@2.0.11`. The runtime CLI is managed independently; startup validates authenticated loopback `/api/status`, then `/api/health`, then `/api/info`, advancing only on HTTP 404 with the same endpoint, credentials and deadline. Each response has its own validated schema and a 64 KiB bound. The shared transport maps canonical `server.info()` to the discovered route. Older services do not expose `paths.tmp`; consumers may use only the metadata actually provided. Discovery does not prove compatibility for other APIs. Review official V2 docs, installed declarations, generated routes and native regression tests together when upgrading.

Cross-runtime adaptation lives in `packages/server/src/opencode/compatibility/`. The shared connection binds authenticated runtime identity, the canonical client and forwarding transport. Known published contracts select their serializer directly; unknown versions require authenticated bounded OpenAPI recognition before calls. Never add operation-specific retry fallbacks in UI stores or Yolo. See `dev-docs/OPENCODE_V2_COMPATIBILITY.md` for the evidence matrix and maintained issue register.
Runtime requirements live in `packages/server/src/opencode/runtime-support.ts`: minimum 2.0.7 for native step-start timestamps, independently of recommended/tested 2.0.11. The shared connection binds authenticated runtime identity, the canonical client and forwarding transport. Admission precedes functional requests/plugin provisioning; unlisted versions, including custom/prerelease/future labels, require authenticated bounded API recognition. Legacy request/response/event and live location translations are retired. Never add operation-specific retry fallbacks in UI stores or Yolo. See `dev-docs/OPENCODE_V2_POST_BETA.md` for precise boundaries, setup and migration evidence.

- Promise client: `import { OpenCode } from "@opencode/client"`
- Service authentication headers: `import { Service } from "@opencode/client/service"`
Expand All @@ -30,7 +30,7 @@ Native methods return decoded Promise values. Follow the installed declarations

Native Forms own pending interruption state. Global Forms use `sessionID: "global"` and `x-opencode-directory: encodeURIComponent(directory)`; ordinary session Forms derive location from the session. Question tool output rendering is independent of pending Forms.

Earlier V2 location identity must survive modern generated-client field selection. Use `locationRequestOptions` (server) / `requestLocationOptions` (UI) for the explicit private context channel; the proxy authorizes the complete pair, translates its directory, and the selected transport serializes the appropriate legacy slots. Modern public APIs still reject workspace selectors. Session move/rollback uses `moveSessionToLocation`, not a cast adding fields to the modern method input.
Historical location identity must not silently disappear through generated-client field selection. `locationRequestOptions` (server) / `requestLocationOptions` (UI) retain an explicit private context channel so the proxy can reject obsolete selectors rather than accidentally authorizing directory-only requests. Supported public APIs reject workspace selectors. Session move/rollback uses `moveSessionToLocation`, not a cast adding fields to the generated method input.

Stable mutations use `permission.reply({ decision })`, `session.command({ name })`, `session.interrupt({ resume })`, `session.fork({ before? })`, `session.inbox.update({ delivery })` and `session.message.get(...)`. Credential removal is global and takes only `credentialID`. There is no plugin activation-wait endpoint; catalog reads and `plugin.updated` supply native state.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
- Native routes are `/api/*`; CodeNomad exposes them only through the authorized `/workspaces/:id/instance` proxy.
- That proxy is an explicit method/path allowlist. Future upstream APIs are not exposed automatically.
- Proxy authorization and forwarding share one acquired connection. A stale generation must be rejected at actual HTTP dispatch, including after asynchronous body preparation; late streams cannot invalidate a replacement connection.
- Adapt legacy HTTP inbox timestamps before the native Solid reducer. Native `session.inbox.enqueued` is a different shape: its timestamp belongs to the event metadata and must not be treated as an HTTP inbox record.
- Compatibility never retries a write using another contract after a 400/404/transport failure. Unknown version numbers are recognized through authenticated OpenAPI structure rather than an exact runtime-version gate.
- The technical minimum is 2.0.7: native `session.step.started.data.started` is consumed directly after removing its older fallback. Recommendation/qualification 2.0.11 is independent. Unknown version labels, prereleases and future majors require bounded authenticated API recognition, including session environment support; they are not rejected solely by label. Legacy HTTP inbox and event conversions are retired. Native `session.inbox.enqueued` still has a distinct shape: its timestamp belongs to event metadata, not an HTTP inbox record.
- Never retry a write using another contract after a 400/404/transport failure. Keep the setup/recovery path distinct from functional transport and never replay prompts.

## Location Is Authority

Expand All @@ -33,7 +33,7 @@
| Session/message/Shell/instructions | OpenCode native API; session Shell remains separate from background Shell and PTY management |
| Background Shell list/metadata/output/remove | Location-scoped OpenCode native API through CodeNomad ownership checks; Status UI refreshes on Shell events/reconnect |
| Interactive PTYs | Separate native `pty.*` API |
| Service status/start/password | CodeNomad adapter using the selected host or WSL CLI; daemon stop remains external |
| Service status/start/password | CodeNomad adapter using the selected host or WSL CLI; only the explicit setup restart may stop/start the shared daemon |
| Workspace and directory authorization | CodeNomad |
| Git status/diff and mutations | CodeNomad |
| Yolo policy/persistence/auto-reply | CodeNomad |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ OpenCode owns standard state/database. Allowed configured environment variables

Workspace creation calls `client.location.get({ location: { directory } })` and records the returned directory. Its `project` field supplies project metadata. Public locations have no workspace selector. Explicit Stop Workspace calls `client.debug.location.evict` before removing the logical workspace. Ordinary tab/window close only detaches local UI and never evicts.

The paragraph above describes the canonical modern wire contract. Preserve any native legacy `workspaceID` internally and authorize it through the connection-scoped adapter; directory-only serialization must not erase identity from cache keys, pending Forms, imported history or move rollback. See `opencode/compatibility/location.ts` for the explicit context channel that survives generated serialization.
The technical minimum is 2.0.7 for the native step-start timestamp, with 2.0.11 independently recommended/tested; old request/response/event and live location translations are retired. Preserve historical `workspaceID` validation internally: obsolete public selectors must be rejected rather than erased from imports, cursors, pending Forms or move rollback. Native 2.0.3→2.0.7/2.0.11 migration collapses workspace selectors to local directory scope while preserving session IDs. See `dev-docs/OPENCODE_V2_POST_BETA.md`.

The instance proxy is method/path allowlisted, rejects unowned paths, `directory`, `location.directory`, and `location[directory]` values, and verifies session location before forwarding. Keep this check at the server trust boundary; new upstream routes require explicit review.

Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

## Coding Principles

- OpenCode minimum requirements must follow demonstrated technical dependencies, never the latest published or solely tested version. Keep required, recommended/tested and unverified versions distinct in `opencode/runtime-support.ts` and setup diagnostics. Validate authenticated daemon metadata/contract before client use/plugin provisioning. Setup uses bundled Node/npm and a versioned user prefix; a running daemon restart is a separate explicit action. Configuration reload is also explicit: native `location.reload` rebuilds every loaded location and cancels pending Forms/permissions, so never use it as an automatic watcher fallback. Retire old wire translations without removing current identity/ownership checks. See `dev-docs/OPENCODE_V2_POST_BETA.md` for version boundaries and isolated validation evidence.
- A selected CLI's `service status` can report `stopped` for a live older daemon. Preserve the bounded, read-only registration fallback in `workspaces/native-service-registration.ts` and authenticate historical metadata before allowing start; `ensure()` repeats discovery. Native service lookup is separate from plugin-root discovery, which must still use the connected daemon's `config.get`. Never write service registration/configuration files from the backend.

- Profile environment variables are applied server-side before each native session prompt, custom command or session shell request, after ownership and worktree-mutation admission. Build a complete execution-host snapshot with `workspaces/session-environment.ts`; never send the profile environment through the browser or skip the per-send write using a cache. Reads and settings edits do not mutate native sessions. Keep native environment failures fail-closed and redact SDK request bodies. See `dev-docs/SESSION_ENVIRONMENT.md`.

- One bundled `codenomad.automation` V2 plugin uses native discovery and backend presence. All browser/developer tools are available without a Developer Mode toggle; native instrumentation starts with the desktop host. Loading, tool availability and execution targeting are separate: retain the authenticated bridge and session/window fences. Tauri preview children receive no application capabilities; primary-renderer reload must dispose them and final-window checks must count native windows. See `dev-docs/BROWSER_AUTOMATION.md`.
Expand Down
Loading
Loading