Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
214 changes: 214 additions & 0 deletions .github/workflows/fff-platform-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
name: FFF native platform validation

on:
pull_request:
branches: [swxtch]
paths:
- ".github/workflows/fff-platform-validation.yml"
- ".github/actions/setup-bun/action.yml"
- "bun.lock"
- "flake.lock"
- "flake.nix"
- "nix/**"
- "package.json"
- "packages/core/src/filesystem/search.ts"
- "packages/core/src/flag/flag.ts"
- "packages/opencode/script/build.ts"
- "packages/opencode/test/fff-compiled.test.ts"
- "packages/opencode/test/fff-distribution.test.ts"
- "packages/script/src/fff-native.ts"
- "patches/@ff-labs%2Ffff-bun@0.9.4.patch"

permissions:
contents: read

concurrency:
group: fff-platform-validation-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1

jobs:
windows-fff:
name: Compiled FFF (${{ matrix.target }})
strategy:
fail-fast: false
matrix:
include:
- target: windows-x64
runner: windows-latest
- target: windows-arm64
runner: windows-11-arm
runs-on: ${{ matrix.runner }}
env:
PR_HEAD: ${{ github.event.pull_request.head.sha }}
TARGET: ${{ matrix.target }}
steps:
- name: Check out pull request merge head
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24"

- name: Resolve native Bun archive
id: bun
shell: bash
run: |
VERSION=$(node -p 'require("./package.json").packageManager.split("@")[1]')
case "$TARGET" in
windows-x64) ARCHIVE="bun-windows-x64-baseline.zip" ;;
windows-arm64) ARCHIVE="bun-windows-aarch64.zip" ;;
*) echo "Unsupported validation target: $TARGET" >&2; exit 1 ;;
esac
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${VERSION}/${ARCHIVE}" >> "$GITHUB_OUTPUT"

- name: Set up native Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ steps.bun.outputs.version }}
bun-download-url: ${{ steps.bun.outputs.url }}

- name: Install dependencies
shell: bash
run: bun install --frozen-lockfile

- name: Record runner and build identity
shell: bash
run: |
{
echo "## Windows compiled FFF validation"
echo "- PR head: $PR_HEAD"
echo "- Build head: $(git rev-parse HEAD)"
echo "- Target: $TARGET"
echo "- Runner: $RUNNER_NAME ($RUNNER_OS/$RUNNER_ARCH)"
echo "- Command: bun test --timeout 300000 test/fff-compiled.test.ts test/fff-distribution.test.ts"
} >> "$GITHUB_STEP_SUMMARY"

- name: Run compiled FFF distribution tests
working-directory: packages/opencode
shell: bash
run: bun test --timeout 300000 test/fff-compiled.test.ts test/fff-distribution.test.ts

nix-node-modules:
name: Nix node_modules (${{ matrix.system }})
strategy:
fail-fast: false
matrix:
include:
- system: x86_64-linux
runner: ubuntu-24.04
- system: aarch64-linux
runner: ubuntu-24.04-arm
- system: x86_64-darwin
runner: macos-15-intel
- system: aarch64-darwin
runner: macos-15
runs-on: ${{ matrix.runner }}
env:
PR_HEAD: ${{ github.event.pull_request.head.sha }}
SYSTEM: ${{ matrix.system }}
steps:
- name: Check out pull request merge head
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Initialize validation report
shell: bash
run: |
REPORT="$RUNNER_TEMP/nix-validation.md"
{
echo "## Nix validation"
echo "- PR head: $PR_HEAD"
echo "- Build head: $(git rev-parse HEAD)"
echo "- Nix system: $SYSTEM"
echo "- Runner: $RUNNER_NAME ($RUNNER_OS/$RUNNER_ARCH)"
} > "$REPORT"
echo "REPORT=$REPORT" >> "$GITHUB_ENV"

- name: Set up Nix
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34

- name: Compute native node_modules hash
shell: bash
run: |
set -euo pipefail
BUILD_LOG="$RUNNER_TEMP/nix-hash-build.log"
HASH_FILE="$RUNNER_TEMP/node-modules-hash.txt"
echo "- Hash command: nix build .#packages.$SYSTEM.node_modules_updater --no-link" >> "$REPORT"
nix build ".#packages.$SYSTEM.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true
HASH="$(python3 - "$BUILD_LOG" <<'PY'
import re
import sys

