Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b5cf858
fix(sdk): curate RPC and plugin operation names
Blind-Striker Sep 5, 2026
f7e64b5
test: prove launcher startup tree reaping
Blind-Striker Sep 5, 2026
240b3a4
test: harden startup tree fixture cleanup
Blind-Striker Sep 5, 2026
2de3fb7
test: own startup tree cleanup through control leases
Blind-Striker Sep 5, 2026
664fa6c
test: prove normal PTY live lifecycle
Blind-Striker Sep 6, 2026
6cfcd4d
test: preserve PTY replay evidence for removal
Blind-Striker Sep 6, 2026
b8571b3
test: cover live location and session isolation
Blind-Striker Sep 6, 2026
8ab56b9
test: cover live message cursor pagination
Blind-Striker Sep 6, 2026
874ab0a
test: isolate live session cleanup budgets
Blind-Striker Sep 6, 2026
a346e69
test: cover durable session log replay
Blind-Striker Sep 6, 2026
256ef8c
test: preserve live log cleanup diagnostics
Blind-Striker Sep 6, 2026
29c07fb
test: isolate simulation host log level
Blind-Striker Sep 6, 2026
54c724f
test: cover live permission grant lifecycle
Blind-Striker Sep 6, 2026
a657e12
test: expose live permission assertions
Blind-Striker Sep 6, 2026
4d358e1
fix(tests): retain late cleanup diagnostics on net472
Blind-Striker Sep 6, 2026
32005d0
test: cover live form lifecycle
Blind-Striker Sep 6, 2026
59eda59
fix(tests): bound live-resource cleanup and retain failures
Blind-Striker Sep 6, 2026
ee7c877
test: make owned cleanup deadline proofs deterministic
Blind-Striker Sep 6, 2026
be2b3d7
test: retain owned live failure diagnostics in CI artifacts
Blind-Striker Sep 6, 2026
402d27a
fix: correct live diagnostic failure attribution
Blind-Striker Sep 6, 2026
bc7a8b5
test: request PTY readiness after attachment
Blind-Striker Sep 6, 2026
cec1df1
test: prove session move live behavior
Blind-Striker Sep 6, 2026
4346a94
test: prove session transfer and stats live
Blind-Striker Sep 6, 2026
54a73ff
test: cover catalog clients against simulated server
Blind-Striker Sep 6, 2026
c7c2591
feat: refresh location-scoped worktree operations
Blind-Striker Sep 6, 2026
99d5bf1
test: cover skills and filesystem against simulated server
Blind-Striker Sep 6, 2026
98c3d59
fix: contain test workspace file writes
Blind-Striker Sep 6, 2026
edbb8c8
test: cover server and project inventory live
Blind-Striker Sep 6, 2026
4cfd197
test: cover debug location eviction live
Blind-Striker Sep 6, 2026
4e5faaf
test: prove session inbox live behavior
Blind-Striker Sep 6, 2026
8de8de1
test: repair session inventory live proof
Blind-Striker Sep 6, 2026
08ddae7
fix(ci): repair filesystem live prerequisites
Blind-Striker Sep 6, 2026
57785e7
test: remove unused simulated diagnostic drain
Blind-Striker Sep 6, 2026
d5cba70
test: cover live VCS metadata and diffs
Blind-Striker Sep 7, 2026
9eaa6f8
test: preserve Git cancellation cleanup failures
Blind-Striker Sep 7, 2026
837d4f8
test: seed owned RPC plugin and guard plugin inventory
Blind-Striker Sep 7, 2026
b48bc0a
test: assert RPC plugin ownership from fixture mode
Blind-Striker Sep 7, 2026
0906575
feat: refresh upstream snapshot to 89f1943 and align Bun runtime
Blind-Striker Sep 7, 2026
3be839d
chore: integrate refreshed snapshot with live coverage
Blind-Striker Sep 7, 2026
8e8b84a
test: cover owned RPC success and error responses
Blind-Striker Sep 7, 2026
258ffe8
test: guard the retained unavailable RPC arms in external mode
Blind-Striker Sep 7, 2026
567b3c5
test: prove the owned RPC event reaches the live event bus
Blind-Striker Sep 7, 2026
34ada9e
fix(sdk): classify a fault the body read's own interruption caused
Blind-Striker Sep 7, 2026
0a271b6
feat(sdk): retain the launched server's output through a caller-owned…
Blind-Striker Sep 7, 2026
a8eea58
fix(sdk): keep the launched server's process id readable after disposal
Blind-Striker Sep 7, 2026
eb35145
test: dogfood OpenCodeServer in the fixtures and retire the CliWrap a…
Blind-Striker Sep 7, 2026
5b1955e
test: verify the shared simulated host's stdin-EOF milestone from its…
Blind-Striker Sep 7, 2026
acfd313
chore: integrate the parked launcher startup-tree proofs
Blind-Striker Sep 7, 2026
7efcec4
test: prove bounded descendant termination after a failed start
Blind-Striker Sep 7, 2026
687dd94
test: share the owned test server policy across the fixtures
Blind-Striker Sep 7, 2026
61de3b1
test: name the failed-start tree contract once and record its platfor…
Blind-Striker Sep 7, 2026
9117276
test: prove the worktree lifecycle and its refusals against a real Gi…
Blind-Striker Sep 7, 2026
9d5d36c
test: observe the linked worktree's absence before creating it and na…
Blind-Striker Sep 7, 2026
549eddb
test: extend the drive controller with the simulated tool protocol
Blind-Striker Sep 7, 2026
f1b3e9f
test: prove the model-originated tool and permission lifecycle end to…
Blind-Striker Sep 7, 2026
fb6740b
test: correlate the drive tool arms to their session and own the scen…
Blind-Striker Sep 7, 2026
09939cc
docs: refresh the suite count for the completed live-coverage arc
Blind-Striker Sep 7, 2026
3188b03
chore(spec): watch the ripgrep version CI provisions
Blind-Striker Sep 7, 2026
9cca5dc
test: prove the launched server's id outlives its disposal
Blind-Striker Sep 7, 2026
2187b4b
docs: name ripgrep as a prerequisite and refresh the roadmap's state
Blind-Striker Sep 7, 2026
530f5c0
docs: carry the suite count the final arc gate recorded
Blind-Striker Sep 7, 2026
1c5d1be
test: settle every correlated event through the one session probe
Blind-Striker Sep 7, 2026
293f782
docs: record the undetected half-open event stream as a known gap
Blind-Striker Sep 7, 2026
540f460
test: accept an already-departed child when arranging the exited-chil…
Blind-Striker Sep 7, 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
97 changes: 81 additions & 16 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -903,24 +903,89 @@ dotnet_diagnostic.CA2000.severity = none
[tests/OpenCode.Sdk.Tests/Support/ScriptedTerminalWebSocket.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# CliWrapServerAdapter parks on TaskCompletionSource handles the adapter itself completes — the
# stdin lease that holds the child's ownership pipe open until disposal, and the first stdout
# line that carries the readiness contract — plus the CliWrap execution task it started. That
# externally-completed lease is the stdio ownership mechanism itself, not a foreign task with
# unknown context. Winner: the stdio-lease control adapter; VSTHRD003 remains enabled for every
# other file.
# StartAsync disposes the adapter on every readiness-failure branch before it throws, and returns
# it undisposed only on the success path where the caller becomes the owner - the same
# ownership-transfer shape OpenCodeServer.StartAsync uses for its Process local. CA2000's flow
# analysis does not resolve that transfer through this method's mix of nested try/catch and a
# CliWrap fluent builder capturing the local by reference on the net472 leg only; the other three
# TFMs verify the same method clean. Winner: the readiness-failure disposal already in the method
# body; CA2000 remains enabled for every other file.
[tests/Shared/CliWrapServerAdapter.cs]
# LateCleanupFailureReport owns the TaskCompletionSource completed only by its registered
# fault-observation continuations. The bridge lets a deterministic test await an otherwise-late
# cleanup failure without sleeping. Winner: the bounded cleanup reporting contract; VSTHRD003
# remains enabled elsewhere.
[tests/Shared/Ownership/LateCleanupFailureReport.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# Cleanup owners retain raw tasks until actual completion and separately bound observation.
# Awaiting those owned tasks directly preserves their original result/cancellation identity;
# callback invocation is isolated on the thread pool, with no foreign synchronization-context
# dependency. Winner: owned cleanup observation; VSTHRD003 remains enabled elsewhere.
[tests/Shared/Ownership/OwnedCleanup.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# OwnedEventReader is the reader half of that same contract: it retains the enumeration task a
# caller handed it and observes it under its own bounded cleanup, so the awaited handle is owned
# work rather than a foreign task. Winner: owned event observation; VSTHRD003 remains enabled
# elsewhere.
[tests/OpenCode.Sdk.Tests/Support/OwnedEventReader.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# SessionEventProbe awaits TaskCompletionSource handles completed only by the reader loop this
# same object started and handed to its owning OwnedEventReader: the connected barrier and the
# per-arrival signal. That is the event-to-async bridge which lets a scenario act the moment the
# subscription is attached, and wait for a correlated event, without polling or sleeping. Its own
# proofs drive that bridge from a scripted stream, awaiting the same signals plus the scenario's
# stall handle, and release and drain every one of them in finally. Winner: the single-reader
# event probe; VSTHRD003 remains enabled for every other file.
[tests/OpenCode.Sdk.Tests/Support/SessionEventProbe.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/SessionEventProbeTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# These per-scenario deadline helpers retain task handles and explicitly control entry, expiry
# and result delivery. Their caller releases signals and drains all owned work in finally;
# there is no foreign synchronization-context dependency. Winner: deterministic deadline control.
[tests/OpenCode.Sdk.Tests/Support/ControlledOperationDeadline.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/OperationDeadlineScenario.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# These cleanup proofs await their own externally-completed barriers/tasks to establish the
# exact ordering under test. Finally releases and observes stalled work and deadline signals;
# no foreign synchronization context is involved. Winner: causal owned-work proofs. Each entry
# names one file; VSTHRD003 remains enabled outside these explicitly owned proof families.
[tests/OpenCode.Sdk.Tests/Support/OwnedCleanupTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/OwnedCleanupDeadlineTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/OwnedEventReaderTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/OwnedOperationDeadlineTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/SessionLogTranscriptTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# The session-removal fault proof controls interrupt entry and expiry, then drains cancellation
# and removal in finally. Its task handles have no foreign synchronization-context dependency.
# Winner: the same causal cleanup proof embedded beside its actual live consumer.
[tests/OpenCode.Sdk.Tests/Sessions/SessionMessagePaginationLiveTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

# This fixture proof owns the disposal task and controls its deadline delivery. Its finally
# releases all signals and drains the raw operations. Winner: causal owned-process failure proof;
# VSTHRD003 remains enabled outside this exact file.
[tests/OpenCode.Sdk.Tests/PinnedOpenCodeServerFixtureFailureTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none
# These diagnostic proofs control their own operation deadlines and retain supplied disposal
# tasks until finally releases and drains them. Winner: causal fixture/status ownership proofs;
# the rule remains enabled outside these exact files.
[tests/OpenCode.Sdk.Tests/Support/PtyFailureDiagnosticsTests.cs]
dotnet_diagnostic.VSTHRD003.severity = none

[tests/OpenCode.Sdk.Tests/Support/ServerDiagnosticScenario.cs]
dotnet_diagnostic.VSTHRD003.severity = none
dotnet_diagnostic.CA2000.severity = none

# Sandbox application's purpose is primarly for testing
# Sandbox application's purpose is primarily for testing
[tests/OpenCode.Sdk.Sandbox/**.cs]
dotnet_diagnostic.MA0076.severity = none

Expand Down
7 changes: 5 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ sign.
[`global.json`](../global.json) (`10.0.302`, `rollForward: latestFeature`)
- The **.NET 8 and .NET 9 runtimes**, so the `net8.0` and `net9.0` test legs can run
- On Windows, the **.NET Framework 4.7.2 targeting pack** for the `net472` legs
- [Bun](https://bun.sh/) `1.3.14` — the pinned-server test fixture builds `external/opencode` with
it, exactly as CI does
- [Bun](https://bun.sh/) at the exact version in `external/opencode/package.json`'s
`packageManager` field — the pinned-server fixture and CI use that runtime
- [ripgrep 15.1.0](https://github.com/BurntSushi/ripgrep/releases/tag/15.1.0) on `PATH` — the
filesystem live tests run against an offline simulated server that refuses the on-demand download
upstream would otherwise perform
- [Git](https://git-scm.com/downloads) with submodule support
- IDE: [Rider](https://www.jetbrains.com/rider/), [Visual Studio](https://visualstudio.microsoft.com/),
or [VS Code](https://code.visualstudio.com/)
Expand Down
64 changes: 61 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,74 @@ jobs:
uses: oven-sh/setup-bun@v2
with:
# The pin's own packageManager row (external/opencode/package.json).
bun-version: 1.3.14
bun-version-file: external/opencode/package.json

- name: Verify pinned Bun runtime
shell: pwsh
run: |
$expected = (Get-Content external/opencode/package.json -Raw | ConvertFrom-Json).packageManager
$actual = "bun@$(& bun --version)"
if ($LASTEXITCODE -ne 0 -or $actual -cne $expected) {
throw "Bun runtime mismatch: expected $expected, got $actual."
}

# fs.find uses upstream's ripgrep resolver. Keep the simulated server offline by placing the
# exact resolver version on the job PATH before tests instead of allowing its runtime download.
- name: Provision ripgrep for pinned server
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$PSNativeCommandUseErrorActionPreference = $true
$version = "15.1.0"
switch ("${{ runner.os }}-${{ runner.arch }}") {
"Windows-X64" {
$archive = "ripgrep-$version-x86_64-pc-windows-msvc.zip"
$sha256 = "124510b94b6baa3380d051fdf4650eaa80a302c876d611e9dba0b2e18d87493a"
}
"Linux-X64" {
$archive = "ripgrep-$version-x86_64-unknown-linux-musl.tar.gz"
$sha256 = "1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599"
}
"macOS-ARM64" {
$archive = "ripgrep-$version-aarch64-apple-darwin.tar.gz"
$sha256 = "378e973289176ca0c6054054ee7f631a065874a352bf43f0fa60ef079b6ba715"
}
default { throw "Unsupported ripgrep runner: ${{ runner.os }}-${{ runner.arch }}" }
}

$download = Join-Path $env:RUNNER_TEMP $archive
Invoke-WebRequest -Uri "https://github.com/BurntSushi/ripgrep/releases/download/$version/$archive" -OutFile $download
$actual = (Get-FileHash -LiteralPath $download -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actual -ne $sha256) {
throw "ripgrep archive digest mismatch: expected $sha256, received $actual"
}

$toolRoot = Join-Path $env:RUNNER_TEMP "ripgrep-$version-${{ runner.os }}-${{ runner.arch }}"
$null = New-Item -ItemType Directory -Path $toolRoot
if ($archive.EndsWith(".zip", [StringComparison]::Ordinal)) {
Expand-Archive -LiteralPath $download -DestinationPath $toolRoot
} else {
tar -xzf $download -C $toolRoot
}

$executableName = if ($IsWindows) { "rg.exe" } else { "rg" }
$executables = @(Get-ChildItem -LiteralPath $toolRoot -Recurse -File -Filter $executableName)
if ($executables.Count -ne 1) {
throw "Expected exactly one $executableName in $archive, found $($executables.Count)"
}

$executable = $executables[0].FullName
Split-Path -Parent $executable | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
& $executable --version

- name: Cache bun install
uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('external/opencode/bun.lock') }}

# --ignore-scripts skips the root postinstall (a node-pty fixup); M4's scenarios create
# no pty, and skipping scripts keeps the install deterministic and fast.
# --ignore-scripts skips the root postinstall, which repairs the separate Node PTY
# implementation. Normal PTYs in this lane use Bun's bun-pty implementation.
- name: Install pinned server dependencies
run: bun install --frozen-lockfile --ignore-scripts
working-directory: external/opencode
Expand Down
27 changes: 22 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ Each released version links straight to its GitHub Release tag.

### ✨ New features

- **Refreshed to upstream `89f1943`.** Worktree operations now resolve their project through an
optional location query. Call create, list, remove, and refresh directly on `WorktreesClient`;
the project-bound `ProjectWorktreesClient` and its factory are gone, which nightly consumers will
meet as a compile break. Create's strategy and destination directory are both optional now, as
upstream declares them; remove retains its required JSON body.
The list operation also exposes its declared `WorktreeError` failure arm. Compaction completion
payloads now expose the optional model and provider state supplied by the upstream schema.
Project updates accept an optional canonical directory; execution interruption events include
the upstream inactivity reason.
- **`OpenCode.Sdk` — the typed client.** **135 of the 140 operations** in the pinned OpenAPI
snapshot are callable across **28 client families**: sessions, PTYs, persistent PTYs, shells,
events, MCP servers, integrations, projects, worktrees, workspaces, providers, language models,
Expand All @@ -23,8 +32,8 @@ Each released version links straight to its GitHub Release tag.
`EventRpc`, tried after the declared literal tags and before the unknown carrier, and
`UnknownEvent` refuses an `rpc.*` tag rather than absorbing it. Four operations arrived —
`v2.plugin.awaitActivation`, `v2.plugin.check`, and `v2.plugin.update` as
`PluginsClient.PostAwaitActivationAsync`, `PostCheckAsync`, and `PostUpdateAsync`, and
`v2.rpc.call` as `PostCallAsync` on the new `RpcClient` family. Upstream also removed and
`PluginsClient.AwaitPluginActivationAsync`, `CheckPluginUpdatesAsync`, and `UpdatePluginsAsync`,
and `v2.rpc.call` as `CallAsync` on the new `RpcClient` family. Upstream also removed and
reshaped the plugin types, which nightly consumers will meet as compile breaks: the
`plugin.added` event arm is gone, and `PluginAdded` and `PluginAddedData` with it; `IPluginInfo`
and its `PluginInfoActive`, `PluginInfoFailed`, and `UnknownPluginInfo` variants are replaced by
Expand All @@ -34,8 +43,11 @@ Each released version links straight to its GitHub Release tag.
`Updating`; and `PluginListResponse.Plugins` is an `IReadOnlyList<PluginInfo>`.
- **A standalone server launcher.** `OpenCodeServer.StartAsync()` starts, monitors, and stops a
private `opencode serve` child — generated lease credential, stdin-EOF ownership, bounded tree
termination — and `CreateClient()` hands back a client already bound to it. Real-process
lifecycle acceptance runs on Windows, Linux, and macOS.
termination — and `CreateClient()` hands back a client already bound to it. An optional
`OpenCodeServerOutput` collector, supplied through `OpenCodeServerOptions.Output`, retains a
bounded tail of the child's stdout and stderr for pull snapshots that report truncation, and it
stays readable after a failed start; `ProcessId` stays readable after disposal for the same
diagnostic use. Real-process lifecycle acceptance runs on Windows, Linux, and macOS.
- **Server-sent event streaming.** `EventsClient.SubscribeAsync` follows the global bus and
`SessionClient.GetLogAsync` follows one session's log, both as `IAsyncEnumerable<T>` of typed
frames over the same transport, decoration, and status walls as one-shot calls. A body cut
Expand Down Expand Up @@ -63,6 +75,11 @@ Each released version links straight to its GitHub Release tag.

### 🛠️ General

- **Breaking (nightly API):** `RpcClient.PostCallAsync` is now `CallAsync`;
`PluginsClient.PostAwaitActivationAsync`, `PostCheckAsync`, and `PostUpdateAsync` are now
`AwaitPluginActivationAsync`, `CheckPluginUpdatesAsync`, and `UpdatePluginsAsync`. Their
`OpenCodeRoutes` members use the corresponding names without `Async`; request/response types and
wire behavior are unchanged.
- **Target frameworks:** `netstandard2.0`, `net472`, `net8.0`, `net9.0`, `net10.0` — for both
packages. The suite runs on `net472` on Windows, real-process launcher tests included, and on
`net8.0`/`net9.0`/`net10.0` on all three OSes. `netstandard2.0` is a consumption target rather
Expand All @@ -73,7 +90,7 @@ Each released version links straight to its GitHub Release tag.
[`spec/SNAPSHOT.md`](spec/SNAPSHOT.md).
- **Generated output is committed and reviewed as source**, locked by a public-API baseline and
verified by regeneration, so a protocol refresh arrives as a readable diff.
- **Test suite:** 4,418 tests green on Windows — the fullest leg, and the only one that adds the
- **Test suite:** 5,010 tests green on Windows — the fullest leg, and the only one that adds the
`net472` assemblies. Linux and macOS run the same suite on the three modern targets.

### 📋 Important Notes
Expand Down
9 changes: 9 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ optional Workspace. Resolving one yields the Project it belongs to.
The repository root a Location resolves to, carrying its own id and canonical path. Sessions,
saved Permissions, and worktrees are scoped to it.

**Worktree**:
A managed local directory in a Project's worktree inventory. The API resolves that Project
through a requested Location; create, list, remove, and refresh are location-scoped operations.

**Worktree strategy**:
A server-registered strategy used to discover and create Worktrees. Creation may select one by
its string identifier; removal uses the recorded strategy. The SDK does not define a closed
strategy list or choose server defaults.

**Workspace**:
A separately provisioned place a Location can point into, created and destroyed through the API;
the optional second member of a Location.
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ thing missing is the first published package.
- ✅ **135 of 140 operations** callable, across 28 client families — sessions, PTYs, persistent
PTYs, shells, events, MCP servers, integrations, providers, permissions, credentials, VCS,
worktrees, websearch, RPC, and more
- ✅ **4,418 tests** green on Windows — the fullest leg, the only one that adds the `net472`
- ✅ **5,010 tests** green on Windows — the fullest leg, the only one that adds the `net472`
assemblies. Linux and macOS run the same suite on `net8.0`, `net9.0`, and `net10.0`
- ✅ **Server-sent event streams**, global and per-session, over the same transport as one-shot calls
- ✅ **PTY and persistent-PTY terminal sessions** through hand-written WebSocket doors
Expand All @@ -36,7 +36,7 @@ thing missing is the first published package.

**Versioning**: the SDK builds against an accepted OpenAPI snapshot, never a live branch. The exact
upstream commit and the refresh procedure live in [`spec/SNAPSHOT.md`](spec/SNAPSHOT.md); today's
pin is `48f24669` on upstream's `v2` branch.
pin is `89f1943d` on upstream's `v2` branch.

## 🚀 Platform Compatibility & Quality Status

Expand Down Expand Up @@ -178,7 +178,9 @@ keep the credentials in environment variables:

No ambient process, no endpoint to configure — the launcher starts a private `opencode serve`
child, mints its credential, and hands you a client bound to it. Disposing the server stops the
child.
child. To keep the child's output for diagnostics, pass an `OpenCodeServerOutput` collector in
`OpenCodeServerOptions.Output` and read its snapshot whenever you like — bounded, and still
readable when the start failed.

```csharp
using OpenCode.Sdk;
Expand Down Expand Up @@ -355,6 +357,12 @@ dotnet build --configuration Release
`external/` holds read-only upstream submodules used as protocol evidence and as the pinned-server
test fixture; `--recurse-submodules` is what makes the fixture-backed tests runnable.

Fixture-backed tests also require the Bun version named by `external/opencode/package.json`, the
pinned server dependencies (`bun install --frozen-lockfile --ignore-scripts` from
`external/opencode`), and ripgrep 15.1.0 on `PATH`. The simulated server deliberately denies
unregistered network access, so its `fs.find` test cannot use upstream's fallback download. CI
provisions the matching official ripgrep archive and verifies its SHA-256 digest before use.

### Sandbox Application

[`tests/OpenCode.Sdk.Sandbox`](tests/OpenCode.Sdk.Sandbox) is a committed playground that drives
Expand Down
Loading