Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changesets

Every user-visible Redcode change includes a Changesets-compatible Markdown file. The release workflow consumes these entries into one Version PR; merging that PR creates the immutable release tag.

```markdown
---
"opencode": patch
---

Describe the user-visible change.
```

Use `patch`, `minor`, or `major` according to the public impact. The Version PR is the only writer of `package.json` versions.
15 changes: 15 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": {
"version": true,
"tag": false
}
}
5 changes: 5 additions & 0 deletions .changeset/fix-default-model-acp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"opencode": patch
---

Fix the ACP default model selection so a configured `model` is honored even when its provider has not finished loading yet. Previously, `defaultModelFromConfig` would skip the configured model when the provider lookup failed and fall back to the built-in `opencode` provider, snapping the footer back to big-pickle whenever sessions switched modes (build → plan → build) or the directory was re-evaluated. The configured model now always wins; any fallback is computed from the connected providers.
8 changes: 8 additions & 0 deletions .changeset/fix-flaky-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"opencode": patch
---

Reduce CI flake from slow `bun run` startup in subprocess tests and an `active`-marker race in the flock stress test.

- `packages/opencode/test/lib/cli-process.ts` — prefer the prebuilt `redcode` binary (`dist/redcode-linux-x64/bin/redcode`) over `bun run --conditions=browser src/index.ts` when the binary is present. Cuts subprocess startup from ~20s to ~5s and keeps the run-process tests comfortably under their 30s `timeoutMs` even when many tests run concurrently.
- `packages/core/test/util/effect-flock.test.ts` — drop the `active` marker from the mutual-exclusion stress test. The marker sits outside the lock directory, so its `wx` create races between a holder's `fs.rm(active)` and the next holder's `fs.writeFile(active)`; on Windows the race window is wide enough to produce intermittent non-zero exits even though the flock itself is correct. The serialized work + `done.log` line count are sufficient to prove mutual exclusion.
88 changes: 73 additions & 15 deletions .github/workflows/red-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
inputs:
tag:
description: "Existing draft Redcode tag to reconcile, e.g. v0.1.1"
required: true
type: string

