From 74b80f197abb6b9637f6e28bee14cd4cdae951b2 Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Wed, 14 Oct 2026 14:36:28 +0000 Subject: [PATCH 1/3] chore: upgrade opencode to 2.0.29, openchamber to 2.3.2, effect to 4.0.4 Both releases are under pnpm's minimum release age, so the excludes move from the 2.0.27 opencode packages to 2.0.29 and add @openchamber/web and @openchamber/sdk 2.3.2 after reviewing their changelogs. Every other pinned tool, image, plugin, MCP package, and action is already at its latest release. Co-Authored-By: Claude Opus 5.5 --- Dockerfile | 4 +-- package.json | 6 ++-- pnpm-lock.yaml | 74 ++++++++++++++++++++++----------------------- pnpm-workspace.yaml | 40 ++++++++++++------------ 4 files changed, 63 insertions(+), 61 deletions(-) diff --git a/Dockerfile b/Dockerfile index 824a5d0..a27f12d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,8 +25,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* \ && ln -s /usr/bin/fdfind /usr/local/bin/fd -ARG OPENCODE_VERSION=2.0.27 -ARG OPENCHAMBER_VERSION=2.2.0 +ARG OPENCODE_VERSION=2.0.29 +ARG OPENCHAMBER_VERSION=2.3.2 ARG TYPESCRIPT_VERSION=7.0.2 ARG PNPM_VERSION=12.11.2 ARG TARGETARCH diff --git a/package.json b/package.json index 5eb53dc..db7b837 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "test": "node --test tests/*.test.mjs" }, "devDependencies": { - "@opencode/plugin": "2.0.27", - "@opencode/schema": "2.0.27", - "effect": "4.0.2", + "@opencode/plugin": "2.0.29", + "@opencode/schema": "2.0.29", + "effect": "4.0.4", "jsonc-parser": "3.3.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def5e74..c3a9ec2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,14 +167,14 @@ importers: .: devDependencies: '@opencode/plugin': - specifier: 2.0.27 - version: 2.0.27(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0)) + specifier: 2.0.29 + version: 2.0.29(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0)) '@opencode/schema': - specifier: 2.0.27 - version: 2.0.27 + specifier: 2.0.29 + version: 2.0.29 effect: - specifier: 4.0.2 - version: 4.0.2 + specifier: 4.0.4 + version: 4.0.4 jsonc-parser: specifier: 3.3.1 version: 3.3.1 @@ -400,11 +400,11 @@ packages: resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} engines: {node: ^20.17.0 || >=22.9.0} - '@opencode/ai@2.0.27': - resolution: {integrity: sha512-RiVtSBo1veffbDnrWo++04XdlcSEzqCAxo1TWDtG/5fZSjWLuiII6OT6Fo/N8ScU/awy9X3omfvun1aq4Lfttw==} + '@opencode/ai@2.0.29': + resolution: {integrity: sha512-Hu5U8FadWuI+do1ToBj9bhEL2wfP01JUaOWq8S6JaeHRaXcjvdsuCCFlGUtlA9PMn084gP4ewdInYPIv0B/98w==} - '@opencode/client@2.0.27': - resolution: {integrity: sha512-RqJodg+QsUgqqJVo+b2PGk2w96l7424nM4BijUSb49Iep9IyUfjDH3zfii4OGfCsmZkNCYK83Mgk9e9EeRA1iQ==} + '@opencode/client@2.0.29': + resolution: {integrity: sha512-fxjzqFAXEGiMDCzCfuKPyZbmrLQAwv0JQIi2rXfrjh3uE3UvItfqPjsiW1/sVPncJvu4UiRXHi+/0ksv3uHDPA==} peerDependencies: effect: 4.0.1 solid-js: '>=1.9.0' @@ -414,10 +414,10 @@ packages: solid-js: optional: true - '@opencode/plugin@2.0.27': - resolution: {integrity: sha512-TlK5PhHDCIieQEt/zjPlb/5BYbsyQnMSjXWhqL3rYjobKIauUNxdG+UCSCaLyQPhS9ab5zThg9v9tF6xiSyFyw==} + '@opencode/plugin@2.0.29': + resolution: {integrity: sha512-ZK1oUWrHl8u/9pt7lEJVDNKhhXQWMdHvaUuQ/96o9tDbbN+XaYTe4p6gt86F5bm0w9YgspE3TEX9WriI402Pbw==} peerDependencies: - '@opencode/theme': 2.0.27 + '@opencode/theme': 2.0.29 '@opentui/core': '>=0.5.17' '@opentui/solid': '>=0.5.17' solid-js: '>=1.9.0' @@ -431,14 +431,14 @@ packages: solid-js: optional: true - '@opencode/protocol@2.0.27': - resolution: {integrity: sha512-t1j+m5T3TBusHaL58t6gSSQQEFoi+qjVyQ0Eta/4mLeZLJDqSV8BNN6N93GFpV2MJ1iBpGmrH0C02n2Q+nDHfw==} + '@opencode/protocol@2.0.29': + resolution: {integrity: sha512-Eik6a2u2znmWdyPgpxCnMXJXySslG0Yj/XQzmM/Z/3pAxy0nMJnKLaFznnvXPNoPPII14L+CH7kUhBjLtOPGgQ==} - '@opencode/schema@2.0.27': - resolution: {integrity: sha512-BxGuiIFo503tW6UYxoBrrgjyczEvPiU3fDn45FxUHtmZgBC8kFE+ldI56ab8kV0Lu5C94ZKOniRb2bWqDu4tAg==} + '@opencode/schema@2.0.29': + resolution: {integrity: sha512-2qSyUGaeItugP3QujZrBhg2f4a4wdL9ab0m0fObHM+hlZ659YmWGzBRNI9qWR7At8AI9gnDCmgnnrQrx5/aprg==} - '@opencode/util@2.0.27': - resolution: {integrity: sha512-2TIEAJsX4UsyV9CHSk+/6jHWrEllgomgD0WQ8Ne+SN4xlmN9cGUvZo/Jgk0XrZZXSmADDOIER2HLcaI0tAr22A==} + '@opencode/util@2.0.29': + resolution: {integrity: sha512-o0xmrV5MFzB4hhapow4tSbStXAWF27598tDabqq+damJAuvPCBGorDBciosenXpOnNsEHhJm37l5KwOhb1+SNA==} '@opentelemetry/api-logs@0.219.0': resolution: {integrity: sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg==} @@ -780,8 +780,8 @@ packages: effect@4.0.1: resolution: {integrity: sha512-b1VlQG9g8fwxE5QnIZuPoOP/0MsqHJSRKxUijjoM80E5q95FXrX5yWtY5XI8G+GJsBQAVSDbLS458z9a++uVvw==} - effect@4.0.2: - resolution: {integrity: sha512-YeYnLqSFrL4+tCOXRrfNP03w8FILoF/wDl+ukraJAGzXrjcS383W7LAAvGBbLatEyktmLxRq+9xJinx7QzXW5A==} + effect@4.0.4: + resolution: {integrity: sha512-Sr50OeVSC3M5UkoUYUhFf3EULryhGaZZUf6OzDHTvaznOw7HRlxOKdF3/4nvQXHL22StOyMG3Cvo8Bt7xUp+Ng==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1697,10 +1697,10 @@ snapshots: node-gyp: 12.4.0 proc-log: 6.1.0 - '@opencode/ai@2.0.27': + '@opencode/ai@2.0.29': dependencies: '@aws-sdk/credential-providers': 3.1057.0 - '@opencode/schema': 2.0.27 + '@opencode/schema': 2.0.29 '@smithy/eventstream-codec': 4.2.14 '@smithy/util-utf8': 4.2.2 aws4fetch: 1.0.20 @@ -1709,21 +1709,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@opencode/client@2.0.27(effect@4.0.1)': + '@opencode/client@2.0.29(effect@4.0.1)': dependencies: - '@opencode/protocol': 2.0.27 - '@opencode/schema': 2.0.27 + '@opencode/protocol': 2.0.29 + '@opencode/schema': 2.0.29 optionalDependencies: effect: 4.0.1 - '@opencode/plugin@2.0.27(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0))': + '@opencode/plugin@2.0.29(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0))': dependencies: '@ai-sdk/provider': 3.0.8 - '@opencode/ai': 2.0.27 - '@opencode/client': 2.0.27(effect@4.0.1) - '@opencode/protocol': 2.0.27 - '@opencode/schema': 2.0.27 - '@opencode/util': 2.0.27(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0)) + '@opencode/ai': 2.0.29 + '@opencode/client': 2.0.29(effect@4.0.1) + '@opencode/protocol': 2.0.29 + '@opencode/schema': 2.0.29 + '@opencode/util': 2.0.29(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0)) effect: 4.0.1 transitivePeerDependencies: - '@opentelemetry/sdk-trace-web' @@ -1733,17 +1733,17 @@ snapshots: - supports-color - utf-8-validate - '@opencode/protocol@2.0.27': + '@opencode/protocol@2.0.29': dependencies: - '@opencode/schema': 2.0.27 + '@opencode/schema': 2.0.29 effect: 4.0.1 - '@opencode/schema@2.0.27': + '@opencode/schema@2.0.29': dependencies: '@standard-schema/spec': 1.1.0 effect: 4.0.1 - '@opencode/util@2.0.27(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0))': + '@opencode/util@2.0.29(@opentelemetry/semantic-conventions@1.43.0)(redis@6.3.0(@opentelemetry/api@1.9.0))': dependencies: '@effect/opentelemetry': 4.0.1(@opentelemetry/api-logs@0.219.0)(@opentelemetry/api@1.9.0)(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.219.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-node@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.43.0)(effect@4.0.1) '@effect/platform-node': 4.0.1(effect@4.0.1)(redis@6.3.0(@opentelemetry/api@1.9.0)) @@ -2100,7 +2100,7 @@ snapshots: effect@4.0.1: {} - effect@4.0.2: {} + effect@4.0.4: {} emoji-regex@8.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5fdb217..ebce58a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,21 +1,23 @@ minimumReleaseAgeStrict: true minimumReleaseAgeExclude: - - '@opencode/ai@2.0.27' - - '@opencode/cli-darwin-arm64@2.0.27' - - '@opencode/cli-darwin-x64-baseline@2.0.27' - - '@opencode/cli-darwin-x64@2.0.27' - - '@opencode/cli-linux-arm64-musl@2.0.27' - - '@opencode/cli-linux-arm64@2.0.27' - - '@opencode/cli-linux-x64-baseline-musl@2.0.27' - - '@opencode/cli-linux-x64-baseline@2.0.27' - - '@opencode/cli-linux-x64-musl@2.0.27' - - '@opencode/cli-linux-x64@2.0.27' - - '@opencode/cli-windows-arm64@2.0.27' - - '@opencode/cli-windows-x64-baseline@2.0.27' - - '@opencode/cli-windows-x64@2.0.27' - - '@opencode/cli@2.0.27' - - '@opencode/client@2.0.27' - - '@opencode/plugin@2.0.27' - - '@opencode/protocol@2.0.27' - - '@opencode/schema@2.0.27' - - '@opencode/util@2.0.27' + - '@openchamber/sdk@2.3.2' + - '@openchamber/web@2.3.2' + - '@opencode/ai@2.0.29' + - '@opencode/cli-darwin-arm64@2.0.29' + - '@opencode/cli-darwin-x64-baseline@2.0.29' + - '@opencode/cli-darwin-x64@2.0.29' + - '@opencode/cli-linux-arm64-musl@2.0.29' + - '@opencode/cli-linux-arm64@2.0.29' + - '@opencode/cli-linux-x64-baseline-musl@2.0.29' + - '@opencode/cli-linux-x64-baseline@2.0.29' + - '@opencode/cli-linux-x64-musl@2.0.29' + - '@opencode/cli-linux-x64@2.0.29' + - '@opencode/cli-windows-arm64@2.0.29' + - '@opencode/cli-windows-x64-baseline@2.0.29' + - '@opencode/cli-windows-x64@2.0.29' + - '@opencode/cli@2.0.29' + - '@opencode/client@2.0.29' + - '@opencode/plugin@2.0.29' + - '@opencode/protocol@2.0.29' + - '@opencode/schema@2.0.29' + - '@opencode/util@2.0.29' From 29c5baa976ba1ca342a81f607539702100d8826e Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Wed, 14 Oct 2026 14:36:29 +0000 Subject: [PATCH 2/3] chore: use the stable top-level policies key OpenCode 2.0.29 stabilizes top-level policies; experimental.policies still merges in but is the legacy spelling. The README now states that /etc/opencode/opencode.json overrides global and project config, which 2.0.29 documents. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- opencode.jsonc | 36 +++++++++++++++++------------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 818bd11..b72696d 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Grafana and Karakeep can connect before their credentials are validated; check a - **Build** is the default agent and can use all configured tools. **General** can implement and test; **Explore** is read-only. Both subagents may use Context7 but deny Grafana, Lunch Money, Karakeep, Notion, Robinhood, and further delegation. Build and General ask before `rm` commands whose text contains `.config`, `.local`, `HOME`, `~`, or `/data/.`, or ends in `/data`. Tool permissions do not sandbox shell access. - [Superpowers](https://github.com/obra/superpowers) supplies planning, debugging, testing, and review skills. The image also ships [`commit-push-pr` and `go-repo`](skills/). [`AGENTS.md`](AGENTS.md) requires repository edits in `/data/workspace/.worktrees//`. - [OpenCode Quota](https://github.com/slkiser/opencode-quota) provides `/quota` and `/quota_status`. -- Defaults and pinned plugins live in [`opencode.jsonc`](opencode.jsonc). The image converts it to `/etc/opencode/opencode.json`; project `opencode.json(c)` files can override settings. See the [V2 configuration guide](https://opencode.ai/v2/docs/config). +- Defaults and pinned plugins live in [`opencode.jsonc`](opencode.jsonc). The image converts it to `/etc/opencode/opencode.json`, which overrides matching settings in global and project `opencode.json(c)` files. See the [V2 configuration guide](https://opencode.ai/v2/docs/config). - Each startup copies the image's [`AGENTS.md`](AGENTS.md) to `/data/.config/opencode/AGENTS.md`, replacing local edits. Change instructions in this repository and rebuild. To replace the image defaults, install dependencies, edit `opencode.jsonc`, run `node scripts/build-config.mjs runtime.json`, and mount the output read-only at `/etc/opencode/opencode.json`. Restart the container after replacing it. diff --git a/opencode.jsonc b/opencode.jsonc index 25f1a04..93dbc80 100644 --- a/opencode.jsonc +++ b/opencode.jsonc @@ -128,23 +128,21 @@ "superpowers@git+https://github.com/obra/superpowers.git#bb92a77741419a4ab5f06e711a283343f1ada0c3", "@slkiser/opencode-quota@5.0.2" ], - "experimental": { - "policies": [ - { - "action": "provider.use", - "resource": "*", - "effect": "deny" - }, - { - "action": "provider.use", - "resource": "google", - "effect": "allow" - }, - { - "action": "provider.use", - "resource": "github-copilot", - "effect": "allow" - } - ] - } + "policies": [ + { + "action": "provider.use", + "resource": "*", + "effect": "deny" + }, + { + "action": "provider.use", + "resource": "google", + "effect": "allow" + }, + { + "action": "provider.use", + "resource": "github-copilot", + "effect": "allow" + } + ] } From 78431a3af50a783120fae0d5dfb24b97ee24014f Mon Sep 17 00:00:00 2001 From: Nat Welch Date: Wed, 14 Oct 2026 14:57:01 +0000 Subject: [PATCH 3/3] test: assert the provider policy hides other providers' models /api/provider lists only connected providers, so it is empty in CI either way. /api/model lists OpenCode's free models when provider.use is not enforced, so checking that every model belongs to google or github-copilot fails if the policies stop loading. Co-Authored-By: Claude Opus 5.5 --- scripts/test-router-integration.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/test-router-integration.mjs b/scripts/test-router-integration.mjs index 703ef7c..58a7355 100644 --- a/scripts/test-router-integration.mjs +++ b/scripts/test-router-integration.mjs @@ -132,6 +132,9 @@ try { assert.ok(data.some(server => server.name === name), `missing ${name}`); return true; }); + // Without credentials, a dropped provider policy shows up as OpenCode's free models. + const models = (await request("/api/model")).data; + assert.ok(models.every(m => ["google", "github-copilot"].includes(m.providerID)), `policies hide other providers' models: ${models.map(m => m.providerID)}`); session = (await request("/api/session", { title: "V2 integration test", agent: "build", location: { directory } })).data; await request(`/api/session/${session.id}/command`, { name: "quota_announcements", text: "" });