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:
2026-10-15 17:58:48 -04:00
parent 719370c8f7
commit 6081e3e325
1743 changed files with 205460 additions and 131705 deletions

View File

@@ -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

View File

@@ -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

View 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
View 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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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