permissions:
contents: write
Expand All @@ -25,52 +31,104 @@ jobs:
token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
fetch-tags: true
ref: ${{ inputs.tag || github.ref }}
- name: Resolve release tag
id: release
env:
INPUT_TAG: ${{ inputs.tag }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
tag="${INPUT_TAG:-$REF_NAME}"
if ! [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::release tag must match v<major>.<minor>.<patch>"
exit 1
fi
git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
- uses: ./.github/actions/setup-bun
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Verify tag contract
env:
TAG: ${{ github.ref_name }}
TAG: ${{ steps.release.outputs.tag }}
run: bun script/red-version.ts "${TAG#v}" --check
- name: Create draft GitHub Release
- name: Create or resume draft GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release view "${{ github.ref_name }}" >/dev/null 2>&1 || gh release create "${{ github.ref_name }}" --verify-tag --draft --generate-notes --title "Redcode ${{ github.ref_name }}"
TAG: ${{ steps.release.outputs.tag }}
run: |
set -euo pipefail
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" --verify-tag --draft --generate-notes --title "Redcode $TAG"
exit 0
fi
if [ "$(gh release view "$TAG" --json isDraft --jq .isDraft)" != true ]; then
echo "::error::released tags are immutable; publish a new patch version instead of replacing $TAG"
exit 1
fi
echo "resuming existing draft $TAG"
- name: Build and upload native binaries
env:
GH_REPO: reddb-io/redcode
GH_TOKEN: ${{ github.token }}
OPENCODE_RELEASE: "1"
run: OPENCODE_VERSION="${GITHUB_REF_NAME#v}" ./packages/opencode/script/build.ts
VERSION: ${{ steps.release.outputs.version }}
run: OPENCODE_VERSION="$VERSION" ./packages/opencode/script/build.ts
- name: Normalize native npm metadata
run: |
bun -e '
import path from "path"
const root = "packages/opencode/dist"
const files = Array.from(new Bun.Glob("*/package.json").scanSync({ cwd: root }))
if (files.length === 0) throw new Error("no native package manifests were built")
await Promise.all(files.map(async (file) => {
const target = path.join(root, file)
const manifest = await Bun.file(target).json()
await Bun.write(target, `${JSON.stringify({ ...manifest, repository: { type: "git", url: "https://github.com/reddb-io/redcode" } }, null, 2)}\n`)
}))
'
- name: Publish native packages and @reddb-io/redcode
env:
NPM_CONFIG_PROVENANCE: "true"
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
if [ -z "${NODE_AUTH_TOKEN:-}" ]; then
echo "::error::NPM_TOKEN secret absent — cannot publish @reddb-io/redcode@${version}"
echo "::error::NPM_TOKEN secret absent — cannot publish @reddb-io/redcode@${VERSION}"
exit 1
fi
npm config set //registry.npmjs.org/:_authToken "${NODE_AUTH_TOKEN}"
OPENCODE_VERSION="$version" ./packages/opencode/script/publish.ts
OPENCODE_VERSION="$VERSION" ./packages/opencode/script/publish.ts
- name: Smoke the published package
env:
VERSION: ${{ github.ref_name }}
VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
root="$(mktemp -d)"
trap 'rm -rf "$root"' EXIT
version="${VERSION#v}"
for attempt in 1 2 3 4 5; do
if npm install --prefix "$root" --no-audit --no-fund "@reddb-io/redcode@${version}"; then break; fi
if [ "$attempt" = 5 ]; then exit 1; fi
sleep $((attempt * 5))
export npm_config_cache="$root/npm-cache"
# npm read replicas can lag a successful publish by several minutes.
# Ten attempts at attempt*15s provide roughly eleven minutes for the
# exact public package to become installable before the release ships.
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if npm install --prefix "$root" --no-audit --no-fund "@reddb-io/redcode@${VERSION}"; then
"$root/node_modules/.bin/redcode" --version | grep -Fx "$VERSION"
exit 0
fi
if [ "$attempt" -eq 10 ]; then
echo "::error::registry did not serve @reddb-io/redcode@${VERSION} after publish"
exit 1
fi
echo "registry smoke attempt ${attempt}/10 not yet resolvable; sleeping ${attempt}*15s"
sleep $((attempt * 15))
done
"$root/node_modules/.bin/redcode" --version | grep -Fx "$version"
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release edit "${{ github.ref_name }}" --draft=false --latest
TAG: ${{ steps.release.outputs.tag }}
run: gh release edit "$TAG" --draft=false --latest
85 changes: 18 additions & 67 deletions .github/workflows/red-release.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
name: red-release

on:
workflow_dispatch:
inputs:
version:
description: "Exact Redcode semver to propose"
required: true
type: string
pull_request:
branches: [dev]
types: [closed]
push:
branches: [main]

permissions: {}

concurrency:
group: red-release
cancel-in-progress: false

jobs:
version-pr:
if: github.event_name == 'workflow_dispatch' && github.repository == 'reddb-io/redcode'
version:
if: github.repository == 'reddb-io/redcode'
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -25,64 +22,18 @@ jobs:
with:
token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
ref: dev
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version-file: package.json
- name: Validate requested version
env:
VERSION: ${{ inputs.version }}
run: |
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::version must be exact semver (major.minor.patch)"
exit 1
fi
node -e 'const requested = process.env.VERSION.split(".").map(Number); const currentVersion = require("./package.json").version; const current = currentVersion.split(".").map(Number); const changed = requested.findIndex((value, index) => value !== current[index]); if (changed === -1 || requested[changed] < current[changed]) { console.error(`requested ${process.env.VERSION} must be newer than ${currentVersion}`); process.exit(1) }'
- name: Update version surfaces
run: bun script/red-version.ts "${{ inputs.version }}"
- name: Open or refresh Version PR
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
VERSION: ${{ inputs.version }}
- uses: ./.github/actions/setup-bun
- name: Configure git identity
run: |
git config user.name "redcode-release[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B red-version-pr
git add package.json packages/opencode/package.json
git commit -m "chore(release): ${VERSION}"
HUSKY=0 git push --force-with-lease origin HEAD:red-version-pr
number="$(gh pr list --head red-version-pr --state open --json number --jq '.[0].number')"
if [ -n "$number" ]; then
gh pr edit "$number" --title "chore(release): ${VERSION}" --body "Publish Redcode ${VERSION} after this PR merges."
exit 0
fi
gh pr create --base dev --head red-version-pr --title "chore(release): ${VERSION}" --body "Publish Redcode ${VERSION} after this PR merges."

tag:
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'red-version-pr' && github.repository == 'reddb-io/redcode'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
token: ${{ secrets.RELEASE_PAT }}
fetch-depth: 0
ref: ${{ github.event.pull_request.merge_commit_sha }}
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
- name: Maintain Version PR or tag release
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d
with:
bun-version-file: package.json
- name: Tag the reviewed version
version: bun run release:version
publish: bun script/red-tag-release.ts
commit: "chore(release): version packages"
title: "chore(release): version packages"
createGithubReleases: false
env:
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
run: |
version="$(node -p "require('./package.json').version")"
bun script/red-version.ts "$version" --check
git config user.name "redcode-release[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git rev-parse "v${version}" >/dev/null 2>&1; then
test "$(git rev-list -n 1 "v${version}")" = "$MERGE_SHA"
exit 0
fi
git tag -a "v${version}" "$MERGE_SHA" -m "Redcode ${version}"
HUSKY=0 git push origin "v${version}"
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
2 changes: 1 addition & 1 deletion .github/workflows/red-workspace-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
merge_group:
push:
branches: [dev]
branches: [main]

permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/storybook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: storybook

on:
push:
branches: [dev]
branches: [main]
paths:
- ".github/workflows/storybook.yml"
- "package.json"
Expand All @@ -11,7 +11,7 @@ on:
- "packages/ui/**"
- "packages/session-ui/**"
pull_request:
branches: [dev]
branches: [main]
paths:
- ".github/workflows/storybook.yml"
- "package.json"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: typecheck

on:
push:
branches: [dev]
branches: [main]
pull_request:
branches: [dev]
branches: [main]
workflow_dispatch:

jobs:
Expand Down
50 changes: 50 additions & 0 deletions .red/adr/0001-hybrid-cordis-effect-plugin-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ADR 0001: Use Cordis as a reversible PluginV2 composition host

Status: Accepted
Date: 2026-08-14

## Context

RedCode needs runtime composition that is inspectable, reversible, and safe to reconfigure. DeepSeek Harness demonstrates an effective model based on Cordis plugin trees, ordered profiles, owned effects, and transactional configuration. RedCode already has Effect layers, Location-scoped services, and documented runtime dependency direction. Replacing that kernel would create a parallel service model and weaken existing SessionV2 invariants.

## Decision

Use a hybrid boundary:

- Effect remains the owner of application services, Location scoping, plugin child scopes, and resource cleanup.
- Cordis owns only the outer PluginV2 composition fibers.
- A Cordis fiber mounts through the public `PluginV2.Interface` and returns an awaited disposer.
- Named profiles are ordered and replace atomically. Failed replacement restores the previous profile.
- PluginV2 exposes a stable active inventory for diagnostics and composition dumps.
- Packages register their own executable checks through a scoped `RuntimeInvariant` service.
- Future declarative profiles and patches must resolve through one Schema-owned pure function shared by boot, dump, reload, and tests.

## Boundaries

- Cordis does not become a general service locator.
- Client runtime code does not import Core or Cordis.
- Dynamic model-authored plugins are not enabled.
- HMR and YAML profile loading are not enabled until transactional resolution, provenance, redaction, and rollback are specified.
- SessionV2 durable admission, delivery semantics, exact retries, and process-local execution coordination remain authoritative.

## Consequences

Plugin profile changes now have deterministic ownership, teardown, ordering, inventory, and rollback. RedCode can adopt more Harness-style composition without a high-risk runtime rewrite. The tradeoff is a deliberately small adapter layer and an exact runtime dependency on `@deepseek-ai/cordis` whose relied-on lifecycle contract must remain covered by tests.

## Validation

- Stable inventory across replacement.
- Awaited teardown when profiles change.
- Restoration of the previous profile when candidate activation fails.
- Scoped invariant registration and removal.
- Real Location boot activates the declared internal profile through Cordis and passes its runtime invariants.
- Effective Location service topology is derived from the same `LayerNode` graph used for compilation.
- Type checking in `packages/plugin`, `packages/core`, and `packages/app`.

## Implementation Status

Implemented. The initial host, inventory, rollback, and invariant registry landed before the production boot path used them. The follow-up runtime audit connected `PluginInternal` to a named Cordis profile, made boot await profile activation, ran invariants at readiness, and added derived `LayerNode` inspection. See the [runtime adoption audit](../researches/2026-08-14-deepseek-harness-2.md).

## Source

See [the DeepSeek Harness research report](../researches/2026-08-14-deepseek-harness.md).
Loading
Loading