with open(sys.argv[1], encoding="utf-8", errors="replace") as stream:
values = re.findall(r"got:\s*(sha256-[A-Za-z0-9+/=]+)", stream.read())
print(values[-1] if values else "")
PY
)"
if [[ -z "$HASH" ]]; then
echo "- Computed hash: unavailable" >> "$REPORT"
echo "- Result: blocked; the Nix build did not report an output hash." >> "$REPORT"
exit 1
fi
printf '%s\n' "$HASH" > "$HASH_FILE"
echo "- Computed hash: $HASH" >> "$REPORT"
EXPECTED="$(python3 - "$SYSTEM" <<'PY'
import json
import sys

with open("nix/hashes.json", encoding="utf-8") as stream:
print(json.load(stream)["nodeModules"].get(sys.argv[1], ""))
PY
)"
echo "- Checked-in hash: ${EXPECTED:-missing}" >> "$REPORT"
if [[ "$HASH" != "$EXPECTED" ]]; then
echo "- Result: mismatch; update nix/hashes.json only from these native runner outputs, then rerun." >> "$REPORT"
exit 1
fi
echo "- Result: hash matches the checked-in value." >> "$REPORT"

- name: Build OpenCode with Nix
shell: bash
run: |
set -euo pipefail
BUILD_LOG="$RUNNER_TEMP/nix-opencode-build.log"
echo "- App build command: nix build .#packages.$SYSTEM.opencode --no-link --print-out-paths -L" >> "$REPORT"
nix build ".#packages.$SYSTEM.opencode" --no-link --print-out-paths -L 2>&1 | tee "$BUILD_LOG"
STORE_PATH="$(python3 - "$BUILD_LOG" <<'PY'
import re
import sys

with open(sys.argv[1], encoding="utf-8", errors="replace") as stream:
paths = re.findall(r"/nix/store/[0-9a-z]{32}-[^\s]+", stream.read())
print(paths[-1] if paths else "unresolved")
PY
)"
echo "- OpenCode output: $STORE_PATH" >> "$REPORT"
echo "- Result: Nix package build passed." >> "$REPORT"

- name: Publish Nix validation report
if: always()
shell: bash
run: |
if [[ -f "$REPORT" ]]; then cat "$REPORT" >> "$GITHUB_STEP_SUMMARY"; fi

- name: Retain Nix runner evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: nix-validation-${{ matrix.system }}
path: |
${{ runner.temp }}/nix-validation.md
${{ runner.temp }}/nix-hash-build.log
${{ runner.temp }}/nix-opencode-build.log
${{ runner.temp }}/node-modules-hash.txt
if-no-files-found: warn
retention-days: 7
21 changes: 21 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ jobs:
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true

- name: Sign Windows CLI native libraries
shell: pwsh
run: |
$files = @()
$files += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
$files += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
$files += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
if ($files.Count -ne 3) {
throw "Expected one FFF native library for each Windows target, found $($files.Count)"
}
$signPaths = @($files)
& "${{ github.workspace }}\script\sign-windows.ps1" @signPaths

- name: Verify Windows CLI signatures
shell: pwsh
run: |
Expand All @@ -180,6 +193,14 @@ jobs:
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\opencode.exe",
"${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\opencode.exe"
)
$native = @()
$native += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-arm64\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
$native += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
$native += Get-ChildItem "${{ github.workspace }}\packages\opencode\dist\opencode-windows-x64-baseline\bin\fff_c-*.dll" | Select-Object -ExpandProperty FullName
if ($native.Count -ne 3) {
throw "Expected one FFF native library for each Windows target, found $($native.Count)"
}
$files += $native

