chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
@@ -142,7 +142,14 @@ jobs:
|
||||
actions: read
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
SOURCE_REF: ${{ github.event.inputs.source_ref || github.event.inputs.tag || github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout smoke tests
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ env.SOURCE_REF }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download binary archives
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
@@ -198,6 +205,7 @@ jobs:
|
||||
run: |
|
||||
"${{ steps.binary.outputs.binary }}" --help
|
||||
"${{ steps.binary.outputs.binary }}" --version
|
||||
node scripts/smoke-test-codemode-binary.mjs "${{ steps.binary.outputs.binary }}"
|
||||
|
||||
stage-github-release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -293,9 +301,17 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The pi-env daemon for every remote system @earendil-works/pi-env supports; the npm package ships them all.
|
||||
env-daemons:
|
||||
uses: ./.github/workflows/env-daemons.yml
|
||||
permissions:
|
||||
contents: read
|
||||
with:
|
||||
ref: ${{ github.event.inputs.source_ref || github.event.inputs.tag || github.ref_name }}
|
||||
|
||||
publish-npm:
|
||||
runs-on: ubuntu-latest
|
||||
needs: stage-github-release
|
||||
needs: [stage-github-release, env-daemons]
|
||||
environment: npm-publish
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -326,12 +342,33 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Download pi-env daemons
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: pi-env-*
|
||||
path: packages/env/bin
|
||||
|
||||
- name: Check pi-env daemons
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for name in linux-x64 linux-arm64 android-arm64 android-x64 darwin-arm64 darwin-x64; do
|
||||
test -s "packages/env/bin/pi-env-${name}/pi-env"
|
||||
done
|
||||
for name in windows-x64 windows-arm64; do
|
||||
test -s "packages/env/bin/pi-env-${name}/pi-env.exe"
|
||||
done
|
||||
chmod +x packages/env/bin/*/pi-env*
|
||||
ls -lR packages/env/bin
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check
|
||||
run: npm run check
|
||||
|
||||
- name: Build the pi-env daemon for tests
|
||||
run: npm run build:daemon --workspace packages/env
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
@@ -343,6 +380,9 @@ jobs:
|
||||
npm install -g npm@11.16.0 --ignore-scripts
|
||||
npm --version
|
||||
|
||||
- name: Validate npm tarball publication with provenance
|
||||
run: node scripts/publish.mjs --dry-run
|
||||
|
||||
- name: Publish npm packages
|
||||
run: node scripts/publish.mjs
|
||||
|
||||
|
||||
23
third_party/pi/.github/workflows/ci.yml
vendored
23
third_party/pi/.github/workflows/ci.yml
vendored
@@ -38,5 +38,28 @@ jobs:
|
||||
- name: Check
|
||||
run: npm run check
|
||||
|
||||
- name: Build the pi-env daemon
|
||||
run: npm run build:daemon --workspace packages/env
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
mcp-conformance:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
# Runs pi's MCP client from source against the pinned official conformance suite and fails on
|
||||
# regressions against packages/coding-agent/test/mcp-conformance/baseline.json.
|
||||
- name: MCP client conformance
|
||||
run: npm run test:mcp-conformance
|
||||
|
||||
89
third_party/pi/.github/workflows/env-daemons.yml
vendored
Normal file
89
third_party/pi/.github/workflows/env-daemons.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: pi-env daemons
|
||||
|
||||
# Builds the pi-env daemon for every remote system @earendil-works/pi-env supports, on native runners, as artifacts
|
||||
# named pi-env-<platform>-<arch>. Build Binaries calls this before publishing npm packages; the Environments workflow
|
||||
# calls it to test the release builds on every change.
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: Source ref to build
|
||||
required: false
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- { runner: ubuntu-latest, target: x86_64-unknown-linux-musl, name: linux-x64 }
|
||||
- { runner: ubuntu-24.04-arm, target: aarch64-unknown-linux-musl, name: linux-arm64 }
|
||||
- { runner: ubuntu-latest, target: aarch64-linux-android, name: android-arm64, android: aarch64-linux-android24 }
|
||||
- { runner: ubuntu-latest, target: x86_64-linux-android, name: android-x64, android: x86_64-linux-android24 }
|
||||
- { runner: macos-latest, target: aarch64-apple-darwin, name: darwin-arm64 }
|
||||
- { runner: macos-latest, target: x86_64-apple-darwin, name: darwin-x64 }
|
||||
- { runner: windows-latest, target: x86_64-pc-windows-msvc, name: windows-x64 }
|
||||
- { runner: windows-11-arm, target: aarch64-pc-windows-msvc, name: windows-arm64 }
|
||||
runs-on: ${{ matrix.runner }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: packages/env/daemon
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Add target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Configure the Android NDK linker
|
||||
if: matrix.android
|
||||
# A fixed NDK, installed when the runner image does not have it.
|
||||
env:
|
||||
NDK_VERSION: 27.2.12479018
|
||||
run: |
|
||||
ndk="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
if [ ! -d "$ndk" ]; then
|
||||
# `yes` ends with SIGPIPE once the licenses are accepted.
|
||||
{ yes || true; } | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null
|
||||
fi
|
||||
linker="$ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.android }}-clang"
|
||||
test -x "$linker"
|
||||
variable="CARGO_TARGET_$(echo '${{ matrix.target }}' | tr 'a-z-' 'A-Z_')_LINKER"
|
||||
echo "$variable=$linker" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build
|
||||
# Static C runtime on Windows, so the daemon needs no Visual C++ redistributable on the remote machine.
|
||||
env:
|
||||
RUSTFLAGS: ${{ contains(matrix.target, 'windows') && '-C target-feature=+crt-static' || '' }}
|
||||
run: cargo build --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Collect
|
||||
run: |
|
||||
suffix=""
|
||||
case "${{ matrix.target }}" in *windows*) suffix=".exe" ;; esac
|
||||
mkdir -p "$GITHUB_WORKSPACE/pi-env-${{ matrix.name }}"
|
||||
cp "target/${{ matrix.target }}/release/pi-env$suffix" "$GITHUB_WORKSPACE/pi-env-${{ matrix.name }}/pi-env$suffix"
|
||||
ls -l "$GITHUB_WORKSPACE/pi-env-${{ matrix.name }}"
|
||||
|
||||
- name: Run the native build
|
||||
if: ${{ !matrix.android && !(matrix.target == 'x86_64-apple-darwin') }}
|
||||
run: |
|
||||
suffix=""
|
||||
case "${{ matrix.target }}" in *windows*) suffix=".exe" ;; esac
|
||||
"$GITHUB_WORKSPACE/pi-env-${{ matrix.name }}/pi-env$suffix" --version
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pi-env-${{ matrix.name }}
|
||||
path: pi-env-${{ matrix.name }}
|
||||
if-no-files-found: error
|
||||
200
third_party/pi/.github/workflows/env.yml
vendored
Normal file
200
third_party/pi/.github/workflows/env.yml
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
name: Environments
|
||||
|
||||
# Execution environments on every OS: Durable's NodeExecutionEnv and pi-env's daemon and RemoteExecutionEnv, which
|
||||
# must give the same results. The main CI runs on Linux only. The release builds of the daemon are tested as shipped,
|
||||
# and so is the npm package's file list.
|
||||
on:
|
||||
push:
|
||||
branches: [main, "durable-env-*"]
|
||||
paths: ["packages/durable/src/env/**", "packages/durable/src/tools/**", "packages/durable/test/env-*", "packages/env/**", ".github/workflows/env.yml", ".github/workflows/env-daemons.yml"]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths: ["packages/durable/src/env/**", "packages/durable/src/tools/**", "packages/durable/test/env-*", "packages/env/**", ".github/workflows/env.yml", ".github/workflows/env-daemons.yml"]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: env-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
daemons:
|
||||
uses: ./.github/workflows/env-daemons.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
check:
|
||||
needs: daemons
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep
|
||||
sudo ln -s $(which fdfind) /usr/local/bin/fd
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Check
|
||||
run: npm run check
|
||||
|
||||
- name: Download the pi-env daemons
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: pi-env-*
|
||||
path: packages/env/bin
|
||||
|
||||
- name: Check the pi-env package contents
|
||||
working-directory: packages/env
|
||||
run: |
|
||||
npm pack --dry-run --json > "$RUNNER_TEMP/pack.json"
|
||||
node -e '
|
||||
const files = new Set(JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))[0].files.map((file) => file.path));
|
||||
const expected = ["dist/index.js", "dist/index.d.ts", "dist/ssh.js", "README.md", "CHANGELOG.md", "docs/protocol.md"];
|
||||
for (const name of ["linux-x64", "linux-arm64", "android-arm64", "android-x64", "darwin-arm64", "darwin-x64"]) expected.push(`bin/pi-env-${name}/pi-env`);
|
||||
for (const name of ["windows-x64", "windows-arm64"]) expected.push(`bin/pi-env-${name}/pi-env.exe`);
|
||||
const missing = expected.filter((file) => !files.has(file));
|
||||
const unexpected = [...files].filter((file) => file.startsWith("src/") || file.startsWith("daemon/") || file.startsWith("test/"));
|
||||
if (missing.length || unexpected.length) { console.error({ missing, unexpected }); process.exit(1); }
|
||||
console.log(`${files.size} files`);
|
||||
' "$RUNNER_TEMP/pack.json"
|
||||
|
||||
# The pi-env tests against each release build of the daemon, as the npm package ships it.
|
||||
release-daemons:
|
||||
needs: daemons
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-latest, name: linux-x64 }
|
||||
- { os: ubuntu-24.04-arm, name: linux-arm64 }
|
||||
- { os: macos-latest, name: darwin-arm64 }
|
||||
# Under Rosetta on the Apple silicon runner.
|
||||
- { os: macos-latest, name: darwin-x64 }
|
||||
- { os: windows-latest, name: windows-x64 }
|
||||
- { os: windows-11-arm, name: windows-arm64 }
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Install an SSH server for the bootstrap tests
|
||||
if: runner.os == 'Linux'
|
||||
run: command -v sshd || test -x /usr/sbin/sshd || (sudo apt-get update && sudo apt-get install -y openssh-server)
|
||||
|
||||
- name: Install Rosetta
|
||||
if: matrix.name == 'darwin-x64'
|
||||
run: softwareupdate --install-rosetta --agree-to-license
|
||||
|
||||
- name: Download the daemon
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: pi-env-${{ matrix.name }}
|
||||
path: ${{ runner.temp }}/daemon
|
||||
|
||||
- name: pi-env tests
|
||||
working-directory: packages/env
|
||||
run: |
|
||||
daemon="$RUNNER_TEMP/daemon/pi-env"
|
||||
case "${{ matrix.name }}" in windows-*) daemon="$daemon.exe" ;; esac
|
||||
chmod +x "$daemon"
|
||||
file "$daemon" || true
|
||||
PI_ENV_DAEMON="$daemon" npx vitest --run
|
||||
|
||||
tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest, windows-11-arm]
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Install an SSH server for the bootstrap tests
|
||||
if: runner.os == 'Linux'
|
||||
run: command -v sshd || test -x /usr/sbin/sshd || (sudo apt-get update && sudo apt-get install -y openssh-server)
|
||||
|
||||
- name: Daemon format, lint and unit tests
|
||||
working-directory: packages/env/daemon
|
||||
run: |
|
||||
cargo fmt --check
|
||||
cargo clippy --locked --all-targets -- -D warnings
|
||||
cargo test --locked
|
||||
|
||||
- name: Build the daemon
|
||||
working-directory: packages/env
|
||||
run: npm run build:daemon
|
||||
|
||||
- name: Durable env tests
|
||||
working-directory: packages/durable
|
||||
run: npx vitest --run test/env-node.test.ts test/env-node-spill.test.ts test/env-node-conformance.test.ts test/env-line-scan.test.ts test/tools.test.ts test/tools-read-differential.test.ts
|
||||
|
||||
- name: pi-env tests
|
||||
working-directory: packages/env
|
||||
run: npx vitest --run
|
||||
|
||||
- name: Start Windows' OpenSSH server
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: pwsh
|
||||
run: |
|
||||
if (-not (Get-Service sshd -ErrorAction SilentlyContinue)) {
|
||||
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null
|
||||
}
|
||||
Start-Service sshd
|
||||
$key = Join-Path $env:RUNNER_TEMP 'ssh_client_key'
|
||||
& "$env:WINDIR\System32\OpenSSH\ssh-keygen.exe" -q -t ed25519 -N '' -f $key
|
||||
icacls $key /inheritance:r /grant "${env:USERNAME}:F" | Out-Null
|
||||
# The runner's user is an administrator; sshd reads administrators' keys from ProgramData.
|
||||
$authorized = "$env:ProgramData\ssh\administrators_authorized_keys"
|
||||
Get-Content "$key.pub" | Set-Content -Path $authorized -Encoding ascii
|
||||
icacls $authorized /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" | Out-Null
|
||||
|
||||
- name: pi-env tests over Windows' OpenSSH, with cmd.exe and then PowerShell as its default shell
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: packages/env
|
||||
env:
|
||||
PI_ENV_SSH_HOST: 127.0.0.1
|
||||
PI_ENV_SSH_KEY: ${{ runner.temp }}\ssh_client_key
|
||||
run: |
|
||||
export PI_ENV_SSH_USER="$USERNAME"
|
||||
export PI_ENV_SSH_PROGRAM="$WINDIR\System32\OpenSSH\ssh.exe"
|
||||
npx vitest --run test/ssh-external.test.ts
|
||||
powershell -NoProfile -Command "New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -Force | Out-Null"
|
||||
PI_ENV_SSH_SHELL=powershell npx vitest --run test/ssh-external.test.ts
|
||||
@@ -19,6 +19,12 @@ jobs:
|
||||
const LAST_READ_LABEL = 'last-read';
|
||||
const TO_DISCUSS_LABEL = 'to-discuss';
|
||||
const INPROGRESS_LABEL = 'inprogress';
|
||||
// Applying one of these labels assigns the user and reopens the issue if it is closed.
|
||||
const LABEL_ASSIGNEES = new Map([
|
||||
['mcp', 'mitsuhiko'],
|
||||
['codemode', 'mitsuhiko'],
|
||||
['durable', 'badlogic'],
|
||||
]);
|
||||
|
||||
function issueHasLabel(issue, labelName) {
|
||||
return (issue.labels ?? []).some((label) => label.name === labelName);
|
||||
@@ -47,9 +53,13 @@ jobs:
|
||||
}
|
||||
}
|
||||
|
||||
async function removeClosedStateLabels(issueNumber, issue) {
|
||||
await removeLabelIfPresent(issueNumber, issue, UNTRIAGED_LABEL);
|
||||
await removeLabelIfPresent(issueNumber, issue, NO_ACTION_LABEL);
|
||||
}
|
||||
|
||||
if (context.payload.action === 'reopened') {
|
||||
await removeLabelIfPresent(context.issue.number, context.payload.issue, UNTRIAGED_LABEL);
|
||||
await removeLabelIfPresent(context.issue.number, context.payload.issue, NO_ACTION_LABEL);
|
||||
await removeClosedStateLabels(context.issue.number, context.payload.issue);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,6 +68,32 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
const labelAssignee =
|
||||
context.payload.action === 'labeled' ? LABEL_ASSIGNEES.get(context.payload.label?.name) : undefined;
|
||||
if (labelAssignee) {
|
||||
const issueNumber = context.issue.number;
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
assignees: [labelAssignee],
|
||||
});
|
||||
console.log(`Assigned ${labelAssignee} to #${issueNumber}`);
|
||||
|
||||
if (context.payload.issue.state === 'closed') {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
state: 'open',
|
||||
});
|
||||
console.log(`Reopened #${issueNumber}`);
|
||||
// Reopening with the workflow token does not trigger the reopened handler above.
|
||||
await removeClosedStateLabels(issueNumber, context.payload.issue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.payload.action !== 'labeled' || context.payload.label?.name !== LAST_READ_LABEL) {
|
||||
console.log('Not a last-read label event');
|
||||
return;
|
||||
|
||||
189
third_party/pi/.github/workflows/nix.yml
vendored
Normal file
189
third_party/pi/.github/workflows/nix.yml
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
name: Nix
|
||||
|
||||
# The flake builds with the model catalog revision pinned in
|
||||
# nix/model-catalog.json. This workflow keeps that pin usable independently of
|
||||
# releases: after pi.dev publishes a catalog from main, it replaces the pin with
|
||||
# the newest catalog only if the current one no longer hydrates main (for
|
||||
# example after a provider was added) or its provider/type/API model groups
|
||||
# differ from the newest catalog for one of main's providers (for example after
|
||||
# a provider gained classifier models or switched APIs), builds the flake, and
|
||||
# commits the new pin to main.
|
||||
#
|
||||
# Release tags are built exactly as tagged, with their committed pin. Once all
|
||||
# systems build, the stable branch is fast-forwarded to the tag so that
|
||||
# `nix run github:earendil-works/pi/stable` runs the latest release.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.github/workflows/nix.yml'
|
||||
- 'flake.lock'
|
||||
- 'flake.nix'
|
||||
- 'nix/**'
|
||||
- 'package-lock.json'
|
||||
- 'packages/*/package.json'
|
||||
- 'packages/coding-agent/install-lock/**'
|
||||
- 'packages/ai/scripts/hydrate-model-catalog.ts'
|
||||
- 'scripts/update-model-catalog-pin.mjs'
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Publish Model Catalog
|
||||
types:
|
||||
- completed
|
||||
# Catches packaging drift from changes pushed to main without a pull request.
|
||||
schedule:
|
||||
- cron: '43 5 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: nix-${{ (github.event_name == 'pull_request' || github.ref_type == 'tag') && github.ref || 'main' }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
pin:
|
||||
if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
revision: ${{ steps.pin.outputs.revision }}
|
||||
updated: ${{ steps.pin.outputs.updated }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Refresh the model catalog pin if stale
|
||||
id: pin
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# A release tag must build with its committed pin; it cannot be changed.
|
||||
if [[ "${GITHUB_REF_TYPE}" != "tag" ]]; then
|
||||
node scripts/update-model-catalog-pin.mjs --if-stale
|
||||
fi
|
||||
revision="$(node -p 'require("./nix/model-catalog.json").revision')"
|
||||
echo "revision=${revision}" >> "${GITHUB_OUTPUT}"
|
||||
if git diff --quiet -- nix/model-catalog.json; then
|
||||
echo "updated=false" >> "${GITHUB_OUTPUT}"
|
||||
else
|
||||
echo "updated=true" >> "${GITHUB_OUTPUT}"
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then
|
||||
echo "::warning::The model catalog pin on main is stale; this build uses ${revision}. The pin is refreshed on main after pi.dev publishes a catalog."
|
||||
fi
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: pin
|
||||
# After a catalog publication, only build when the pin changed.
|
||||
if: ${{ github.event_name != 'workflow_run' || needs.pin.outputs.updated == 'true' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runner: ubuntu-latest
|
||||
system: x86_64-linux
|
||||
- runner: ubuntu-24.04-arm
|
||||
system: aarch64-linux
|
||||
- runner: macos-latest
|
||||
system: aarch64-darwin
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use the resolved model catalog pin
|
||||
env:
|
||||
REVISION: ${{ needs.pin.outputs.revision }}
|
||||
run: |
|
||||
printf '{\n "revision": "%s"\n}\n' "${REVISION}" > nix/model-catalog.json
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
|
||||
with:
|
||||
github_access_token: ${{ github.token }}
|
||||
|
||||
- name: Evaluate all systems
|
||||
if: ${{ matrix.system == 'x86_64-linux' }}
|
||||
run: nix flake check --all-systems --no-build
|
||||
|
||||
- name: Build
|
||||
run: nix build -L .
|
||||
|
||||
- name: Smoke-test
|
||||
run: |
|
||||
./result/bin/pi --version
|
||||
./result/bin/pi --help > /dev/null
|
||||
if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
|
||||
test "$(./result/bin/pi --version)" = "${GITHUB_REF_NAME#v}"
|
||||
fi
|
||||
|
||||
update-stable:
|
||||
needs: build
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
# Full history, so git can verify that the push to stable is a fast-forward.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Without --force, the push fails unless it is a fast-forward, so stable
|
||||
# never moves back to an older release.
|
||||
- name: Fast-forward stable to the release
|
||||
run: git push origin "${GITHUB_SHA}:refs/heads/stable"
|
||||
|
||||
commit-pin:
|
||||
needs:
|
||||
- pin
|
||||
- build
|
||||
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && needs.pin.outputs.updated == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Commit and push the model catalog pin
|
||||
env:
|
||||
REVISION: ${{ needs.pin.outputs.revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
printf '{\n "revision": "%s"\n}\n' "${REVISION}" > nix/model-catalog.json
|
||||
git add nix/model-catalog.json
|
||||
if git diff --staged --quiet; then
|
||||
echo "Model catalog pin is already ${REVISION}."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore: update Nix model catalog pin"
|
||||
for attempt in 1 2 3; do
|
||||
if git push origin HEAD:main; then
|
||||
exit 0
|
||||
fi
|
||||
sleep $((attempt * 5))
|
||||
git pull --rebase origin main
|
||||
done
|
||||
exit 1
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
run: npm ci --ignore-scripts --no-audit --no-fund
|
||||
|
||||
- name: Audit production vulnerabilities
|
||||
run: npm audit --omit=dev --audit-level=moderate
|
||||
run: node scripts/npm-audit.mjs
|
||||
|
||||
- name: Verify registry signatures
|
||||
run: npm audit signatures --omit=dev
|
||||
|
||||
@@ -12,6 +12,7 @@ on:
|
||||
- '.gitignore'
|
||||
- 'package.json'
|
||||
- 'packages/ai/**'
|
||||
- 'scripts/model-catalog-protocol.ts'
|
||||
- 'scripts/publish-model-catalog.mjs'
|
||||
# GitHub schedules use UTC. Run hourly candidates across the CET/CEST
|
||||
# boundaries; the publish job only uploads at 10:17, 12:17, and 14:17
|
||||
|
||||
Reference in New Issue
Block a user