chore(opencode): qualify 2.0.16 with aligned clients and plugin (#772)

Align the server/UI client, bundled plugin and recommended runtime at 2.0.16 while retaining the demonstrated 2.0.7 minimum. Verify the real generated-client detailed errors without duplicate proxy prefixes or failed mutation replay; retain explicit installation and daemon restart boundaries.

Independent gatekeeper audited all 63 upstream commits and the full comparison, published packages, native consumers and targeted regressions: zero actionable findings. Native pruning/UI and historical migrations pass across Windows, Linux and macOS; minimum/current runtime contracts and dedicated Tauri jobs pass. Generic browser failures were independently reproduced on the exact 2.0.15 base and documented in the published review. Admin squash merge explicitly requested by the user.
This commit is contained in:
Pascal André 2026-09-24 20:42:08 +02:00 • committed by GitHub
parent 1693077731
commit 1eb42feaae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 198 additions and 218 deletions

View file

@ -18,7 +18,7 @@ description: |
- 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.15` together; the pruning plugin pins `@opencode/plugin@2.0.15`. 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.
- Server and UI pin the official `@opencode/client@2.0.16` together; the pruning plugin pins `@opencode/plugin@2.0.16`. 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.

View file

@ -20,7 +20,7 @@ Client-state V3 is a per-window envelope over the V2 content-addressed partition
| Owner | Responsibilities | Main paths |
|---|---|---|
| OpenCode V2 | Sessions, messages, permissions, Forms, files, session Shell/instructions, background Shells, interactive PTYs | pinned stable `@opencode/client@2.0.15` contract across server and UI |
| OpenCode V2 | Sessions, messages, permissions, Forms, files, session Shell/instructions, background Shells, interactive PTYs | pinned stable `@opencode/client@2.0.16` contract across server and UI |
| CodeNomad server | Shared service lifecycle, locations, proxy authorization, Git mutations, Yolo, auth, storage, speech, SSE multiplexing, Developer Mode bridge | `packages/server/src/` |
| CodeNomad UI | Generated Promise clients, state reconciliation, interaction and rendering | `packages/ui/src/` |
| Desktop hosts | Start CodeNomad and provide native OS integration | `packages/electron-app/`, `packages/tauri-app/` |

View file

@ -2,11 +2,11 @@
## Package
CodeNomad server and UI pin `@opencode/client@2.0.15`. 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.
CodeNomad server and UI pin `@opencode/client@2.0.16`. 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.
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.15. 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.
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.16. 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.
The Promise client preserves the full `baseUrl` path prefix. Forward its generated URL unchanged through `createInstanceFetch`; remove the proxy prefix only when classifying reads for scheduling. Declared API errors are `Error` instances retaining their native `_tag`/data fields; undeclared statuses remain `ClientError("UnexpectedStatus")`.
The Promise client preserves the full `baseUrl` path prefix. Forward its generated URL unchanged through `createInstanceFetch`; remove the proxy prefix only when classifying reads for scheduling. Declared API errors are `Error` instances retaining their native `_tag`/data fields. Client errors retain structured `reason`/`cause`; 2.0.16 adds status, content type or cause detail to `message`. Classify by structured fields rather than exact message text.
- Promise client: `import { OpenCode } from "@opencode/client"`
- Service authentication headers: `import { Service } from "@opencode/client/service"`

View file

@ -2,12 +2,12 @@
## Contract
- Server and UI pin `@opencode/client@2.0.15`. Manage the runtime CLI independently: startup checks authenticated loopback `/api/status`, then `/api/health`, then `/api/info`, advancing only on HTTP 404. All probes share the endpoint, credentials, 64 KiB response bound and absolute deadline; authentication, transport and malformed response failures do not trigger fallback. The shared transport maps canonical `server.info()` to the discovered route. Older services do not provide `paths.tmp`; never infer that path from the backend host. Discovery alone does not prove client/API compatibility. Review documentation, installed declarations and proxy/API parity whenever the client contract changes.
- Server and UI pin `@opencode/client@2.0.16`. Manage the runtime CLI independently: startup checks authenticated loopback `/api/status`, then `/api/health`, then `/api/info`, advancing only on HTTP 404. All probes share the endpoint, credentials, 64 KiB response bound and absolute deadline; authentication, transport and malformed response failures do not trigger fallback. The shared transport maps canonical `server.info()` to the discovered route. Older services do not provide `paths.tmp`; never infer that path from the backend host. Discovery alone does not prove client/API compatibility. Review documentation, installed declarations and proxy/API parity whenever the client contract changes.
- The package root is the generated zero-Effect Promise client. Use installed declarations, not current public `@opencode-ai/sdk` examples.
- 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.
- 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.15 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.
- 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.16 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

View file

@ -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 technical minimum is 2.0.7 for the native step-start timestamp, with 2.0.15 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.15 migration collapses workspace selectors to local directory scope while preserving session IDs. See `dev-docs/OPENCODE_V2_COMPATIBILITY.md` for current qualification and `dev-docs/OPENCODE_V2_POST_BETA.md` for the transition record.
The technical minimum is 2.0.7 for the native step-start timestamp, with 2.0.16 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 historical migrations collapse workspace selectors to local directory scope while preserving session IDs. See `dev-docs/OPENCODE_V2_COMPATIBILITY.md` for current qualification and `dev-docs/OPENCODE_V2_POST_BETA.md` for the transition record.
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.

View file

@ -15,7 +15,7 @@ runtimes. Keep detailed results in the change's PR and CI logs, not in per-versi
reports. Update this reference in place.
PR #696's corrected technical minimum is **2.0.7**, when native step-start events
gain the `data.started` field consumed by the current Solid reducer. **2.0.15**
gain the `data.started` field consumed by the current Solid reducer. **2.0.16**
is the recommended release-tested target, not the minimum. Unlisted versions,
including prereleases/custom labels/future majors, undergo authenticated contract
recognition rather than being refused solely for their label. Missing canonical
@ -74,7 +74,7 @@ it mocks HTTP APIs with `{}` and does not provide the current bounded history
query contract. Those tests use synthetic browser data, not a 2.0.14 daemon;
they are not native-runtime qualification evidence.
### Current stable target: 2.0.15
### 2.0.15 qualification baseline
Server/UI client, bundled plugin and recommendation advance together to **2.0.15**;
the technical minimum stays **2.0.7**. The upstream client now preserves a
@ -116,6 +116,47 @@ startup phase diagnostics for a recurrence; this passing rerun does not establis
the cause or a fix for the intermittent exit. Detailed results and remaining CI
gates belong in #752 rather than being inferred from other passing platforms.
### Current stable target: 2.0.16
Server/UI client, bundled plugin and recommendation advance together to **2.0.16**;
the evidence-based minimum remains **2.0.7**. Comparing the tagged
`packages/protocol/openapi.json` documents for 2.0.15 and 2.0.16 finds identical
paths and component schemas. Published client declarations differ only in the
optional `ClientError` detail; plugin declarations are unchanged.
Client failures now include status/content-type/cause detail in `message`
([#50929](https://github.com/anomalyco/opencode/pull/50929)). Structured `reason`,
`cause`, and declared native error fields remain available. Generated-client
regressions exercise the real proxy adapter and error formatter for HTTP 500,
wrong content type and transport errors, retaining one prefixed request with
credentials and no mutation replay. The 2.0.15 URL correction remains required.
The release also adds MCP resource tools and Code Mode operations, provider/media
fixes and runtime read/subagent improvements. These are upstream runtime changes;
this qualification does not expose additional proxy APIs or introduce new media,
provider-authentication or transcript UI workflows. The shared npm native-upgrade
boundary stays at 2.0.15; installer activation remains separate from daemon restart.
Windows qualification with Node 24.20.0 passes 98 server tests, 75 UI tests,
25 setup/auth-recovery browser scenarios and three tool-image browser scenarios,
plus server/UI/Electron typechecks and production server/UI/plugin builds. All
four historical migrations (2.0.3 and beta-19271 to 2.0.7 and 2.0.16) pass.
The 2.0.16 native pruning/UI suite passes through concurrency, persistence and
restart. The isolated npm upgrade advances the installed CLI to 2.0.16 while
preserving the running 2.0.15 daemon's version and PID.
Native suites pass on both 2.0.7 and 2.0.16 with the 2.0.16 dependencies:
automation/discovery, relay/proxy/ownership, worktrees, Forms/permissions,
history/pruning/concurrency, per-send environment, inclusive forks, idle/busy
side questions and blank-session cleanup. The Git-degraded scenario passes on
2.0.16 and on the isolated minimum-runtime rerun described below.
The minimum-runtime Git-degraded fixture initially retained the advisory after
PATH recovery; its isolated rerun passes without code changes. Preserve that
failure as unresolved intermittent evidence, not a corrected regression. The
independent gatekeeper reports zero actionable findings. Detailed native suite
completion and cross-platform qualification belong in the PR/CI; do not treat
unchanged wire types or a passing rerun as proof of untested behavior.
The audit and implementation record below is historical context, not a runtime
qualification matrix to maintain.

286
package-lock.json generated
View file

@ -4844,14 +4844,14 @@
}
},
"node_modules/@opencode/ai": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.15.tgz",
"integrity": "sha512-dY3HG6bp+UMJOi+m1nfzvQaTPpCSmMNDswAOLQq/MJghGrD54WrcEC4IhAQ1hJY7b/hhQYOzPAMeJsvF4aRYYQ==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.16.tgz",
"integrity": "sha512-QNOut+LUDNqMm2gqzr5xiOkj2xxD3JQX/DG3Z358aBJMgYZkDHY+ui20J7Jl7XdudQb4Jot2hIQ7t6q17BpOFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@aws-sdk/credential-providers": "3.1057.0",
"@opencode/schema": "2.0.15",
"@opencode/schema": "2.0.16",
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
"aws4fetch": "1.0.20",
@ -4860,13 +4860,13 @@
}
},
"node_modules/@opencode/client": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.15.tgz",
"integrity": "sha512-Lu3Pb6Z/lJ797VryQrsdqEgVBXjtYZWOxLNZKyBUDgXhO/GpzOpXi1H1Uo4g+qWrH8LwUISDBdxHHOWJy5HcqQ==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.16.tgz",
"integrity": "sha512-lEZM5NY8cKVwnlIJVHiERIaIMJfWfKTlQvKKkIz7NcNEwUXlrTXOA79kS5n17HtjYer1u1XiQIIggYtEYJXHRw==",
"license": "MIT",
"dependencies": {
"@opencode/protocol": "2.0.15",
"@opencode/schema": "2.0.15"
"@opencode/protocol": "2.0.16",
"@opencode/schema": "2.0.16"
},
"peerDependencies": {
"effect": "4.0.0-rc.112",
@ -4882,24 +4882,24 @@
}
},
"node_modules/@opencode/plugin": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.15.tgz",
"integrity": "sha512-TsSXd2g/433De7ViY+5d/tlLtkZCSmYKo8ScnhtcmPVoM/U5ptQDFXEXx5psTlAfkKcISJh+5vMSOe4u3uCqUQ==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.16.tgz",
"integrity": "sha512-Ir+7fJNUqo81dfzLAcQ8cZVLREbUxx/jbbiPQFR6wCjKWdJXLtMWJIAnYnYxBLf4Im7GcQQfdnlsG5xT9NYNrQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ai-sdk/provider": "3.0.8",
"@opencode/ai": "2.0.15",
"@opencode/client": "2.0.15",
"@opencode/protocol": "2.0.15",
"@opencode/schema": "2.0.15",
"@opencode/util": "2.0.15",
"@opencode/ai": "2.0.16",
"@opencode/client": "2.0.16",
"@opencode/protocol": "2.0.16",
"@opencode/schema": "2.0.16",
"@opencode/util": "2.0.16",
"@standard-schema/spec": "1.1.0",
"effect": "4.0.0-rc.112",
"zod": "4.1.8"
},
"peerDependencies": {
"@opencode/theme": "2.0.15",
"@opencode/theme": "2.0.16",
"@opentui/core": ">=0.5.10",
"@opentui/solid": ">=0.5.10",
"solid-js": ">=1.9.0"
@ -4930,19 +4930,19 @@
}
},
"node_modules/@opencode/protocol": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.15.tgz",
"integrity": "sha512-fFi3VYdgNQzT4TswfKGul7DzseOyhA4b/hE/elUe3xxm5iki4tP/pjT8CU3rRWX8sK/75ICr36NAUhSayFtJuw==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.16.tgz",
"integrity": "sha512-TQQOXLngSH4D9Z/B6DkyioFsHyApaczjU9EdD5BJ0UtwihKs3iT5mLQlicctKzcWomNoCqIM45XEjBv+WrSynA==",
"license": "MIT",
"dependencies": {
"@opencode/schema": "2.0.15",
"@opencode/schema": "2.0.16",
"effect": "4.0.0-rc.112"
}
},
"node_modules/@opencode/schema": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.15.tgz",
"integrity": "sha512-D0JDwF4uTNmFA2MyiSnZ5DlT/t2PF1EEwVbRPulB/Ppcss/z5Fcn6VybHrapki2bKDVQu1Mnn8zBW3G6KP5I6g==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.16.tgz",
"integrity": "sha512-MXENqPxsEzv77AAQDZsyYM4kSqUSApGEKD3xQ6kDDDvm5f5SHMffcNQ+uJXbh7rFo7Fm7nzdvoDHeZwiXm2UvQ==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "1.1.0",
@ -4950,9 +4950,9 @@
}
},
"node_modules/@opencode/util": {
"version": "2.0.15",
"resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.15.tgz",
"integrity": "sha512-s0Z+2rxb+2Tif+ZV1EvTiGQ0tRiemIZq3WOOjBw2XPBSBt2Sfv9rWXJhTzSpkOZucfTyymuIVXslXPGFF7aaQA==",
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.16.tgz",
"integrity": "sha512-VfY65xppJgSQSXT3x5hH3jM6e5Z08pYEaRA3sKv1K7I8jIFNma8IGZXSoYWaSXPnDt1svxd1sZ9b7tA1RRhOGw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4962,10 +4962,14 @@
"@npmcli/arborist": "9.4.0",
"@npmcli/config": "10.8.1",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/context-async-hooks": "2.6.1",
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
"@opentelemetry/sdk-trace-base": "2.6.1",
"@opentelemetry/sdk-trace-node": "2.6.1",
"@opentelemetry/api-logs": "0.219.0",
"@opentelemetry/context-async-hooks": "2.8.0",
"@opentelemetry/exporter-trace-otlp-http": "0.219.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/sdk-logs": "0.219.0",
"@opentelemetry/sdk-metrics": "2.8.0",
"@opentelemetry/sdk-trace-base": "2.8.0",
"@opentelemetry/sdk-trace-node": "2.8.0",
"cross-spawn": "7.0.6",
"effect": "4.0.0-rc.112",
"glob": "13.0.5",
@ -5108,9 +5112,9 @@
}
},
"node_modules/@opentelemetry/api-logs": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz",
"integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==",
"version": "0.219.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.219.0.tgz",
"integrity": "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
@ -5122,9 +5126,9 @@
}
},
"node_modules/@opentelemetry/context-async-hooks": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.1.tgz",
"integrity": "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.8.0.tgz",
"integrity": "sha512-/3FIraneMcng67SUJCxvyInk/oxzwsxyadufk0wwfOBLf5wqtAGX4MoQASwSbndBPeARzBryUM9Azr5kHIdWLw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@ -5135,9 +5139,9 @@
}
},
"node_modules/@opentelemetry/core": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz",
"integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz",
"integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -5151,17 +5155,17 @@
}
},
"node_modules/@opentelemetry/exporter-trace-otlp-http": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.214.0.tgz",
"integrity": "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw==",
"version": "0.219.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.219.0.tgz",
"integrity": "sha512-9t6SvBXXBEjOBcIzgozvBbd3jWrv3Gt3ngGhl1fhdZ/zRc7oZDVOFEqbi2zlBpW9BXhgDMKv422J0DL/3iQWfw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.6.1",
"@opentelemetry/otlp-exporter-base": "0.214.0",
"@opentelemetry/otlp-transformer": "0.214.0",
"@opentelemetry/resources": "2.6.1",
"@opentelemetry/sdk-trace-base": "2.6.1"
"@opentelemetry/core": "2.8.0",
"@opentelemetry/otlp-exporter-base": "0.219.0",
"@opentelemetry/otlp-transformer": "0.219.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/sdk-trace-base": "2.8.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
@ -5171,14 +5175,14 @@
}
},
"node_modules/@opentelemetry/otlp-exporter-base": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.214.0.tgz",
"integrity": "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg==",
"version": "0.219.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.219.0.tgz",
"integrity": "sha512-zvIxQX/AZUVKDU+hCuYx+7UkiP7GRdnk1ZbFQRYzHvYp47cAWR4j3IhoPhV9KaeXEv2xdGq3IA6PnpzDmLcmSA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.6.1",
"@opentelemetry/otlp-transformer": "0.214.0"
"@opentelemetry/core": "2.8.0",
"@opentelemetry/otlp-transformer": "0.219.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
@ -5188,19 +5192,18 @@
}
},
"node_modules/@opentelemetry/otlp-transformer": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.214.0.tgz",
"integrity": "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w==",
"version": "0.219.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.219.0.tgz",
"integrity": "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api-logs": "0.214.0",
"@opentelemetry/core": "2.6.1",
"@opentelemetry/resources": "2.6.1",
"@opentelemetry/sdk-logs": "0.214.0",
"@opentelemetry/sdk-metrics": "2.6.1",
"@opentelemetry/sdk-trace-base": "2.6.1",
"protobufjs": "^7.0.0"
"@opentelemetry/api-logs": "0.219.0",
"@opentelemetry/core": "2.8.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/sdk-logs": "0.219.0",
"@opentelemetry/sdk-metrics": "2.8.0",
"@opentelemetry/sdk-trace-base": "2.8.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
@ -5210,14 +5213,14 @@
}
},
"node_modules/@opentelemetry/resources": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz",
"integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz",
"integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.6.1",
"@opentelemetry/core": "2.8.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
@ -5228,16 +5231,16 @@
}
},
"node_modules/@opentelemetry/sdk-logs": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz",
"integrity": "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==",
"version": "0.219.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.219.0.tgz",
"integrity": "sha512-s6lTKRakaPClvKoWHRChxnXjDMkM/TQ30ff78jN6EBGf7MI7VzANE5PU3f4z9qDUudWjvZjOLHG0rBnBKYvoXA==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/api-logs": "0.214.0",
"@opentelemetry/core": "2.6.1",
"@opentelemetry/resources": "2.6.1",
"@opentelemetry/api-logs": "0.219.0",
"@opentelemetry/core": "2.8.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
@ -5248,15 +5251,15 @@
}
},
"node_modules/@opentelemetry/sdk-metrics": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz",
"integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz",
"integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.6.1",
"@opentelemetry/resources": "2.6.1"
"@opentelemetry/core": "2.8.0",
"@opentelemetry/resources": "2.8.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
@ -5266,15 +5269,15 @@
}
},
"node_modules/@opentelemetry/sdk-trace-base": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz",
"integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz",
"integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.6.1",
"@opentelemetry/resources": "2.6.1",
"@opentelemetry/core": "2.8.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
@ -5285,16 +5288,16 @@
}
},
"node_modules/@opentelemetry/sdk-trace-node": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.1.tgz",
"integrity": "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.8.0.tgz",
"integrity": "sha512-nZt9OGufioAc3AfoLTqA9bsAeaMJAictYDdI2VcNQ+PmT+3rfKjAZDZvgPfd8VPX0O5Bw1hdQF6kDK8VSpZiWg==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/context-async-hooks": "2.6.1",
"@opentelemetry/core": "2.6.1",
"@opentelemetry/sdk-trace-base": "2.6.1"
"@opentelemetry/context-async-hooks": "2.8.0",
"@opentelemetry/core": "2.8.0",
"@opentelemetry/sdk-trace-base": "2.8.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
@ -5340,72 +5343,6 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@quansync/fs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz",
@ -12252,13 +12189,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@ -14219,30 +14149,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/protobufjs": {
"version": "7.6.6",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
"integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
"dev": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.5",
"@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.1",
"@types/node": ">=13.7.0",
"long": "^5.3.2"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -18560,7 +18466,7 @@
"@fastify/cors": "^8.5.0",
"@fastify/reply-from": "^9.8.0",
"@fastify/static": "^7.0.4",
"@opencode/client": "2.0.15",
"@opencode/client": "2.0.16",
"commander": "^12.1.0",
"fastify": "^4.28.1",
"fuzzysort": "^2.0.4",
@ -18576,7 +18482,7 @@
"codenomad": "dist/bin.js"
},
"devDependencies": {
"@opencode/plugin": "2.0.15",
"@opencode/plugin": "2.0.16",
"@types/node-forge": "^1.3.14",
"@types/yauzl": "^2.10.0",
"cross-env": "^7.0.3",
@ -18616,7 +18522,7 @@
"dependencies": {
"@git-diff-view/solid": "^0.0.8",
"@kobalte/core": "0.13.11",
"@opencode/client": "2.0.15",
"@opencode/client": "2.0.16",
"@solidjs/router": "^0.13.0",
"@suid/icons-material": "^0.9.0",
"@suid/material": "^0.19.0",

View file

@ -30,7 +30,7 @@
"@fastify/cors": "^8.5.0",
"@fastify/reply-from": "^9.8.0",
"@fastify/static": "^7.0.4",
"@opencode/client": "2.0.15",
"@opencode/client": "2.0.16",
"commander": "^12.1.0",
"fastify": "^4.28.1",
"fuzzysort": "^2.0.4",
@ -44,7 +44,7 @@
},
"devDependencies": {
"esbuild": "^0.25.12",
"@opencode/plugin": "2.0.15",
"@opencode/plugin": "2.0.16",
"@types/node-forge": "^1.3.14",
"@types/yauzl": "^2.10.0",
"cross-env": "^7.0.3",

View file

@ -277,29 +277,29 @@ test("beta version comparison remains numeric", () => {
assert.equal(compareOpenCodeVersionStrings("0.0.0-beta-10000", "0.0.0-beta-9999") > 0, true)
})
test("2.0.7 through 2.0.14 remain usable; recommendation only offers an optional update", async () => {
for (const version of ["2.0.7", "2.0.8", "2.0.9", "2.0.10", "2.0.11", "2.0.12", "2.0.13", "2.0.14", "2.0.15"]) {
test("2.0.7 through 2.0.16 remain usable; recommendation only offers an optional update", async () => {
for (const version of ["2.0.7", "2.0.8", "2.0.9", "2.0.10", "2.0.11", "2.0.12", "2.0.13", "2.0.14", "2.0.15", "2.0.16"]) {
const endpoint: Endpoint = { url: "http://127.0.0.1:9876" }
rememberRuntime(endpoint, { version, pid: 123, discovery: "info" })
const service = new OpenCodeUpdateService(deps({
probeBinary: () => ({ valid: true, version }), resolveLatestVersion: async () => "2.0.15",
probeBinary: () => ({ valid: true, version }), resolveLatestVersion: async () => "2.0.16",
lifecycle: async () => ({ discover: async () => endpoint, ensure: async () => { throw new Error("must retain daemon") } }),
}))
const status = await service.start()
assert.equal(status.minimumVersion, "2.0.7")
assert.equal(status.recommendedVersion, "2.0.15")
assert.equal(status.recommendedVersion, "2.0.16")
assert.equal(status.state, "ready")
assert.equal(status.serviceState, "ready")
assert.equal(status.versionAssessment, version === "2.0.15" ? "tested" : "untested", "only the current recommendation is release-qualified")
assert.equal(status.versionAssessment, version === "2.0.16" ? "tested" : "untested", "only the current recommendation is release-qualified")
assert.equal(status.incompatibilityReason, undefined)
assert.equal(status.canUpgrade, version !== "2.0.15")
assert.equal(status.canUpgrade, version !== "2.0.16")
assert.equal(status.canRestart, false)
}
})
test("a current daemon remains usable through an older selected discovery CLI", async () => {
const endpoint: Endpoint = { url: "http://127.0.0.1:9876" }
rememberRuntime(endpoint, { version: "2.0.15", pid: 123, discovery: "info" })
rememberRuntime(endpoint, { version: "2.0.16", pid: 123, discovery: "info" })
const service = new OpenCodeUpdateService(deps({
probeBinary: () => ({ valid: true, version: "2.0.3" }),
lifecycle: async () => ({ discover: async () => endpoint, ensure: async () => { throw new Error("must retain daemon") } }),

View file

@ -7,8 +7,8 @@ import { OpenCodeSharedService } from "../workspaces/opencode-service"
test("native timestamp minimum is distinct from recommendation and unfamiliar version labels", () => {
assert.equal(MINIMUM_OPENCODE_VERSION, "2.0.7")
assert.equal(RECOMMENDED_OPENCODE_VERSION, "2.0.15")
for (const version of ["2.0.7", "2.0.8", "2.0.9", "2.0.10", "2.0.11", "2.0.12", "2.0.13", "2.0.14", "2.0.15", "2.1.0", "2.0.15-dev.1", "3.0.0", "custom-build"]) {
assert.equal(RECOMMENDED_OPENCODE_VERSION, "2.0.16")
for (const version of ["2.0.7", "2.0.8", "2.0.9", "2.0.10", "2.0.11", "2.0.12", "2.0.13", "2.0.14", "2.0.15", "2.0.16", "2.1.0", "2.0.16-dev.1", "3.0.0", "custom-build"]) {
assert.equal(isBelowOpenCodeMinimum(version), false, version)
}
for (const version of ["2.0.0", "2.0.4", "2.0.5", "2.0.6", "0.0.0-beta-19507"]) assert.equal(isBelowOpenCodeMinimum(version), true, version)

View file

@ -5,7 +5,7 @@
*/
export const MINIMUM_OPENCODE_VERSION = "2.0.7"
/** Qualification target, independent of the technically required minimum. */
export const RECOMMENDED_OPENCODE_VERSION = "2.0.15"
export const RECOMMENDED_OPENCODE_VERSION = "2.0.16"
export const OPENCODE_UPDATE_REQUIRED = "opencode_update_required"
export function isBelowOpenCodeMinimum(version: string): boolean {

View file

@ -17,7 +17,7 @@
"transaction.ts", "tui.ts", "tui-reload.ts"
],
"dependencies": {
"@opencode/plugin": "2.0.15",
"@opencode/plugin": "2.0.16",
"zod": "^3.25.76"
}
}

View file

@ -14,7 +14,7 @@
"dependencies": {
"@git-diff-view/solid": "^0.0.8",
"@kobalte/core": "0.13.11",
"@opencode/client": "2.0.15",
"@opencode/client": "2.0.16",
"@solidjs/router": "^0.13.0",
"@suid/icons-material": "^0.9.0",
"@suid/material": "^0.19.0",

View file

@ -14,10 +14,9 @@ export function getOpencodeErrorMessage(error: unknown, fallback: string): strin
const nested = extract(candidate.cause) ?? extract(candidate.error) ?? extract(candidate.body)
if (nested) return nested
// The generated client reports undeclared HTTP statuses as a bare reason
// (for example "UnexpectedStatus" with { status: 500 } as the cause),
// which hides the real failure in send dialogs. Surface the status code
// only when no deeper detail exists.
// Classify undeclared HTTP statuses by structured reason/cause, independently
// of the client's message formatting (2.0.16 adds detail to that message).
// Surface the status code only when no deeper detail exists.
if (candidate.reason === "UnexpectedStatus") {
const status = Number(candidate.cause?.status)
if (Number.isInteger(status) && status >= 100 && status <= 599) return `Unexpected status ${status}`

View file

@ -4,6 +4,7 @@ import { OpenCode } from "@opencode/client"
import { buildInstanceBaseUrl, createInstanceFetch, sdkManager } from "./sdk-manager.ts"
import { tGlobal } from "./i18n"
import { getOpencodeErrorMessage } from "./opencode-api"
afterEach(() => {
sdkManager.destroyClientsForInstance("instance-a")
@ -11,6 +12,39 @@ afterEach(() => {
})
describe("SDKManager", () => {
it("keeps detailed client failures actionable without replaying a mutation", async () => {
const original = globalThis.fetch
const baseUrl = buildInstanceBaseUrl("/workspaces/first/instance", "https://codenomad.test/tenant")
const client = OpenCode.make({ baseUrl, fetch: createInstanceFetch(baseUrl) })
const cases = [
{ response: () => new Response("private upstream body", { status: 500 }), reason: "UnexpectedStatus",
message: "UnexpectedStatus: 500", display: "Unexpected status 500" },
{ response: () => new Response("private HTML", { headers: { "content-type": "text/html" } }), reason: "UnsupportedContentType",
message: "UnsupportedContentType: text/html", display: "UnsupportedContentType: text/html" },
{ response: () => { throw new TypeError("Fixture connection refused") }, reason: "Transport",
message: "Transport: Fixture connection refused", display: "Fixture connection refused" },
]
try {
for (const fixture of cases) {
let calls = 0
globalThis.fetch = async (input, init) => {
calls++
assert.equal(String(input), `${baseUrl}api/session/owned/prompt`)
assert.equal(init?.method, "POST")
assert.equal(init?.credentials, "include")
return fixture.response()
}
await assert.rejects(client.session.prompt({ sessionID: "owned", text: "fixture" }), error => {
assert.ok(error instanceof Error)
assert.equal((error as Error & { reason: string }).reason, fixture.reason)
assert.equal(error.message, fixture.message)
assert.equal(getOpencodeErrorMessage(error, "fallback"), fixture.display)
return true
})
assert.equal(calls, 1)
}
} finally { globalThis.fetch = original }
})
it("preserves the generated client's proxy prefix exactly once for instructions and declared errors", async () => {
const original = globalThis.fetch
const requests: string[] = []