foreach ($file in $files) {
$sig = Get-AuthenticodeSignature $file
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

# Turborepo telemetry is on by default and POSTs anonymous usage data to
# Vercel. This fork runs only these two workflows, so opting out here is the
# whole surface. Raised by sol during the #22 review as the one outward-facing
# action still reachable from this fork.
# Turborepo telemetry is enabled by default and sends anonymous usage data to Vercel, so this workflow disables telemetry and tracking (review #22).
TURBO_TELEMETRY_DISABLED: 1
DO_NOT_TRACK: 1

Expand Down
12 changes: 12 additions & 0 deletions CHANGESET.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Newest first. Each entry names the upstream range brought in.

### Fork maintenance and CI

- **#150** The repository-read-only `fff-platform-validation.yml` builds and runs compiled FFF distributions on native Windows x64 and ARM64 runners, computes and checks Nix node_modules hashes on native Linux and macOS runners, retains runner evidence, and builds OpenCode with Nix after each hash matches. _Fork-only._
- **#98** `tests/run.sh` runs the unit suite from the repository root as CI's "Run unit tests" step does
(`GITHUB_ACTIONS=false bun turbo test`), returning its exit status, and fails when Bun or the installed workspace
is missing. The root `npm test` still refuses. _Fork-only._
Expand Down Expand Up @@ -139,6 +140,17 @@ Newest first. Each entry names the upstream range brought in.
_Partly upstream:_ upstream #50439 fixed the cycle and its version was taken in the 2026-09-22 sync; the
layer-node guard is fork-only.

### Native FFF distribution

- **#150** Use the root `packageManager` Bun pin for Nix-built OpenCode and desktop binaries (`package.json`,
`nix/bun.nix`, and `flake.nix`). _Fork-only (swxtchio/swx-opencode#97)._
- **#150** Patch `@ff-labs/fff-bun` (`patches/@ff-labs%2Ffff-bun@0.9.4.patch`) so compiled builds load the
content-addressed, target-specific FFF sidecar beside the executable; carry the sidecar through build, install,
postinstall, local-release, Docker, and generated release-package paths. _Fork-only (swxtchio/swx-opencode#97)._
- **#150** Enable native FFF by default in packaged Windows builds (`packages/core/src/flag/flag.ts`), preserve
`OPENCODE_DISABLE_FFF=1` as the ripgrep fallback, and report the expected sidecar path when loading is unavailable
(`packages/core/src/filesystem/search.ts`). _Fork-only (swxtchio/swx-opencode#97)._

### Features

- **#73** V1 server prompt queue core (swxtchio/swx-opencode#68): every V1 prompt except `noReply` is admitted to the durable `session_prompt_queue` table before it becomes a user message. `prompt`, `prompt_async` and `command` take `delivery: "steer" | "queue"`; the default steer reaches the next step as before, while `queue` waits until the run would go idle and then runs as its own turn, one per boundary in admission order. Steers wait behind a pending compaction; an abort or error parks pending prompts until the next admission or wake; `/session/:id/queue` lists, withdraws (404 `QueueItemNotPending` once delivered), restores and re-delivers them, with `session.queue.updated` carrying the full list. A sync prompt answers with the final reply of the turn its prompt joined, recorded for it where the loop ends that turn: steers share the reply of the turn they steered, and a queued prompt gets its own turn's; an editor's withdraw that wins answers it with 409 `PromptWithdrawn`. Deliberately, a compaction whose summary turn stopped on an abort or error is not retried, where upstream V1 retried it on the next prompt with that prompt as its parent (summarising it instead of answering it); if the context is still too large, the next overflow check starts a new compaction. Also closes the V1 lost wakeup where a prompt admitted as a run finished joined it and was never answered. Known ordering-only residuals: a `noReply` prompt and the synthetic message a subtask writes to have its output summarised are stored directly rather than through the queue, so they can land out of admission order relative to pending items. _Fork-only; the lost-wakeup fix is upstreamable._
Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 29 additions & 20 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,63 @@
"aarch64-darwin"
"x86_64-darwin"
];
forEachSystem = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
forEachSystem = f:
nixpkgs.lib.genAttrs systems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
bun = pkgs.callPackage ./nix/bun.nix { };
in
f pkgs bun
);
rev = self.shortRev or self.dirtyShortRev or "dirty";
in
{
devShells = forEachSystem (pkgs: {
devShells = forEachSystem (pkgs: bun: {
default = pkgs.mkShell {
packages = with pkgs; [
bun
nodejs_20
pkg-config
openssl
git
];
packages = [ bun pkgs.nodejs_20 pkgs.pkg-config pkgs.openssl pkgs.git ];
};
});

overlays = {
default =
final: _prev:
final: prev:
let
bun = final.callPackage ./nix/bun.nix { bun = prev.bun; };
node_modules = final.callPackage ./nix/node_modules.nix {
# This fixed-output derivation only materializes dependencies; binary builds use the fixed Bun.
bun = prev.bun;
inherit rev;
};
in
rec {
opencode = final.callPackage ./nix/opencode.nix {
inherit node_modules;
inherit bun node_modules;
};
in
{
inherit bun opencode;
opencode-desktop = final.callPackage ./nix/desktop.nix {
inherit opencode;
inherit bun opencode;
};
};
};

packages = forEachSystem (
pkgs:
pkgs: bun:
let
node_modules = pkgs.callPackage ./nix/node_modules.nix {
# This fixed-output derivation only materializes dependencies; binary builds use the fixed Bun.
bun = pkgs.bun;
inherit rev;
};
in
rec {
default = opencode;
opencode = pkgs.callPackage ./nix/opencode.nix {
inherit node_modules;
inherit bun node_modules;
};
in
{
default = opencode;
inherit opencode;
opencode-desktop = pkgs.callPackage ./nix/desktop.nix {
inherit opencode;
inherit bun opencode;
};
# Updater derivation with fakeHash - build fails and reveals correct hash
node_modules_updater = node_modules.override {
Expand Down
Loading
Loading