Skip to content

Improve Web workbench and settings with native Code Mode defaults #1561

Improve Web workbench and settings with native Code Mode defaults

Improve Web workbench and settings with native Code Mode defaults #1561

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
# Keep download caches outside the checkout and still install from the lockfile.
env:
CI_APT_CACHE_DIR: /tmp/openpi-apt-download-cache
jobs:
test-node:
name: Runtime Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
node-version: [22.19.0, 24, 26]
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node-version }}
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Install dependencies
env:
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
run: bun install --frozen-lockfile
- run: bun run check
- name: Verify committed Web build
run: |
changes="$(git status --porcelain=v1 --untracked-files=all -- web/dist)"
if [[ -n "$changes" ]]; then
printf '%s\n' "$changes"
echo "web/dist is missing or stale; run bun run build:web and commit the result"
exit 1
fi
- run: bun run test node
- name: Smoke-test Pi package discovery
run: |
agent_dir="$(mktemp -d)"
trap 'rm -rf "$agent_dir"' EXIT
PI_CODING_AGENT_DIR="$agent_dir" ./node_modules/.bin/pi install "$PWD"
output="$(PI_CODING_AGENT_DIR="$agent_dir" ./node_modules/.bin/pi --offline --no-session --mode rpc </dev/null)"
grep -q '"statusKey":"plan-mode"' <<<"$output"
grep -q '"widgetKey":"session-tasks-panel"' <<<"$output"
- name: Smoke-test packed extensions without optional peers
run: |
root="$(mktemp -d)"
trap 'rm -rf "$root"' EXIT
mkdir "$root/app" "$root/agent"
printf '{"private":true}\n' >"$root/app/package.json"
npm pack --ignore-scripts --silent --pack-destination "$root" >/dev/null
tarballs=("$root"/*.tgz)
test "${#tarballs[@]}" -eq 1
npm install --prefix "$root/app" --ignore-scripts --legacy-peer-deps --no-audit --no-fund "${tarballs[0]}"
test ! -e "$root/app/node_modules/ioredis"
package="$root/app/node_modules/@tt-a1i/openpi"
PI_CODING_AGENT_DIR="$root/agent" ./node_modules/.bin/pi \
--offline \
--mode rpc \
--no-extensions \
--extension "$package/extensions/file-search/index.ts" \
--extension "$package/extensions/git-info/index.ts" \
--extension "$package/extensions/ai-providers/index.ts" \
</dev/null
- name: Smoke-test packed Web CLI with locked Pi host
run: |
root="$(mktemp -d)"
server_pid=""
cleanup() {
if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then
kill -TERM "$server_pid"
wait "$server_pid"
fi
rm -rf "$root"
}
trap cleanup EXIT
mkdir "$root/app" "$root/workspace" "$root/agent"
printf '{"private":true}\n' >"$root/app/package.json"
npm pack --ignore-scripts --silent --pack-destination "$root" >/dev/null
tarballs=("$root"/*.tgz)
test "${#tarballs[@]}" -eq 1
pi_ai_version="$(node -p 'require("./node_modules/@earendil-works/pi-ai/package.json").version')"
pi_agent_version="$(node -p 'require("./node_modules/@earendil-works/pi-coding-agent/package.json").version')"
pi_tui_version="$(node -p 'require("./node_modules/@earendil-works/pi-tui/package.json").version')"
typebox_version="$(node -p 'require("./node_modules/typebox/package.json").version')"
npm install --prefix "$root/app" --ignore-scripts --no-audit --no-fund \
"@earendil-works/pi-ai@$pi_ai_version" \
"@earendil-works/pi-coding-agent@$pi_agent_version" \
"@earendil-works/pi-tui@$pi_tui_version" \
"typebox@$typebox_version" \
"${tarballs[0]}"
test ! -e "$root/app/node_modules/@earendil-works/pi-server"
(cd "$root/app" && node --input-type=module -e 'await import("@earendil-works/pi-coding-agent")')
PI_CODING_AGENT_DIR="$root/agent" \
OPENPI_WEB_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
"$root/app/node_modules/.bin/openpi" web "$root/workspace" --port 0 --no-open \
>"$root/host.log" 2>&1 &
server_pid=$!
for _ in {1..100}; do
grep -q '^OpenPI Web Workbench is running at ' "$root/host.log" && break
kill -0 "$server_pid"
sleep 0.1
done
origin="$(sed -n 's/^OpenPI Web Workbench is running at //p' "$root/host.log" | tail -n 1)"
test -n "$origin"
curl --fail --silent --show-error --output "$root/index.html" "$origin/"
grep -q '<div id="root"></div>' "$root/index.html"
grep -Eq '<script type="module"[^>]*src="/app\.js"' "$root/index.html"
grep -Eq '<link rel="stylesheet"[^>]*href="/styles\.css"' "$root/index.html"
grep -q 'href="/favicon.svg"' "$root/index.html"
if grep -Eq '(src|href)="https?://' "$root/index.html"; then
echo "packed Web entrypoint references a remote asset"
exit 1
fi
curl --fail --silent --show-error --output "$root/app.js" "$origin/app.js"
curl --fail --silent --show-error --output "$root/styles.css" "$origin/styles.css"
curl --fail --silent --show-error --output "$root/favicon.svg" "$origin/favicon.svg"
test -s "$root/app.js"
test -s "$root/styles.css"
test -s "$root/favicon.svg"
kill -TERM "$server_pid"
wait "$server_pid"
server_pid=""
- name: Smoke-test committed Git Web source
if: matrix.node-version == '22.19.0'
run: |
root="$(mktemp -d)"
server_pid=""
cleanup() {
if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then
kill -TERM "$server_pid"
wait "$server_pid"
fi
rm -rf "$root"
}
trap cleanup EXIT
mkdir "$root/source" "$root/workspace"
git archive HEAD | tar -x -C "$root/source"
test -s "$root/source/web/dist/index.html"
test -s "$root/source/web/dist/app.js"
test -s "$root/source/web/dist/styles.css"
test -s "$root/source/web/dist/favicon.svg"
npm install --prefix "$root/source" --ignore-scripts --omit=dev --no-audit --no-fund
test ! -e "$root/source/node_modules/@earendil-works/pi-coding-agent"
# Exercise the CLI with the lockfile's Pi host, not package-owned peers.
mkdir -p "$root/source/node_modules/@earendil-works"
for peer in pi-ai pi-coding-agent pi-tui; do
ln -s \
"$PWD/node_modules/@earendil-works/$peer" \
"$root/source/node_modules/@earendil-works/$peer"
done
ln -s "$PWD/node_modules/typebox" "$root/source/node_modules/typebox"
OPENPI_WEB_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
node "$root/source/bin/openpi.js" web "$root/workspace" --port 0 --no-open \
>"$root/host.log" 2>&1 &
server_pid=$!
for _ in {1..100}; do
grep -q '^OpenPI Web Workbench is running at ' "$root/host.log" && break
kill -0 "$server_pid"
sleep 0.1
done
origin="$(sed -n 's/^OpenPI Web Workbench is running at //p' "$root/host.log" | tail -n 1)"
test -n "$origin"
curl --fail --silent --show-error --output "$root/source-index.html" "$origin/"
grep -q '<div id="root"></div>' "$root/source-index.html"
kill -TERM "$server_pid"
wait "$server_pid"
server_pid=""
web-e2e-shards:
name: Web E2E shard ${{ matrix.shard }}/2
strategy:
fail-fast: false
matrix:
shard: [1, 2]
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Install dependencies
env:
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
run: bun install --frozen-lockfile
- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('bun.lock') }}
- name: Prepare system dependency download cache
run: |
mkdir -p "$CI_APT_CACHE_DIR"
sudo tee /etc/apt/apt.conf.d/99-openpi-ci-cache >/dev/null <<EOF
Dir::Cache::archives "$CI_APT_CACHE_DIR";
APT::Keep-Downloaded-Packages "true";
Binary::apt-get::APT::Keep-Downloaded-Packages "true";
Acquire::Retries "2";
Acquire::http::Timeout "30";
Acquire::https::Timeout "30";
EOF
- name: Cache Chromium system package downloads
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ env.CI_APT_CACHE_DIR }}/*.deb
key: apt-${{ runner.os }}-${{ runner.arch }}-ubuntu24-${{ hashFiles('bun.lock') }}
restore-keys: apt-${{ runner.os }}-${{ runner.arch }}-ubuntu24-
- name: Install Chromium and D-Bus
timeout-minutes: 10
run: |
bunx playwright install --with-deps chromium
sudo apt-get install --no-install-recommends -y dbus-daemon
# Independent runners isolate each shard's server, settings and filesystem.
- run: dbus-run-session -- bun run test:web:e2e --shard=${{ matrix.shard }}/2
env:
# The runner's installed Chrome has Ubuntu's native sandbox profile.
OPENPI_CHROME_PATH: /opt/google/chrome/chrome
- name: Test Plan Ready provider handoff
if: matrix.shard == 1
run: bunx playwright test --config tests/web/playwright.plan-provider.config.ts
- name: Upload Playwright failure artifacts
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: openpi-web-playwright-results-${{ matrix.shard }}
path: |
/tmp/openpi-web-playwright-results
/tmp/openpi-web-plan-provider-playwright-results
if-no-files-found: ignore
retention-days: 7
windows-node:
name: Runtime Node (Windows ${{ matrix.group }})
runs-on: windows-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- group: parallel 1/2
arguments: node-parallel --shard=1/2
- group: parallel 2/2
arguments: node-parallel --shard=2/2
- group: isolated processes
arguments: node-serial
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Cache Bun downloads
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ runner.temp }}/bun-install-cache
key: bun-${{ runner.os }}-${{ runner.arch }}-1.3.14-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-${{ runner.arch }}-1.3.14-
- name: Install dependencies
env:
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
run: bun install --frozen-lockfile
- name: Test Windows Node suite with process isolation
# Process tests stay serial inside their own VM; ordinary files are
# split into disjoint shards on separate VMs, with every shard required.
timeout-minutes: 10
run: bun run test ${{ matrix.arguments }}
test-ui:
name: UI ${{ matrix.os }} Node ${{ matrix.node-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node-version: [22.19.0, 24, 26]
include:
- os: windows-latest
node-version: 22.19.0
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node-version }}
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Cache Bun downloads
if: runner.os == 'Windows'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ runner.temp }}/bun-install-cache
key: bun-${{ runner.os }}-${{ runner.arch }}-1.3.14-${{ hashFiles('bun.lock') }}
restore-keys: bun-${{ runner.os }}-${{ runner.arch }}-1.3.14-
- name: Install dependencies
env:
BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache
run: bun install --frozen-lockfile
- run: bun run test ui
# Keep existing check names for branch protection. always() makes skipped,
# cancelled and failed dependencies fail the gates rather than bypass them.
test:
name: Node ${{ matrix.node-version }}
if: always()
needs: [test-node, test-ui, windows-node, web-e2e-shards]
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.19.0, 24, 26]
steps:
- name: Require all test jobs
env:
NODE_RESULT: ${{ needs.test-node.result }}
UI_RESULT: ${{ needs.test-ui.result }}
WINDOWS_RESULT: ${{ needs.windows-node.result }}
E2E_RESULT: ${{ needs.web-e2e-shards.result }}
run: |
test "$NODE_RESULT" = success
test "$UI_RESULT" = success
test "$WINDOWS_RESULT" = success
test "$E2E_RESULT" = success
windows-background-terminals:
name: Background terminals (Windows)
if: always()
needs: [windows-node, test-ui]
runs-on: ubuntu-latest
steps:
- name: Require Windows runtime and UI tests
env:
NODE_RESULT: ${{ needs.windows-node.result }}
UI_RESULT: ${{ needs.test-ui.result }}
run: test "$NODE_RESULT" = success && test "$UI_RESULT" = success
web-e2e:
name: Web E2E (Node 22.19.0)
if: always()
needs: [web-e2e-shards]
runs-on: ubuntu-latest
steps:
- name: Require every E2E shard and provider handoff
env:
E2E_RESULT: ${{ needs.web-e2e-shards.result }}
run: test "$E2E_RESULT" = success